1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 18:05:07 +00:00

LibVideo: Propagate decoder errors in the Matroska Reader

Matroska::Reader functions now return DecoderErrorOr instead of values
being declared Optional. Useful errors can be handled by the users of
the parser, similarly to the VP9 decoder. A lot of the error checking
in the reader is a lot cleaner thanks to this change, since all reads
can be range checked in Streamer::read_octet() now.

Most functions for the Streamer class are now also out-of-line in
Reader.cpp now instead of residing in the header.
This commit is contained in:
Zaggy1024 2022-11-09 23:38:50 -06:00 committed by Andreas Kling
parent 9cf7e8c5aa
commit 2dfd236dcd
6 changed files with 338 additions and 440 deletions

View file

@ -11,8 +11,7 @@
static void decode_video(StringView path, size_t expected_frame_count) static void decode_video(StringView path, size_t expected_frame_count)
{ {
auto matroska_document = Video::Matroska::Reader::parse_matroska_from_file(path); auto matroska_document = MUST(Video::Matroska::Reader::parse_matroska_from_file(path));
VERIFY(matroska_document);
auto video_track_optional = matroska_document->track_for_track_type(Video::Matroska::TrackEntry::TrackType::Video); auto video_track_optional = matroska_document->track_for_track_type(Video::Matroska::TrackEntry::TrackType::Video);
VERIFY(video_track_optional.has_value()); VERIFY(video_track_optional.has_value());
auto video_track_entry = video_track_optional.value(); auto video_track_entry = video_track_optional.value();

View file

@ -10,22 +10,12 @@ namespace Video::Matroska {
DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_file(StringView filename) DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_file(StringView filename)
{ {
// FIXME: MatroskaReader should return errors. return make<MatroskaDemuxer>(TRY(Reader::parse_matroska_from_file(filename)));
auto nullable_document = Reader::parse_matroska_from_file(filename);
if (!nullable_document)
return DecoderError::format(DecoderErrorCategory::IO, "Failed to open matroska from file '{}'", filename);
auto document = nullable_document.release_nonnull();
return make<MatroskaDemuxer>(document);
} }
DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_data(ReadonlyBytes data) DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> MatroskaDemuxer::from_data(ReadonlyBytes data)
{ {
// FIXME: MatroskaReader should return errors. return make<MatroskaDemuxer>(TRY(Reader::parse_matroska_from_data(data.data(), data.size())));
auto nullable_document = Reader::parse_matroska_from_data(data.data(), data.size());
if (!nullable_document)
return DecoderError::format(DecoderErrorCategory::IO, "Failed to open matroska from data");
auto document = nullable_document.release_nonnull();
return make<MatroskaDemuxer>(document);
} }
Vector<Track> MatroskaDemuxer::get_tracks_for_type(TrackType type) Vector<Track> MatroskaDemuxer::get_tracks_for_type(TrackType type)

View file

@ -20,7 +20,7 @@ public:
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_file(StringView filename); static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_file(StringView filename);
static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_data(ReadonlyBytes data); static DecoderErrorOr<NonnullOwnPtr<MatroskaDemuxer>> from_data(ReadonlyBytes data);
MatroskaDemuxer(NonnullOwnPtr<MatroskaDocument>& document) MatroskaDemuxer(NonnullOwnPtr<MatroskaDocument>&& document)
: m_document(move(document)) : m_document(move(document))
{ {
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com> * Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com>
* Copyright (c) 2022, Gregory Bertilson <Zaggy1024@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -13,9 +14,7 @@
namespace Video::Matroska { namespace Video::Matroska {
#define CHECK_HAS_VALUE(x) \ #define TRY_READ(expression) DECODER_TRY(DecoderErrorCategory::Corrupted, expression)
if (!(x).has_value()) \
return false
constexpr u32 EBML_MASTER_ELEMENT_ID = 0x1A45DFA3; constexpr u32 EBML_MASTER_ELEMENT_ID = 0x1A45DFA3;
constexpr u32 SEGMENT_ELEMENT_ID = 0x18538067; constexpr u32 SEGMENT_ELEMENT_ID = 0x18538067;
@ -56,415 +55,335 @@ constexpr u32 BIT_DEPTH_ID = 0x6264;
constexpr u32 SIMPLE_BLOCK_ID = 0xA3; constexpr u32 SIMPLE_BLOCK_ID = 0xA3;
constexpr u32 TIMESTAMP_ID = 0xE7; constexpr u32 TIMESTAMP_ID = 0xE7;
OwnPtr<MatroskaDocument> Reader::parse_matroska_from_file(StringView path) DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse_matroska_from_file(StringView path)
{ {
auto mapped_file_result = Core::MappedFile::map(path); auto mapped_file = DECODER_TRY(DecoderErrorCategory::IO, Core::MappedFile::map(path));
if (mapped_file_result.is_error())
return {};
auto mapped_file = mapped_file_result.release_value();
return parse_matroska_from_data((u8*)mapped_file->data(), mapped_file->size()); return parse_matroska_from_data((u8*)mapped_file->data(), mapped_file->size());
} }
OwnPtr<MatroskaDocument> Reader::parse_matroska_from_data(u8 const* data, size_t size) DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse_matroska_from_data(u8 const* data, size_t size)
{ {
Reader reader(data, size); Reader reader(data, size);
return reader.parse(); return reader.parse();
} }
OwnPtr<MatroskaDocument> Reader::parse() DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> Reader::parse()
{ {
auto first_element_id = m_streamer.read_variable_size_integer(false); auto first_element_id = TRY_READ(m_streamer.read_variable_size_integer(false));
dbgln_if(MATROSKA_TRACE_DEBUG, "First element ID is {:#010x}\n", first_element_id.value()); dbgln_if(MATROSKA_TRACE_DEBUG, "First element ID is {:#010x}\n", first_element_id);
if (!first_element_id.has_value() || first_element_id.value() != EBML_MASTER_ELEMENT_ID) if (first_element_id != EBML_MASTER_ELEMENT_ID)
return {}; return DecoderError::corrupted("First element was not an EBML header"sv);
auto header = parse_ebml_header();
if (!header.has_value())
return {};
auto header = TRY(parse_ebml_header());
dbgln_if(MATROSKA_DEBUG, "Parsed EBML header"); dbgln_if(MATROSKA_DEBUG, "Parsed EBML header");
auto root_element_id = m_streamer.read_variable_size_integer(false); auto root_element_id = TRY_READ(m_streamer.read_variable_size_integer(false));
if (!root_element_id.has_value() || root_element_id.value() != SEGMENT_ELEMENT_ID) if (root_element_id != SEGMENT_ELEMENT_ID)
return {}; return DecoderError::corrupted("Second element was not a segment element"sv);
auto matroska_document = make<MatroskaDocument>(header.value());
auto segment_parse_success = parse_segment_elements(*matroska_document);
if (!segment_parse_success)
return {};
auto matroska_document = make<MatroskaDocument>(header);
TRY(parse_segment_elements(*matroska_document));
return matroska_document; return matroska_document;
} }
bool Reader::parse_master_element([[maybe_unused]] StringView element_name, Function<bool(u64)> element_consumer) DecoderErrorOr<void> Reader::parse_master_element([[maybe_unused]] StringView element_name, Function<DecoderErrorOr<void>(u64)> element_consumer)
{ {
auto element_data_size = m_streamer.read_variable_size_integer(); auto element_data_size = TRY_READ(m_streamer.read_variable_size_integer());
CHECK_HAS_VALUE(element_data_size); dbgln_if(MATROSKA_DEBUG, "{} has {} octets of data.", element_name, element_data_size);
dbgln_if(MATROSKA_DEBUG, "{} has {} octets of data.", element_name, element_data_size.value());
m_streamer.push_octets_read(); m_streamer.push_octets_read();
while (m_streamer.octets_read() < element_data_size.value()) { while (m_streamer.octets_read() < element_data_size) {
dbgln_if(MATROSKA_TRACE_DEBUG, "====== Reading element ======"); dbgln_if(MATROSKA_TRACE_DEBUG, "====== Reading element ======");
auto optional_element_id = m_streamer.read_variable_size_integer(false); auto element_id = TRY_READ(m_streamer.read_variable_size_integer(false));
CHECK_HAS_VALUE(optional_element_id);
auto element_id = optional_element_id.value();
dbgln_if(MATROSKA_TRACE_DEBUG, "{:s} element ID is {:#010x}\n", element_name, element_id); dbgln_if(MATROSKA_TRACE_DEBUG, "{:s} element ID is {:#010x}\n", element_name, element_id);
if (!element_consumer(element_id)) { TRY(element_consumer(element_id));
dbgln_if(MATROSKA_DEBUG, "{:s} consumer failed on ID {:#010x}\n", element_name.to_string().characters(), element_id);
return false;
}
dbgln_if(MATROSKA_TRACE_DEBUG, "Read {} octets of the {} so far.", m_streamer.octets_read(), element_name); dbgln_if(MATROSKA_TRACE_DEBUG, "Read {} octets of the {} so far.", m_streamer.octets_read(), element_name);
} }
m_streamer.pop_octets_read(); m_streamer.pop_octets_read();
return true; return {};
} }
Optional<EBMLHeader> Reader::parse_ebml_header() DecoderErrorOr<EBMLHeader> Reader::parse_ebml_header()
{ {
EBMLHeader header; EBMLHeader header;
auto success = parse_master_element("Header"sv, [&](u64 element_id) { TRY(parse_master_element("Header"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == DOCTYPE_ELEMENT_ID) { switch (element_id) {
auto doc_type = read_string_element(); case DOCTYPE_ELEMENT_ID:
CHECK_HAS_VALUE(doc_type); header.doc_type = TRY_READ(m_streamer.read_string());
header.doc_type = doc_type.value(); dbgln_if(MATROSKA_DEBUG, "Read DocType attribute: {}", header.doc_type);
dbgln_if(MATROSKA_DEBUG, "Read DocType attribute: {}", doc_type.value()); break;
} else if (element_id == DOCTYPE_VERSION_ELEMENT_ID) { case DOCTYPE_VERSION_ELEMENT_ID:
auto doc_type_version = read_u64_element(); header.doc_type_version = TRY_READ(m_streamer.read_u64());
CHECK_HAS_VALUE(doc_type_version); dbgln_if(MATROSKA_DEBUG, "Read DocTypeVersion attribute: {}", header.doc_type_version);
header.doc_type_version = doc_type_version.value(); break;
dbgln_if(MATROSKA_DEBUG, "Read DocTypeVersion attribute: {}", doc_type_version.value()); default:
} else { TRY_READ(m_streamer.read_unknown_element());
return read_unknown_element();
} }
return true;
});
if (!success)
return {}; return {};
}));
return header; return header;
} }
bool Reader::parse_segment_elements(MatroskaDocument& matroska_document) DecoderErrorOr<void> Reader::parse_segment_elements(MatroskaDocument& matroska_document)
{ {
dbgln_if(MATROSKA_DEBUG, "Parsing segment elements"); dbgln_if(MATROSKA_DEBUG, "Parsing segment elements");
auto success = parse_master_element("Segment"sv, [&](u64 element_id) { return parse_master_element("Segment"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == SEGMENT_INFORMATION_ELEMENT_ID) { switch (element_id) {
auto segment_information = parse_information(); case SEGMENT_INFORMATION_ELEMENT_ID:
if (!segment_information) matroska_document.set_segment_information(TRY(parse_information()));
return false; break;
matroska_document.set_segment_information(move(segment_information)); case TRACK_ELEMENT_ID:
} else if (element_id == TRACK_ELEMENT_ID) { TRY(parse_tracks(matroska_document));
return parse_tracks(matroska_document); break;
} else if (element_id == CLUSTER_ELEMENT_ID) { case CLUSTER_ELEMENT_ID:
auto cluster = parse_cluster(); matroska_document.clusters().append(TRY(parse_cluster()));
if (!cluster) break;
return false; default:
matroska_document.clusters().append(cluster.release_nonnull()); TRY_READ(m_streamer.read_unknown_element());
} else {
return read_unknown_element();
} }
return true; return {};
}); });
dbgln_if(MATROSKA_DEBUG, "Success {}", success);
return success;
} }
OwnPtr<SegmentInformation> Reader::parse_information() DecoderErrorOr<NonnullOwnPtr<SegmentInformation>> Reader::parse_information()
{ {
auto segment_information = make<SegmentInformation>(); auto segment_information = make<SegmentInformation>();
auto success = parse_master_element("Segment Information"sv, [&](u64 element_id) { TRY(parse_master_element("Segment Information"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == TIMESTAMP_SCALE_ID) { switch (element_id) {
auto timestamp_scale = read_u64_element(); case TIMESTAMP_SCALE_ID:
CHECK_HAS_VALUE(timestamp_scale); segment_information->set_timestamp_scale(TRY_READ(m_streamer.read_u64()));
segment_information->set_timestamp_scale(timestamp_scale.value()); dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", segment_information->timestamp_scale());
dbgln_if(MATROSKA_DEBUG, "Read TimestampScale attribute: {}", timestamp_scale.value()); break;
} else if (element_id == MUXING_APP_ID) { case MUXING_APP_ID:
auto muxing_app = read_string_element(); segment_information->set_muxing_app(TRY_READ(m_streamer.read_string()));
CHECK_HAS_VALUE(muxing_app); dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", segment_information->muxing_app().as_string());
segment_information->set_muxing_app(muxing_app.value()); break;
dbgln_if(MATROSKA_DEBUG, "Read MuxingApp attribute: {}", muxing_app.value()); case WRITING_APP_ID:
} else if (element_id == WRITING_APP_ID) { segment_information->set_writing_app(TRY_READ(m_streamer.read_string()));
auto writing_app = read_string_element(); dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", segment_information->writing_app().as_string());
CHECK_HAS_VALUE(writing_app); break;
segment_information->set_writing_app(writing_app.value()); case DURATION_ID:
dbgln_if(MATROSKA_DEBUG, "Read WritingApp attribute: {}", writing_app.value()); segment_information->set_duration(TRY_READ(m_streamer.read_float()));
} else if (element_id == DURATION_ID) { dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", segment_information->duration().value());
auto duration = read_float_element(); break;
CHECK_HAS_VALUE(duration); default:
segment_information->set_duration(duration.value()); TRY_READ(m_streamer.read_unknown_element());
dbgln_if(MATROSKA_DEBUG, "Read Duration attribute: {}", duration.value());
} else {
return read_unknown_element();
} }
return true;
});
if (!success)
return {}; return {};
}));
return segment_information; return segment_information;
} }
bool Reader::parse_tracks(MatroskaDocument& matroska_document) DecoderErrorOr<void> Reader::parse_tracks(MatroskaDocument& matroska_document)
{ {
auto success = parse_master_element("Tracks"sv, [&](u64 element_id) { return parse_master_element("Tracks"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == TRACK_ENTRY_ID) { if (element_id == TRACK_ENTRY_ID) {
dbgln_if(MATROSKA_DEBUG, "Parsing track"); dbgln_if(MATROSKA_DEBUG, "Parsing track");
auto track_entry = parse_track_entry(); auto track_entry = TRY(parse_track_entry());
if (!track_entry)
return false;
auto track_number = track_entry->track_number(); auto track_number = track_entry->track_number();
matroska_document.add_track(track_number, track_entry.release_nonnull()); dbgln_if(MATROSKA_DEBUG, "Adding track {} to document", track_number);
dbgln_if(MATROSKA_DEBUG, "Track {} added to document", track_number); matroska_document.add_track(track_number, track_entry.release_nonnull<TrackEntry>());
} else { } else {
return read_unknown_element(); TRY_READ(m_streamer.read_unknown_element());
} }
return true; return {};
}); });
return success;
} }
OwnPtr<TrackEntry> Reader::parse_track_entry() DecoderErrorOr<NonnullOwnPtr<TrackEntry>> Reader::parse_track_entry()
{ {
auto track_entry = make<TrackEntry>(); auto track_entry = make<TrackEntry>();
auto success = parse_master_element("Track"sv, [&](u64 element_id) { TRY(parse_master_element("Track"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == TRACK_NUMBER_ID) { switch (element_id) {
auto track_number = read_u64_element(); case TRACK_NUMBER_ID:
CHECK_HAS_VALUE(track_number); track_entry->set_track_number(TRY_READ(m_streamer.read_u64()));
track_entry->set_track_number(track_number.value()); dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_entry->track_number());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackNumber attribute: {}", track_number.value()); break;
} else if (element_id == TRACK_UID_ID) { case TRACK_UID_ID:
auto track_uid = read_u64_element(); track_entry->set_track_uid(TRY_READ(m_streamer.read_u64()));
CHECK_HAS_VALUE(track_uid); dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_entry->track_uid());
track_entry->set_track_uid(track_uid.value()); break;
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackUID attribute: {}", track_uid.value()); case TRACK_TYPE_ID:
} else if (element_id == TRACK_TYPE_ID) { track_entry->set_track_type(static_cast<TrackEntry::TrackType>(TRY_READ(m_streamer.read_u64())));
auto track_type = read_u64_element(); dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackType attribute: {}", track_entry->track_type());
CHECK_HAS_VALUE(track_type); break;
track_entry->set_track_type(static_cast<TrackEntry::TrackType>(track_type.value())); case TRACK_LANGUAGE_ID:
dbgln_if(MATROSKA_TRACE_DEBUG, "Read TrackType attribute: {}", track_type.value()); track_entry->set_language(TRY_READ(m_streamer.read_string()));
} else if (element_id == TRACK_LANGUAGE_ID) { dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", track_entry->language());
auto language = read_string_element(); break;
CHECK_HAS_VALUE(language); case TRACK_CODEC_ID:
track_entry->set_language(language.value()); track_entry->set_codec_id(TRY_READ(m_streamer.read_string()));
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's Language attribute: {}", language.value()); dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", track_entry->codec_id());
} else if (element_id == TRACK_CODEC_ID) { break;
auto codec_id = read_string_element(); case TRACK_VIDEO_ID:
CHECK_HAS_VALUE(codec_id); track_entry->set_video_track(TRY(parse_video_track_information()));
track_entry->set_codec_id(codec_id.value()); break;
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Track's CodecID attribute: {}", codec_id.value()); case TRACK_AUDIO_ID:
} else if (element_id == TRACK_VIDEO_ID) { track_entry->set_audio_track(TRY(parse_audio_track_information()));
auto video_track = parse_video_track_information(); break;
CHECK_HAS_VALUE(video_track); default:
track_entry->set_video_track(video_track.value()); TRY_READ(m_streamer.read_unknown_element());
} else if (element_id == TRACK_AUDIO_ID) {
auto audio_track = parse_audio_track_information();
CHECK_HAS_VALUE(audio_track);
track_entry->set_audio_track(audio_track.value());
} else {
return read_unknown_element();
} }
return true;
});
if (!success)
return {}; return {};
}));
return track_entry; return track_entry;
} }
Optional<TrackEntry::ColorFormat> Reader::parse_video_color_information() DecoderErrorOr<TrackEntry::ColorFormat> Reader::parse_video_color_information()
{ {
TrackEntry::ColorFormat color_format {}; TrackEntry::ColorFormat color_format {};
auto success = parse_master_element("Colour"sv, [&](u64 element_id) { TRY(parse_master_element("Colour"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
switch (element_id) { switch (element_id) {
case PRIMARIES_ID: { case PRIMARIES_ID:
auto primaries_result = read_u64_element(); color_format.color_primaries = static_cast<ColorPrimaries>(TRY_READ(m_streamer.read_u64()));
CHECK_HAS_VALUE(primaries_result);
color_format.color_primaries = static_cast<ColorPrimaries>(primaries_result.value());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's Primaries attribute: {}", color_primaries_to_string(color_format.color_primaries)); dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's Primaries attribute: {}", color_primaries_to_string(color_format.color_primaries));
break; break;
} case TRANSFER_CHARACTERISTICS_ID:
case TRANSFER_CHARACTERISTICS_ID: { color_format.transfer_characteristics = static_cast<TransferCharacteristics>(TRY_READ(m_streamer.read_u64()));
auto transfer_characteristics_result = read_u64_element();
CHECK_HAS_VALUE(transfer_characteristics_result);
color_format.transfer_characteristics = static_cast<TransferCharacteristics>(transfer_characteristics_result.value());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's TransferCharacteristics attribute: {}", transfer_characteristics_to_string(color_format.transfer_characteristics)); dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's TransferCharacteristics attribute: {}", transfer_characteristics_to_string(color_format.transfer_characteristics));
break; break;
} case MATRIX_COEFFICIENTS_ID:
case MATRIX_COEFFICIENTS_ID: { color_format.matrix_coefficients = static_cast<MatrixCoefficients>(TRY_READ(m_streamer.read_u64()));
auto matrix_coefficients_result = read_u64_element();
CHECK_HAS_VALUE(matrix_coefficients_result);
color_format.matrix_coefficients = static_cast<MatrixCoefficients>(matrix_coefficients_result.value());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's MatrixCoefficients attribute: {}", matrix_coefficients_to_string(color_format.matrix_coefficients)); dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's MatrixCoefficients attribute: {}", matrix_coefficients_to_string(color_format.matrix_coefficients));
break; break;
} case BITS_PER_CHANNEL_ID:
case BITS_PER_CHANNEL_ID: { color_format.bits_per_channel = TRY_READ(m_streamer.read_u64());
auto bits_per_channel_result = read_u64_element();
CHECK_HAS_VALUE(bits_per_channel_result);
color_format.bits_per_channel = bits_per_channel_result.value();
dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's BitsPerChannel attribute: {}", color_format.bits_per_channel); dbgln_if(MATROSKA_TRACE_DEBUG, "Read Colour's BitsPerChannel attribute: {}", color_format.bits_per_channel);
break; break;
}
default: default:
return read_unknown_element(); TRY_READ(m_streamer.read_unknown_element());
} }
return true;
});
if (!success)
return {}; return {};
}));
return color_format; return color_format;
} }
Optional<TrackEntry::VideoTrack> Reader::parse_video_track_information() DecoderErrorOr<TrackEntry::VideoTrack> Reader::parse_video_track_information()
{ {
TrackEntry::VideoTrack video_track {}; TrackEntry::VideoTrack video_track {};
auto success = parse_master_element("VideoTrack"sv, [&](u64 element_id) { TRY(parse_master_element("VideoTrack"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == PIXEL_WIDTH_ID) { switch (element_id) {
auto pixel_width = read_u64_element(); case PIXEL_WIDTH_ID:
CHECK_HAS_VALUE(pixel_width); video_track.pixel_width = TRY_READ(m_streamer.read_u64());
video_track.pixel_width = pixel_width.value(); dbgln_if(MATROSKA_TRACE_DEBUG, "Read VideoTrack's PixelWidth attribute: {}", video_track.pixel_width);
dbgln_if(MATROSKA_TRACE_DEBUG, "Read VideoTrack's PixelWidth attribute: {}", pixel_width.value()); break;
} else if (element_id == PIXEL_HEIGHT_ID) { case PIXEL_HEIGHT_ID:
auto pixel_height = read_u64_element(); video_track.pixel_height = TRY_READ(m_streamer.read_u64());
CHECK_HAS_VALUE(pixel_height); dbgln_if(MATROSKA_TRACE_DEBUG, "Read VideoTrack's PixelHeight attribute: {}", video_track.pixel_height);
video_track.pixel_height = pixel_height.value(); break;
dbgln_if(MATROSKA_TRACE_DEBUG, "Read VideoTrack's PixelHeight attribute: {}", pixel_height.value()); case COLOR_ENTRY_ID:
} else if (element_id == COLOR_ENTRY_ID) { video_track.color_format = TRY(parse_video_color_information());
auto color_information_result = parse_video_color_information(); break;
CHECK_HAS_VALUE(color_information_result); default:
video_track.color_format = color_information_result.value(); TRY_READ(m_streamer.read_unknown_element());
} else {
return read_unknown_element();
} }
return true;
});
if (!success)
return {}; return {};
}));
return video_track; return video_track;
} }
Optional<TrackEntry::AudioTrack> Reader::parse_audio_track_information() DecoderErrorOr<TrackEntry::AudioTrack> Reader::parse_audio_track_information()
{ {
TrackEntry::AudioTrack audio_track {}; TrackEntry::AudioTrack audio_track {};
auto success = parse_master_element("AudioTrack"sv, [&](u64 element_id) { TRY(parse_master_element("AudioTrack"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == CHANNELS_ID) { switch (element_id) {
auto channels = read_u64_element(); case CHANNELS_ID:
CHECK_HAS_VALUE(channels); audio_track.channels = TRY_READ(m_streamer.read_u64());
audio_track.channels = channels.value(); dbgln_if(MATROSKA_TRACE_DEBUG, "Read AudioTrack's Channels attribute: {}", audio_track.channels);
dbgln_if(MATROSKA_TRACE_DEBUG, "Read AudioTrack's Channels attribute: {}", channels.value()); break;
} else if (element_id == BIT_DEPTH_ID) { case BIT_DEPTH_ID:
auto bit_depth = read_u64_element(); audio_track.bit_depth = TRY_READ(m_streamer.read_u64());
CHECK_HAS_VALUE(bit_depth); dbgln_if(MATROSKA_TRACE_DEBUG, "Read AudioTrack's BitDepth attribute: {}", audio_track.bit_depth);
audio_track.bit_depth = bit_depth.value(); break;
dbgln_if(MATROSKA_TRACE_DEBUG, "Read AudioTrack's BitDepth attribute: {}", bit_depth.value()); default:
} else { TRY_READ(m_streamer.read_unknown_element());
return read_unknown_element();
} }
return true;
});
if (!success)
return {}; return {};
}));
return audio_track; return audio_track;
} }
OwnPtr<Cluster> Reader::parse_cluster() DecoderErrorOr<NonnullOwnPtr<Cluster>> Reader::parse_cluster()
{ {
auto cluster = make<Cluster>(); auto cluster = make<Cluster>();
auto success = parse_master_element("Cluster"sv, [&](u64 element_id) { TRY(parse_master_element("Cluster"sv, [&](u64 element_id) -> DecoderErrorOr<void> {
if (element_id == SIMPLE_BLOCK_ID) { switch (element_id) {
auto simple_block = parse_simple_block(); case SIMPLE_BLOCK_ID:
if (!simple_block) cluster->blocks().append(TRY(parse_simple_block()));
return false; break;
cluster->blocks().append(simple_block.release_nonnull()); case TIMESTAMP_ID:
} else if (element_id == TIMESTAMP_ID) { cluster->set_timestamp(TRY_READ(m_streamer.read_u64()));
auto timestamp = read_u64_element(); break;
if (!timestamp.has_value()) default:
return false; TRY_READ(m_streamer.read_unknown_element());
cluster->set_timestamp(timestamp.value());
} else {
auto success = read_unknown_element();
if (!success)
return false;
} }
return true;
});
if (!success)
return {}; return {};
}));
return cluster; return cluster;
} }
OwnPtr<Block> Reader::parse_simple_block() DecoderErrorOr<NonnullOwnPtr<Block>> Reader::parse_simple_block()
{ {
auto block = make<Block>(); auto block = make<Block>();
auto content_size = m_streamer.read_variable_size_integer(); auto content_size = TRY_READ(m_streamer.read_variable_size_integer());
if (!content_size.has_value())
return {};
auto octets_read_before_track_number = m_streamer.octets_read(); auto octets_read_before_track_number = m_streamer.octets_read();
auto track_number = m_streamer.read_variable_size_integer(); auto track_number = TRY_READ(m_streamer.read_variable_size_integer());
if (!track_number.has_value()) block->set_track_number(track_number);
return {};
block->set_track_number(track_number.value());
if (m_streamer.remaining() < 3) block->set_timestamp(TRY_READ(m_streamer.read_i16()));
return {};
block->set_timestamp(m_streamer.read_i16());
auto flags = m_streamer.read_octet(); auto flags = TRY_READ(m_streamer.read_octet());
block->set_only_keyframes(flags & (1u << 7u)); block->set_only_keyframes(flags & (1u << 7u));
block->set_invisible(flags & (1u << 3u)); block->set_invisible(flags & (1u << 3u));
block->set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u)); block->set_lacing(static_cast<Block::Lacing>((flags & 0b110u) >> 1u));
block->set_discardable(flags & 1u); block->set_discardable(flags & 1u);
auto total_frame_content_size = content_size.value() - (m_streamer.octets_read() - octets_read_before_track_number); auto total_frame_content_size = content_size - (m_streamer.octets_read() - octets_read_before_track_number);
if (block->lacing() == Block::Lacing::EBML) { if (block->lacing() == Block::Lacing::EBML) {
auto octets_read_before_frame_sizes = m_streamer.octets_read(); auto octets_read_before_frame_sizes = m_streamer.octets_read();
auto frame_count = m_streamer.read_octet() + 1; auto frame_count = TRY_READ(m_streamer.read_octet()) + 1;
Vector<u64> frame_sizes; Vector<u64> frame_sizes;
frame_sizes.ensure_capacity(frame_count); frame_sizes.ensure_capacity(frame_count);
u64 frame_size_sum = 0; u64 frame_size_sum = 0;
u64 previous_frame_size; u64 previous_frame_size;
auto first_frame_size = m_streamer.read_variable_size_integer(); auto first_frame_size = TRY_READ(m_streamer.read_variable_size_integer());
if (!first_frame_size.has_value()) frame_sizes.append(first_frame_size);
return {}; frame_size_sum += first_frame_size;
frame_sizes.append(first_frame_size.value()); previous_frame_size = first_frame_size;
frame_size_sum += first_frame_size.value();
previous_frame_size = first_frame_size.value();
for (int i = 0; i < frame_count - 2; i++) { for (int i = 0; i < frame_count - 2; i++) {
auto frame_size_difference = m_streamer.read_variable_sized_signed_integer(); auto frame_size_difference = TRY_READ(m_streamer.read_variable_size_signed_integer());
if (!frame_size_difference.has_value())
return {};
u64 frame_size; u64 frame_size;
if (frame_size_difference.value() < 0) // FIXME: x - (-y) == x + y??
frame_size = previous_frame_size - (-frame_size_difference.value()); if (frame_size_difference < 0)
frame_size = previous_frame_size - (-frame_size_difference);
else else
frame_size = previous_frame_size + frame_size_difference.value(); frame_size = previous_frame_size + frame_size_difference;
frame_sizes.append(frame_size); frame_sizes.append(frame_size);
frame_size_sum += frame_size; frame_size_sum += frame_size;
previous_frame_size = frame_size; previous_frame_size = frame_size;
@ -472,64 +391,131 @@ OwnPtr<Block> Reader::parse_simple_block()
frame_sizes.append(total_frame_content_size - frame_size_sum - (m_streamer.octets_read() - octets_read_before_frame_sizes)); frame_sizes.append(total_frame_content_size - frame_size_sum - (m_streamer.octets_read() - octets_read_before_frame_sizes));
for (int i = 0; i < frame_count; i++) { for (int i = 0; i < frame_count; i++) {
// FIXME: ReadonlyBytes instead of copying the frame data?
auto current_frame_size = frame_sizes.at(i); auto current_frame_size = frame_sizes.at(i);
auto frame_result = ByteBuffer::copy(m_streamer.data(), current_frame_size); block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(m_streamer.data(), current_frame_size)));
if (frame_result.is_error()) TRY_READ(m_streamer.drop_octets(current_frame_size));
return {};
block->add_frame(frame_result.release_value());
m_streamer.drop_octets(current_frame_size);
} }
} else if (block->lacing() == Block::Lacing::FixedSize) { } else if (block->lacing() == Block::Lacing::FixedSize) {
auto frame_count = m_streamer.read_octet() + 1; auto frame_count = TRY_READ(m_streamer.read_octet()) + 1;
auto individual_frame_size = total_frame_content_size / frame_count; auto individual_frame_size = total_frame_content_size / frame_count;
for (int i = 0; i < frame_count; i++) { for (int i = 0; i < frame_count; i++) {
auto frame_result = ByteBuffer::copy(m_streamer.data(), individual_frame_size); block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(m_streamer.data(), individual_frame_size)));
if (frame_result.is_error()) TRY_READ(m_streamer.drop_octets(individual_frame_size));
return {};
block->add_frame(frame_result.release_value());
m_streamer.drop_octets(individual_frame_size);
} }
} else { } else {
auto frame_result = ByteBuffer::copy(m_streamer.data(), total_frame_content_size); block->add_frame(DECODER_TRY_ALLOC(ByteBuffer::copy(m_streamer.data(), total_frame_content_size)));
if (frame_result.is_error()) TRY_READ(m_streamer.drop_octets(total_frame_content_size));
return {};
block->add_frame(frame_result.release_value());
m_streamer.drop_octets(total_frame_content_size);
} }
return block; return block;
} }
Optional<String> Reader::read_string_element() ErrorOr<String> Reader::Streamer::read_string()
{ {
auto string_length = m_streamer.read_variable_size_integer(); auto string_length = TRY(read_variable_size_integer());
if (!string_length.has_value() || m_streamer.remaining() < string_length.value()) if (remaining() < string_length)
return {}; return Error::from_string_literal("String length extends past the end of the stream");
auto string_value = String(m_streamer.data_as_chars(), string_length.value()); auto string_value = String(data_as_chars(), string_length);
m_streamer.drop_octets(string_length.value()); TRY(drop_octets(string_length));
return string_value; return string_value;
} }
Optional<u64> Reader::read_u64_element() ErrorOr<u8> Reader::Streamer::read_octet()
{ {
auto integer_length = m_streamer.read_variable_size_integer(); if (!has_octet()) {
if (!integer_length.has_value() || m_streamer.remaining() < integer_length.value()) dbgln_if(MATROSKA_TRACE_DEBUG, "Ran out of stream data");
return {}; return Error::from_string_literal("Stream is out of data");
u64 result = 0; }
for (size_t i = 0; i < integer_length.value(); i++) { m_size_remaining--;
if (!m_streamer.has_octet()) m_octets_read.last()++;
return {}; return *(m_data_ptr++);
result = (result << 8u) + m_streamer.read_octet(); }
ErrorOr<i16> Reader::Streamer::read_i16()
{
return (TRY(read_octet()) << 8) | TRY(read_octet());
}
ErrorOr<u64> Reader::Streamer::read_variable_size_integer(bool mask_length)
{
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading from offset {:p}", m_data_ptr);
auto length_descriptor = TRY(read_octet());
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading VINT, first byte is {:#02x}", length_descriptor);
if (length_descriptor == 0)
return Error::from_string_literal("read_variable_size_integer: Length descriptor has no terminating set bit");
size_t length = 0;
while (length < 8) {
if (((length_descriptor >> (8 - length)) & 1) == 1)
break;
length++;
}
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading VINT of total length {}", length);
if (length > 8)
return Error::from_string_literal("read_variable_size_integer: Length is too large");
u64 result;
if (mask_length)
result = length_descriptor & ~(1u << (8 - length));
else
result = length_descriptor;
dbgln_if(MATROSKA_TRACE_DEBUG, "Beginning of VINT is {:#02x}", result);
for (size_t i = 1; i < length; i++) {
u8 next_octet = TRY(read_octet());
dbgln_if(MATROSKA_TRACE_DEBUG, "Read octet of {:#02x}", next_octet);
result = (result << 8u) | next_octet;
dbgln_if(MATROSKA_TRACE_DEBUG, "New result is {:#010x}", result);
} }
return result; return result;
} }
Optional<double> Reader::read_float_element() ErrorOr<i64> Reader::Streamer::read_variable_size_signed_integer()
{ {
auto length = m_streamer.read_variable_size_integer(); auto length_descriptor = TRY(read_octet());
if (!length.has_value() || m_streamer.remaining() < length.value()) if (length_descriptor == 0)
return {}; return Error::from_string_literal("read_variable_sized_signed_integer: Length descriptor has no terminating set bit");
i64 length = 0;
while (length < 8) {
if (((length_descriptor >> (8 - length)) & 1) == 1)
break;
length++;
}
if (length > 8)
return Error::from_string_literal("read_variable_size_integer: Length is too large");
i64 result = length_descriptor & ~(1u << (8 - length));
for (i64 i = 1; i < length; i++) {
u8 next_octet = TRY(read_octet());
result = (result << 8u) | next_octet;
}
result -= AK::exp2<i64>(length * 7 - 1) - 1;
return result;
}
ErrorOr<void> Reader::Streamer::drop_octets(size_t num_octets)
{
if (remaining() < num_octets)
return Error::from_string_literal("Tried to drop octets past the end of the stream");
m_size_remaining -= num_octets;
m_octets_read.last() += num_octets;
m_data_ptr += num_octets;
return {};
}
ErrorOr<u64> Reader::Streamer::read_u64()
{
auto integer_length = TRY(read_variable_size_integer());
u64 result = 0;
for (size_t i = 0; i < integer_length; i++) {
result = (result << 8u) + TRY(read_octet());
}
return result;
}
ErrorOr<double> Reader::Streamer::read_float()
{
auto length = TRY(read_variable_size_integer());
if (length != 4u && length != 8u) if (length != 4u && length != 8u)
return {}; return Error::from_string_literal("Float size must be 4 or 8 bytes");
union { union {
u64 value; u64 value;
@ -537,24 +523,18 @@ Optional<double> Reader::read_float_element()
double double_value; double double_value;
} read_data; } read_data;
read_data.value = 0; read_data.value = 0;
for (size_t i = 0; i < length.value(); i++) { for (size_t i = 0; i < length; i++) {
if (!m_streamer.has_octet()) read_data.value = (read_data.value << 8u) + TRY(read_octet());
return {};
read_data.value = (read_data.value << 8u) + m_streamer.read_octet();
} }
if (length == 4u) if (length == 4u)
return read_data.float_value; return read_data.float_value;
return read_data.double_value; return read_data.double_value;
} }
bool Reader::read_unknown_element() ErrorOr<void> Reader::Streamer::read_unknown_element()
{ {
auto element_length = m_streamer.read_variable_size_integer(); auto element_length = TRY(read_variable_size_integer());
if (!element_length.has_value() || m_streamer.remaining() < element_length.value()) return drop_octets(element_length);
return false;
m_streamer.drop_octets(element_length.value());
return true;
} }
} }

