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/alis.hpp b/include/mbmff/boxes/alis.hpp new file mode 100644 index 0000000..2217efd --- /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 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..538a396 --- /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 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/av1C.hpp b/include/mbmff/boxes/av1C.hpp index 16d02c1..96a8e9d 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 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/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..287545d --- /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 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 e67e583..e757b2d 100644 --- a/include/mbmff/boxes/box_view.hpp +++ b/include/mbmff/boxes/box_view.hpp @@ -3,34 +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(sdtp) \ + MACRO(smhd) \ + MACRO(stbl) \ + MACRO(stco) \ + MACRO(stsc) \ + MACRO(stsd) \ + 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 \ @@ -52,7 +86,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 { @@ -110,7 +147,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{}; @@ -125,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; }; //------------------------------------------------------------------------------------------------------------ @@ -182,10 +221,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/co64.hpp b/include/mbmff/boxes/co64.hpp new file mode 100644 index 0000000..204ba32 --- /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 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..fcbd1a1 --- /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 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 32feb59..bc242ad 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,12 +76,20 @@ 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 { - 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 { @@ -87,7 +98,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 { @@ -99,7 +114,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 @@ -127,13 +142,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}, + }}; + } }; //------------------------------------------------------------------------------------------------------------ @@ -174,9 +198,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/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..29679d1 --- /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 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/dref.hpp b/include/mbmff/boxes/dref.hpp new file mode 100644 index 0000000..012352f --- /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 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..2206a86 --- /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 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/esds.hpp b/include/mbmff/boxes/esds.hpp new file mode 100644 index 0000000..df9e868 --- /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 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..90da571 --- /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 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..85c72f2 --- /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 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/ftyp.hpp b/include/mbmff/boxes/ftyp.hpp index b5a90d2..10c8929 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 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/gmin.hpp b/include/mbmff/boxes/gmin.hpp new file mode 100644 index 0000000..7c816dc --- /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 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/hdlr.hpp b/include/mbmff/boxes/hdlr.hpp index 712a6f6..1511597 100644 --- a/include/mbmff/boxes/hdlr.hpp +++ b/include/mbmff/boxes/hdlr.hpp @@ -15,7 +15,7 @@ struct basic_box_view : public mbmff::box_view_base { 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/hev1.hpp b/include/mbmff/boxes/hev1.hpp new file mode 100644 index 0000000..cb0fa29 --- /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 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..f664e6a --- /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 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/iinf.hpp b/include/mbmff/boxes/iinf.hpp index b2d1663..3882204 100644 --- a/include/mbmff/boxes/iinf.hpp +++ b/include/mbmff/boxes/iinf.hpp @@ -9,13 +9,13 @@ 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; + 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..7d4539d 100644 --- a/include/mbmff/boxes/iloc.hpp +++ b/include/mbmff/boxes/iloc.hpp @@ -93,7 +93,7 @@ struct basic_box_view : public mbmff::box_view_base { 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..8514cd3 100644 --- a/include/mbmff/boxes/infe.hpp +++ b/include/mbmff/boxes/infe.hpp @@ -20,7 +20,7 @@ struct basic_box_view : public mbmff::box_view_base { 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..00fb16e 100644 --- a/include/mbmff/boxes/ipma.hpp +++ b/include/mbmff/boxes/ipma.hpp @@ -54,7 +54,7 @@ struct basic_box_view : public mbmff::box_view_base { 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..8f0809e 100644 --- a/include/mbmff/boxes/iref.hpp +++ b/include/mbmff/boxes/iref.hpp @@ -5,12 +5,12 @@ 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; + 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..f828768 100644 --- a/include/mbmff/boxes/ispe.hpp +++ b/include/mbmff/boxes/ispe.hpp @@ -15,7 +15,7 @@ struct basic_box_view : public mbmff::box_view_base { 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/load.hpp b/include/mbmff/boxes/load.hpp new file mode 100644 index 0000000..7331a15 --- /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 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/mdat.hpp b/include/mbmff/boxes/mdat.hpp index 1be213f..142cf80 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 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..87f456c --- /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 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..0b2c94c 100644 --- a/include/mbmff/boxes/meta.hpp +++ b/include/mbmff/boxes/meta.hpp @@ -10,7 +10,7 @@ struct basic_box_view : public mbmff::box_view_base { 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/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/mvhd.hpp b/include/mbmff/boxes/mvhd.hpp new file mode 100644 index 0000000..5100f78 --- /dev/null +++ b/include/mbmff/boxes/mvhd.hpp @@ -0,0 +1,114 @@ +#pragma once +#include +#include "box_view.hpp" + +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 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{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 mbmff::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/boxes/pasp.hpp b/include/mbmff/boxes/pasp.hpp index 8f759e8..e45c93f 100644 --- a/include/mbmff/boxes/pasp.hpp +++ b/include/mbmff/boxes/pasp.hpp @@ -15,7 +15,7 @@ struct basic_box_view : public mbmff::box_view_base { 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..d170565 100644 --- a/include/mbmff/boxes/pitm.hpp +++ b/include/mbmff/boxes/pitm.hpp @@ -14,7 +14,7 @@ struct basic_box_view : public mbmff::box_view_base { 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..0e498a3 100644 --- a/include/mbmff/boxes/pixi.hpp +++ b/include/mbmff/boxes/pixi.hpp @@ -14,7 +14,7 @@ struct basic_box_view : public mbmff::box_view_base { 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/sdtp.hpp b/include/mbmff/boxes/sdtp.hpp new file mode 100644 index 0000000..7d26658 --- /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 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/smhd.hpp b/include/mbmff/boxes/smhd.hpp new file mode 100644 index 0000000..360334d --- /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 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/stco.hpp b/include/mbmff/boxes/stco.hpp new file mode 100644 index 0000000..c423796 --- /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 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..625b641 --- /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 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/stsd.hpp b/include/mbmff/boxes/stsd.hpp new file mode 100644 index 0000000..cfff32e --- /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 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/stss.hpp b/include/mbmff/boxes/stss.hpp new file mode 100644 index 0000000..b075a80 --- /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 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..7b411f6 --- /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 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/stts.hpp b/include/mbmff/boxes/stts.hpp new file mode 100644 index 0000000..7e47e59 --- /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 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..2f91113 --- /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 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/tmcd.hpp b/include/mbmff/boxes/tmcd.hpp new file mode 100644 index 0000000..4278796 --- /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 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/url.hpp b/include/mbmff/boxes/url.hpp new file mode 100644 index 0000000..3b0bfd0 --- /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 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..b16af09 --- /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 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/boxes/wide.hpp b/include/mbmff/boxes/wide.hpp new file mode 100644 index 0000000..e67e556 --- /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 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 5833c0d..ccc6c01 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -1,38 +1,101 @@ #pragma once -#include "boxes/ftyp.hpp" -#include "boxes/meta.hpp" -#include "boxes/mdat.hpp" -#include "boxes/containers.hpp" +#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/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/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/ipma.hpp" -#include "boxes/infe.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 { -#define MBMFF_ITERATE_USING(name) using name##_box = basic_box_view; +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 = 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 { -#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) + case mbmff::box_type::url: + return mbmff::basic_box_view::properties; default: return mbmff::box_properties::none; } #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) + case mbmff::box_type::url: + 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) noexcept -> mbmff::result { auto header_result = mbmff::parse_box_header(data); @@ -42,32 +105,50 @@ 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 constexpr (!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) + case mbmff::box_type::url: + return mbmff::basic_box_view::validate(box); + 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,70 +160,66 @@ 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) - +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 - ) noexcept + constexpr explicit box_iterator(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_); + 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_); - if (!op_result) { + 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* 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_ = {}; @@ -151,13 +228,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; @@ -166,6 +243,232 @@ struct box_iterator { } }; +using recursive_box_iterator = mbmff::box_iterator; +using partial_box_iterator = mbmff::box_iterator; + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST + +template +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(!box_not_implemented, "Box type " #name " is not implemented."); + +MBMFF_ITERATE_BOX_TYPES(MBMFF_TEST_ALL_IMPLEMENTED) +static_assert(!box_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; + }(), + "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; + }(), + "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; + }(), + "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; + }(), + "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; + + 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; + }(), + "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; + + // 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; + }(), + "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; + + if (it == end) { + return false; + } + if (!(*it) || (*it)->type_ != mbmff::box_type::mdat) { + return false; + } + ++it; + + return it == end; + }(), + "recursive allow_partial should descend into truncated container" +); + +#endif + } // namespace mbmff #undef MBMFF_ITERATE_BOX_TYPES diff --git a/test/entry_main.cpp b/test/entry_main.cpp index 54b1bb4..216526f 100644 --- a/test/entry_main.cpp +++ b/test/entry_main.cpp @@ -1,208 +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: - 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; - 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 1c06dcb..2a75eb8 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -1,7 +1,46 @@ #pragma once +#include #include #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 @@ -14,7 +53,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 += "]\""; @@ -22,14 +63,55 @@ 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 <> 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); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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=\"{}\"", box.size_), + 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 ); } @@ -48,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 <> @@ -62,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 <> @@ -99,7 +260,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 +280,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 +325,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); } @@ -178,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 <> @@ -191,15 +409,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 +432,537 @@ 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 + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> +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 + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> +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 + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> +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=\"{:.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) { + if (i) { + out += " | "; + } + 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); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> +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 + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> +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) { + if (i) { + out += " | "; + } + 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 + ); + } +}; + +//------------------------------------------------------------------------------------------------------------ +// 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 <> @@ -216,11 +971,247 @@ 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 ); } }; + +//------------------------------------------------------------------------------------------------------------ +// 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]; + auto uc = static_cast(c); + if (uc < 0x20 || (uc > 0x7e && uc != 0xa9)) { + 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::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; + 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::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; + case mbmff::box_type::url: + 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/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