From f2243f8a2efe86ec0918c9175d19a09666abf72d Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Mon, 15 Jun 2026 20:43:49 +0200 Subject: [PATCH 1/6] Added partial parsing and a small example page-test is a lot faster for testing an preview --- README.md | 1 - include/mbmff/boxes/box_view.hpp | 2 +- include/mbmff/boxes/common.hpp | 13 +- include/mbmff/mbmff.hpp | 260 ++++++++++++++++++++++++++----- test/CMakeLists.txt | 9 ++ test/page_test.cpp | 239 ++++++++++++++++++++++++++++ 6 files changed, 481 insertions(+), 43 deletions(-) create mode 100644 test/page_test.cpp diff --git a/README.md b/README.md index 1212b59..9ee35a5 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,6 @@ int main(int argc, char* argv[]) ## Requirements - C++20 compiler (tested with MSVC 2022, GCC 14, Clang 18) -- C++23 for the test runner (`std::format`) ## Consumption diff --git a/include/mbmff/boxes/box_view.hpp b/include/mbmff/boxes/box_view.hpp index e67e583..a8008e2 100644 --- a/include/mbmff/boxes/box_view.hpp +++ b/include/mbmff/boxes/box_view.hpp @@ -182,10 +182,10 @@ static_assert( static_assert( []() { std::array data{ - std::byte(12), // size = 12 std::byte(0), std::byte(0), std::byte(0), + std::byte(12), // size = 12 std::byte('m'), std::byte('d'), std::byte('a'), diff --git a/include/mbmff/boxes/common.hpp b/include/mbmff/boxes/common.hpp index 32feb59..88a299e 100644 --- a/include/mbmff/boxes/common.hpp +++ b/include/mbmff/boxes/common.hpp @@ -11,6 +11,7 @@ enum class error_code { success = 0, invalid_format, need_more_data, + truncated, }; //------------------------------------------------------------------------------------------------------------ @@ -23,6 +24,8 @@ static constexpr auto to_string(mbmff::error_code code) noexcept -> std::string_ return "Invalid format"; case mbmff::error_code::need_more_data: return "Need more data"; + case mbmff::error_code::truncated: + return "Truncated"; default: return "Unknown error code"; } @@ -73,6 +76,12 @@ constexpr auto make_result(Type value) noexcept -> mbmff::result return {value}; } +template +constexpr auto make_result(Type value, mbmff::error_code code) noexcept -> mbmff::result +{ + return {value, code}; +} + //------------------------------------------------------------------------------------------------------------ constexpr auto fourcc(const char* str) noexcept -> std::uint32_t @@ -174,9 +183,9 @@ constexpr auto read_be(std::span data) noexcept -> T value <<= 8; value |= static_cast(data[i]); } - } else { - std::memcpy(&value, data.data(), sizeof(value)); + return value; } + std::memcpy(&value, data.data(), sizeof(value)); return mbmff::byteswap(value); } diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index 5833c0d..637ff3c 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -1,30 +1,38 @@ #pragma once -#include "boxes/ftyp.hpp" -#include "boxes/meta.hpp" -#include "boxes/mdat.hpp" -#include "boxes/containers.hpp" #include "boxes/av1C.hpp" +#include "boxes/containers.hpp" +#include "boxes/ftyp.hpp" +#include "boxes/hdlr.hpp" #include "boxes/iinf.hpp" -#include "boxes/iref.hpp" #include "boxes/iloc.hpp" -#include "boxes/hdlr.hpp" -#include "boxes/pitm.hpp" +#include "boxes/infe.hpp" +#include "boxes/ipma.hpp" +#include "boxes/iref.hpp" #include "boxes/ispe.hpp" -#include "boxes/pixi.hpp" +#include "boxes/mdat.hpp" +#include "boxes/meta.hpp" #include "boxes/pasp.hpp" -#include "boxes/ipma.hpp" -#include "boxes/infe.hpp" +#include "boxes/pitm.hpp" +#include "boxes/pixi.hpp" namespace mbmff { +enum class iterator_flags : std::uint32_t { + none = 0, + recursive = 1 << 0, + allow_partial = 1 << 1, +}; +MBMFF_FLAG_OPERATORS(iterator_flags) + #define MBMFF_ITERATE_USING(name) using name##_box = basic_box_view; MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_USING) #undef MBMFF_ITERATE_USING inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff::box_properties { -#define MBMFF_CASE_PROP(name) \ - case mbmff::box_type::name: return mbmff::basic_box_view::properties; +#define MBMFF_CASE_PROP(name) \ + case mbmff::box_type::name: \ + return mbmff::basic_box_view::properties; switch (type) { MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_PROP) default: @@ -33,7 +41,10 @@ inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff #undef MBMFF_CASE_PROP } -inline constexpr auto parse(std::span data) noexcept -> mbmff::result +inline constexpr auto parse( + std::span data, + mbmff::iterator_flags flags = mbmff::iterator_flags::none +) noexcept -> mbmff::result { auto header_result = mbmff::parse_box_header(data); if (!header_result) { @@ -42,32 +53,48 @@ inline constexpr auto parse(std::span data) noexcept -> mbmff:: auto header = header_result->value; auto header_size = header_result->consumed; + bool partial = false; + std::size_t payload_size; + if (header.size() != 0 && data.size() < header.size()) { - return mbmff::make_result( - mbmff::error_code::need_more_data, - static_cast(header.size()) - ); - } - if (header.size() != 0 && header.size() < header_size) { - return mbmff::make_result(mbmff::error_code::invalid_format); + if (!mbmff::has(flags, mbmff::iterator_flags::allow_partial)) { + return mbmff::make_result( + mbmff::error_code::need_more_data, + static_cast(header.size()) + ); + } + partial = true; + payload_size = data.size() - header_size; + } else { + if (header.size() != 0 && header.size() < header_size) { + return mbmff::make_result(mbmff::error_code::invalid_format); + } + payload_size = (header.size() == 0) ? (data.size() - header_size) + : (static_cast(header.size()) - header_size); } - auto payload_size = (header.size() == 0) ? (data.size() - header_size) - : (static_cast(header.size()) - header_size); auto payload_span = data.subspan(header_size, payload_size); - mbmff::any_box_view box{{header.size_, header.type_}, payload_span}; + auto validate_result = [&]() -> mbmff::result { #define MBMFF_CASE_VALIDATE(name) \ case mbmff::box_type::name: \ return mbmff::basic_box_view::validate(box); + switch (header.type_) { + MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_VALIDATE) + default: + return {box}; + } +#undef MBMFF_CASE_VALIDATE + }(); - switch (header.type_) { - MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_VALIDATE) - default: - return {box}; + if (!validate_result) { + return validate_result; } -#undef MBMFF_CASE_VALIDATE + if (partial) { + return mbmff::make_result(*validate_result, mbmff::error_code::truncated); + } + return validate_result; } template @@ -79,12 +106,6 @@ constexpr auto box_cast(const mbmff::any_box_view& box) noexcept -> mbmff::basic return static_cast>(box); } -enum class iterator_flags : std::uint32_t { - none = 0, - recursive = 1 << 0, -}; -MBMFF_FLAG_OPERATORS(iterator_flags) - struct box_iterator { using iterator_category = std::forward_iterator_tag; using value_type = mbmff::result; @@ -115,7 +136,7 @@ struct box_iterator { } constexpr auto try_get() const noexcept -> mbmff::result { - return mbmff::parse(remaining_); + return mbmff::parse(remaining_, flags_); } constexpr auto operator*() const noexcept -> mbmff::result { @@ -127,8 +148,8 @@ struct box_iterator { remaining_ = {}; return *this; } - auto op_result = mbmff::parse(remaining_); - if (!op_result) { + auto op_result = mbmff::parse(remaining_, flags_); + if (!op_result && op_result.code != mbmff::error_code::truncated) { remaining_ = {}; return *this; } @@ -136,13 +157,18 @@ struct box_iterator { if (mbmff::has(flags_, mbmff::iterator_flags::recursive)) { auto properties = mbmff::get_box_properties(op_result->type_); if (mbmff::has(properties, mbmff::box_properties::container)) { - auto* current_end = &remaining_.back() + 1; auto* payload_start = op_result->payload.data(); - remaining_ = std::span(payload_start, current_end); + auto* payload_end = payload_start + op_result->payload.size(); + remaining_ = std::span(payload_start, payload_end); return *this; } } + if (op_result.code == mbmff::error_code::truncated) { + remaining_ = {}; + return *this; + } + auto box_size = (op_result->size_ == 0) ? remaining_.size() : static_cast(op_result->size_); if (box_size == 0 || box_size > remaining_.size()) { remaining_ = {}; @@ -166,6 +192,162 @@ struct box_iterator { } }; +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST + +// parse() without flags rejects truncated data (backward compat) +static_assert([] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, + std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, + }; + auto r = parse(std::span(data)); + return !r && r.code == error_code::need_more_data && r.needed == 1000; +}()); + +// parse() with allow_partial returns truncated for incomplete box +static_assert([] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x03}, + std::byte{0xE8}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, + }; + auto r = parse(std::span(data), iterator_flags::allow_partial); + return !r && r.code == error_code::truncated && r->type_ == box_type::mdat && r->size_ == 1000 + && r->payload.size() == 4; +}()); + +// parse() with allow_partial returns success for complete box +static_assert([] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x0C}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, + std::byte{0x01}, + std::byte{0x02}, + std::byte{0x03}, + std::byte{0x04}, + }; + auto r = parse(std::span(data), iterator_flags::allow_partial); + return r && r.code == error_code::success && r->type_ == box_type::mdat; +}()); + +// allow_partial still rejects when even the validate minimum is absent +static_assert([] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x10}, + std::byte{'f'}, + std::byte{'t'}, + std::byte{'y'}, + std::byte{'p'}, + std::byte{'a'}, + std::byte{'v'}, + std::byte{'0'}, + std::byte{'1'}, + }; + auto r = parse(std::span(data), iterator_flags::allow_partial); + return !r && r.code == error_code::need_more_data && r.needed == 8; +}()); + +// box_iterator with allow_partial continues past truncated boxes +static_assert([] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, + std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, + std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, + }; + auto it = box_iterator(std::span(data), iterator_flags::allow_partial); + auto end = box_iterator{}; + + if (it == end) { + return false; + } + auto r1 = *it; + if (!r1 || r1->type_ != box_type::mdat) { + return false; + } + ++it; + + if (it == end) { + return false; + } + auto r2 = *it; + if (r2.code != error_code::truncated || r2->type_ != box_type::moov) { + return false; + } + ++it; + + return it == end; +}()); +// box_iterator without allow_partial stops at truncated data +static_assert([] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, + std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, + std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, + std::byte{'m'}, std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, + std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, + }; + auto it = box_iterator(std::span(data)); + auto end = box_iterator{}; + + if (it == end) return false; + if (!(*it)) return false; // mdat + ++it; + + // truncated moov — parse fails → iterator terminates + if (it == end) return false; + if ((*it).code != error_code::need_more_data) return false; + ++it; + + return it == end; +}()); +// recursive + allow_partial descends into a truncated container's children +static_assert([] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, + std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, + std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + }; + auto it = box_iterator(std::span(data), iterator_flags::allow_partial | iterator_flags::recursive); + auto end = box_iterator{}; + + if (it == end) { + return false; + } + if ((*it).code != error_code::truncated || (*it)->type_ != box_type::moov) { + return false; + } + ++it; + + if (it == end) { + return false; + } + if (!(*it) || (*it)->type_ != box_type::mdat) { + return false; + } + ++it; + + return it == end; +}()); + +#endif + } // namespace mbmff #undef MBMFF_ITERATE_BOX_TYPES diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 03c9e64..f5e0154 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,3 +1,4 @@ +cmake_minimum_required(VERSION 3.20) project(mbmff-test) add_executable(mbmff-test entry_main.cpp) @@ -7,3 +8,11 @@ set_target_properties(mbmff-test PROPERTIES CXX_EXTENSIONS OFF ) target_link_libraries(mbmff-test PRIVATE mbmff) + +add_executable(mbmff-page-test page_test.cpp) +set_target_properties(mbmff-page-test PROPERTIES + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF +) +target_link_libraries(mbmff-page-test PRIVATE mbmff) diff --git a/test/page_test.cpp b/test/page_test.cpp new file mode 100644 index 0000000..bcd1dce --- /dev/null +++ b/test/page_test.cpp @@ -0,0 +1,239 @@ +#include +#include +#include +#include +#include + +namespace { +constexpr std::size_t page_size = 4096; + +static constexpr bool is_box_implemented(mbmff::box_type type) noexcept +{ + switch (type) { + case mbmff::box_type::ftyp: + case mbmff::box_type::meta: + case mbmff::box_type::mdat: + case mbmff::box_type::av1C: + case mbmff::box_type::moov: + case mbmff::box_type::trak: + case mbmff::box_type::mdia: + case mbmff::box_type::minf: + case mbmff::box_type::stbl: + case mbmff::box_type::dinf: + case mbmff::box_type::edts: + case mbmff::box_type::udta: + case mbmff::box_type::mvex: + case mbmff::box_type::moof: + case mbmff::box_type::traf: + case mbmff::box_type::mfra: + case mbmff::box_type::ipco: + case mbmff::box_type::iprp: + case mbmff::box_type::iinf: + case mbmff::box_type::iref: + case mbmff::box_type::iloc: + case mbmff::box_type::hdlr: + case mbmff::box_type::pitm: + case mbmff::box_type::ispe: + case mbmff::box_type::pixi: + case mbmff::box_type::pasp: + case mbmff::box_type::ipma: + case mbmff::box_type::infe: + return true; + default: + return false; + } +} + +static bool is_container(const mbmff::any_box_view& box) noexcept +{ + return mbmff::has(mbmff::get_box_properties(box.type()), mbmff::box_properties::container); +} + +// --------------------------------------------------------------------------- +// Box tree printer +// --------------------------------------------------------------------------- +static void print_box_info(const mbmff::any_box_view& box, std::size_t depth) +{ + for (std::size_t i = 0; i < depth; ++i) { + std::cout << " "; + } + + if (!is_box_implemented(box.type())) { + std::cout << "\x1b[33m[!]\x1b[0m "; + } + + std::cout << '<' << box.type_string().view(); + std::cout << " size=\"" << box.size() << '\"'; + std::cout << (is_container(box) ? ">" : " />") << '\n'; +} + +static void walk_children(std::span data, std::size_t depth) +{ + for (const auto& result : mbmff::box_iterator(data, mbmff::iterator_flags::allow_partial)) { + if (!result) { + if (result.code == mbmff::error_code::need_more_data) { + return; + } + if (result.code == mbmff::error_code::truncated) { + print_box_info(*result, depth); + return; + } + return; + } + auto& box = *result; + print_box_info(box, depth); + if (is_container(box) && !box.payload.empty()) { + walk_children(box.payload, depth + 1); + } + } +} + +static void walk_box(const mbmff::any_box_view& box, std::size_t depth) +{ + print_box_info(box, depth); + if (is_container(box) && !box.payload.empty()) { + walk_children(box.payload, depth + 1); + } +} + +// --------------------------------------------------------------------------- +// Growing reader — buffer grows by appending pages; existing spans stay valid. +// Tracks the absolute file position of buf_[0]. +// --------------------------------------------------------------------------- +struct growing_reader { + std::ifstream& file_; + std::vector buf_; + std::size_t file_offset_ = 0; // absolute file position of buf_[0] + + explicit growing_reader(std::ifstream& file) + : file_(file) + {} + + void init() + { + file_offset_ = static_cast(file_.tellg()); + buf_.resize(page_size); + file_.read(reinterpret_cast(buf_.data()), page_size); + buf_.resize(static_cast(file_.gcount())); + } + + bool ensure(std::size_t needed) + { + while (buf_.size() < needed) { + std::size_t old = buf_.size(); + buf_.resize(old + page_size); + file_.read(reinterpret_cast(buf_.data() + old), page_size); + buf_.resize(old + static_cast(file_.gcount())); + if (file_.gcount() == 0) { + return false; + } + } + return true; + } + + void seek_to(std::size_t abs_pos) + { + file_.seekg(static_cast(abs_pos)); + file_offset_ = abs_pos; + buf_.clear(); + init(); + } + + std::span span() const + { + return std::span(buf_); + } +}; + +// --------------------------------------------------------------------------- +// Top-level loop +// --------------------------------------------------------------------------- +static void walk_file(growing_reader& reader) +{ + std::size_t offset = 0; + + while (true) { + if (!reader.ensure(offset + 8)) { + break; + } + + auto data = reader.span().subspan(offset); + auto result = mbmff::parse(data, mbmff::iterator_flags::allow_partial); + + if (!result) { + if (result.code == mbmff::error_code::need_more_data) { + reader.ensure(offset + result.needed); + continue; + } + if (result.code == mbmff::error_code::truncated) { + auto& box = *result; + + if (box.type_ == mbmff::box_type::mdat) { + // Skip mdat content: seek file past it + const auto abs_file_pos = reader.file_offset_ + offset; + reader.seek_to(abs_file_pos + static_cast(box.size_)); + offset = 0; + continue; + } + + // Non-md: load the full box so the next parse succeeds + if (box.size_ != 0) { + if (!reader.ensure(offset + static_cast(box.size_))) { + break; + } + } + continue; + } + break; + } + + auto& box = *result; + + // mdat that was small enough to fit in the buffer + if (box.type_ == mbmff::box_type::mdat) { + if (box.size_ == 0) { + break; + } + offset += static_cast(box.size_); + continue; + } + + walk_box(box, 0); + + if (box.size_ == 0) { + break; + } + offset += static_cast(box.size_); + } +} + +} // namespace + +int main(int argc, char* argv[]) +{ + if (argc < 2) { + std::cerr << "Usage: mbmff-page-test \n"; + return 1; + } + + std::filesystem::path path{argv[1]}; + if (!std::filesystem::exists(path)) { + std::cerr << "File does not exist: " << argv[1] << '\n'; + return 1; + } + + std::ifstream file(path, std::ios::binary); + if (!file) { + std::cerr << "Failed to open: " << argv[1] << '\n'; + return 1; + } + + growing_reader reader(file); + reader.init(); + if (reader.span().empty()) { + return 0; + } + + walk_file(reader); + return 0; +} From c1185bf8fc193564c70d1646fd7223ef103c1f68 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Mon, 15 Jun 2026 21:09:23 +0200 Subject: [PATCH 2/6] added mvhd box --- include/mbmff/boxes/box_view.hpp | 3 +- include/mbmff/boxes/mvhd.hpp | 126 +++++++++++++++++++++++++++++++ include/mbmff/mbmff.hpp | 1 + test/entry_main.cpp | 4 + test/formatters.hpp | 16 ++++ test/page_test.cpp | 44 ++++++----- 6 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 include/mbmff/boxes/mvhd.hpp diff --git a/include/mbmff/boxes/box_view.hpp b/include/mbmff/boxes/box_view.hpp index a8008e2..61acac0 100644 --- a/include/mbmff/boxes/box_view.hpp +++ b/include/mbmff/boxes/box_view.hpp @@ -30,7 +30,8 @@ MACRO(pixi) \ MACRO(ipma) \ MACRO(pasp) \ - MACRO(infe) + MACRO(infe) \ + MACRO(mvhd) #define MBMFF_FLAG_OPERATORS(EnumType) \ constexpr auto operator|(EnumType a, EnumType b) noexcept -> EnumType \ diff --git a/include/mbmff/boxes/mvhd.hpp b/include/mbmff/boxes/mvhd.hpp new file mode 100644 index 0000000..61b5cb3 --- /dev/null +++ b/include/mbmff/boxes/mvhd.hpp @@ -0,0 +1,126 @@ +#pragma once +#include "box_view.hpp" +#include + +namespace mbmff { + +struct mvhd_data { + std::uint64_t creation_time = 0; + std::uint64_t modification_time = 0; + std::uint32_t timescale = 0; + std::uint64_t duration = 0; + std::uint32_t rate = 0; + std::uint16_t volume = 0; + std::array matrix{}; + std::uint32_t next_track_id = 0; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::mvhd_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto needed = (box.version() == 1) ? std::size_t{108} : std::size_t{96}; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::mvhd_data +{ + mbmff::mvhd_data result{}; + if (payload.empty() && payload.data() == nullptr) { + return result; + } + + if (version() == 1) { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(8)); + result.timescale = mbmff::read_be(payload.subspan(16)); + result.duration = mbmff::read_be(payload.subspan(20)); + result.rate = mbmff::read_be(payload.subspan(28)); + result.volume = mbmff::read_be(payload.subspan(32)); + for (std::size_t i = 0; i < 9; ++i) { + result.matrix[i] = mbmff::read_be(payload.subspan(44 + i * 4)); + } + result.next_track_id = mbmff::read_be(payload.subspan(104)); + } else { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(4)); + result.timescale = mbmff::read_be(payload.subspan(8)); + result.duration = mbmff::read_be(payload.subspan(12)); + result.rate = mbmff::read_be(payload.subspan(16)); + result.volume = mbmff::read_be(payload.subspan(20)); + for (std::size_t i = 0; i < 9; ++i) { + result.matrix[i] = mbmff::read_be(payload.subspan(32 + i * 4)); + } + result.next_track_id = mbmff::read_be(payload.subspan(92)); + } + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// validate must reject payload < 4 bytes +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span{}})); + +// validate must reject version 0 payload < 96 bytes (after 4-byte full header) +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span(std::array{std::byte{0x00}}.data(), 4)})); + +// value on a valid version 0 mvhd (data from ISOBMFF test) +static_assert([] { + constexpr std::array data{ + std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, + std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x27}, std::byte{0x10}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x37}, std::byte{0x02}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x40}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, + }; + mbmff::basic_box_view mvhd; + mvhd.version_ = 0; + mvhd.payload = std::span(data); + auto v = mvhd.value(); + return v.creation_time == 0xe400b334 + && v.modification_time == 0xe400b334 + && v.timescale == 10000 + && v.duration == 14082 + && v.rate == 0x00010000 + && v.volume == 0x0100 + && v.next_track_id == 3; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index 637ff3c..a7bd3fc 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -11,6 +11,7 @@ #include "boxes/ispe.hpp" #include "boxes/mdat.hpp" #include "boxes/meta.hpp" +#include "boxes/mvhd.hpp" #include "boxes/pasp.hpp" #include "boxes/pitm.hpp" #include "boxes/pixi.hpp" diff --git a/test/entry_main.cpp b/test/entry_main.cpp index 54b1bb4..2e5366e 100644 --- a/test/entry_main.cpp +++ b/test/entry_main.cpp @@ -35,6 +35,7 @@ static constexpr auto is_box_implemented(mbmff::box_type type) noexcept -> bool case mbmff::box_type::pasp: case mbmff::box_type::ipma: case mbmff::box_type::infe: + case mbmff::box_type::mvhd: return true; default: return false; @@ -120,6 +121,9 @@ static void print_box(const mbmff::any_box_view& box, std::size_t depth) case mbmff::box_type::av1C: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::mvhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; default: std::cout << " size=\"" << box.size_ << '"'; break; diff --git a/test/formatters.hpp b/test/formatters.hpp index 1c06dcb..1291883 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -208,6 +208,22 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// mvhd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::mvhd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" version=\"{}\" creation_time=\"{}\" modification_time=\"{}\"" + " timescale=\"{}\" duration=\"{}\" rate=\"{}\" volume=\"{}\" next_track_id=\"{}\"", + box.size_, box.version(), v.creation_time, v.modification_time, + v.timescale, v.duration, v.rate, v.volume, v.next_track_id); + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // iloc template <> diff --git a/test/page_test.cpp b/test/page_test.cpp index bcd1dce..c13144a 100644 --- a/test/page_test.cpp +++ b/test/page_test.cpp @@ -38,6 +38,7 @@ static constexpr bool is_box_implemented(mbmff::box_type type) noexcept case mbmff::box_type::pasp: case mbmff::box_type::ipma: case mbmff::box_type::infe: + case mbmff::box_type::mvhd: return true; default: return false; @@ -67,6 +68,7 @@ static void print_box_info(const mbmff::any_box_view& box, std::size_t depth) std::cout << (is_container(box) ? ">" : " />") << '\n'; } +static void walk_box(const mbmff::any_box_view& box, std::size_t depth); static void walk_children(std::span data, std::size_t depth) { for (const auto& result : mbmff::box_iterator(data, mbmff::iterator_flags::allow_partial)) { @@ -80,11 +82,7 @@ static void walk_children(std::span data, std::size_t depth) } return; } - auto& box = *result; - print_box_info(box, depth); - if (is_container(box) && !box.payload.empty()) { - walk_children(box.payload, depth + 1); - } + walk_box(*result, depth); } } @@ -101,20 +99,14 @@ static void walk_box(const mbmff::any_box_view& box, std::size_t depth) // Tracks the absolute file position of buf_[0]. // --------------------------------------------------------------------------- struct growing_reader { - std::ifstream& file_; + std::ifstream file_; std::vector buf_; std::size_t file_offset_ = 0; // absolute file position of buf_[0] - - explicit growing_reader(std::ifstream& file) - : file_(file) - {} - - void init() +public: + explicit growing_reader(std::ifstream&& file) + : file_(std::move(file)) { - file_offset_ = static_cast(file_.tellg()); - buf_.resize(page_size); - file_.read(reinterpret_cast(buf_.data()), page_size); - buf_.resize(static_cast(file_.gcount())); + reset(); } bool ensure(std::size_t needed) @@ -136,13 +128,26 @@ struct growing_reader { file_.seekg(static_cast(abs_pos)); file_offset_ = abs_pos; buf_.clear(); - init(); + reset(); } std::span span() const { return std::span(buf_); } + + bool is_empty() const + { + return buf_.empty(); + } + + void reset() + { + file_offset_ = static_cast(file_.tellg()); + buf_.resize(page_size); + file_.read(reinterpret_cast(buf_.data()), page_size); + buf_.resize(static_cast(file_.gcount())); + } }; // --------------------------------------------------------------------------- @@ -228,9 +233,8 @@ int main(int argc, char* argv[]) return 1; } - growing_reader reader(file); - reader.init(); - if (reader.span().empty()) { + growing_reader reader(std::move(file)); + if (reader.is_empty()) { return 0; } From 41e96730980cd6368ca8ad639804f352d8a0a78d Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 16 Jun 2026 19:09:39 +0200 Subject: [PATCH 3/6] refactor example, no more full file loading. more convenience functions --- include/mbmff/mbmff.hpp | 102 ++++++++----- test/CMakeLists.txt | 9 -- test/entry_main.cpp | 310 ++++++++++++++++++---------------------- test/formatters.hpp | 135 +++++++++++++++++ test/page_test.cpp | 243 ------------------------------- 5 files changed, 340 insertions(+), 459 deletions(-) delete mode 100644 test/page_test.cpp diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index a7bd3fc..e8143f6 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -42,9 +42,29 @@ inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff #undef MBMFF_CASE_PROP } +inline constexpr bool is_box_implemented(mbmff::box_type type) noexcept +{ +#define MBMFF_CASE_IMPL(name) \ +case mbmff::box_type::name: + + switch (type) { + MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_IMPL) + return true; + default: + return false; + } +#undef MBMFF_CASE_IMPL +} + +inline constexpr bool is_container(const mbmff::any_box_view& box) noexcept +{ + return mbmff::has(mbmff::get_box_properties(box.type()), mbmff::box_properties::container); +} + + +template inline constexpr auto parse( - std::span data, - mbmff::iterator_flags flags = mbmff::iterator_flags::none + std::span data ) noexcept -> mbmff::result { auto header_result = mbmff::parse_box_header(data); @@ -58,7 +78,7 @@ inline constexpr auto parse( std::size_t payload_size; if (header.size() != 0 && data.size() < header.size()) { - if (!mbmff::has(flags, mbmff::iterator_flags::allow_partial)) { + if constexpr (!mbmff::has(flags, mbmff::iterator_flags::allow_partial)) { return mbmff::make_result( mbmff::error_code::need_more_data, static_cast(header.size()) @@ -107,55 +127,54 @@ constexpr auto box_cast(const mbmff::any_box_view& box) noexcept -> mbmff::basic return static_cast>(box); } -struct box_iterator { +template + struct box_iterator { using iterator_category = std::forward_iterator_tag; using value_type = mbmff::result; using difference_type = std::ptrdiff_t; + using iterator = mbmff::box_iterator; private: std::span remaining_; - mbmff::iterator_flags flags_ = mbmff::iterator_flags::none; public: constexpr box_iterator() noexcept = default; constexpr explicit box_iterator( - std::span data, - mbmff::iterator_flags flags = mbmff::iterator_flags::none + std::span data ) noexcept : remaining_(data) - , flags_(flags) {} public: - constexpr auto begin() const noexcept -> mbmff::box_iterator + constexpr auto begin() const noexcept -> iterator { return *this; } - constexpr auto end() const noexcept -> mbmff::box_iterator + constexpr auto end() const noexcept -> iterator { return {}; } constexpr auto try_get() const noexcept -> mbmff::result { - return mbmff::parse(remaining_, flags_); + return mbmff::parse(remaining_); } constexpr auto operator*() const noexcept -> mbmff::result { return try_get(); } - constexpr auto operator++() noexcept -> mbmff::box_iterator& + constexpr auto operator++() noexcept -> iterator& { if (remaining_.empty()) { remaining_ = {}; return *this; } - auto op_result = mbmff::parse(remaining_, flags_); + auto op_result = mbmff::parse(remaining_); if (!op_result && op_result.code != mbmff::error_code::truncated) { remaining_ = {}; return *this; } - if (mbmff::has(flags_, mbmff::iterator_flags::recursive)) { + if constexpr (mbmff::has(flags, mbmff::iterator_flags::recursive)) { auto properties = mbmff::get_box_properties(op_result->type_); if (mbmff::has(properties, mbmff::box_properties::container)) { auto* payload_start = op_result->payload.data(); @@ -178,13 +197,13 @@ struct box_iterator { } return *this; } - constexpr auto operator++(int) noexcept -> mbmff::box_iterator + constexpr auto operator++(int) noexcept -> iterator { auto tmp = *this; ++(*this); return tmp; } - constexpr bool operator==(const mbmff::box_iterator& other) const noexcept + constexpr bool operator==(const iterator& other) const noexcept { if (remaining_.empty() && other.remaining_.empty()) { return true; @@ -193,6 +212,10 @@ struct box_iterator { } }; +using recursive_box_iterator = mbmff::box_iterator; +using partial_box_iterator = mbmff::box_iterator; + + #ifdef MBMFF_ENABLE_CONSTEXPR_TEST // parse() without flags rejects truncated data (backward compat) @@ -201,8 +224,8 @@ static_assert([] { std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, }; - auto r = parse(std::span(data)); - return !r && r.code == error_code::need_more_data && r.needed == 1000; + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::need_more_data && r.needed == 1000; }()); // parse() with allow_partial returns truncated for incomplete box @@ -217,8 +240,8 @@ static_assert([] { std::byte{'a'}, std::byte{'t'}, }; - auto r = parse(std::span(data), iterator_flags::allow_partial); - return !r && r.code == error_code::truncated && r->type_ == box_type::mdat && r->size_ == 1000 + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::truncated && r->type_ == mbmff::box_type::mdat && r->size_ == 1000 && r->payload.size() == 4; }()); @@ -238,8 +261,10 @@ static_assert([] { std::byte{0x03}, std::byte{0x04}, }; - auto r = parse(std::span(data), iterator_flags::allow_partial); - return r && r.code == error_code::success && r->type_ == box_type::mdat; + auto r = mbmff::parse( + std::span(data) + ); + return r && r.code == mbmff::error_code::success && r->type_ == mbmff::box_type::mdat; }()); // allow_partial still rejects when even the validate minimum is absent @@ -258,8 +283,8 @@ static_assert([] { std::byte{'0'}, std::byte{'1'}, }; - auto r = parse(std::span(data), iterator_flags::allow_partial); - return !r && r.code == error_code::need_more_data && r.needed == 8; + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::need_more_data && r.needed == 8; }()); // box_iterator with allow_partial continues past truncated boxes @@ -270,14 +295,16 @@ static_assert([] { std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, }; - auto it = box_iterator(std::span(data), iterator_flags::allow_partial); - auto end = box_iterator{}; + auto it = mbmff::box_iterator( + std::span(data) + ); + auto end = it.end(); if (it == end) { return false; } auto r1 = *it; - if (!r1 || r1->type_ != box_type::mdat) { + if (!r1 || r1->type_ != mbmff::box_type::mdat) { return false; } ++it; @@ -286,7 +313,7 @@ static_assert([] { return false; } auto r2 = *it; - if (r2.code != error_code::truncated || r2->type_ != box_type::moov) { + if (r2.code != mbmff::error_code::truncated || r2->type_ != mbmff::box_type::moov) { return false; } ++it; @@ -303,8 +330,8 @@ static_assert([] { std::byte{'m'}, std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, }; - auto it = box_iterator(std::span(data)); - auto end = box_iterator{}; + auto it = mbmff::box_iterator(std::span(data)); + auto end = it.end(); if (it == end) return false; if (!(*it)) return false; // mdat @@ -312,7 +339,9 @@ static_assert([] { // truncated moov — parse fails → iterator terminates if (it == end) return false; - if ((*it).code != error_code::need_more_data) return false; + if ((*it).code != mbmff::error_code::need_more_data) { + return false; + } ++it; return it == end; @@ -325,13 +354,16 @@ static_assert([] { std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, }; - auto it = box_iterator(std::span(data), iterator_flags::allow_partial | iterator_flags::recursive); - auto end = box_iterator{}; + auto it = mbmff::box_iterator( + std::span(data) + + ); + auto end = it.end(); if (it == end) { return false; } - if ((*it).code != error_code::truncated || (*it)->type_ != box_type::moov) { + if ((*it).code != mbmff::error_code::truncated || (*it)->type_ != mbmff::box_type::moov) { return false; } ++it; @@ -339,7 +371,7 @@ static_assert([] { if (it == end) { return false; } - if (!(*it) || (*it)->type_ != box_type::mdat) { + if (!(*it) || (*it)->type_ != mbmff::box_type::mdat) { return false; } ++it; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f5e0154..03c9e64 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,3 @@ -cmake_minimum_required(VERSION 3.20) project(mbmff-test) add_executable(mbmff-test entry_main.cpp) @@ -8,11 +7,3 @@ set_target_properties(mbmff-test PROPERTIES CXX_EXTENSIONS OFF ) target_link_libraries(mbmff-test PRIVATE mbmff) - -add_executable(mbmff-page-test page_test.cpp) -set_target_properties(mbmff-page-test PROPERTIES - CXX_STANDARD 23 - CXX_STANDARD_REQUIRED ON - CXX_EXTENSIONS OFF -) -target_link_libraries(mbmff-page-test PRIVATE mbmff) diff --git a/test/entry_main.cpp b/test/entry_main.cpp index 2e5366e..216526f 100644 --- a/test/entry_main.cpp +++ b/test/entry_main.cpp @@ -1,212 +1,178 @@ #include +#include #include #include #include #include "formatters.hpp" -static constexpr auto is_box_implemented(mbmff::box_type type) noexcept -> bool +constexpr std::size_t page_size = 4096; + +static void walk_box(const mbmff::any_box_view& box, std::size_t depth); +static void walk_children(std::span data, std::size_t depth) { - switch (type) { - case mbmff::box_type::ftyp: - case mbmff::box_type::meta: - case mbmff::box_type::mdat: - case mbmff::box_type::av1C: - case mbmff::box_type::moov: - case mbmff::box_type::trak: - case mbmff::box_type::mdia: - case mbmff::box_type::minf: - case mbmff::box_type::stbl: - case mbmff::box_type::dinf: - case mbmff::box_type::edts: - case mbmff::box_type::udta: - case mbmff::box_type::mvex: - case mbmff::box_type::moof: - case mbmff::box_type::traf: - case mbmff::box_type::mfra: - case mbmff::box_type::ipco: - case mbmff::box_type::iprp: - case mbmff::box_type::iinf: - case mbmff::box_type::iref: - case mbmff::box_type::iloc: - case mbmff::box_type::hdlr: - case mbmff::box_type::pitm: - case mbmff::box_type::ispe: - case mbmff::box_type::pixi: - case mbmff::box_type::pasp: - case mbmff::box_type::ipma: - case mbmff::box_type::infe: - case mbmff::box_type::mvhd: - return true; - default: - return false; + for (const auto& result : mbmff::partial_box_iterator(data)) { + if (!result) { + if (result.code == mbmff::error_code::need_more_data) { + return; + } + if (result.code == mbmff::error_code::truncated) { + print_box(*result, depth); + return; + } + return; + } + walk_box(*result, depth); } } -static auto display_fourcc(const mbmff::fourcc_string& fc) -> std::string +static void walk_box(const mbmff::any_box_view& box, std::size_t depth) { - std::string out; - out.reserve(12); - bool all_printable = true; - for (std::size_t i = 0; i < 4; ++i) { - auto c = fc[i]; - if (static_cast(c) < 0x20 || static_cast(c) > 0x7e) { - all_printable = false; - break; - } + print_box(box, depth); + if (mbmff::is_container(box) && !box.payload.empty()) { + walk_children(box.payload, depth + 1); } - if (all_printable) { - out.append(fc.view()); - } else { - out += '['; - for (std::size_t i = 0; i < 4; ++i) { - if (i) out += ' '; - auto uc = static_cast(fc[i]); - auto hex = "0123456789abcdef"; - out += "0x"; - out += hex[uc >> 4]; - out += hex[uc & 0xf]; +} + +// --------------------------------------------------------------------------- +// Growing reader — buffer grows by appending pages; existing spans stay valid. +// Tracks the absolute file position of buf_[0]. +// --------------------------------------------------------------------------- +struct growing_reader { + std::ifstream file_; + std::vector buf_; + std::size_t file_offset_ = 0; // absolute file position of buf_[0] +public: + explicit growing_reader(std::ifstream&& file) + : file_(std::move(file)) + { + reset(); + } + + bool ensure(std::size_t needed) + { + while (buf_.size() < needed) { + std::size_t old = buf_.size(); + buf_.resize(old + page_size); + file_.read(reinterpret_cast(buf_.data() + old), page_size); + buf_.resize(old + static_cast(file_.gcount())); + if (file_.gcount() == 0) { + return false; + } } - out += ']'; + return true; } - return out; -} -static void print_box(const mbmff::any_box_view& box, std::size_t depth) -{ - for (std::size_t i = 0; i < depth; ++i) std::cout << " "; + void seek_to(std::size_t abs_pos) + { + file_.seekg(static_cast(abs_pos)); + file_offset_ = abs_pos; + buf_.clear(); + reset(); + } + + std::span span() const + { + return std::span(buf_); + } - if (!is_box_implemented(box.type_)) { - std::cout << "\x1b[33m[!]\x1b[0m "; + bool is_empty() const + { + return buf_.empty(); } - std::cout << '<' << display_fourcc(box.type_string()); - - switch (box.type_) { - case mbmff::box_type::ftyp: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::meta: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::mdat: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::iinf: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::iloc: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::hdlr: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::pitm: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::ispe: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::pixi: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::pasp: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::ipma: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::infe: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::av1C: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - case mbmff::box_type::mvhd: - std::cout << std::format("{}", mbmff::box_cast(box)); - break; - default: - std::cout << " size=\"" << box.size_ << '"'; - break; + void reset() + { + file_offset_ = static_cast(file_.tellg()); + buf_.resize(page_size); + file_.read(reinterpret_cast(buf_.data()), page_size); + buf_.resize(static_cast(file_.gcount())); } - bool is_container = mbmff::has(mbmff::get_box_properties(box.type_), mbmff::box_properties::container); - std::cout << (is_container ? ">" : " />") << '\n'; - - if (box.type_ == mbmff::box_type::iloc) { - auto iloc = mbmff::box_cast(box); - auto data = iloc.value(); - auto iter = mbmff::iloc_item_iterator(data, iloc.version()); - for (; iter != mbmff::iloc_item_iterator{}; ++iter) { - auto item = *iter; - for (std::size_t i = 0; i < depth + 1; ++i) std::cout << " "; - std::cout << std::format("\n", - item.item_id, item.base_offset, - item.construction_method, item.data_reference_index, item.size()); - for (std::size_t e = 0; e < item.size(); ++e) { - auto ext = item[e]; - for (std::size_t i = 0; i < depth + 2; ++i) std::cout << " "; - std::cout << std::format("(data); + + if (!result) { + if (result.code == mbmff::error_code::need_more_data) { + ensure(offset + result.needed); + continue; + } + if (result.code == mbmff::error_code::truncated) { + auto& box = *result; + + if (box.type_ == mbmff::box_type::mdat) { + // Skip mdat content: seek file past it + const auto abs_file_pos = file_offset_ + offset; + seek_to(abs_file_pos + static_cast(box.size_)); + offset = 0; + continue; + } + + // Non-md: load the full box so the next parse succeeds + if (box.size_ != 0) { + if (!ensure(offset + static_cast(box.size_))) { + break; + } + } + continue; } - std::cout << " />\n"; + break; } - for (std::size_t i = 0; i < depth + 1; ++i) std::cout << " "; - std::cout << "\n"; - } - } -} -static void walk_boxes_impl(const mbmff::any_box_view& box, std::size_t depth) -{ - print_box(box, depth); - auto props = mbmff::get_box_properties(box.type_); - if (mbmff::has(props, mbmff::box_properties::container) && !box.payload.empty()) { - for (const auto& child : mbmff::box_iterator(box.payload)) { - if (!child) { - std::cerr << "\x1b[31;1m" << std::string(depth * 2, ' ') - << "Parse error: " << mbmff::to_string(child.code) - << " (needed " << child.needed << " bytes)\x1b[0m\n"; - return; + auto& box = *result; + + // mdat that was small enough to fit in the buffer + if (box.type_ == mbmff::box_type::mdat) { + if (box.size_ == 0) { + break; + } + offset += static_cast(box.size_); + continue; } - walk_boxes_impl(*child, depth + 1); - } - } -} -static void walk_boxes(std::span data) -{ - for (const auto& result : mbmff::box_iterator(data)) { - if (!result) { - std::cerr << "\x1b[31;1mParse error at top level: " - << mbmff::to_string(result.code) - << " (needed " << result.needed << " bytes)\x1b[0m\n"; - return; + walk_box(box, 0); + + if (box.size_ == 0) { + break; + } + offset += static_cast(box.size_); } - walk_boxes_impl(*result, 0); + return 0; } -} +}; int main(int argc, char* argv[]) { if (argc < 2) { - std::cerr << "Usage: mbmff-test \n"; + std::cerr << "Usage: mbmff-page-test \n"; + return 1; + } + + std::filesystem::path path{argv[1]}; + if (!std::filesystem::exists(path)) { + std::cerr << "File does not exist: " << argv[1] << '\n'; return 1; } - std::ifstream file(argv[1], std::ios::binary); + std::ifstream file(path, std::ios::binary); if (!file) { - std::cerr << "Failed to open file: " << argv[1] << '\n'; + std::cerr << "Failed to open: " << argv[1] << '\n'; return 1; } - std::vector file_data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - std::span file_span( - reinterpret_cast(file_data.data()), - file_data.size() - ); + growing_reader reader(std::move(file)); + if (reader.is_empty()) { + return 0; + } - walk_boxes(file_span); - return 0; + return reader.walk_file(); } diff --git a/test/formatters.hpp b/test/formatters.hpp index 1291883..3efcaa8 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -240,3 +240,138 @@ struct std::formatter : std::formatter { ); } }; + +//------------------------------------------------------------------------------------------------------------ +// Display fourcc as string if printable, otherwise as hex +static auto display_fourcc(const mbmff::fourcc_string& fc) -> std::string +{ + std::string out; + out.reserve(12); + bool all_printable = true; + for (std::size_t i = 0; i < 4; ++i) { + auto c = fc[i]; + if (static_cast(c) < 0x20 || static_cast(c) > 0x7e) { + all_printable = false; + break; + } + } + if (all_printable) { + out.append(fc.view()); + } else { + out += '['; + for (std::size_t i = 0; i < 4; ++i) { + if (i) { + out += ' '; + } + auto uc = static_cast(fc[i]); + auto hex = "0123456789abcdef"; + out += "0x"; + out += hex[uc >> 4]; + out += hex[uc & 0xf]; + } + out += ']'; + } + return out; +} + +//------------------------------------------------------------------------------------------------------------ +// Print box information with indentation based on depth +static void print_box(const mbmff::any_box_view& box, std::size_t depth) +{ + for (std::size_t i = 0; i < depth; ++i) { + std::cout << " "; + } + + if (!mbmff::is_box_implemented(box.type_)) { + std::cout << "\x1b[33m[!]\x1b[0m "; + } + + std::cout << '<' << display_fourcc(box.type_string()); + + switch (box.type_) { + case mbmff::box_type::ftyp: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::meta: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::mdat: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::iinf: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::iloc: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::hdlr: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pitm: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::ispe: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pixi: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pasp: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::ipma: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::infe: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::av1C: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::mvhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + default: + std::cout << " size=\"" << box.size_ << '"'; + break; + } + + bool is_container = mbmff::is_container(box); + std::cout << (is_container ? ">" : " />") << '\n'; + + if (box.type_ == mbmff::box_type::iloc) { + auto iloc = mbmff::box_cast(box); + auto data = iloc.value(); + auto iter = mbmff::iloc_item_iterator(data, iloc.version()); + for (; iter != mbmff::iloc_item_iterator{}; ++iter) { + auto item = *iter; + for (std::size_t i = 0; i < depth + 1; ++i) { + std::cout << " "; + } + std::cout << std::format( + "\n", + item.item_id, + item.base_offset, + item.construction_method, + item.data_reference_index, + item.size() + ); + for (std::size_t e = 0; e < item.size(); ++e) { + auto ext = item[e]; + for (std::size_t i = 0; i < depth + 2; ++i) { + std::cout << " "; + } + std::cout << std::format("\n"; + } + for (std::size_t i = 0; i < depth + 1; ++i) { + std::cout << " "; + } + std::cout << "\n"; + } + } +} diff --git a/test/page_test.cpp b/test/page_test.cpp deleted file mode 100644 index c13144a..0000000 --- a/test/page_test.cpp +++ /dev/null @@ -1,243 +0,0 @@ -#include -#include -#include -#include -#include - -namespace { -constexpr std::size_t page_size = 4096; - -static constexpr bool is_box_implemented(mbmff::box_type type) noexcept -{ - switch (type) { - case mbmff::box_type::ftyp: - case mbmff::box_type::meta: - case mbmff::box_type::mdat: - case mbmff::box_type::av1C: - case mbmff::box_type::moov: - case mbmff::box_type::trak: - case mbmff::box_type::mdia: - case mbmff::box_type::minf: - case mbmff::box_type::stbl: - case mbmff::box_type::dinf: - case mbmff::box_type::edts: - case mbmff::box_type::udta: - case mbmff::box_type::mvex: - case mbmff::box_type::moof: - case mbmff::box_type::traf: - case mbmff::box_type::mfra: - case mbmff::box_type::ipco: - case mbmff::box_type::iprp: - case mbmff::box_type::iinf: - case mbmff::box_type::iref: - case mbmff::box_type::iloc: - case mbmff::box_type::hdlr: - case mbmff::box_type::pitm: - case mbmff::box_type::ispe: - case mbmff::box_type::pixi: - case mbmff::box_type::pasp: - case mbmff::box_type::ipma: - case mbmff::box_type::infe: - case mbmff::box_type::mvhd: - return true; - default: - return false; - } -} - -static bool is_container(const mbmff::any_box_view& box) noexcept -{ - return mbmff::has(mbmff::get_box_properties(box.type()), mbmff::box_properties::container); -} - -// --------------------------------------------------------------------------- -// Box tree printer -// --------------------------------------------------------------------------- -static void print_box_info(const mbmff::any_box_view& box, std::size_t depth) -{ - for (std::size_t i = 0; i < depth; ++i) { - std::cout << " "; - } - - if (!is_box_implemented(box.type())) { - std::cout << "\x1b[33m[!]\x1b[0m "; - } - - std::cout << '<' << box.type_string().view(); - std::cout << " size=\"" << box.size() << '\"'; - std::cout << (is_container(box) ? ">" : " />") << '\n'; -} - -static void walk_box(const mbmff::any_box_view& box, std::size_t depth); -static void walk_children(std::span data, std::size_t depth) -{ - for (const auto& result : mbmff::box_iterator(data, mbmff::iterator_flags::allow_partial)) { - if (!result) { - if (result.code == mbmff::error_code::need_more_data) { - return; - } - if (result.code == mbmff::error_code::truncated) { - print_box_info(*result, depth); - return; - } - return; - } - walk_box(*result, depth); - } -} - -static void walk_box(const mbmff::any_box_view& box, std::size_t depth) -{ - print_box_info(box, depth); - if (is_container(box) && !box.payload.empty()) { - walk_children(box.payload, depth + 1); - } -} - -// --------------------------------------------------------------------------- -// Growing reader — buffer grows by appending pages; existing spans stay valid. -// Tracks the absolute file position of buf_[0]. -// --------------------------------------------------------------------------- -struct growing_reader { - std::ifstream file_; - std::vector buf_; - std::size_t file_offset_ = 0; // absolute file position of buf_[0] -public: - explicit growing_reader(std::ifstream&& file) - : file_(std::move(file)) - { - reset(); - } - - bool ensure(std::size_t needed) - { - while (buf_.size() < needed) { - std::size_t old = buf_.size(); - buf_.resize(old + page_size); - file_.read(reinterpret_cast(buf_.data() + old), page_size); - buf_.resize(old + static_cast(file_.gcount())); - if (file_.gcount() == 0) { - return false; - } - } - return true; - } - - void seek_to(std::size_t abs_pos) - { - file_.seekg(static_cast(abs_pos)); - file_offset_ = abs_pos; - buf_.clear(); - reset(); - } - - std::span span() const - { - return std::span(buf_); - } - - bool is_empty() const - { - return buf_.empty(); - } - - void reset() - { - file_offset_ = static_cast(file_.tellg()); - buf_.resize(page_size); - file_.read(reinterpret_cast(buf_.data()), page_size); - buf_.resize(static_cast(file_.gcount())); - } -}; - -// --------------------------------------------------------------------------- -// Top-level loop -// --------------------------------------------------------------------------- -static void walk_file(growing_reader& reader) -{ - std::size_t offset = 0; - - while (true) { - if (!reader.ensure(offset + 8)) { - break; - } - - auto data = reader.span().subspan(offset); - auto result = mbmff::parse(data, mbmff::iterator_flags::allow_partial); - - if (!result) { - if (result.code == mbmff::error_code::need_more_data) { - reader.ensure(offset + result.needed); - continue; - } - if (result.code == mbmff::error_code::truncated) { - auto& box = *result; - - if (box.type_ == mbmff::box_type::mdat) { - // Skip mdat content: seek file past it - const auto abs_file_pos = reader.file_offset_ + offset; - reader.seek_to(abs_file_pos + static_cast(box.size_)); - offset = 0; - continue; - } - - // Non-md: load the full box so the next parse succeeds - if (box.size_ != 0) { - if (!reader.ensure(offset + static_cast(box.size_))) { - break; - } - } - continue; - } - break; - } - - auto& box = *result; - - // mdat that was small enough to fit in the buffer - if (box.type_ == mbmff::box_type::mdat) { - if (box.size_ == 0) { - break; - } - offset += static_cast(box.size_); - continue; - } - - walk_box(box, 0); - - if (box.size_ == 0) { - break; - } - offset += static_cast(box.size_); - } -} - -} // namespace - -int main(int argc, char* argv[]) -{ - if (argc < 2) { - std::cerr << "Usage: mbmff-page-test \n"; - return 1; - } - - std::filesystem::path path{argv[1]}; - if (!std::filesystem::exists(path)) { - std::cerr << "File does not exist: " << argv[1] << '\n'; - return 1; - } - - std::ifstream file(path, std::ios::binary); - if (!file) { - std::cerr << "Failed to open: " << argv[1] << '\n'; - return 1; - } - - growing_reader reader(std::move(file)); - if (reader.is_empty()) { - return 0; - } - - walk_file(reader); - return 0; -} From 115201838a4d5c14899ac46137a45eac5762324c Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 16 Jun 2026 20:26:23 +0200 Subject: [PATCH 4/6] more boxes --- include/mbmff/boxes/av1C.hpp | 16 +- include/mbmff/boxes/box_view.hpp | 18 +- include/mbmff/boxes/common.hpp | 19 ++- include/mbmff/boxes/dref.hpp | 77 +++++++++ include/mbmff/boxes/elst.hpp | 166 ++++++++++++++++++ include/mbmff/boxes/ftyp.hpp | 22 +-- include/mbmff/boxes/hdlr.hpp | 6 +- include/mbmff/boxes/iinf.hpp | 10 +- include/mbmff/boxes/iloc.hpp | 19 ++- include/mbmff/boxes/infe.hpp | 9 +- include/mbmff/boxes/ipma.hpp | 9 +- include/mbmff/boxes/iref.hpp | 8 +- include/mbmff/boxes/ispe.hpp | 6 +- include/mbmff/boxes/mdat.hpp | 2 +- include/mbmff/boxes/mdhd.hpp | 93 +++++++++++ include/mbmff/boxes/meta.hpp | 6 +- include/mbmff/boxes/mvhd.hpp | 70 ++++---- include/mbmff/boxes/pasp.hpp | 6 +- include/mbmff/boxes/pitm.hpp | 6 +- include/mbmff/boxes/pixi.hpp | 6 +- include/mbmff/boxes/smhd.hpp | 66 ++++++++ include/mbmff/boxes/stsd.hpp | 76 +++++++++ include/mbmff/boxes/stts.hpp | 120 +++++++++++++ include/mbmff/boxes/tkhd.hpp | 117 +++++++++++++ include/mbmff/boxes/url.hpp | 45 +++++ include/mbmff/boxes/vmhd.hpp | 75 +++++++++ include/mbmff/mbmff.hpp | 74 ++++---- test/formatters.hpp | 279 ++++++++++++++++++++++++++++--- 28 files changed, 1261 insertions(+), 165 deletions(-) create mode 100644 include/mbmff/boxes/dref.hpp create mode 100644 include/mbmff/boxes/elst.hpp create mode 100644 include/mbmff/boxes/mdhd.hpp create mode 100644 include/mbmff/boxes/smhd.hpp create mode 100644 include/mbmff/boxes/stsd.hpp create mode 100644 include/mbmff/boxes/stts.hpp create mode 100644 include/mbmff/boxes/tkhd.hpp create mode 100644 include/mbmff/boxes/url.hpp create mode 100644 include/mbmff/boxes/vmhd.hpp diff --git a/include/mbmff/boxes/av1C.hpp b/include/mbmff/boxes/av1C.hpp index 16d02c1..6756719 100644 --- a/include/mbmff/boxes/av1C.hpp +++ b/include/mbmff/boxes/av1C.hpp @@ -22,13 +22,13 @@ struct av1C_data { }; template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::none; +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::av1C_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -37,7 +37,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -constexpr auto basic_box_view::value() const noexcept -> mbmff::av1C_data +constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::av1C_data { av1C_data result{}; std::uint8_t read_byte = static_cast(payload[0]); @@ -70,14 +70,14 @@ constexpr auto basic_box_view::value() const noexcept -> mbmff:: #ifdef MBMFF_ENABLE_CONSTEXPR_TEST // validate must reject empty payload -static_assert(!mbmff::basic_box_view::validate( - {mbmff::box_header{}, std::span{}})); +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); // validate must accept 4-byte payload static_assert([] { constexpr std::byte min_data[4]{}; - auto r = mbmff::basic_box_view::validate( - {mbmff::box_header{}, std::span(min_data)}); + auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(min_data)}); return static_cast(r); }()); #endif diff --git a/include/mbmff/boxes/box_view.hpp b/include/mbmff/boxes/box_view.hpp index 61acac0..fa589c5 100644 --- a/include/mbmff/boxes/box_view.hpp +++ b/include/mbmff/boxes/box_view.hpp @@ -31,7 +31,15 @@ MACRO(ipma) \ MACRO(pasp) \ MACRO(infe) \ - MACRO(mvhd) + MACRO(mvhd) \ + MACRO(tkhd) \ + MACRO(elst) \ + MACRO(mdhd) \ + MACRO(vmhd) \ + MACRO(smhd) \ + MACRO(stts) \ + MACRO(stsd) \ + MACRO(dref) #define MBMFF_FLAG_OPERATORS(EnumType) \ constexpr auto operator|(EnumType a, EnumType b) noexcept -> EnumType \ @@ -53,7 +61,10 @@ namespace mbmff { #define MBMFF_ITERATE_ENUM(name) name = mbmff::fourcc(#name), -enum class box_type : std::uint32_t { unknown, MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_ENUM) }; +enum class box_type : std::uint32_t { + unknown, + MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_ENUM) url = mbmff::fourcc("url ") +}; //------------------------------------------------------------------------------------------------------------ enum class box_properties : std::uint32_t { @@ -111,7 +122,8 @@ struct box_header { } }; -using any_box_view = struct box_view_base; +struct box_view_base; +using any_box_view = mbmff::box_view_base; struct box_view_base : mbmff::box_header { std::span payload{}; diff --git a/include/mbmff/boxes/common.hpp b/include/mbmff/boxes/common.hpp index 88a299e..3be360c 100644 --- a/include/mbmff/boxes/common.hpp +++ b/include/mbmff/boxes/common.hpp @@ -96,7 +96,11 @@ struct fourcc_string { public: constexpr auto view() const noexcept -> std::string_view { - return std::string_view(data_.data(), data_.size()); + auto len = std::size_t{0}; + while (len < 4 && data_[len] != 0) { + ++len; + } + return std::string_view(data_.data(), len); } constexpr auto data() const noexcept -> const std::span { @@ -108,7 +112,7 @@ struct fourcc_string { } constexpr auto to_uint32() const noexcept -> std::uint32_t { - return fourcc(data_.data()); + return mbmff::fourcc(data_.data()); } constexpr auto operator==(const mbmff::fourcc_string& other) const noexcept -> bool @@ -136,13 +140,22 @@ struct fourcc_string { } constexpr static auto from_uint32(std::uint32_t value) noexcept -> mbmff::fourcc_string { - return fourcc_string{std::array{ + return mbmff::fourcc_string{std::array{ static_cast(value & 0xFF), static_cast((value >> 8) & 0xFF), static_cast((value >> 16) & 0xFF), static_cast((value >> 24) & 0xFF), }}; } + constexpr static auto from_language(std::uint16_t lang) noexcept -> mbmff::fourcc_string + { + return mbmff::fourcc_string{std::array{ + static_cast('a' + ((lang >> 10) & 0x1F)), + static_cast('a' + ((lang >> 5) & 0x1F)), + static_cast('a' + ((lang >> 0) & 0x1F)), + char{0}, + }}; + } }; //------------------------------------------------------------------------------------------------------------ diff --git a/include/mbmff/boxes/dref.hpp b/include/mbmff/boxes/dref.hpp new file mode 100644 index 0000000..be672ef --- /dev/null +++ b/include/mbmff/boxes/dref.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct dref_data { + std::uint32_t entry_count = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box + | mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::dref_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + box.payload = box.payload.subspan(4); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::dref_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + return {mbmff::read_be(std::span(payload.data() - 4, 4))}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, // full header: v=0, f=0 + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, // entry_count = 1 + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x0c}, // child box header start + }; + // Simulate what validate does: eat full header and entry_count + mbmff::basic_box_view dref; + dref.version_ = 0; + dref.payload = std::span(data).subspan(8); + auto v = dref.value(); + return v.entry_count == 1; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/elst.hpp b/include/mbmff/boxes/elst.hpp new file mode 100644 index 0000000..13031a6 --- /dev/null +++ b/include/mbmff/boxes/elst.hpp @@ -0,0 +1,166 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct elst_entry { + std::uint64_t segment_duration; + std::int64_t media_time; + std::int16_t media_rate_integer; + std::int16_t media_rate_fraction; +}; + +struct elst_data { + std::uint32_t entry_count = 0; + std::uint8_t version_ = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::elst_entry + { + auto entry_size = (version_ == 1) ? std::size_t{20} : std::size_t{12}; + auto offset = index * entry_size; + if (offset + entry_size > entries_data.size()) { + return {}; + } + auto span = entries_data.subspan(offset); + if (version_ == 1) { + return { + mbmff::read_be(span.subspan(0)), + static_cast(mbmff::read_be(span.subspan(8))), + static_cast(mbmff::read_be(span.subspan(16))), + static_cast(mbmff::read_be(span.subspan(18))), + }; + } + return { + mbmff::read_be(span.subspan(0)), + static_cast(static_cast(mbmff::read_be(span.subspan(4)))), + static_cast(mbmff::read_be(span.subspan(8))), + static_cast(mbmff::read_be(span.subspan(10))), + }; + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::elst_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_size = (box.version() == 1) ? std::size_t{20} : std::size_t{12}; + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * entry_size; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::elst_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, version(), payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +// validate must reject when entry_count=1 but payload too small for one entry +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, // full header: v0, flags=0 + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, // entry_count=1 + } + .data(), + 8 + )} +)); + +// value with 1 version 0 entry (from ISOBMFF test) +static_assert([] { + mbmff::basic_box_view elst; + elst.version_ = 0; + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x18}, + std::byte{0xc8}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + }; + elst.payload = std::span(data); + auto d = elst.value(); + if (d.size() != 1) { + return false; + } + auto e = d[0]; + return e.segment_duration == 6344 && e.media_time == 0 && e.media_rate_integer == 1 && e.media_rate_fraction == 0; +}()); + +// value with 2 version 0 entries (second entry has negative media_time) +static_assert([] { + mbmff::basic_box_view elst; + elst.version_ = 0; + constexpr std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x02}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x03}, std::byte{0xe8}, std::byte{0xff}, std::byte{0xff}, std::byte{0xff}, std::byte{0xff}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x13}, std::byte{0x88}, std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, std::byte{0xf4}, + std::byte{0x00}, std::byte{0x02}, std::byte{0x80}, std::byte{0x00}, + }; + elst.payload = std::span(data); + auto d = elst.value(); + if (d.size() != 2) { + return false; + } + auto e0 = d[0]; + if (e0.segment_duration != 1000 || e0.media_time != -1) { + return false; + } + auto e1 = d[1]; + return e1.segment_duration == 5000 && e1.media_time == 500 && e1.media_rate_integer == 2 + && e1.media_rate_fraction == static_cast(0x8000); +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/ftyp.hpp b/include/mbmff/boxes/ftyp.hpp index b5a90d2..6c23a1d 100644 --- a/include/mbmff/boxes/ftyp.hpp +++ b/include/mbmff/boxes/ftyp.hpp @@ -9,13 +9,13 @@ struct ftyp_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { - constexpr static box_properties properties = box_properties::none; +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ftyp_data; }; -inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 8) { @@ -29,29 +29,29 @@ inline constexpr auto mbmff::basic_box_view::validate(mbmff::any return {box}; } -constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ftyp_data +constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ftyp_data { const auto compatible_data = payload.subspan(8); - std::span compatible_brands( - reinterpret_cast(compatible_data.data()), + std::span compatible_brands( + reinterpret_cast(compatible_data.data()), compatible_data.size() / 4 ); return mbmff::ftyp_data{ - fourcc_string::from_data(payload), + mbmff::fourcc_string::from_data(payload), mbmff::read_be(payload.subspan(4)), compatible_brands }; } #ifdef MBMFF_ENABLE_CONSTEXPR_TEST // validate must reject payload < 8 bytes -static_assert(!mbmff::basic_box_view::validate( - {mbmff::box_header{}, std::span{}})); +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); // validate must reject payload not divisible by 4 static_assert([] { constexpr std::byte bad_div[10]{}; - auto r = mbmff::basic_box_view::validate( - {mbmff::box_header{}, std::span(bad_div)}); + auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(bad_div)}); return !r; }()); #endif diff --git a/include/mbmff/boxes/hdlr.hpp b/include/mbmff/boxes/hdlr.hpp index 712a6f6..59ed194 100644 --- a/include/mbmff/boxes/hdlr.hpp +++ b/include/mbmff/boxes/hdlr.hpp @@ -9,13 +9,13 @@ struct hdlr_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::hdlr_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -30,7 +30,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::hdlr_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::hdlr_data { mbmff::hdlr_data result{}; result.handler_type = mbmff::fourcc_string::from_data(payload.subspan(4)); diff --git a/include/mbmff/boxes/iinf.hpp b/include/mbmff/boxes/iinf.hpp index b2d1663..e024783 100644 --- a/include/mbmff/boxes/iinf.hpp +++ b/include/mbmff/boxes/iinf.hpp @@ -8,14 +8,14 @@ struct iinf_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { - constexpr static mbmff::box_properties properties - = mbmff::box_properties::full_box | mbmff::box_properties::container; +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box + | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::iinf_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -32,7 +32,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::iinf_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::iinf_data { mbmff::iinf_data result{}; auto entry_size = (version() == 0) ? std::size_t{2} : std::size_t{4}; diff --git a/include/mbmff/boxes/iloc.hpp b/include/mbmff/boxes/iloc.hpp index 69362d8..bcd10db 100644 --- a/include/mbmff/boxes/iloc.hpp +++ b/include/mbmff/boxes/iloc.hpp @@ -87,13 +87,13 @@ struct iloc_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::iloc_data; }; -inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -112,10 +112,12 @@ inline constexpr auto mbmff::basic_box_view::validate(mbmff::any return {box}; } -inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::iloc_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::iloc_data { mbmff::iloc_data result{}; - if (payload.size() < 2) return result; + if (payload.size() < 2) { + return result; + } auto first_byte = static_cast(payload[0]); result.offset_size = (first_byte >> 4) & 0x0F; @@ -135,7 +137,8 @@ inline constexpr auto mbmff::basic_box_view::value() const noexc return result; } -class iloc_item_iterator { +class iloc_item_iterator +{ using iterator_category = std::forward_iterator_tag; using value_type = mbmff::iloc_item; using difference_type = std::ptrdiff_t; @@ -243,8 +246,7 @@ class iloc_item_iterator { const std::byte* data = current_item.extent_data.data(); std::size_t total_extent_size = current_item.extent_count * extent_size(); - remaining_ = {data + total_extent_size, - remaining_.size() - (data - remaining_.data()) - total_extent_size}; + remaining_ = {data + total_extent_size, remaining_.size() - (data - remaining_.data()) - total_extent_size}; return *this; } @@ -260,8 +262,7 @@ class iloc_item_iterator { if (remaining_.empty() && other.remaining_.empty()) { return true; } - return remaining_.data() == other.remaining_.data() - && remaining_.size() == other.remaining_.size(); + return remaining_.data() == other.remaining_.data() && remaining_.size() == other.remaining_.size(); } }; diff --git a/include/mbmff/boxes/infe.hpp b/include/mbmff/boxes/infe.hpp index 5448acd..56cfe23 100644 --- a/include/mbmff/boxes/infe.hpp +++ b/include/mbmff/boxes/infe.hpp @@ -14,13 +14,13 @@ struct infe_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::infe_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -29,15 +29,14 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi box.fill_full_header(box.payload); box.payload = box.payload.subspan(4); - auto need = (box.version() == 0 || box.version() == 1 || box.version() == 2) - ? std::size_t{4} : std::size_t{6}; + auto need = (box.version() == 0 || box.version() == 1 || box.version() == 2) ? std::size_t{4} : std::size_t{6}; if (box.payload.size() < need) { return mbmff::make_result(mbmff::error_code::need_more_data, need); } return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::infe_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::infe_data { mbmff::infe_data result{}; auto xversion = version(); diff --git a/include/mbmff/boxes/ipma.hpp b/include/mbmff/boxes/ipma.hpp index a0d97e4..bcc43e6 100644 --- a/include/mbmff/boxes/ipma.hpp +++ b/include/mbmff/boxes/ipma.hpp @@ -48,13 +48,13 @@ struct ipma_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ipma_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -70,7 +70,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::ipma_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ipma_data { mbmff::ipma_data result{}; result.entry_count = mbmff::read_be(payload); @@ -80,7 +80,8 @@ inline constexpr auto basic_box_view::value() const noexcept -> return result; } -class ipma_entry_iterator { +class ipma_entry_iterator +{ using iterator_category = std::forward_iterator_tag; using value_type = mbmff::ipma_entry; using difference_type = std::ptrdiff_t; diff --git a/include/mbmff/boxes/iref.hpp b/include/mbmff/boxes/iref.hpp index 3134af9..03f80a5 100644 --- a/include/mbmff/boxes/iref.hpp +++ b/include/mbmff/boxes/iref.hpp @@ -4,13 +4,13 @@ namespace mbmff { template <> -struct basic_box_view : public mbmff::box_view_base { - constexpr static mbmff::box_properties properties - = mbmff::box_properties::full_box | mbmff::box_properties::container; +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box + | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { diff --git a/include/mbmff/boxes/ispe.hpp b/include/mbmff/boxes/ispe.hpp index 1137581..d31941e 100644 --- a/include/mbmff/boxes/ispe.hpp +++ b/include/mbmff/boxes/ispe.hpp @@ -9,13 +9,13 @@ struct ispe_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ispe_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -30,7 +30,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::ispe_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ispe_data { mbmff::ispe_data result{}; result.image_width = mbmff::read_be(payload); diff --git a/include/mbmff/boxes/mdat.hpp b/include/mbmff/boxes/mdat.hpp index 1be213f..6ec1773 100644 --- a/include/mbmff/boxes/mdat.hpp +++ b/include/mbmff/boxes/mdat.hpp @@ -3,7 +3,7 @@ namespace mbmff { template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; }; diff --git a/include/mbmff/boxes/mdhd.hpp b/include/mbmff/boxes/mdhd.hpp new file mode 100644 index 0000000..30af627 --- /dev/null +++ b/include/mbmff/boxes/mdhd.hpp @@ -0,0 +1,93 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct mdhd_data { + std::uint64_t creation_time = 0; + std::uint64_t modification_time = 0; + std::uint32_t timescale = 0; + std::uint64_t duration = 0; + mbmff::fourcc_string language{}; + std::uint16_t predefined = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::mdhd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto needed = (box.version() == 1) ? std::size_t{32} : std::size_t{20}; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::mdhd_data +{ + mbmff::mdhd_data result{}; + if (payload.empty() && payload.data() == nullptr) { + return result; + } + + if (version() == 1) { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(8)); + result.timescale = mbmff::read_be(payload.subspan(16)); + result.duration = mbmff::read_be(payload.subspan(20)); + result.language = mbmff::fourcc_string::from_language(mbmff::read_be(payload.subspan(28))); + result.predefined = mbmff::read_be(payload.subspan(30)); + } else { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(4)); + result.timescale = mbmff::read_be(payload.subspan(8)); + result.duration = mbmff::read_be(payload.subspan(12)); + result.language = mbmff::fourcc_string::from_language(mbmff::read_be(payload.subspan(16))); + result.predefined = mbmff::read_be(payload.subspan(18)); + } + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +static_assert(mbmff::fourcc_string::from_language(0x51A3).view() == "und"); +static_assert(mbmff::fourcc_string::from_language(0x11A6).view() == "eng"); + +// value on a valid version 0 mdhd +static_assert([] { + constexpr std::array data{ + std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, std::byte{0xe4}, + std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, std::byte{0x00}, std::byte{0x01}, + std::byte{0x5f}, std::byte{0x90}, std::byte{0x00}, std::byte{0x01}, std::byte{0xef}, + std::byte{0x13}, std::byte{0x51}, std::byte{0xA3}, // lang='und' (0x51A3) + std::byte{0x00}, std::byte{0x00}, + }; + mbmff::basic_box_view mdhd; + mdhd.version_ = 0; + mdhd.payload = std::span(data); + auto v = mdhd.value(); + return v.creation_time == 0xe400b334 && v.modification_time == 0xe400b334 && v.timescale == 90000 + && v.duration == 126739 && v.language.view() == "und" && v.predefined == 0; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/meta.hpp b/include/mbmff/boxes/meta.hpp index fba5273..b2955bd 100644 --- a/include/mbmff/boxes/meta.hpp +++ b/include/mbmff/boxes/meta.hpp @@ -3,14 +3,14 @@ namespace mbmff { template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { // full_box is not ticked here because `meta` can // be a regular box in some formats (e.g. QTFF) constexpr static mbmff::box_properties properties = mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { constexpr auto hdlr_type = "hdlr"; @@ -38,7 +38,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi // payload starting with "hdlr" → not full_box → passes validation static_assert([] { constexpr std::byte no_fullbox[4]{std::byte{'h'}, std::byte{'d'}, std::byte{'l'}, std::byte{'r'}}; - auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(no_fullbox)}); + auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(no_fullbox)}); return static_cast(r); }()); #endif diff --git a/include/mbmff/boxes/mvhd.hpp b/include/mbmff/boxes/mvhd.hpp index 61b5cb3..6de2900 100644 --- a/include/mbmff/boxes/mvhd.hpp +++ b/include/mbmff/boxes/mvhd.hpp @@ -1,6 +1,6 @@ #pragma once -#include "box_view.hpp" #include +#include "box_view.hpp" namespace mbmff { @@ -16,13 +16,13 @@ struct mvhd_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::mvhd_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -38,7 +38,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::mvhd_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::mvhd_data { mbmff::mvhd_data result{}; if (payload.empty() && payload.data() == nullptr) { @@ -73,53 +73,41 @@ inline constexpr auto basic_box_view::value() const noexcept -> #ifdef MBMFF_ENABLE_CONSTEXPR_TEST // validate must reject payload < 4 bytes -static_assert(!mbmff::basic_box_view::validate( - {mbmff::box_header{}, std::span{}})); +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); // validate must reject version 0 payload < 96 bytes (after 4-byte full header) -static_assert(!mbmff::basic_box_view::validate( - {mbmff::box_header{}, - std::span(std::array{std::byte{0x00}}.data(), 4)})); +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); // value on a valid version 0 mvhd (data from ISOBMFF test) static_assert([] { constexpr std::array data{ - std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, - std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x27}, std::byte{0x10}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x37}, std::byte{0x02}, - std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x40}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, + std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, std::byte{0xe4}, std::byte{0x00}, + std::byte{0xb3}, std::byte{0x34}, std::byte{0x00}, std::byte{0x00}, std::byte{0x27}, std::byte{0x10}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x37}, std::byte{0x02}, std::byte{0x00}, std::byte{0x01}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x40}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, }; - mbmff::basic_box_view mvhd; + mbmff::basic_box_view mvhd; mvhd.version_ = 0; mvhd.payload = std::span(data); auto v = mvhd.value(); - return v.creation_time == 0xe400b334 - && v.modification_time == 0xe400b334 - && v.timescale == 10000 - && v.duration == 14082 - && v.rate == 0x00010000 - && v.volume == 0x0100 - && v.next_track_id == 3; + return v.creation_time == 0xe400b334 && v.modification_time == 0xe400b334 && v.timescale == 10000 + && v.duration == 14082 && v.rate == 0x00010000 && v.volume == 0x0100 && v.next_track_id == 3; }()); #endif diff --git a/include/mbmff/boxes/pasp.hpp b/include/mbmff/boxes/pasp.hpp index 8f759e8..7115842 100644 --- a/include/mbmff/boxes/pasp.hpp +++ b/include/mbmff/boxes/pasp.hpp @@ -9,13 +9,13 @@ struct pasp_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pasp_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 8) { @@ -24,7 +24,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pasp_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::pasp_data { mbmff::pasp_data result{}; result.h_spacing = mbmff::read_be(payload); diff --git a/include/mbmff/boxes/pitm.hpp b/include/mbmff/boxes/pitm.hpp index 6adbfb0..711ad15 100644 --- a/include/mbmff/boxes/pitm.hpp +++ b/include/mbmff/boxes/pitm.hpp @@ -8,13 +8,13 @@ struct pitm_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pitm_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -30,7 +30,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pitm_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::pitm_data { mbmff::pitm_data result{}; if (version() == 0) { diff --git a/include/mbmff/boxes/pixi.hpp b/include/mbmff/boxes/pixi.hpp index 01aa101..a888dbc 100644 --- a/include/mbmff/boxes/pixi.hpp +++ b/include/mbmff/boxes/pixi.hpp @@ -8,13 +8,13 @@ struct pixi_data { }; template <> -struct basic_box_view : public mbmff::box_view_base { +struct mbmff::basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pixi_data; }; -inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept -> mbmff::result { if (box.payload.size() < 4) { @@ -29,7 +29,7 @@ inline constexpr auto basic_box_view::validate(mbmff::any_box_vi return {box}; } -inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pixi_data +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::pixi_data { mbmff::pixi_data result{}; auto num_channels = static_cast(payload[0]); diff --git a/include/mbmff/boxes/smhd.hpp b/include/mbmff/boxes/smhd.hpp new file mode 100644 index 0000000..7cf320f --- /dev/null +++ b/include/mbmff/boxes/smhd.hpp @@ -0,0 +1,66 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct smhd_data { + std::int16_t balance = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::smhd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::smhd_data +{ + return {static_cast(mbmff::read_be(payload))}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 8)} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, // full header + std::byte{0x00}, + std::byte{0x00}, // balance = 0 + std::byte{0x00}, + std::byte{0x00}, // reserved + }; + mbmff::basic_box_view smhd; + smhd.version_ = 0; + smhd.payload = std::span(data).subspan(4); + auto v = smhd.value(); + return v.balance == 0; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stsd.hpp b/include/mbmff/boxes/stsd.hpp new file mode 100644 index 0000000..62fff15 --- /dev/null +++ b/include/mbmff/boxes/stsd.hpp @@ -0,0 +1,76 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stsd_data { + std::uint32_t entry_count = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box + | mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stsd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + box.payload = box.payload.subspan(4); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stsd_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + return {mbmff::read_be(std::span(payload.data() - 4, 4))}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x10}, + }; + mbmff::basic_box_view stsd; + stsd.version_ = 0; + stsd.payload = std::span(data).subspan(8); + auto v = stsd.value(); + return v.entry_count == 1; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stts.hpp b/include/mbmff/boxes/stts.hpp new file mode 100644 index 0000000..d25b331 --- /dev/null +++ b/include/mbmff/boxes/stts.hpp @@ -0,0 +1,120 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stts_entry { + std::uint32_t sample_count; + std::uint32_t sample_delta; +}; + +struct stts_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::stts_entry + { + auto offset = index * 8; + if (offset + 8 > entries_data.size()) { + return {}; + } + auto span = entries_data.subspan(offset); + return { + mbmff::read_be(span.subspan(0)), + mbmff::read_be(span.subspan(4)), + }; + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stts_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 8; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stts_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x03}, + std::byte{0xe8}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + }; + mbmff::basic_box_view stts; + stts.version_ = 0; + stts.payload = std::span(data); + auto v = stts.value(); + if (v.size() != 1) { + return false; + } + auto e = v[0]; + return e.sample_count == 1000 && e.sample_delta == 1; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/tkhd.hpp b/include/mbmff/boxes/tkhd.hpp new file mode 100644 index 0000000..20917c9 --- /dev/null +++ b/include/mbmff/boxes/tkhd.hpp @@ -0,0 +1,117 @@ +#pragma once +#include +#include "box_view.hpp" + +namespace mbmff { + +struct tkhd_data { + std::uint64_t creation_time = 0; + std::uint64_t modification_time = 0; + std::uint32_t track_id = 0; + std::uint64_t duration = 0; + std::uint16_t layer = 0; + std::uint16_t alternate_group = 0; + std::uint16_t volume = 0; + std::array matrix{}; + std::uint32_t width = 0; + std::uint32_t height = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::tkhd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto needed = (box.version() == 1) ? std::size_t{92} : std::size_t{80}; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::tkhd_data +{ + mbmff::tkhd_data result{}; + if (payload.empty() && payload.data() == nullptr) { + return result; + } + + if (version() == 1) { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(8)); + result.track_id = mbmff::read_be(payload.subspan(16)); + result.duration = mbmff::read_be(payload.subspan(24)); + result.layer = mbmff::read_be(payload.subspan(40)); + result.alternate_group = mbmff::read_be(payload.subspan(42)); + result.volume = mbmff::read_be(payload.subspan(44)); + for (std::size_t i = 0; i < 9; ++i) { + result.matrix[i] = mbmff::read_be(payload.subspan(48 + i * 4)); + } + result.width = mbmff::read_be(payload.subspan(84)); + result.height = mbmff::read_be(payload.subspan(88)); + } else { + result.creation_time = mbmff::read_be(payload.subspan(0)); + result.modification_time = mbmff::read_be(payload.subspan(4)); + result.track_id = mbmff::read_be(payload.subspan(8)); + result.duration = mbmff::read_be(payload.subspan(16)); + result.layer = mbmff::read_be(payload.subspan(28)); + result.alternate_group = mbmff::read_be(payload.subspan(30)); + result.volume = mbmff::read_be(payload.subspan(32)); + for (std::size_t i = 0; i < 9; ++i) { + result.matrix[i] = mbmff::read_be(payload.subspan(36 + i * 4)); + } + result.width = mbmff::read_be(payload.subspan(72)); + result.height = mbmff::read_be(payload.subspan(76)); + } + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +// validate must reject version 0 payload < 80 bytes (after 4-byte full header) +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +// value on a valid version 0 tkhd (data from ISOBMFF test) +static_assert([] { + constexpr std::array data{ + std::byte{0xe4}, std::byte{0x00}, std::byte{0xb3}, std::byte{0x34}, std::byte{0xe4}, std::byte{0x00}, + std::byte{0xb3}, std::byte{0x34}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x37}, std::byte{0x02}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x40}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x06}, std::byte{0xc0}, std::byte{0x00}, std::byte{0x00}, std::byte{0x09}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, + }; + mbmff::basic_box_view tkhd; + tkhd.version_ = 0; + tkhd.payload = std::span(data); + auto v = tkhd.value(); + return v.creation_time == 0xe400b334 && v.modification_time == 0xe400b334 && v.track_id == 1 && v.duration == 0x3702 + && v.layer == 0 && v.alternate_group == 0 && v.volume == 0 && v.width == 0x06c00000 && v.height == 0x09000000; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/url.hpp b/include/mbmff/boxes/url.hpp new file mode 100644 index 0000000..a5a461c --- /dev/null +++ b/include/mbmff/boxes/url.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct url_data {}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::url_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::url_data +{ + return {}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 3)} +)); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 4)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/vmhd.hpp b/include/mbmff/boxes/vmhd.hpp new file mode 100644 index 0000000..16ca760 --- /dev/null +++ b/include/mbmff/boxes/vmhd.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include "box_view.hpp" + +namespace mbmff { + +struct vmhd_data { + std::uint16_t graphicsmode = 0; + std::array opcolor{}; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::vmhd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 8) { + return mbmff::make_result(mbmff::error_code::need_more_data, 8); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::vmhd_data +{ + mbmff::vmhd_data result{}; + if (payload.empty() && payload.data() == nullptr) { + return result; + } + result.graphicsmode = mbmff::read_be(payload.subspan(0)); + for (int i = 0; i < 3; ++i) { + result.opcolor[i] = mbmff::read_be(payload.subspan(2 + i * 2)); + } + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, // graphicsmode = 0 (copy) + std::byte{0x00}, + std::byte{0x00}, // opcolor[0] = 0 + std::byte{0x00}, + std::byte{0x00}, // opcolor[1] = 0 + std::byte{0x00}, + std::byte{0x00}, // opcolor[2] = 0 + }; + mbmff::basic_box_view vmhd; + vmhd.version_ = 0; + vmhd.payload = std::span(data); + auto v = vmhd.value(); + return v.graphicsmode == 0 && v.opcolor[0] == 0 && v.opcolor[1] == 0 && v.opcolor[2] == 0; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index e8143f6..59e7877 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -1,6 +1,8 @@ #pragma once #include "boxes/av1C.hpp" #include "boxes/containers.hpp" +#include "boxes/dref.hpp" +#include "boxes/elst.hpp" #include "boxes/ftyp.hpp" #include "boxes/hdlr.hpp" #include "boxes/iinf.hpp" @@ -10,11 +12,18 @@ #include "boxes/iref.hpp" #include "boxes/ispe.hpp" #include "boxes/mdat.hpp" +#include "boxes/mdhd.hpp" #include "boxes/meta.hpp" #include "boxes/mvhd.hpp" #include "boxes/pasp.hpp" #include "boxes/pitm.hpp" #include "boxes/pixi.hpp" +#include "boxes/smhd.hpp" +#include "boxes/stsd.hpp" +#include "boxes/stts.hpp" +#include "boxes/tkhd.hpp" +#include "boxes/url.hpp" +#include "boxes/vmhd.hpp" namespace mbmff { @@ -25,8 +34,9 @@ enum class iterator_flags : std::uint32_t { }; MBMFF_FLAG_OPERATORS(iterator_flags) -#define MBMFF_ITERATE_USING(name) using name##_box = basic_box_view; +#define MBMFF_ITERATE_USING(name) using name##_box = mbmff::basic_box_view; MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_USING) +using url_box = mbmff::basic_box_view; #undef MBMFF_ITERATE_USING inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff::box_properties @@ -36,6 +46,8 @@ inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff return mbmff::basic_box_view::properties; switch (type) { MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_PROP) + case mbmff::box_type::url: + return mbmff::basic_box_view::properties; default: return mbmff::box_properties::none; } @@ -44,11 +56,11 @@ inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff inline constexpr bool is_box_implemented(mbmff::box_type type) noexcept { -#define MBMFF_CASE_IMPL(name) \ -case mbmff::box_type::name: +#define MBMFF_CASE_IMPL(name) case mbmff::box_type::name: switch (type) { MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_IMPL) + case mbmff::box_type::url: return true; default: return false; @@ -61,11 +73,8 @@ inline constexpr bool is_container(const mbmff::any_box_view& box) noexcept return mbmff::has(mbmff::get_box_properties(box.type()), mbmff::box_properties::container); } - template -inline constexpr auto parse( - std::span data -) noexcept -> mbmff::result +inline constexpr auto parse(std::span data) noexcept -> mbmff::result { auto header_result = mbmff::parse_box_header(data); if (!header_result) { @@ -103,6 +112,8 @@ inline constexpr auto parse( return mbmff::basic_box_view::validate(box); switch (header.type_) { MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_VALIDATE) + case mbmff::box_type::url: + return mbmff::basic_box_view::validate(box); default: return {box}; } @@ -128,7 +139,7 @@ constexpr auto box_cast(const mbmff::any_box_view& box) noexcept -> mbmff::basic } template - struct box_iterator { +struct box_iterator { using iterator_category = std::forward_iterator_tag; using value_type = mbmff::result; using difference_type = std::ptrdiff_t; @@ -139,9 +150,7 @@ template public: constexpr box_iterator() noexcept = default; - constexpr explicit box_iterator( - std::span data - ) noexcept + constexpr explicit box_iterator(std::span data) noexcept : remaining_(data) {} @@ -215,14 +224,19 @@ template using recursive_box_iterator = mbmff::box_iterator; using partial_box_iterator = mbmff::box_iterator; - #ifdef MBMFF_ENABLE_CONSTEXPR_TEST // parse() without flags rejects truncated data (backward compat) static_assert([] { std::array data{ - std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, - std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x03}, + std::byte{0xE8}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, }; auto r = mbmff::parse(std::span(data)); return !r && r.code == mbmff::error_code::need_more_data && r.needed == 1000; @@ -261,9 +275,7 @@ static_assert([] { std::byte{0x03}, std::byte{0x04}, }; - auto r = mbmff::parse( - std::span(data) - ); + auto r = mbmff::parse(std::span(data)); return r && r.code == mbmff::error_code::success && r->type_ == mbmff::box_type::mdat; }()); @@ -295,9 +307,7 @@ static_assert([] { std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, }; - auto it = mbmff::box_iterator( - std::span(data) - ); + auto it = mbmff::box_iterator(std::span(data)); auto end = it.end(); if (it == end) { @@ -323,22 +333,26 @@ static_assert([] { // box_iterator without allow_partial stops at truncated data static_assert([] { std::array data{ - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, - std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, std::byte{'t'}, - std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, - std::byte{'m'}, std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, - std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, + std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, + std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, }; auto it = mbmff::box_iterator(std::span(data)); auto end = it.end(); - if (it == end) return false; - if (!(*it)) return false; // mdat + if (it == end) { + return false; + } + if (!(*it)) { + return false; // mdat + } ++it; // truncated moov — parse fails → iterator terminates - if (it == end) return false; + if (it == end) { + return false; + } if ((*it).code != mbmff::error_code::need_more_data) { return false; } @@ -356,7 +370,7 @@ static_assert([] { }; auto it = mbmff::box_iterator( std::span(data) - + ); auto end = it.end(); diff --git a/test/formatters.hpp b/test/formatters.hpp index 3efcaa8..90c8f7b 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -1,7 +1,7 @@ #pragma once +#include #include #include -#include //------------------------------------------------------------------------------------------------------------ // ftyp @@ -14,7 +14,9 @@ struct std::formatter : std::formatter { out += std::format(" size=\"{}\" major=\"{}\"", box.size_, v.major_brand.view()); out += std::format(" minor=\"{}\" compatible=\"[", v.minor_version); for (std::size_t i = 0; i < v.compatible_brands.size(); ++i) { - if (i) out += ','; + if (i) { + out += ','; + } out += v.compatible_brands[i].view(); } out += "]\""; @@ -28,10 +30,7 @@ template <> struct std::formatter : std::formatter { auto format(const mbmff::meta_box& box, std::format_context& ctx) const { - return std::formatter::format( - std::format(" size=\"{}\"", box.size_), - ctx - ); + return std::formatter::format(std::format(" size=\"{}\"", box.size_), ctx); } }; @@ -99,7 +98,12 @@ struct std::formatter : std::formatter { { auto v = box.value(); return std::formatter::format( - std::format(" size=\"{}\" image_width=\"{}\" image_height=\"{}\"", box.size_, v.image_width, v.image_height), + std::format( + " size=\"{}\" image_width=\"{}\" image_height=\"{}\"", + box.size_, + v.image_width, + v.image_height + ), ctx ); } @@ -114,7 +118,9 @@ struct std::formatter : std::formatter { auto v = box.value(); std::string out = std::format(" size=\"{}\" bits_per_channel=\"[", box.size_); for (std::size_t i = 0; i < v.bits_per_channel.size(); ++i) { - if (i) out += ','; + if (i) { + out += ','; + } out += std::format("{}", v.bits_per_channel[i]); } out += "]\""; @@ -157,8 +163,12 @@ struct std::formatter : std::formatter { auto format(const mbmff::infe_box& box, std::format_context& ctx) const { auto v = box.value(); - std::string out = std::format(" size=\"{}\" id=\"{}\" protection=\"{}\"", - box.size_, v.item_id, v.item_protection_index); + std::string out = std::format( + " size=\"{}\" id=\"{}\" protection=\"{}\"", + box.size_, + v.item_id, + v.item_protection_index + ); if (!v.item_name.empty()) { out += std::format(" name=\"{}\"", v.item_name); } @@ -191,15 +201,21 @@ struct std::formatter : std::formatter { " chroma_subsampling_x=\"{}\" chroma_subsampling_y=\"{}\"" " chroma_sample_position=\"{}\"", box.size_, - static_cast(v.seq_profile), static_cast(v.seq_level_idx_0), + static_cast(v.seq_profile), + static_cast(v.seq_level_idx_0), static_cast(v.seq_tier_0), - static_cast(v.high_bitdepth), static_cast(v.twelve_bit), + static_cast(v.high_bitdepth), + static_cast(v.twelve_bit), static_cast(v.monochrome), - static_cast(v.chroma_subsampling_x), static_cast(v.chroma_subsampling_y), - static_cast(v.chroma_sample_position)); + static_cast(v.chroma_subsampling_x), + static_cast(v.chroma_subsampling_y), + static_cast(v.chroma_sample_position) + ); if (v.initial_presentation_delay_present) { - out += std::format(" initial_presentation_delay_minus_one=\"{}\"", - static_cast(v.initial_presentation_delay_minus_one)); + out += std::format( + " initial_presentation_delay_minus_one=\"{}\"", + static_cast(v.initial_presentation_delay_minus_one) + ); } if (!v.config_obus.empty()) { out += std::format(" config_obus_size=\"{}\"", v.config_obus.size()); @@ -208,6 +224,58 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// mdhd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::mdhd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" version=\"{}\" creation_time=\"{}\" modification_time=\"{}\"" + " timescale=\"{}\" duration=\"{}\" language=\"{}\" predefined=\"{}\"", + box.size_, + box.version(), + v.creation_time, + v.modification_time, + v.timescale, + v.duration, + v.language.view(), + v.predefined + ), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// dref +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::dref_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), v.entry_count), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// url +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::url_box& box, std::format_context& ctx) const + { + return std::formatter::format( + std::format(" size=\"{}\" version=\"{}\"", box.size_, box.version()), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // mvhd template <> @@ -217,13 +285,144 @@ struct std::formatter : std::formatter { auto v = box.value(); std::string out = std::format( " size=\"{}\" version=\"{}\" creation_time=\"{}\" modification_time=\"{}\"" - " timescale=\"{}\" duration=\"{}\" rate=\"{}\" volume=\"{}\" next_track_id=\"{}\"", - box.size_, box.version(), v.creation_time, v.modification_time, - v.timescale, v.duration, v.rate, v.volume, v.next_track_id); + " timescale=\"{}\" duration=\"{}\" rate=\"{:.2f}\" volume=\"{:.2f}\" next_track_id=\"{}\"", + box.size_, + box.version(), + v.creation_time, + v.modification_time, + v.timescale, + v.duration, + static_cast(v.rate) / 65536.0, + static_cast(v.volume) / 256.0, + v.next_track_id + ); + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// tkhd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::tkhd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" version=\"{}\" creation_time=\"{}\" modification_time=\"{}\"" + " track_id=\"{}\" duration=\"{}\" layer=\"{}\" alternate_group=\"{}\"" + " volume=\"{:.2f}\" width=\"{:.2f}\" height=\"{:.2f}\"", + box.size_, + box.version(), + v.creation_time, + v.modification_time, + v.track_id, + v.duration, + v.layer, + v.alternate_group, + static_cast(v.volume) / 256.0, + static_cast(v.width) / 65536.0, + static_cast(v.height) / 65536.0 + ); return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// elst +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::elst_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size(); ++i) { + auto e = d[i]; + out += std::format( + " entry=\"{}\" segment_duration=\"{}\" media_time=\"{}\"" + " media_rate_integer=\"{}\" media_rate_fraction=\"{}\"", + i, + e.segment_duration, + e.media_time, + e.media_rate_integer, + e.media_rate_fraction + ); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// smhd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::smhd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" version=\"{}\" balance=\"{:.2f}\"", + box.size_, + box.version(), + static_cast(v.balance) / 256.0 + ), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// stts +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stts_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size(); ++i) { + auto e = d[i]; + out += std::format(" sample_count=\"{}\" sample_delta=\"{}\"", e.sample_count, e.sample_delta); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// stsd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stsd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), v.entry_count), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// vmhd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::vmhd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" graphicsmode=\"{}\" opcolor=\"{} {} {}\"", + box.size_, + v.graphicsmode, + v.opcolor[0], + v.opcolor[1], + v.opcolor[2] + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // iloc template <> @@ -232,10 +431,17 @@ struct std::formatter : std::formatter { { auto v = box.value(); return std::formatter::format( - std::format(" size=\"{}\" version=\"{}\" offset_size=\"{}\" length_size=\"{}\"" - " base_offset_size=\"{}\" index_size=\"{}\" item_count=\"{}\"", - box.size_, box.version(), - v.offset_size, v.length_size, v.base_offset_size, v.index_size, v.item_count), + std::format( + " size=\"{}\" version=\"{}\" offset_size=\"{}\" length_size=\"{}\"" + " base_offset_size=\"{}\" index_size=\"{}\" item_count=\"{}\"", + box.size_, + box.version(), + v.offset_size, + v.length_size, + v.base_offset_size, + v.index_size, + v.item_count + ), ctx ); } @@ -331,6 +537,33 @@ static void print_box(const mbmff::any_box_view& box, std::size_t depth) case mbmff::box_type::mvhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::tkhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::elst: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::mdhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::stsd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::stts: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::smhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::vmhd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::dref: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::url: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; default: std::cout << " size=\"" << box.size_ << '"'; break; From 7d9e30b1642be6b18e169df2e6329b561c717ea7 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 16 Jun 2026 23:11:36 +0200 Subject: [PATCH 5/6] a lot more bawxes. More constexpr verification and stripped these from single header output --- include/mbmff/boxes/alis.hpp | 37 ++ include/mbmff/boxes/apcn.hpp | 62 +++ include/mbmff/boxes/avc1.hpp | 62 +++ include/mbmff/boxes/avcC.hpp | 75 ++++ include/mbmff/boxes/box_view.hpp | 82 ++-- include/mbmff/boxes/co64.hpp | 102 +++++ include/mbmff/boxes/colr.hpp | 74 ++++ include/mbmff/boxes/common.hpp | 6 +- include/mbmff/boxes/containers.hpp | 3 + include/mbmff/boxes/ctts.hpp | 120 ++++++ include/mbmff/boxes/esds.hpp | 155 ++++++++ include/mbmff/boxes/fiel.hpp | 71 ++++ include/mbmff/boxes/free.hpp | 13 + include/mbmff/boxes/gmin.hpp | 79 ++++ include/mbmff/boxes/hev1.hpp | 62 +++ include/mbmff/boxes/hvcC.hpp | 102 +++++ include/mbmff/boxes/load.hpp | 77 ++++ include/mbmff/boxes/mp4a.hpp | 54 +++ include/mbmff/boxes/sdtp.hpp | 105 +++++ include/mbmff/boxes/stco.hpp | 110 ++++++ include/mbmff/boxes/stsc.hpp | 122 ++++++ include/mbmff/boxes/stss.hpp | 110 ++++++ include/mbmff/boxes/stsz.hpp | 146 +++++++ include/mbmff/boxes/tmcd.hpp | 68 ++++ include/mbmff/boxes/wide.hpp | 13 + include/mbmff/mbmff.hpp | 375 +++++++++++------- test/formatters.hpp | 609 ++++++++++++++++++++++++++++- tools/amalgamate.py | 10 + 28 files changed, 2724 insertions(+), 180 deletions(-) create mode 100644 include/mbmff/boxes/alis.hpp create mode 100644 include/mbmff/boxes/apcn.hpp create mode 100644 include/mbmff/boxes/avc1.hpp create mode 100644 include/mbmff/boxes/avcC.hpp create mode 100644 include/mbmff/boxes/co64.hpp create mode 100644 include/mbmff/boxes/colr.hpp create mode 100644 include/mbmff/boxes/ctts.hpp create mode 100644 include/mbmff/boxes/esds.hpp create mode 100644 include/mbmff/boxes/fiel.hpp create mode 100644 include/mbmff/boxes/free.hpp create mode 100644 include/mbmff/boxes/gmin.hpp create mode 100644 include/mbmff/boxes/hev1.hpp create mode 100644 include/mbmff/boxes/hvcC.hpp create mode 100644 include/mbmff/boxes/load.hpp create mode 100644 include/mbmff/boxes/mp4a.hpp create mode 100644 include/mbmff/boxes/sdtp.hpp create mode 100644 include/mbmff/boxes/stco.hpp create mode 100644 include/mbmff/boxes/stsc.hpp create mode 100644 include/mbmff/boxes/stss.hpp create mode 100644 include/mbmff/boxes/stsz.hpp create mode 100644 include/mbmff/boxes/tmcd.hpp create mode 100644 include/mbmff/boxes/wide.hpp diff --git a/include/mbmff/boxes/alis.hpp b/include/mbmff/boxes/alis.hpp new file mode 100644 index 0000000..e9bc989 --- /dev/null +++ b/include/mbmff/boxes/alis.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct alis_data {}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::alis_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + (void)box.payload.subspan(0); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::alis_data +{ + return {}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/apcn.hpp b/include/mbmff/boxes/apcn.hpp new file mode 100644 index 0000000..52ff7cf --- /dev/null +++ b/include/mbmff/boxes/apcn.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct apcn_data { + std::uint16_t data_reference_index; + std::uint16_t width; + std::uint16_t height; + std::uint32_t horizresolution; + std::uint32_t vertresolution; + std::uint16_t frame_count; + std::span compressorname; + std::uint16_t depth; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::apcn_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 78) { + return mbmff::make_result(mbmff::error_code::need_more_data, 78); + } + box.payload = box.payload.subspan(78); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::apcn_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto span = std::span(payload.data() - 78, 78); + return { + mbmff::read_be(span.subspan(6)), + mbmff::read_be(span.subspan(24)), + mbmff::read_be(span.subspan(26)), + mbmff::read_be(span.subspan(28)), + mbmff::read_be(span.subspan(32)), + mbmff::read_be(span.subspan(40)), + span.subspan(42, 32), + mbmff::read_be(span.subspan(74)), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 78)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/avc1.hpp b/include/mbmff/boxes/avc1.hpp new file mode 100644 index 0000000..a3b62e6 --- /dev/null +++ b/include/mbmff/boxes/avc1.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct avc1_data { + std::uint16_t data_reference_index; + std::uint16_t width; + std::uint16_t height; + std::uint32_t horizresolution; + std::uint32_t vertresolution; + std::uint16_t frame_count; + std::span compressorname; + std::uint16_t depth; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::avc1_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 78) { + return mbmff::make_result(mbmff::error_code::need_more_data, 78); + } + box.payload = box.payload.subspan(78); + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::avc1_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto span = std::span(payload.data() - 78, 78); + return { + mbmff::read_be(span.subspan(6)), + mbmff::read_be(span.subspan(24)), + mbmff::read_be(span.subspan(26)), + mbmff::read_be(span.subspan(28)), + mbmff::read_be(span.subspan(32)), + mbmff::read_be(span.subspan(40)), + span.subspan(42, 32), + mbmff::read_be(span.subspan(74)), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 78)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/avcC.hpp b/include/mbmff/boxes/avcC.hpp new file mode 100644 index 0000000..aa8bdac --- /dev/null +++ b/include/mbmff/boxes/avcC.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct avcC_data { + std::uint8_t configurationVersion = 0; + std::uint8_t AVCProfileIndication = 0; + std::uint8_t profile_compatibility = 0; + std::uint8_t AVCLevelIndication = 0; + std::uint8_t lengthSizeMinusOne = 0; + std::span nalu_data{}; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::avcC_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 6) { + return mbmff::make_result(mbmff::error_code::need_more_data, 6); + } + return {box}; +} + +constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::avcC_data +{ + avcC_data result{}; + result.configurationVersion = static_cast(payload[0]); + result.AVCProfileIndication = static_cast(payload[1]); + result.profile_compatibility = static_cast(payload[2]); + result.AVCLevelIndication = static_cast(payload[3]); + result.lengthSizeMinusOne = static_cast(payload[4]) & 0x03; + result.nalu_data = payload.subspan(6); + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert([] { + constexpr std::byte min_data[6]{}; + auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(min_data)}); + return static_cast(r); +}()); + +static_assert([] { + constexpr std::array data{ + std::byte{0x01}, + std::byte{0x64}, + std::byte{0x00}, + std::byte{0x1e}, + std::byte{0xff}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x04}, + std::byte{0xbe}, + std::byte{0xef}, + }; + mbmff::basic_box_view avcc; + avcc.payload = std::span(data); + auto v = avcc.value(); + return v.configurationVersion == 1 && v.AVCProfileIndication == 100 && v.profile_compatibility == 0 + && v.AVCLevelIndication == 30 && v.lengthSizeMinusOne == 3 && v.nalu_data.size() == 4; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/box_view.hpp b/include/mbmff/boxes/box_view.hpp index fa589c5..e757b2d 100644 --- a/include/mbmff/boxes/box_view.hpp +++ b/include/mbmff/boxes/box_view.hpp @@ -3,43 +3,68 @@ #include "common.hpp" #define MBMFF_ITERATE_BOX_TYPES(MACRO) \ + MACRO(alis) \ + MACRO(apcn) \ + MACRO(av1C) \ + MACRO(avc1) \ + MACRO(avcC) \ + MACRO(co64) \ + MACRO(colr) \ + MACRO(ctts) \ + MACRO(dinf) \ + MACRO(dref) \ + MACRO(edts) \ + MACRO(elst) \ + MACRO(esds) \ + MACRO(fiel) \ + MACRO(free) \ MACRO(ftyp) \ - MACRO(meta) \ + MACRO(gmhd) \ + MACRO(gmin) \ + MACRO(hdlr) \ + MACRO(hev1) \ + MACRO(hvcC) \ + MACRO(iinf) \ + MACRO(iloc) \ + MACRO(ilst) \ + MACRO(infe) \ + MACRO(ipco) \ + MACRO(ipma) \ + MACRO(iprp) \ + MACRO(iref) \ + MACRO(ispe) \ + MACRO(load) \ MACRO(mdat) \ - MACRO(moov) \ - MACRO(trak) \ + MACRO(mdhd) \ MACRO(mdia) \ + MACRO(meta) \ + MACRO(mfra) \ MACRO(minf) \ - MACRO(stbl) \ - MACRO(dinf) \ - MACRO(edts) \ - MACRO(udta) \ - MACRO(mvex) \ MACRO(moof) \ - MACRO(traf) \ - MACRO(mfra) \ - MACRO(iprp) \ - MACRO(ipco) \ - MACRO(iinf) \ - MACRO(iref) \ - MACRO(iloc) \ - MACRO(hdlr) \ + MACRO(moov) \ + MACRO(mp4a) \ + MACRO(mvex) \ + MACRO(mvhd) \ + MACRO(pasp) \ MACRO(pitm) \ - MACRO(ispe) \ - MACRO(av1C) \ MACRO(pixi) \ - MACRO(ipma) \ - MACRO(pasp) \ - MACRO(infe) \ - MACRO(mvhd) \ - MACRO(tkhd) \ - MACRO(elst) \ - MACRO(mdhd) \ - MACRO(vmhd) \ + MACRO(sdtp) \ MACRO(smhd) \ - MACRO(stts) \ + MACRO(stbl) \ + MACRO(stco) \ + MACRO(stsc) \ MACRO(stsd) \ - MACRO(dref) + MACRO(stss) \ + MACRO(stsz) \ + MACRO(stts) \ + MACRO(tkhd) \ + MACRO(tmcd) \ + MACRO(traf) \ + MACRO(trak) \ + MACRO(tref) \ + MACRO(udta) \ + MACRO(vmhd) \ + MACRO(wide) #define MBMFF_FLAG_OPERATORS(EnumType) \ constexpr auto operator|(EnumType a, EnumType b) noexcept -> EnumType \ @@ -138,6 +163,7 @@ struct box_view_base : mbmff::box_header { template struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static bool not_implemented = true; }; //------------------------------------------------------------------------------------------------------------ diff --git a/include/mbmff/boxes/co64.hpp b/include/mbmff/boxes/co64.hpp new file mode 100644 index 0000000..0a85750 --- /dev/null +++ b/include/mbmff/boxes/co64.hpp @@ -0,0 +1,102 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct co64_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> std::uint64_t + { + auto offset = index * 8; + if (offset + 8 > entries_data.size()) { + return 0; + } + return mbmff::read_be(entries_data.subspan(offset)); + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::co64_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 8; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::co64_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x02}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x64}, + }; + mbmff::basic_box_view co64; + co64.version_ = 0; + co64.payload = std::span(data); + auto v = co64.value(); + if (v.size() != 2) { + return false; + } + return v[0] == 1 && v[1] == 100; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/colr.hpp b/include/mbmff/boxes/colr.hpp new file mode 100644 index 0000000..acbaded --- /dev/null +++ b/include/mbmff/boxes/colr.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct colr_data { + mbmff::fourcc_string colour_type; + std::uint16_t colour_primaries; + std::uint16_t transfer_characteristics; + std::uint16_t matrix_coefficients; + bool full_range_flag; + std::span icc_profile; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::colr_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + auto colour_type = mbmff::read_be(box.payload); + if (colour_type == mbmff::fourcc("nclx")) { + if (box.payload.size() < 10) { + return mbmff::make_result(mbmff::error_code::need_more_data, 10); + } + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::colr_data +{ + if (payload.size() < 4) { + return {}; + } + auto colour_type_raw = mbmff::read_be(payload); + auto colour_type = mbmff::fourcc_string::from_uint32(colour_type_raw); + if (colour_type_raw == mbmff::fourcc("nclx")) { + return { + colour_type, + mbmff::read_be(payload.subspan(4)), + mbmff::read_be(payload.subspan(6)), + mbmff::read_be(payload.subspan(8)), + static_cast(payload[10] & std::byte{0x80}), + {}, + }; + } + return { + colour_type, + 0, + 0, + 0, + false, + payload.subspan(4), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 10)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/common.hpp b/include/mbmff/boxes/common.hpp index 3be360c..bc242ad 100644 --- a/include/mbmff/boxes/common.hpp +++ b/include/mbmff/boxes/common.hpp @@ -86,8 +86,10 @@ constexpr auto make_result(Type value, mbmff::error_code code) noexcept -> mbmff constexpr auto fourcc(const char* str) noexcept -> std::uint32_t { - return (static_cast(str[3]) << 24) | (static_cast(str[2]) << 16) - | (static_cast(str[1]) << 8) | static_cast(str[0]); + return (static_cast(static_cast(str[3])) << 24) + | (static_cast(static_cast(str[2])) << 16) + | (static_cast(static_cast(str[1])) << 8) + | static_cast(static_cast(str[0])); } struct fourcc_string { diff --git a/include/mbmff/boxes/containers.hpp b/include/mbmff/boxes/containers.hpp index 073bd28..6107d34 100644 --- a/include/mbmff/boxes/containers.hpp +++ b/include/mbmff/boxes/containers.hpp @@ -23,6 +23,8 @@ MBMFF_DEFINE_PURE_CONTAINER(minf); MBMFF_DEFINE_PURE_CONTAINER(stbl); MBMFF_DEFINE_PURE_CONTAINER(dinf); MBMFF_DEFINE_PURE_CONTAINER(edts); +MBMFF_DEFINE_PURE_CONTAINER(gmhd); +MBMFF_DEFINE_PURE_CONTAINER(tref); MBMFF_DEFINE_PURE_CONTAINER(mvex); MBMFF_DEFINE_PURE_CONTAINER(udta); MBMFF_DEFINE_PURE_CONTAINER(moof); @@ -30,6 +32,7 @@ MBMFF_DEFINE_PURE_CONTAINER(traf); MBMFF_DEFINE_PURE_CONTAINER(mfra); MBMFF_DEFINE_PURE_CONTAINER(iprp); MBMFF_DEFINE_PURE_CONTAINER(ipco); +MBMFF_DEFINE_PURE_CONTAINER(ilst); #undef MBMFF_DEFINE_PURE_CONTAINER diff --git a/include/mbmff/boxes/ctts.hpp b/include/mbmff/boxes/ctts.hpp new file mode 100644 index 0000000..55eccbb --- /dev/null +++ b/include/mbmff/boxes/ctts.hpp @@ -0,0 +1,120 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct ctts_entry { + std::uint32_t sample_count; + std::int32_t sample_offset; +}; + +struct ctts_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::ctts_entry + { + auto offset = index * 8; + if (offset + 8 > entries_data.size()) { + return {}; + } + auto span = entries_data.subspan(offset); + return { + mbmff::read_be(span.subspan(0)), + static_cast(mbmff::read_be(span.subspan(4))), + }; + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::ctts_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 8; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ctts_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x05}, + std::byte{0xFF}, + std::byte{0xFF}, + std::byte{0xFF}, + std::byte{0xFE}, + }; + mbmff::basic_box_view ctts; + ctts.version_ = 0; + ctts.payload = std::span(data); + auto v = ctts.value(); + if (v.size() != 1) { + return false; + } + auto e = v[0]; + return e.sample_count == 5 && e.sample_offset == -2; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/esds.hpp b/include/mbmff/boxes/esds.hpp new file mode 100644 index 0000000..f52f635 --- /dev/null +++ b/include/mbmff/boxes/esds.hpp @@ -0,0 +1,155 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct esds_data { + std::uint8_t object_type = 0; + std::uint8_t stream_type = 0; + std::uint32_t buffer_size = 0; + std::uint32_t max_bitrate = 0; + std::uint32_t avg_bitrate = 0; + std::span audio_specific_config{}; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::esds_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + return {box}; +} + +constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::esds_data +{ + esds_data result{}; + if (payload.size() < 2) { + return result; + } + + std::size_t pos = 0; + + if (pos >= payload.size() || static_cast(payload[pos++]) != 0x03) { + return result; + } + + for (int i = 0; i < 4 && pos < payload.size(); ++i) { + auto b = static_cast(payload[pos++]); + if (!(b & 0x80)) { + break; + } + } + + if (pos + 3 > payload.size()) { + return result; + } + pos += 2; + auto flags = static_cast(payload[pos++]); + + if (flags & 0x80) { + pos += 2; + } + if (flags & 0x40) { + if (pos >= payload.size()) { + return result; + } + pos += 1 + static_cast(payload[pos]); + } + if (flags & 0x20) { + pos += 2; + } + + while (pos + 1 < payload.size()) { + auto tag = static_cast(payload[pos++]); + auto len_start = pos; + std::uint32_t dcd_len = 0; + for (int i = 0; i < 4 && pos < payload.size(); ++i) { + auto b = static_cast(payload[pos++]); + dcd_len = (dcd_len << 7) | (b & 0x7F); + if (!(b & 0x80)) { + break; + } + } + + if (tag == 0x04) { + if (pos + 13 > payload.size()) { + return result; + } + result.object_type = static_cast(payload[pos]); + result.stream_type = (static_cast(payload[pos + 1]) >> 2) & 0x3F; + result.buffer_size = (static_cast(static_cast(payload[pos + 2])) << 16) + | (static_cast(payload[pos + 3]) << 8) + | static_cast(payload[pos + 4]); + result.max_bitrate = mbmff::read_be(payload.subspan(pos + 5)); + result.avg_bitrate = mbmff::read_be(payload.subspan(pos + 9)); + + { + auto inner = pos + 13; + auto inner_end = pos + dcd_len; + while (inner + 1 < inner_end && inner < payload.size()) { + auto t = static_cast(payload[inner]); + if (t > 0x3F) { + ++inner; + continue; + } + ++inner; + std::uint32_t inner_len = 0; + for (int i = 0; i < 4 && inner < payload.size(); ++i) { + auto b = static_cast(payload[inner++]); + inner_len = (inner_len << 7) | (b & 0x7F); + if (!(b & 0x80)) { + break; + } + } + if (t == 0x05 && inner + inner_len <= payload.size()) { + result.audio_specific_config = payload.subspan(inner, inner_len); + break; + } + inner += inner_len; + } + } + break; + } + pos = len_start + dcd_len; + } + + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 4)} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0x03}, std::byte{0x18}, std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x04}, + std::byte{0x13}, std::byte{0x40}, std::byte{0x14}, std::byte{0x00}, std::byte{0x00}, std::byte{0x10}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x05}, std::byte{0x04}, std::byte{0x11}, std::byte{0x90}, + std::byte{0x20}, std::byte{0x12}, + }; + mbmff::basic_box_view esds; + esds.version_ = 0; + esds.payload = std::span(data); + auto v = esds.value(); + return v.object_type == 0x40 && v.stream_type == 5 && v.buffer_size == 0x10 && v.max_bitrate == 0 + && v.avg_bitrate == 0 && v.audio_specific_config.size() == 4; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/fiel.hpp b/include/mbmff/boxes/fiel.hpp new file mode 100644 index 0000000..b0d9aa4 --- /dev/null +++ b/include/mbmff/boxes/fiel.hpp @@ -0,0 +1,71 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct fiel_data { + std::uint16_t field_count; + std::uint8_t field_details; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::fiel_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 2) { + return mbmff::make_result(mbmff::error_code::need_more_data, 2); + } + auto field_count = mbmff::read_be(box.payload); + if (field_count == 2 && box.payload.size() < 3) { + return mbmff::make_result(mbmff::error_code::need_more_data, 3); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::fiel_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto field_count = mbmff::read_be(payload); + auto field_details = (field_count == 2 && payload.size() >= 3) ? static_cast(payload[2]) + : std::uint8_t{0}; + return {field_count, field_details}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 2 + )} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x01}, + }; + mbmff::basic_box_view fiel; + fiel.payload = std::span(data); + auto v = fiel.value(); + return v.field_count == 1 && v.field_details == 0; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/free.hpp b/include/mbmff/boxes/free.hpp new file mode 100644 index 0000000..951d4d4 --- /dev/null +++ b/include/mbmff/boxes/free.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct free_data {}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; +}; + +} // namespace mbmff diff --git a/include/mbmff/boxes/gmin.hpp b/include/mbmff/boxes/gmin.hpp new file mode 100644 index 0000000..940c4d9 --- /dev/null +++ b/include/mbmff/boxes/gmin.hpp @@ -0,0 +1,79 @@ +#pragma once +#include +#include "box_view.hpp" + +namespace mbmff { + +struct gmin_data { + std::uint16_t graphicsmode = 0; + std::array opcolor{}; + std::uint16_t balance = 0; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::gmin_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 12) { + return mbmff::make_result(mbmff::error_code::need_more_data, 12); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::gmin_data +{ + mbmff::gmin_data result{}; + if (payload.empty() && payload.data() == nullptr) { + return result; + } + result.graphicsmode = mbmff::read_be(payload.subspan(0)); + for (int i = 0; i < 3; ++i) { + result.opcolor[i] = mbmff::read_be(payload.subspan(2 + i * 2)); + } + result.balance = mbmff::read_be(payload.subspan(8)); + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{std::byte{0x00}}.data(), 4)} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + }; + mbmff::basic_box_view gmin; + gmin.version_ = 0; + gmin.payload = std::span(data); + auto v = gmin.value(); + return v.graphicsmode == 0 && v.opcolor[0] == 0 && v.opcolor[1] == 0 && v.opcolor[2] == 0 && v.balance == 0; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/hev1.hpp b/include/mbmff/boxes/hev1.hpp new file mode 100644 index 0000000..69b48a6 --- /dev/null +++ b/include/mbmff/boxes/hev1.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct hev1_data { + std::uint16_t data_reference_index; + std::uint16_t width; + std::uint16_t height; + std::uint32_t horizresolution; + std::uint32_t vertresolution; + std::uint16_t frame_count; + std::span compressorname; + std::uint16_t depth; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::hev1_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 78) { + return mbmff::make_result(mbmff::error_code::need_more_data, 78); + } + box.payload = box.payload.subspan(78); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::hev1_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto span = std::span(payload.data() - 78, 78); + return { + mbmff::read_be(span.subspan(6)), + mbmff::read_be(span.subspan(24)), + mbmff::read_be(span.subspan(26)), + mbmff::read_be(span.subspan(28)), + mbmff::read_be(span.subspan(32)), + mbmff::read_be(span.subspan(40)), + span.subspan(42, 32), + mbmff::read_be(span.subspan(74)), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 78)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/hvcC.hpp b/include/mbmff/boxes/hvcC.hpp new file mode 100644 index 0000000..921f9c7 --- /dev/null +++ b/include/mbmff/boxes/hvcC.hpp @@ -0,0 +1,102 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct hvcC_data { + std::uint8_t configuration_version; + std::uint8_t general_profile_space; + std::uint8_t general_tier_flag; + std::uint8_t general_profile_idc; + std::uint32_t general_profile_compatibility_flags; + std::uint64_t general_constraint_indicator_flags; + std::uint8_t general_level_idc; + std::uint16_t min_spatial_segmentation_idc; + std::uint8_t parallelism_type; + std::uint8_t chroma_format; + std::uint8_t bit_depth_luma_minus8; + std::uint8_t bit_depth_chroma_minus8; + std::uint16_t avg_frame_rate; + std::uint8_t constant_frame_rate; + std::uint8_t num_temporal_layers; + std::uint8_t temporal_id_nested; + std::uint8_t length_size_minus_one; + std::uint8_t num_arrays; + std::span arrays_data; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::hvcC_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 23) { + return mbmff::make_result(mbmff::error_code::need_more_data, 23); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::hvcC_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto span = payload; + auto b1 = static_cast(span[1]); + auto b21 = static_cast(span[21]); + auto ci_hi = mbmff::read_be(span.subspan(6)); + auto ci_lo = mbmff::read_be(span.subspan(8)); + return { + .configuration_version = static_cast(span[0]), + .general_profile_space = static_cast((b1 >> 6) & 3), + .general_tier_flag = static_cast((b1 >> 5) & 1), + .general_profile_idc = static_cast(b1 & 0x1F), + .general_profile_compatibility_flags = mbmff::read_be(span.subspan(2)), + .general_constraint_indicator_flags = (static_cast(ci_hi) << 32) | ci_lo, + .general_level_idc = static_cast(span[12]), + .min_spatial_segmentation_idc = static_cast( + mbmff::read_be(span.subspan(13)) & 0x0FFF + ), + .parallelism_type = static_cast(static_cast(span[15]) & 3), + .chroma_format = static_cast(static_cast(span[16]) & 3), + .bit_depth_luma_minus8 = static_cast(static_cast(span[17]) & 7), + .bit_depth_chroma_minus8 = static_cast(static_cast(span[18]) & 7), + .avg_frame_rate = mbmff::read_be(span.subspan(19)), + .constant_frame_rate = static_cast((b21 >> 6) & 3), + .num_temporal_layers = static_cast((b21 >> 3) & 7), + .temporal_id_nested = static_cast((b21 >> 2) & 1), + .length_size_minus_one = static_cast(b21 & 3), + .num_arrays = static_cast(span[22]), + .arrays_data = span.subspan(23), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 23)} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + }; + mbmff::basic_box_view hvcC; + hvcC.payload = std::span(data); + auto v = hvcC.value(); + return v.configuration_version == 1; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/load.hpp b/include/mbmff/boxes/load.hpp new file mode 100644 index 0000000..7d5b40c --- /dev/null +++ b/include/mbmff/boxes/load.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct load_data { + std::int32_t preload_time; + std::int32_t preload_size; + std::uint8_t default_loading; + std::int32_t duration_to_load; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::load_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 16) { + return mbmff::make_result(mbmff::error_code::need_more_data, 16); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::load_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + return { + .preload_time = mbmff::read_be(payload), + .preload_size = mbmff::read_be(payload.subspan(4)), + .default_loading = static_cast(payload[8]), + .duration_to_load = mbmff::read_be(payload.subspan(12)), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 16)} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0xFF}, + std::byte{0xFF}, + std::byte{0xFF}, + std::byte{0xFF}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x64}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x32}, + }; + mbmff::basic_box_view load; + load.payload = std::span(data); + auto v = load.value(); + return v.preload_time == -1 && v.preload_size == 100 && v.default_loading == 1 && v.duration_to_load == 50; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/mp4a.hpp b/include/mbmff/boxes/mp4a.hpp new file mode 100644 index 0000000..c7c701d --- /dev/null +++ b/include/mbmff/boxes/mp4a.hpp @@ -0,0 +1,54 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct mp4a_data { + std::uint16_t data_reference_index; + std::uint16_t channelcount; + std::uint16_t samplesize; + std::uint32_t samplerate; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::mp4a_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 28) { + return mbmff::make_result(mbmff::error_code::need_more_data, 28); + } + box.payload = box.payload.subspan(28); + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::mp4a_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto span = std::span(payload.data() - 28, 28); + return { + mbmff::read_be(span.subspan(6)), // data_reference_index + mbmff::read_be(span.subspan(16)), // channelcount + mbmff::read_be(span.subspan(18)), // samplesize + mbmff::read_be(span.subspan(24)), // samplerate + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 28)} +))); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/sdtp.hpp b/include/mbmff/boxes/sdtp.hpp new file mode 100644 index 0000000..b6883c6 --- /dev/null +++ b/include/mbmff/boxes/sdtp.hpp @@ -0,0 +1,105 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct sdtp_entry { + std::uint8_t is_leading; + std::uint8_t sample_depends_on; + std::uint8_t sample_is_depended_on; + std::uint8_t sample_has_redundancy; +}; + +struct sdtp_data { + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::size_t + { + return entries_data.size(); + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::sdtp_entry + { + auto byte = static_cast(entries_data[index]); + return { + .is_leading = static_cast((byte >> 6) & 3), + .sample_depends_on = static_cast((byte >> 4) & 3), + .sample_is_depended_on = static_cast((byte >> 2) & 3), + .sample_has_redundancy = static_cast(byte & 3), + }; + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::sdtp_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + (void)box.payload.subspan(0); + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::sdtp_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + return {payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0xC0}, + std::byte{0xE0}, + std::byte{0x80}, + std::byte{0xFF}, + }; + mbmff::basic_box_view sdtp; + sdtp.version_ = 0; + sdtp.payload = std::span(data); + auto v = sdtp.value(); + if (v.size() != 4) { + return false; + } + // byte 0xC0 = 1100 0000 -> is_leading=3, depends_on=0, depended_on=0, redundancy=0 + if (v[0].is_leading != 3 || v[0].sample_depends_on != 0 || v[0].sample_is_depended_on != 0 + || v[0].sample_has_redundancy != 0) { + return false; + } + // byte 0xE0 = 1110 0000 -> is_leading=3, depends_on=2, depended_on=0, redundancy=0 + if (v[1].is_leading != 3 || v[1].sample_depends_on != 2 || v[1].sample_is_depended_on != 0 + || v[1].sample_has_redundancy != 0) { + return false; + } + // byte 0x80 = 1000 0000 -> is_leading=2, depends_on=0, depended_on=0, redundancy=0 + if (v[2].is_leading != 2 || v[2].sample_depends_on != 0 || v[2].sample_is_depended_on != 0 + || v[2].sample_has_redundancy != 0) { + return false; + } + // byte 0xFF = 1111 1111 -> all 3 + if (v[3].is_leading != 3 || v[3].sample_depends_on != 3 || v[3].sample_is_depended_on != 3 + || v[3].sample_has_redundancy != 3) { + return false; + } + return true; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stco.hpp b/include/mbmff/boxes/stco.hpp new file mode 100644 index 0000000..5570a85 --- /dev/null +++ b/include/mbmff/boxes/stco.hpp @@ -0,0 +1,110 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stco_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> std::uint32_t + { + auto offset = index * 4; + if (offset + 4 > entries_data.size()) { + return 0; + } + return mbmff::read_be(entries_data.subspan(offset)); + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stco_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 4; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stco_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x02}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x64}, + }; + mbmff::basic_box_view stco; + stco.version_ = 0; + stco.payload = std::span(data); + auto v = stco.value(); + if (v.size() != 2) { + return false; + } + return v[0] == 1 && v[1] == 100; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stsc.hpp b/include/mbmff/boxes/stsc.hpp new file mode 100644 index 0000000..a9c616c --- /dev/null +++ b/include/mbmff/boxes/stsc.hpp @@ -0,0 +1,122 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stsc_entry { + std::uint32_t first_chunk; + std::uint32_t samples_per_chunk; + std::uint32_t sample_description_index; +}; + +struct stsc_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::stsc_entry + { + auto offset = index * 12; + auto span = entries_data.subspan(offset, 12); + return { + mbmff::read_be(span.subspan(0, 4)), + mbmff::read_be(span.subspan(4, 4)), + mbmff::read_be(span.subspan(8, 4)), + }; + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stsc_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 12; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stsc_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x02}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + }; + mbmff::basic_box_view stsc; + stsc.version_ = 0; + stsc.payload = std::span(data); + auto v = stsc.value(); + if (v.size() != 1) { + return false; + } + return v[0].first_chunk == 1 && v[0].samples_per_chunk == 2 && v[0].sample_description_index == 1; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stss.hpp b/include/mbmff/boxes/stss.hpp new file mode 100644 index 0000000..39803f4 --- /dev/null +++ b/include/mbmff/boxes/stss.hpp @@ -0,0 +1,110 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stss_data { + std::uint32_t entry_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return entry_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> std::uint32_t + { + auto offset = index * 4; + if (offset + 4 > entries_data.size()) { + return 0; + } + return mbmff::read_be(entries_data.subspan(offset)); + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stss_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + auto entry_count = mbmff::read_be(box.payload); + auto needed = std::size_t{4} + entry_count * 4; + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stss_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto entry_count = mbmff::read_be(payload); + return {entry_count, payload.subspan(4)}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x02}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x64}, + }; + mbmff::basic_box_view stss; + stss.version_ = 0; + stss.payload = std::span(data); + auto v = stss.value(); + if (v.size() != 2) { + return false; + } + return v[0] == 1 && v[1] == 100; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/stsz.hpp b/include/mbmff/boxes/stsz.hpp new file mode 100644 index 0000000..351a89b --- /dev/null +++ b/include/mbmff/boxes/stsz.hpp @@ -0,0 +1,146 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct stsz_data { + std::uint32_t sample_size = 0; + std::uint32_t sample_count = 0; + std::span entries_data{}; + + constexpr auto size() const noexcept -> std::uint32_t + { + return sample_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> std::uint32_t + { + if (sample_size != 0) { + return sample_size; + } + auto offset = index * 4; + if (offset + 4 > entries_data.size()) { + return 0; + } + return mbmff::read_be(entries_data.subspan(offset)); + } +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::stsz_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 8) { + return mbmff::make_result(mbmff::error_code::need_more_data, 8); + } + + auto sample_size = mbmff::read_be(box.payload); + auto sample_count = mbmff::read_be(box.payload.subspan(4)); + auto needed = std::size_t{8} + (sample_size == 0 ? sample_count * 4 : 0); + if (box.payload.size() < needed) { + return mbmff::make_result(mbmff::error_code::need_more_data, needed); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::stsz_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + auto sample_size = mbmff::read_be(payload); + auto sample_count = mbmff::read_be(payload.subspan(4)); + auto entries_data = sample_size == 0 ? payload.subspan(8) : std::span{}; + return {sample_size, sample_count, entries_data}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, + std::span( + std::array{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x01}, + } + .data(), + 8 + )} +)); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x64}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x05}, + }; + mbmff::basic_box_view stsz; + stsz.version_ = 0; + stsz.payload = std::span(data); + auto v = stsz.value(); + if (v.size() != 5) { + return false; + } + if (v[0] != 100 || v[4] != 100) { + return false; + } + return true; +}()); + +// variable sample_size == 0, entry table +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x02}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x0A}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x14}, + }; + mbmff::basic_box_view stsz; + stsz.version_ = 0; + stsz.payload = std::span(data); + auto v = stsz.value(); + if (v.size() != 2) { + return false; + } + return v[0] == 10 && v[1] == 20; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/tmcd.hpp b/include/mbmff/boxes/tmcd.hpp new file mode 100644 index 0000000..428e3d9 --- /dev/null +++ b/include/mbmff/boxes/tmcd.hpp @@ -0,0 +1,68 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct tmcd_data { + std::uint16_t data_reference_index; + std::uint32_t flags; + std::uint32_t timescale; + std::uint32_t frame_duration; + std::uint16_t number_of_frames; +}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::tmcd_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 24) { + return mbmff::make_result(mbmff::error_code::need_more_data, 24); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::tmcd_data +{ + if (payload.empty() && payload.data() == nullptr) { + return {}; + } + return { + mbmff::read_be(payload.subspan(8)), + mbmff::read_be(payload.subspan(10)), + mbmff::read_be(payload.subspan(14)), + mbmff::read_be(payload.subspan(18)), + mbmff::read_be(payload.subspan(22)), + }; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + !mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); + +static_assert(static_cast(mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(std::array{}.data(), 24)} +))); + +static_assert([] { + constexpr std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x01}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x02}, std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x19}, std::byte{0x00}, std::byte{0x1E}, + }; + mbmff::basic_box_view tmcd; + tmcd.payload = std::span(data); + auto v = tmcd.value(); + return v.data_reference_index == 1 && v.flags == 2 && v.timescale == 3 && v.frame_duration == 25 + && v.number_of_frames == 30; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/wide.hpp b/include/mbmff/boxes/wide.hpp new file mode 100644 index 0000000..9721466 --- /dev/null +++ b/include/mbmff/boxes/wide.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct wide_data {}; + +template <> +struct mbmff::basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; +}; + +} // namespace mbmff diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index 59e7877..bb9ce40 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -1,29 +1,51 @@ #pragma once +#include "boxes/alis.hpp" +#include "boxes/apcn.hpp" #include "boxes/av1C.hpp" +#include "boxes/avc1.hpp" +#include "boxes/avcC.hpp" +#include "boxes/co64.hpp" +#include "boxes/colr.hpp" #include "boxes/containers.hpp" +#include "boxes/ctts.hpp" #include "boxes/dref.hpp" #include "boxes/elst.hpp" +#include "boxes/esds.hpp" +#include "boxes/fiel.hpp" +#include "boxes/free.hpp" #include "boxes/ftyp.hpp" +#include "boxes/gmin.hpp" #include "boxes/hdlr.hpp" +#include "boxes/hev1.hpp" +#include "boxes/hvcC.hpp" #include "boxes/iinf.hpp" #include "boxes/iloc.hpp" #include "boxes/infe.hpp" #include "boxes/ipma.hpp" #include "boxes/iref.hpp" #include "boxes/ispe.hpp" +#include "boxes/load.hpp" #include "boxes/mdat.hpp" #include "boxes/mdhd.hpp" #include "boxes/meta.hpp" +#include "boxes/mp4a.hpp" #include "boxes/mvhd.hpp" #include "boxes/pasp.hpp" #include "boxes/pitm.hpp" #include "boxes/pixi.hpp" +#include "boxes/sdtp.hpp" #include "boxes/smhd.hpp" +#include "boxes/stco.hpp" +#include "boxes/stsc.hpp" #include "boxes/stsd.hpp" +#include "boxes/stss.hpp" +#include "boxes/stsz.hpp" #include "boxes/stts.hpp" #include "boxes/tkhd.hpp" +#include "boxes/tmcd.hpp" #include "boxes/url.hpp" #include "boxes/vmhd.hpp" +#include "boxes/wide.hpp" namespace mbmff { @@ -226,172 +248,227 @@ using partial_box_iterator = mbmff::box_iterator::not_implemented); }, \ + "Box type " #name " is not implemented." \ + ); + +MBMFF_ITERATE_BOX_TYPES(MBMFF_TEST_ALL_IMPLEMENTED) +static_assert( + !requires { requires(mbmff::basic_box_view::not_implemented); }, + "Box type url is not implemented." +); +# undef MBMFF_TEST_ALL_IMPLEMENTED + +// test if container property is correctly detected +static_assert( + [] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x08}, + std::byte{'m'}, + std::byte{'o'}, + std::byte{'o'}, + std::byte{'v'}, + }; + auto r = mbmff::parse(std::span(data)); + return r && r->type_ == mbmff::box_type::moov + && mbmff::has(mbmff::get_box_properties(r->type_), mbmff::box_properties::container); + }(), + "moov container property should be detected" +); + // parse() without flags rejects truncated data (backward compat) -static_assert([] { - std::array data{ - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x03}, - std::byte{0xE8}, - std::byte{'m'}, - std::byte{'d'}, - std::byte{'a'}, - std::byte{'t'}, - }; - auto r = mbmff::parse(std::span(data)); - return !r && r.code == mbmff::error_code::need_more_data && r.needed == 1000; -}()); +static_assert( + [] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x03}, + std::byte{0xE8}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, + }; + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::need_more_data && r.needed == 1000; + }(), + "parse() without flags should reject truncated data" +); // parse() with allow_partial returns truncated for incomplete box -static_assert([] { - std::array data{ - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x03}, - std::byte{0xE8}, - std::byte{'m'}, - std::byte{'d'}, - std::byte{'a'}, - std::byte{'t'}, - }; - auto r = mbmff::parse(std::span(data)); - return !r && r.code == mbmff::error_code::truncated && r->type_ == mbmff::box_type::mdat && r->size_ == 1000 - && r->payload.size() == 4; -}()); +static_assert( + [] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x03}, + std::byte{0xE8}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, + }; + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::truncated && r->type_ == mbmff::box_type::mdat && r->size_ == 1000 + && r->payload.size() == 4; + }(), + "parse(allow_partial) should return truncated for incomplete box" +); // parse() with allow_partial returns success for complete box -static_assert([] { - std::array data{ - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x0C}, - std::byte{'m'}, - std::byte{'d'}, - std::byte{'a'}, - std::byte{'t'}, - std::byte{0x01}, - std::byte{0x02}, - std::byte{0x03}, - std::byte{0x04}, - }; - auto r = mbmff::parse(std::span(data)); - return r && r.code == mbmff::error_code::success && r->type_ == mbmff::box_type::mdat; -}()); +static_assert( + [] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x0C}, + std::byte{'m'}, + std::byte{'d'}, + std::byte{'a'}, + std::byte{'t'}, + std::byte{0x01}, + std::byte{0x02}, + std::byte{0x03}, + std::byte{0x04}, + }; + auto r = mbmff::parse(std::span(data)); + return r && r.code == mbmff::error_code::success && r->type_ == mbmff::box_type::mdat; + }(), + "parse(allow_partial) should return success for complete box" +); // allow_partial still rejects when even the validate minimum is absent -static_assert([] { - std::array data{ - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x00}, - std::byte{0x10}, - std::byte{'f'}, - std::byte{'t'}, - std::byte{'y'}, - std::byte{'p'}, - std::byte{'a'}, - std::byte{'v'}, - std::byte{'0'}, - std::byte{'1'}, - }; - auto r = mbmff::parse(std::span(data)); - return !r && r.code == mbmff::error_code::need_more_data && r.needed == 8; -}()); +static_assert( + [] { + std::array data{ + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x00}, + std::byte{0x10}, + std::byte{'f'}, + std::byte{'t'}, + std::byte{'y'}, + std::byte{'p'}, + std::byte{'a'}, + std::byte{'v'}, + std::byte{'0'}, + std::byte{'1'}, + }; + auto r = mbmff::parse(std::span(data)); + return !r && r.code == mbmff::error_code::need_more_data && r.needed == 8; + }(), + "allow_partial should still reject data lacking validate minimum" +); // box_iterator with allow_partial continues past truncated boxes -static_assert([] { - std::array data{ - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, - std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, - std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, - }; - auto it = mbmff::box_iterator(std::span(data)); - auto end = it.end(); - - if (it == end) { - return false; - } - auto r1 = *it; - if (!r1 || r1->type_ != mbmff::box_type::mdat) { - return false; - } - ++it; +static_assert( + [] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, + std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, + std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, + }; + auto it = mbmff::box_iterator(std::span(data)); + auto end = it.end(); + + if (it == end) { + return false; + } + auto r1 = *it; + if (!r1 || r1->type_ != mbmff::box_type::mdat) { + return false; + } + ++it; - if (it == end) { - return false; - } - auto r2 = *it; - if (r2.code != mbmff::error_code::truncated || r2->type_ != mbmff::box_type::moov) { - return false; - } - ++it; + if (it == end) { + return false; + } + auto r2 = *it; + if (r2.code != mbmff::error_code::truncated || r2->type_ != mbmff::box_type::moov) { + return false; + } + ++it; - return it == end; -}()); + return it == end; + }(), + "box_iterator(allow_partial) should continue past truncated boxes" +); // box_iterator without allow_partial stops at truncated data -static_assert([] { - std::array data{ - std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, - std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, - std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, - std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, - }; - auto it = mbmff::box_iterator(std::span(data)); - auto end = it.end(); - - if (it == end) { - return false; - } - if (!(*it)) { - return false; // mdat - } - ++it; +static_assert( + [] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, + std::byte{'a'}, std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, std::byte{'o'}, + std::byte{'o'}, std::byte{'v'}, std::byte{0xAA}, std::byte{0xBB}, std::byte{0xCC}, std::byte{0xDD}, + }; + auto it = mbmff::box_iterator(std::span(data)); + auto end = it.end(); + + if (it == end) { + return false; + } + if (!(*it)) { + return false; // mdat + } + ++it; - // truncated moov — parse fails → iterator terminates - if (it == end) { - return false; - } - if ((*it).code != mbmff::error_code::need_more_data) { - return false; - } - ++it; + // truncated moov — parse fails → iterator terminates + if (it == end) { + return false; + } + if ((*it).code != mbmff::error_code::need_more_data) { + return false; + } + ++it; - return it == end; -}()); + return it == end; + }(), + "box_iterator should stop at truncated data" +); // recursive + allow_partial descends into a truncated container's children -static_assert([] { - std::array data{ - std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, - std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0x00}, std::byte{0x00}, - std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, - std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, - }; - auto it = mbmff::box_iterator( - std::span(data) - - ); - auto end = it.end(); - - if (it == end) { - return false; - } - if ((*it).code != mbmff::error_code::truncated || (*it)->type_ != mbmff::box_type::moov) { - return false; - } - ++it; +static_assert( + [] { + std::array data{ + std::byte{0x00}, std::byte{0x00}, std::byte{0x03}, std::byte{0xE8}, std::byte{'m'}, + std::byte{'o'}, std::byte{'o'}, std::byte{'v'}, std::byte{0x00}, std::byte{0x00}, + std::byte{0x00}, std::byte{0x0C}, std::byte{'m'}, std::byte{'d'}, std::byte{'a'}, + std::byte{'t'}, std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}, + }; + auto it = mbmff::box_iterator( + std::span(data) + + ); + auto end = it.end(); + if (it == end) { + return false; + } - if (it == end) { - return false; - } - if (!(*it) || (*it)->type_ != mbmff::box_type::mdat) { - return false; - } - ++it; + if ((*it).code != mbmff::error_code::truncated || (*it)->type_ != mbmff::box_type::moov) { + return false; + } + ++it; + + if (it == end) { + return false; + } + if (!(*it) || (*it)->type_ != mbmff::box_type::mdat) { + return false; + } + ++it; - return it == end; -}()); + return it == end; + }(), + "recursive allow_partial should descend into truncated container" +); #endif diff --git a/test/formatters.hpp b/test/formatters.hpp index 90c8f7b..2a75eb8 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -3,6 +3,45 @@ #include #include +//------------------------------------------------------------------------------------------------------------ +// fiel +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::fiel_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" field_count=\"{}\" field_details=\"{}\"", + box.size_, + v.field_count, + v.field_details + ), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// free +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::free_box& box, std::format_context& ctx) const + { + return std::formatter::format(std::format(" size=\"{}\"", box.size_), ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// wide +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::wide_box& box, std::format_context& ctx) const + { + return std::formatter::format(std::format(" size=\"{}\"", box.size_), ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // ftyp template <> @@ -24,6 +63,28 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// gmin +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::gmin_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" graphicsmode=\"{}\" opcolor=\"{} {} {}\" balance=\"{}\"", + box.size_, + v.graphicsmode, + v.opcolor[0], + v.opcolor[1], + v.opcolor[2], + v.balance + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // meta template <> @@ -34,6 +95,28 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// load +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::load_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" preload_time=\"{}\" preload_size=\"{}\"" + " default_loading=\"{}\" duration_to_load=\"{}\"", + box.size_, + v.preload_time, + v.preload_size, + v.default_loading, + v.duration_to_load + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // mdat template <> @@ -47,6 +130,52 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// hvcC +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::hvcC_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" config_version=\"{}\" profile_space=\"{}\"" + " tier_flag=\"{}\" profile_idc=\"{}\"" + " profile_compat=\"{:#010x}\"" + " constraint_indicator=\"{:#018x}\"" + " level_idc=\"{}\"" + " min_spatial_seg=\"{}\" parallelism=\"{}\"" + " chroma_format=\"{}\" bit_depth_luma=\"{}\"" + " bit_depth_chroma=\"{}\"" + " avg_framerate=\"{}\"" + " constant_framerate=\"{}\" temporal_layers=\"{}\"" + " temporal_nested=\"{}\" length_size=\"{}\"" + " num_arrays=\"{}\"", + box.size_, + v.configuration_version, + v.general_profile_space, + v.general_tier_flag, + v.general_profile_idc, + v.general_profile_compatibility_flags, + v.general_constraint_indicator_flags, + v.general_level_idc, + v.min_spatial_segmentation_idc, + v.parallelism_type, + v.chroma_format, + v.bit_depth_luma_minus8 + 8, + v.bit_depth_chroma_minus8 + 8, + v.avg_frame_rate, + v.constant_frame_rate, + v.num_temporal_layers, + v.temporal_id_nested, + v.length_size_minus_one + 1, + v.num_arrays + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // iinf template <> @@ -61,6 +190,39 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// hev1 +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::hev1_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" width=\"{}\" height=\"{}\"" + " horizresolution=\"{:.2f}\" vertresolution=\"{:.2f}\"" + " frame_count=\"{}\" depth=\"{}\"", + box.size_, + v.width, + v.height, + static_cast(v.horizresolution) / 65536.0, + static_cast(v.vertresolution) / 65536.0, + v.frame_count, + v.depth + ); + if (!v.compressorname.empty()) { + auto len = static_cast(v.compressorname[0]); + if (len > 31) { + len = 31; + } + std::string name(reinterpret_cast(v.compressorname.data() + 1), len); + if (!name.empty()) { + out += std::format(" name=\"{}\"", name); + } + } + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // hdlr template <> @@ -188,6 +350,52 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// alis +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::alis_box& box, std::format_context& ctx) const + { + return std::formatter::format( + std::format(" size=\"{}\" version=\"{}\"", box.size_, box.version()), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// apcn +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::apcn_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" width=\"{}\" height=\"{}\"" + " horizresolution=\"{:.2f}\" vertresolution=\"{:.2f}\"" + " frame_count=\"{}\" depth=\"{}\"", + box.size_, + v.width, + v.height, + static_cast(v.horizresolution) / 65536.0, + static_cast(v.vertresolution) / 65536.0, + v.frame_count, + v.depth + ); + if (!v.compressorname.empty()) { + auto len = static_cast(v.compressorname[0]); + if (len > 31) { + len = 31; + } + std::string name(reinterpret_cast(v.compressorname.data() + 1), len); + if (!name.empty()) { + out += std::format(" name=\"{}\"", name); + } + } + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // av1C template <> @@ -249,6 +457,77 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// co64 +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::co64_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + out += std::format(" chunk_offset=\"{}\"", d[i]); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// colr +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::colr_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format(" size=\"{}\" colour_type=\"{}\"", box.size_, v.colour_type.view()); + if (v.colour_type == mbmff::fourcc_string::from_uint32(mbmff::fourcc("nclx"))) { + out += std::format( + " primaries=\"{}\" transfer=\"{}\" matrix=\"{}\" full_range=\"{}\"", + v.colour_primaries, + v.transfer_characteristics, + v.matrix_coefficients, + v.full_range_flag + ); + } else if ( + v.colour_type == mbmff::fourcc_string::from_uint32(mbmff::fourcc("rICC")) + || v.colour_type == mbmff::fourcc_string::from_uint32(mbmff::fourcc("prof")) + ) { + out += std::format(" icc_size=\"{}\"", v.icc_profile.size()); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// ctts +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::ctts_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + auto e = d[i]; + out += std::format(" sample_count=\"{}\" sample_offset=\"{}\"", e.sample_count, e.sample_offset); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // dref template <> @@ -263,6 +542,29 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// tmcd +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::tmcd_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" data_ref=\"{}\" flags=\"{}\"" + " timescale=\"{}\" frame_dur=\"{}\" num_frames=\"{}\"", + box.size_, + v.data_reference_index, + v.flags, + v.timescale, + v.frame_duration, + v.number_of_frames + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // url template <> @@ -276,6 +578,82 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// avcC +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::avcC_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" profile=\"{}\" compatibility=\"{}\" level=\"{}\"" + " length_size=\"{}\" nalu_size=\"{}\"", + box.size_, + v.AVCProfileIndication, + v.profile_compatibility, + v.AVCLevelIndication, + v.lengthSizeMinusOne + 1, + v.nalu_data.size() + ), + ctx + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// avc1 +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::avc1_box& box, std::format_context& ctx) const + { + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" width=\"{}\" height=\"{}\"" + " horizresolution=\"{:.2f}\" vertresolution=\"{:.2f}\"" + " frame_count=\"{}\" depth=\"{}\"", + box.size_, + v.width, + v.height, + static_cast(v.horizresolution) / 65536.0, + static_cast(v.vertresolution) / 65536.0, + v.frame_count, + v.depth + ); + if (!v.compressorname.empty()) { + auto len = static_cast(v.compressorname[0]); + if (len > 31) { + len = 31; + } + std::string name(reinterpret_cast(v.compressorname.data() + 1), len); + if (!name.empty()) { + out += std::format(" name=\"{}\"", name); + } + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// mp4a +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::mp4a_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" channels=\"{}\" sample_size=\"{}\" sample_rate=\"{:.2f}\"", + box.size_, + v.channelcount, + v.samplesize, + static_cast(v.samplerate) / 65536.0 + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // mvhd template <> @@ -337,6 +715,9 @@ struct std::formatter : std::formatter { std::string out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); for (std::uint32_t i = 0; i < d.size(); ++i) { + if (i) { + out += " | "; + } auto e = d[i]; out += std::format( " entry=\"{}\" segment_duration=\"{}\" media_time=\"{}\"" @@ -352,6 +733,34 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// sdtp +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::sdtp_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string out = std::format(" size=\"{}\" entry_count=\"{}\"", box.size_, d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + auto e = d[i]; + out += std::format( + " is_leading={} depends_on={} depended_on={} redundancy={}", + e.is_leading, + e.sample_depends_on, + e.sample_is_depended_on, + e.sample_has_redundancy + ); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // smhd template <> @@ -371,6 +780,107 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// stss +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stss_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + out += std::format(" sample_number=\"{}\"", d[i]); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// stco +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stco_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + out += std::format(" chunk_offset=\"{}\"", d[i]); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// stsc +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stsc_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string + out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + auto e = d[i]; + out += std::format( + " first_chunk=\"{}\" samples_per_chunk=\"{}\" description_index=\"{}\"", + e.first_chunk, + e.samples_per_chunk, + e.sample_description_index + ); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + return std::formatter::format(out, ctx); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// stsz +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::stsz_box& box, std::format_context& ctx) const + { + auto d = box.value(); + std::string out = std::format( + " size=\"{}\" version=\"{}\" sample_size=\"{}\" sample_count=\"{}\"", + box.size_, + box.version(), + d.sample_size, + d.size() + ); + if (d.sample_size == 0) { + for (std::uint32_t i = 0; i < d.size() && i < 3; ++i) { + if (i) { + out += " | "; + } + out += std::format(" entry_size=\"{}\"", d[i]); + } + if (d.size() > 3) { + out += std::format(" ..."); + } + } + return std::formatter::format(out, ctx); + } +}; + //------------------------------------------------------------------------------------------------------------ // stts template <> @@ -381,6 +891,9 @@ struct std::formatter : std::formatter { std::string out = std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), d.size()); for (std::uint32_t i = 0; i < d.size(); ++i) { + if (i) { + out += " | "; + } auto e = d[i]; out += std::format(" sample_count=\"{}\" sample_delta=\"{}\"", e.sample_count, e.sample_delta); } @@ -423,6 +936,33 @@ struct std::formatter : std::formatter { } }; +//------------------------------------------------------------------------------------------------------------ +// esds +template <> +struct std::formatter : std::formatter { + auto format(const mbmff::esds_box& box, std::format_context& ctx) const + { + auto v = box.value(); + return std::formatter::format( + std::format( + " size=\"{}\" version=\"{}\" object_type=\"{:#04x}\"" + " stream_type=\"{}\" buffer_size=\"{}\"" + " max_bitrate=\"{}\" avg_bitrate=\"{}\"" + " asc_size=\"{}\"", + box.size_, + box.version(), + v.object_type, + v.stream_type, + v.buffer_size, + v.max_bitrate, + v.avg_bitrate, + v.audio_specific_config.size() + ), + ctx + ); + } +}; + //------------------------------------------------------------------------------------------------------------ // iloc template <> @@ -456,7 +996,8 @@ static auto display_fourcc(const mbmff::fourcc_string& fc) -> std::string bool all_printable = true; for (std::size_t i = 0; i < 4; ++i) { auto c = fc[i]; - if (static_cast(c) < 0x20 || static_cast(c) > 0x7e) { + auto uc = static_cast(c); + if (uc < 0x20 || (uc > 0x7e && uc != 0xa9)) { all_printable = false; break; } @@ -495,15 +1036,42 @@ static void print_box(const mbmff::any_box_view& box, std::size_t depth) std::cout << '<' << display_fourcc(box.type_string()); switch (box.type_) { + case mbmff::box_type::fiel: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::free: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::wide: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::gmin: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::ftyp: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::avcC: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::avc1: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::hev1: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::meta: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::load: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::mdat: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::hvcC: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::iinf: std::cout << std::format("{}", mbmff::box_cast(box)); break; @@ -531,33 +1099,72 @@ static void print_box(const mbmff::any_box_view& box, std::size_t depth) case mbmff::box_type::infe: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::alis: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::apcn: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::av1C: std::cout << std::format("{}", mbmff::box_cast(box)); break; case mbmff::box_type::mvhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::tmcd: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::tkhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::mp4a: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::esds: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::elst: std::cout << std::format("{}", mbmff::box_cast(box)); break; case mbmff::box_type::mdhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::stco: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::stsc: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::stsd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::stss: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::stsz: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::stts: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::sdtp: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::smhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; case mbmff::box_type::vmhd: std::cout << std::format("{}", mbmff::box_cast(box)); break; + case mbmff::box_type::co64: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::colr: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::ctts: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; case mbmff::box_type::dref: std::cout << std::format("{}", mbmff::box_cast(box)); break; diff --git a/tools/amalgamate.py b/tools/amalgamate.py index f6d8b57..a15ecb7 100644 --- a/tools/amalgamate.py +++ b/tools/amalgamate.py @@ -49,10 +49,20 @@ def process_file(filepath: str, depth: int = 0) -> list[str]: inlined.add(norm) lines: list[str] = [] + skip_until_endif = 0 with open(filepath, "r", encoding="utf-8") as f: for line in f: stripped = line.strip() + if stripped.startswith("#ifdef MBMFF_ENABLE_CONSTEXPR_TEST"): + skip_until_endif += 1 + continue + + if skip_until_endif: + if stripped.startswith("#endif"): + skip_until_endif -= 1 + continue + if stripped == "#pragma once": continue From cacfbff8fff83d7b4607710d04408657640bd065 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Tue, 16 Jun 2026 23:23:48 +0200 Subject: [PATCH 6/6] exchanged for concept --- include/mbmff/boxes/alis.hpp | 2 +- include/mbmff/boxes/apcn.hpp | 2 +- include/mbmff/boxes/av1C.hpp | 2 +- include/mbmff/boxes/avcC.hpp | 2 +- include/mbmff/boxes/co64.hpp | 2 +- include/mbmff/boxes/colr.hpp | 2 +- include/mbmff/boxes/ctts.hpp | 2 +- include/mbmff/boxes/dref.hpp | 2 +- include/mbmff/boxes/elst.hpp | 2 +- include/mbmff/boxes/esds.hpp | 2 +- include/mbmff/boxes/fiel.hpp | 2 +- include/mbmff/boxes/free.hpp | 2 +- include/mbmff/boxes/ftyp.hpp | 2 +- include/mbmff/boxes/gmin.hpp | 2 +- include/mbmff/boxes/hdlr.hpp | 2 +- include/mbmff/boxes/hev1.hpp | 2 +- include/mbmff/boxes/hvcC.hpp | 2 +- include/mbmff/boxes/iinf.hpp | 2 +- include/mbmff/boxes/iloc.hpp | 2 +- include/mbmff/boxes/infe.hpp | 2 +- include/mbmff/boxes/ipma.hpp | 2 +- include/mbmff/boxes/iref.hpp | 2 +- include/mbmff/boxes/ispe.hpp | 2 +- include/mbmff/boxes/load.hpp | 2 +- include/mbmff/boxes/mdat.hpp | 2 +- include/mbmff/boxes/mdhd.hpp | 2 +- include/mbmff/boxes/meta.hpp | 2 +- include/mbmff/boxes/mvhd.hpp | 2 +- include/mbmff/boxes/pasp.hpp | 2 +- include/mbmff/boxes/pitm.hpp | 2 +- include/mbmff/boxes/pixi.hpp | 2 +- include/mbmff/boxes/sdtp.hpp | 2 +- include/mbmff/boxes/smhd.hpp | 2 +- include/mbmff/boxes/stco.hpp | 2 +- include/mbmff/boxes/stsc.hpp | 2 +- include/mbmff/boxes/stsd.hpp | 2 +- include/mbmff/boxes/stss.hpp | 2 +- include/mbmff/boxes/stsz.hpp | 2 +- include/mbmff/boxes/stts.hpp | 2 +- include/mbmff/boxes/tkhd.hpp | 2 +- include/mbmff/boxes/tmcd.hpp | 2 +- include/mbmff/boxes/url.hpp | 2 +- include/mbmff/boxes/vmhd.hpp | 2 +- include/mbmff/boxes/wide.hpp | 2 +- include/mbmff/mbmff.hpp | 15 ++++++--------- 45 files changed, 50 insertions(+), 53 deletions(-) diff --git a/include/mbmff/boxes/alis.hpp b/include/mbmff/boxes/alis.hpp index e9bc989..2217efd 100644 --- a/include/mbmff/boxes/alis.hpp +++ b/include/mbmff/boxes/alis.hpp @@ -6,7 +6,7 @@ namespace mbmff { struct alis_data {}; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::alis_data; diff --git a/include/mbmff/boxes/apcn.hpp b/include/mbmff/boxes/apcn.hpp index 52ff7cf..538a396 100644 --- a/include/mbmff/boxes/apcn.hpp +++ b/include/mbmff/boxes/apcn.hpp @@ -15,7 +15,7 @@ struct apcn_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::apcn_data; diff --git a/include/mbmff/boxes/av1C.hpp b/include/mbmff/boxes/av1C.hpp index 6756719..96a8e9d 100644 --- a/include/mbmff/boxes/av1C.hpp +++ b/include/mbmff/boxes/av1C.hpp @@ -22,7 +22,7 @@ struct av1C_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::av1C_data; diff --git a/include/mbmff/boxes/avcC.hpp b/include/mbmff/boxes/avcC.hpp index aa8bdac..287545d 100644 --- a/include/mbmff/boxes/avcC.hpp +++ b/include/mbmff/boxes/avcC.hpp @@ -13,7 +13,7 @@ struct avcC_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::avcC_data; diff --git a/include/mbmff/boxes/co64.hpp b/include/mbmff/boxes/co64.hpp index 0a85750..204ba32 100644 --- a/include/mbmff/boxes/co64.hpp +++ b/include/mbmff/boxes/co64.hpp @@ -22,7 +22,7 @@ struct co64_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::co64_data; diff --git a/include/mbmff/boxes/colr.hpp b/include/mbmff/boxes/colr.hpp index acbaded..fcbd1a1 100644 --- a/include/mbmff/boxes/colr.hpp +++ b/include/mbmff/boxes/colr.hpp @@ -13,7 +13,7 @@ struct colr_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::colr_data; diff --git a/include/mbmff/boxes/ctts.hpp b/include/mbmff/boxes/ctts.hpp index 55eccbb..29679d1 100644 --- a/include/mbmff/boxes/ctts.hpp +++ b/include/mbmff/boxes/ctts.hpp @@ -31,7 +31,7 @@ struct ctts_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ctts_data; diff --git a/include/mbmff/boxes/dref.hpp b/include/mbmff/boxes/dref.hpp index be672ef..012352f 100644 --- a/include/mbmff/boxes/dref.hpp +++ b/include/mbmff/boxes/dref.hpp @@ -8,7 +8,7 @@ struct dref_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; diff --git a/include/mbmff/boxes/elst.hpp b/include/mbmff/boxes/elst.hpp index 13031a6..2206a86 100644 --- a/include/mbmff/boxes/elst.hpp +++ b/include/mbmff/boxes/elst.hpp @@ -45,7 +45,7 @@ struct elst_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::elst_data; diff --git a/include/mbmff/boxes/esds.hpp b/include/mbmff/boxes/esds.hpp index f52f635..df9e868 100644 --- a/include/mbmff/boxes/esds.hpp +++ b/include/mbmff/boxes/esds.hpp @@ -13,7 +13,7 @@ struct esds_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::esds_data; diff --git a/include/mbmff/boxes/fiel.hpp b/include/mbmff/boxes/fiel.hpp index b0d9aa4..90da571 100644 --- a/include/mbmff/boxes/fiel.hpp +++ b/include/mbmff/boxes/fiel.hpp @@ -9,7 +9,7 @@ struct fiel_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::fiel_data; diff --git a/include/mbmff/boxes/free.hpp b/include/mbmff/boxes/free.hpp index 951d4d4..85c72f2 100644 --- a/include/mbmff/boxes/free.hpp +++ b/include/mbmff/boxes/free.hpp @@ -6,7 +6,7 @@ namespace mbmff { struct free_data {}; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; }; diff --git a/include/mbmff/boxes/ftyp.hpp b/include/mbmff/boxes/ftyp.hpp index 6c23a1d..10c8929 100644 --- a/include/mbmff/boxes/ftyp.hpp +++ b/include/mbmff/boxes/ftyp.hpp @@ -9,7 +9,7 @@ struct ftyp_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ftyp_data; diff --git a/include/mbmff/boxes/gmin.hpp b/include/mbmff/boxes/gmin.hpp index 940c4d9..7c816dc 100644 --- a/include/mbmff/boxes/gmin.hpp +++ b/include/mbmff/boxes/gmin.hpp @@ -11,7 +11,7 @@ struct gmin_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::gmin_data; diff --git a/include/mbmff/boxes/hdlr.hpp b/include/mbmff/boxes/hdlr.hpp index 59ed194..1511597 100644 --- a/include/mbmff/boxes/hdlr.hpp +++ b/include/mbmff/boxes/hdlr.hpp @@ -9,7 +9,7 @@ struct hdlr_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::hdlr_data; diff --git a/include/mbmff/boxes/hev1.hpp b/include/mbmff/boxes/hev1.hpp index 69b48a6..cb0fa29 100644 --- a/include/mbmff/boxes/hev1.hpp +++ b/include/mbmff/boxes/hev1.hpp @@ -15,7 +15,7 @@ struct hev1_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::hev1_data; diff --git a/include/mbmff/boxes/hvcC.hpp b/include/mbmff/boxes/hvcC.hpp index 921f9c7..f664e6a 100644 --- a/include/mbmff/boxes/hvcC.hpp +++ b/include/mbmff/boxes/hvcC.hpp @@ -26,7 +26,7 @@ struct hvcC_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::hvcC_data; diff --git a/include/mbmff/boxes/iinf.hpp b/include/mbmff/boxes/iinf.hpp index e024783..3882204 100644 --- a/include/mbmff/boxes/iinf.hpp +++ b/include/mbmff/boxes/iinf.hpp @@ -8,7 +8,7 @@ struct iinf_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; diff --git a/include/mbmff/boxes/iloc.hpp b/include/mbmff/boxes/iloc.hpp index bcd10db..7d4539d 100644 --- a/include/mbmff/boxes/iloc.hpp +++ b/include/mbmff/boxes/iloc.hpp @@ -87,7 +87,7 @@ struct iloc_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::iloc_data; diff --git a/include/mbmff/boxes/infe.hpp b/include/mbmff/boxes/infe.hpp index 56cfe23..8514cd3 100644 --- a/include/mbmff/boxes/infe.hpp +++ b/include/mbmff/boxes/infe.hpp @@ -14,7 +14,7 @@ struct infe_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::infe_data; diff --git a/include/mbmff/boxes/ipma.hpp b/include/mbmff/boxes/ipma.hpp index bcc43e6..00fb16e 100644 --- a/include/mbmff/boxes/ipma.hpp +++ b/include/mbmff/boxes/ipma.hpp @@ -48,7 +48,7 @@ struct ipma_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ipma_data; diff --git a/include/mbmff/boxes/iref.hpp b/include/mbmff/boxes/iref.hpp index 03f80a5..8f0809e 100644 --- a/include/mbmff/boxes/iref.hpp +++ b/include/mbmff/boxes/iref.hpp @@ -4,7 +4,7 @@ namespace mbmff { template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; diff --git a/include/mbmff/boxes/ispe.hpp b/include/mbmff/boxes/ispe.hpp index d31941e..f828768 100644 --- a/include/mbmff/boxes/ispe.hpp +++ b/include/mbmff/boxes/ispe.hpp @@ -9,7 +9,7 @@ struct ispe_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::ispe_data; diff --git a/include/mbmff/boxes/load.hpp b/include/mbmff/boxes/load.hpp index 7d5b40c..7331a15 100644 --- a/include/mbmff/boxes/load.hpp +++ b/include/mbmff/boxes/load.hpp @@ -11,7 +11,7 @@ struct load_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::load_data; diff --git a/include/mbmff/boxes/mdat.hpp b/include/mbmff/boxes/mdat.hpp index 6ec1773..142cf80 100644 --- a/include/mbmff/boxes/mdat.hpp +++ b/include/mbmff/boxes/mdat.hpp @@ -3,7 +3,7 @@ namespace mbmff { template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; }; diff --git a/include/mbmff/boxes/mdhd.hpp b/include/mbmff/boxes/mdhd.hpp index 30af627..87f456c 100644 --- a/include/mbmff/boxes/mdhd.hpp +++ b/include/mbmff/boxes/mdhd.hpp @@ -13,7 +13,7 @@ struct mdhd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::mdhd_data; diff --git a/include/mbmff/boxes/meta.hpp b/include/mbmff/boxes/meta.hpp index b2955bd..0b2c94c 100644 --- a/include/mbmff/boxes/meta.hpp +++ b/include/mbmff/boxes/meta.hpp @@ -3,7 +3,7 @@ namespace mbmff { template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { // full_box is not ticked here because `meta` can // be a regular box in some formats (e.g. QTFF) constexpr static mbmff::box_properties properties = mbmff::box_properties::container; diff --git a/include/mbmff/boxes/mvhd.hpp b/include/mbmff/boxes/mvhd.hpp index 6de2900..5100f78 100644 --- a/include/mbmff/boxes/mvhd.hpp +++ b/include/mbmff/boxes/mvhd.hpp @@ -16,7 +16,7 @@ struct mvhd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::mvhd_data; diff --git a/include/mbmff/boxes/pasp.hpp b/include/mbmff/boxes/pasp.hpp index 7115842..e45c93f 100644 --- a/include/mbmff/boxes/pasp.hpp +++ b/include/mbmff/boxes/pasp.hpp @@ -9,7 +9,7 @@ struct pasp_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pasp_data; diff --git a/include/mbmff/boxes/pitm.hpp b/include/mbmff/boxes/pitm.hpp index 711ad15..d170565 100644 --- a/include/mbmff/boxes/pitm.hpp +++ b/include/mbmff/boxes/pitm.hpp @@ -8,7 +8,7 @@ struct pitm_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pitm_data; diff --git a/include/mbmff/boxes/pixi.hpp b/include/mbmff/boxes/pixi.hpp index a888dbc..0e498a3 100644 --- a/include/mbmff/boxes/pixi.hpp +++ b/include/mbmff/boxes/pixi.hpp @@ -8,7 +8,7 @@ struct pixi_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::pixi_data; diff --git a/include/mbmff/boxes/sdtp.hpp b/include/mbmff/boxes/sdtp.hpp index b6883c6..7d26658 100644 --- a/include/mbmff/boxes/sdtp.hpp +++ b/include/mbmff/boxes/sdtp.hpp @@ -30,7 +30,7 @@ struct sdtp_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::sdtp_data; diff --git a/include/mbmff/boxes/smhd.hpp b/include/mbmff/boxes/smhd.hpp index 7cf320f..360334d 100644 --- a/include/mbmff/boxes/smhd.hpp +++ b/include/mbmff/boxes/smhd.hpp @@ -8,7 +8,7 @@ struct smhd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::smhd_data; diff --git a/include/mbmff/boxes/stco.hpp b/include/mbmff/boxes/stco.hpp index 5570a85..c423796 100644 --- a/include/mbmff/boxes/stco.hpp +++ b/include/mbmff/boxes/stco.hpp @@ -22,7 +22,7 @@ struct stco_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::stco_data; diff --git a/include/mbmff/boxes/stsc.hpp b/include/mbmff/boxes/stsc.hpp index a9c616c..625b641 100644 --- a/include/mbmff/boxes/stsc.hpp +++ b/include/mbmff/boxes/stsc.hpp @@ -30,7 +30,7 @@ struct stsc_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::stsc_data; diff --git a/include/mbmff/boxes/stsd.hpp b/include/mbmff/boxes/stsd.hpp index 62fff15..cfff32e 100644 --- a/include/mbmff/boxes/stsd.hpp +++ b/include/mbmff/boxes/stsd.hpp @@ -8,7 +8,7 @@ struct stsd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box | mbmff::box_properties::container; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; diff --git a/include/mbmff/boxes/stss.hpp b/include/mbmff/boxes/stss.hpp index 39803f4..b075a80 100644 --- a/include/mbmff/boxes/stss.hpp +++ b/include/mbmff/boxes/stss.hpp @@ -22,7 +22,7 @@ struct stss_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::stss_data; diff --git a/include/mbmff/boxes/stsz.hpp b/include/mbmff/boxes/stsz.hpp index 351a89b..7b411f6 100644 --- a/include/mbmff/boxes/stsz.hpp +++ b/include/mbmff/boxes/stsz.hpp @@ -26,7 +26,7 @@ struct stsz_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::stsz_data; diff --git a/include/mbmff/boxes/stts.hpp b/include/mbmff/boxes/stts.hpp index d25b331..7e47e59 100644 --- a/include/mbmff/boxes/stts.hpp +++ b/include/mbmff/boxes/stts.hpp @@ -31,7 +31,7 @@ struct stts_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::stts_data; diff --git a/include/mbmff/boxes/tkhd.hpp b/include/mbmff/boxes/tkhd.hpp index 20917c9..2f91113 100644 --- a/include/mbmff/boxes/tkhd.hpp +++ b/include/mbmff/boxes/tkhd.hpp @@ -18,7 +18,7 @@ struct tkhd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::tkhd_data; diff --git a/include/mbmff/boxes/tmcd.hpp b/include/mbmff/boxes/tmcd.hpp index 428e3d9..4278796 100644 --- a/include/mbmff/boxes/tmcd.hpp +++ b/include/mbmff/boxes/tmcd.hpp @@ -12,7 +12,7 @@ struct tmcd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::tmcd_data; diff --git a/include/mbmff/boxes/url.hpp b/include/mbmff/boxes/url.hpp index a5a461c..3b0bfd0 100644 --- a/include/mbmff/boxes/url.hpp +++ b/include/mbmff/boxes/url.hpp @@ -6,7 +6,7 @@ namespace mbmff { struct url_data {}; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::url_data; diff --git a/include/mbmff/boxes/vmhd.hpp b/include/mbmff/boxes/vmhd.hpp index 16ca760..b16af09 100644 --- a/include/mbmff/boxes/vmhd.hpp +++ b/include/mbmff/boxes/vmhd.hpp @@ -10,7 +10,7 @@ struct vmhd_data { }; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; constexpr auto value() const noexcept -> mbmff::vmhd_data; diff --git a/include/mbmff/boxes/wide.hpp b/include/mbmff/boxes/wide.hpp index 9721466..e67e556 100644 --- a/include/mbmff/boxes/wide.hpp +++ b/include/mbmff/boxes/wide.hpp @@ -6,7 +6,7 @@ namespace mbmff { struct wide_data {}; template <> -struct mbmff::basic_box_view : public mbmff::box_view_base { +struct basic_box_view : public mbmff::box_view_base { constexpr static mbmff::box_properties properties = mbmff::box_properties::none; }; diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index bb9ce40..ccc6c01 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -248,18 +248,15 @@ using partial_box_iterator = mbmff::box_iterator +concept box_not_implemented = requires { mbmff::basic_box_view::not_implemented; }; + // Test if all boxes are implemented -# define MBMFF_TEST_ALL_IMPLEMENTED(name) \ - static_assert( \ - !requires { requires(mbmff::basic_box_view::not_implemented); }, \ - "Box type " #name " is not implemented." \ - ); +# define MBMFF_TEST_ALL_IMPLEMENTED(name) \ + static_assert(!box_not_implemented, "Box type " #name " is not implemented."); MBMFF_ITERATE_BOX_TYPES(MBMFF_TEST_ALL_IMPLEMENTED) -static_assert( - !requires { requires(mbmff::basic_box_view::not_implemented); }, - "Box type url is not implemented." -); +static_assert(!box_not_implemented, "Box type url is not implemented."); # undef MBMFF_TEST_ALL_IMPLEMENTED // test if container property is correctly detected