View file

@ -1,17 +1,20 @@
/* /*
* Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com> * Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com>
* Copyright (c) 2022, Gregory Bertilson <Zaggy1024@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include "Document.h"
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/IntegralMath.h> #include <AK/IntegralMath.h>
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <AK/Optional.h> #include <AK/Optional.h>
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <LibVideo/DecoderError.h>
#include "Document.h"
namespace Video::Matroska { namespace Video::Matroska {
@ -22,10 +25,10 @@ public:
{ {
} }
static OwnPtr<MatroskaDocument> parse_matroska_from_file(StringView path); static DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse_matroska_from_file(StringView path);
static OwnPtr<MatroskaDocument> parse_matroska_from_data(u8 const*, size_t); static DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse_matroska_from_data(u8 const*, size_t);
OwnPtr<MatroskaDocument> parse(); DecoderErrorOr<NonnullOwnPtr<MatroskaDocument>> parse();
private: private:
class Streamer { class Streamer {
@ -40,19 +43,6 @@ private:
char const* data_as_chars() { return reinterpret_cast<char const*>(m_data_ptr); } char const* data_as_chars() { return reinterpret_cast<char const*>(m_data_ptr); }
u8 read_octet()
{
VERIFY(m_size_remaining >= 1);
m_size_remaining--;
m_octets_read.last()++;
return *(m_data_ptr++);
}
i16 read_i16()
{
return (read_octet() << 8) | read_octet();
}
size_t octets_read() { return m_octets_read.last(); } size_t octets_read() { return m_octets_read.last(); }
void push_octets_read() { m_octets_read.append(0); } void push_octets_read() { m_octets_read.append(0); }
@ -64,81 +54,23 @@ private:
m_octets_read.last() += popped; m_octets_read.last() += popped;
} }
Optional<u64> read_variable_size_integer(bool mask_length = true) ErrorOr<u8> read_octet();
{
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading from offset {:p}", m_data_ptr);
if (!has_octet()) {
dbgln_if(MATROSKA_TRACE_DEBUG, "Ran out of stream data");
return {};
}
auto length_descriptor = read_octet();
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading VINT, first byte is {:#02x}", length_descriptor);
if (length_descriptor == 0)
return {};
size_t length = 0;
while (length < 8) {
if (length_descriptor & (1u << (8 - length)))
break;
length++;
}
dbgln_if(MATROSKA_TRACE_DEBUG, "Reading VINT of total length {}", length);
if (length > 8)
return {};
u64 result; ErrorOr<i16> read_i16();
if (mask_length)
result = length_descriptor & ~(1u << (8 - length));
else
result = length_descriptor;
dbgln_if(MATROSKA_TRACE_DEBUG, "Beginning of VINT is {:#02x}", result);
for (size_t i = 1; i < length; i++) {
if (!has_octet()) {
dbgln_if(MATROSKA_TRACE_DEBUG, "Ran out of stream data");
return {};
}
u8 next_octet = read_octet();
dbgln_if(MATROSKA_TRACE_DEBUG, "Read octet of {:#02x}", next_octet);
result = (result << 8u) | next_octet;
dbgln_if(MATROSKA_TRACE_DEBUG, "New result is {:#010x}", result);
}
return result;
}
Optional<i64> read_variable_sized_signed_integer() ErrorOr<u64> read_variable_size_integer(bool mask_length = true);
{ ErrorOr<i64> read_variable_size_signed_integer();
auto length_descriptor = read_octet();
if (length_descriptor == 0)
return {};
size_t length = 0;
while (length < 8) {
if (length_descriptor & (1u << (8 - length)))
break;
length++;
}
if (length > 8)
return {};
i64 result = length_descriptor & ~(1u << (8 - length)); ErrorOr<u64> read_u64();
for (size_t i = 1; i < length; i++) { ErrorOr<double> read_float();
if (!has_octet()) {
return {};
}
u8 next_octet = read_octet();
result = (result << 8u) | next_octet;
}
result -= AK::exp2<i64>(length * 7 - 1) - 1;
return result;
}
void drop_octets(size_t num_octets) ErrorOr<String> read_string();
{
VERIFY(m_size_remaining >= num_octets);
m_size_remaining -= num_octets;
m_octets_read.last() += num_octets;
m_data_ptr += num_octets;
}
bool at_end() const { return !m_size_remaining; } ErrorOr<void> read_unknown_element();
ErrorOr<void> drop_octets(size_t num_octets);
bool at_end() const { return m_size_remaining == 0; }
bool has_octet() const { return m_size_remaining >= 1; } bool has_octet() const { return m_size_remaining >= 1; }
size_t remaining() const { return m_size_remaining; } size_t remaining() const { return m_size_remaining; }
@ -150,24 +82,19 @@ private:
Vector<size_t> m_octets_read { 0 }; Vector<size_t> m_octets_read { 0 };
}; };
bool parse_master_element(StringView element_name, Function<bool(u64 element_id)> element_consumer); DecoderErrorOr<void> parse_master_element(StringView element_name, Function<DecoderErrorOr<void>(u64 element_id)> element_consumer);
Optional<EBMLHeader> parse_ebml_header(); DecoderErrorOr<EBMLHeader> parse_ebml_header();
bool parse_segment_elements(MatroskaDocument&); DecoderErrorOr<void> parse_segment_elements(MatroskaDocument&);
OwnPtr<SegmentInformation> parse_information(); DecoderErrorOr<NonnullOwnPtr<SegmentInformation>> parse_information();
bool parse_tracks(MatroskaDocument&); DecoderErrorOr<void> parse_tracks(MatroskaDocument&);
OwnPtr<TrackEntry> parse_track_entry(); DecoderErrorOr<NonnullOwnPtr<TrackEntry>> parse_track_entry();
Optional<TrackEntry::VideoTrack> parse_video_track_information(); DecoderErrorOr<TrackEntry::VideoTrack> parse_video_track_information();
Optional<TrackEntry::ColorFormat> parse_video_color_information(); DecoderErrorOr<TrackEntry::ColorFormat> parse_video_color_information();
Optional<TrackEntry::AudioTrack> parse_audio_track_information(); DecoderErrorOr<TrackEntry::AudioTrack> parse_audio_track_information();
OwnPtr<Cluster> parse_cluster(); DecoderErrorOr<NonnullOwnPtr<Cluster>> parse_cluster();
OwnPtr<Block> parse_simple_block(); DecoderErrorOr<NonnullOwnPtr<Block>> parse_simple_block();
Optional<String> read_string_element();
Optional<u64> read_u64_element();
Optional<double> read_float_element();
bool read_unknown_element();
Streamer m_streamer; Streamer m_streamer;
}; };

View file

@ -10,10 +10,12 @@
ErrorOr<int> serenity_main(Main::Arguments) ErrorOr<int> serenity_main(Main::Arguments)
{ {
auto document = Video::Matroska::Reader::parse_matroska_from_file("/home/anon/Videos/test-webm.webm"sv); auto document_result = Video::Matroska::Reader::parse_matroska_from_file("/home/anon/Videos/test-webm.webm"sv);
if (!document) { if (document_result.is_error()) {
outln("Encountered an error during parsing: {}", document_result.release_error().string_literal());
return Error::from_string_literal("Failed to parse :("); return Error::from_string_literal("Failed to parse :(");
} }
auto document = document_result.release_value();
outln("DocType is {}", document->header().doc_type.characters()); outln("DocType is {}", document->header().doc_type.characters());
outln("DocTypeVersion is {}", document->header().doc_type_version); outln("DocTypeVersion is {}", document->header().doc_type_version);