1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 06:07:34 +00:00

LibAudio: Load WAV metadata

This is quite straightforward with our new RIFF infrastructure.
This commit is contained in:
kleines Filmröllchen 2023-03-15 23:47:50 +01:00 committed by Jelle Raaijmakers
parent 70970b2fa9
commit 9e496a96a6
4 changed files with 92 additions and 2 deletions

View file

@ -8,6 +8,7 @@
#include <AK/Endian.h>
#include <AK/Stream.h>
#include <AK/Try.h>
#include <AK/TypeCasts.h>
namespace Audio::RIFF {
@ -26,6 +27,17 @@ ErrorOr<Chunk> Chunk::read_from_stream(Stream& stream)
auto data = TRY(FixedArray<u8>::create(size));
TRY(stream.read_until_filled(data.span()));
// RIFF chunks may have trailing padding to align to x86 "words" (i.e. 2 bytes).
if (is<SeekableStream>(stream)) {
if (!stream.is_eof()) {
auto stream_position = TRY(static_cast<SeekableStream&>(stream).tell());
if (stream_position % 2 != 0)
TRY(static_cast<SeekableStream&>(stream).seek(1, SeekMode::FromCurrentPosition));
}
} else {
dbgln("RIFF Warning: Cannot align stream to 2-byte boundary, next chunk may be bogus!");
}
return Chunk {
id,
size,
@ -33,6 +45,19 @@ ErrorOr<Chunk> Chunk::read_from_stream(Stream& stream)
};
}
ErrorOr<List> List::read_from_stream(Stream& stream)
{
auto type = TRY(stream.read_value<ChunkID>());
Vector<Chunk> chunks;
while (!stream.is_eof())
TRY(chunks.try_append(TRY(stream.read_value<Chunk>())));
return List {
.type = type,
.chunks = move(chunks),
};
}
StringView ChunkID::as_ascii_string() const
{
return StringView { id_data.span() };

View file

@ -52,4 +52,12 @@ struct Chunk {
FixedArray<u8> data;
};
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf page 23 (LIST type)
struct List {
static ErrorOr<List> read_from_stream(Stream& stream);
ChunkID type;
Vector<Chunk> chunks;
};
}

View file

@ -268,8 +268,25 @@ MaybeLoaderError WavLoaderPlugin::parse_header()
} else {
TRY(m_stream->seek(-RIFF::chunk_id_size, SeekMode::FromCurrentPosition));
auto chunk = TRY(m_stream->read_value<RIFF::Chunk>());
dbgln_if(AWAVLOADER_DEBUG, "Unhandled WAV chunk of type {}, size {} bytes", chunk.id.as_ascii_string(), chunk.size);
// TODO: Handle LIST INFO chunks.
if (chunk.id == RIFF::list_chunk_id) {
auto maybe_list = chunk.data_stream().read_value<RIFF::List>();
if (maybe_list.is_error()) {
dbgln("WAV Warning: LIST chunk invalid, error: {}", maybe_list.release_error());
continue;
}
auto list = maybe_list.release_value();
if (list.type == RIFF::info_chunk_id) {
auto maybe_error = load_wav_info_block(move(list.chunks));
if (maybe_error.is_error())
dbgln("WAV Warning: INFO chunk invalid, error: {}", maybe_error.release_error());
} else {
dbgln("Unhandled WAV list of type {} with {} subchunks", list.type.as_ascii_string(), list.chunks.size());
}
} else {
dbgln_if(AWAVLOADER_DEBUG, "Unhandled WAV chunk of type {}, size {} bytes", chunk.id.as_ascii_string(), chunk.size);
}
}
}
@ -288,4 +305,42 @@ MaybeLoaderError WavLoaderPlugin::parse_header()
return {};
}
// http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf page 23 (LIST type)
// We only recognize the relevant official metadata types; types added in later errata of RIFF are not relevant for audio.
MaybeLoaderError WavLoaderPlugin::load_wav_info_block(Vector<RIFF::Chunk> info_chunks)
{
for (auto const& chunk : info_chunks) {
auto metadata_name = chunk.id.as_ascii_string();
// Chunk contents are zero-terminated strings "ZSTR", so we just drop the null terminator.
StringView metadata_text { chunk.data.span().trim(chunk.data.size() - 1) };
// Note that we assume chunks to be unique, since that seems to almost always be the case.
// Worst case we just drop some metadata.
if (metadata_name == "IART"sv) {
// Artists are combined together with semicolons, at least when you edit them in Windows File Explorer.
auto artists = metadata_text.split_view(";"sv);
for (auto artist : artists)
TRY(m_metadata.add_person(Person::Role::Artist, TRY(String::from_utf8(artist))));
} else if (metadata_name == "ICMT"sv) {
m_metadata.comment = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "ICOP"sv) {
m_metadata.copyright = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "ICRD"sv) {
m_metadata.unparsed_time = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "IENG"sv) {
TRY(m_metadata.add_person(Person::Role::Engineer, TRY(String::from_utf8(metadata_text))));
} else if (metadata_name == "IGNR"sv) {
m_metadata.genre = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "INAM"sv) {
m_metadata.title = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "ISFT"sv) {
m_metadata.encoder = TRY(String::from_utf8(metadata_text));
} else if (metadata_name == "ISRC"sv) {
TRY(m_metadata.add_person(Person::Role::Publisher, TRY(String::from_utf8(metadata_text))));
} else {
TRY(m_metadata.add_miscellaneous(TRY(String::from_utf8(metadata_name)), TRY(String::from_utf8(metadata_text))));
}
}
return {};
}
}

View file

@ -14,6 +14,7 @@
#include <AK/Span.h>
#include <AK/StringView.h>
#include <LibAudio/Loader.h>
#include <LibAudio/RIFFTypes.h>
namespace Audio {
@ -46,6 +47,7 @@ private:
MaybeLoaderError initialize();
MaybeLoaderError parse_header();
MaybeLoaderError load_wav_info_block(Vector<RIFF::Chunk> info_chunks);
LoaderSamples samples_from_pcm_data(Bytes const& data, size_t samples_to_read) const;
template<typename SampleReader>