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:
parent
70970b2fa9
commit
9e496a96a6
4 changed files with 92 additions and 2 deletions
|
@ -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() };
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue