diff options
author | kleines Filmröllchen <filmroellchen@serenityos.org> | 2023-03-07 16:50:32 +0100 |
---|---|---|
committer | Tim Flynn <trflynn89@pm.me> | 2023-03-13 12:35:17 -0400 |
commit | d8e8ddedf37e16b41186c2b8dd0b247e1676c4af (patch) | |
tree | 145a2f0e35d1b064664d0dc9629ea72adc84a0c7 /Userland | |
parent | d1dd753a95540f8b573f9bcf6313e28d01e369dd (diff) | |
download | serenity-d8e8ddedf37e16b41186c2b8dd0b247e1676c4af.zip |
LibAudio: Add a generic audio metadata container
This container has several design goals:
- Represent all common and relevant metadata fields of audio files in a
unified way.
- Allow perfect recreation of any metadata format from the in-memory
structure. This requires that we allow non-detected fields to reside
in an "untyped" miscellaneous collection.
Like with pictures, plugins are free to store their metadata into the
m_metadata field whenever they read it. It is recommended that this
happens on loader creation; however failing to read metadata should not
cause an error in the plugin.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibAudio/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Loader.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Metadata.cpp | 94 | ||||
-rw-r--r-- | Userland/Libraries/LibAudio/Metadata.h | 69 |
4 files changed, 168 insertions, 0 deletions
diff --git a/Userland/Libraries/LibAudio/CMakeLists.txt b/Userland/Libraries/LibAudio/CMakeLists.txt index cd13d42af7..b77d0bedb7 100644 --- a/Userland/Libraries/LibAudio/CMakeLists.txt +++ b/Userland/Libraries/LibAudio/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES WavLoader.cpp FlacLoader.cpp WavWriter.cpp + Metadata.cpp MP3Loader.cpp QOALoader.cpp QOATypes.cpp diff --git a/Userland/Libraries/LibAudio/Loader.h b/Userland/Libraries/LibAudio/Loader.h index 1b247c244c..37bb668a5f 100644 --- a/Userland/Libraries/LibAudio/Loader.h +++ b/Userland/Libraries/LibAudio/Loader.h @@ -18,6 +18,7 @@ #include <AK/Try.h> #include <LibAudio/GenericTypes.h> #include <LibAudio/LoaderError.h> +#include <LibAudio/Metadata.h> #include <LibAudio/Sample.h> #include <LibAudio/SampleFormats.h> @@ -67,12 +68,14 @@ public: virtual DeprecatedString format_name() = 0; virtual PcmSampleFormat pcm_format() = 0; + Metadata const& metadata() const { return m_metadata; } Vector<PictureData> const& pictures() const { return m_pictures; }; protected: NonnullOwnPtr<SeekableStream> m_stream; Vector<PictureData> m_pictures; + Metadata m_metadata; }; class Loader : public RefCounted<Loader> { @@ -96,6 +99,7 @@ public: u16 num_channels() const { return m_plugin->num_channels(); } DeprecatedString format_name() const { return m_plugin->format_name(); } u16 bits_per_sample() const { return pcm_bits_per_sample(m_plugin->pcm_format()); } + Metadata const& metadata() const { return m_plugin->metadata(); } Vector<PictureData> const& pictures() const { return m_plugin->pictures(); }; private: diff --git a/Userland/Libraries/LibAudio/Metadata.cpp b/Userland/Libraries/LibAudio/Metadata.cpp new file mode 100644 index 0000000000..394760a4d2 --- /dev/null +++ b/Userland/Libraries/LibAudio/Metadata.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Metadata.h" +#include <AK/Assertions.h> +#include <LibCore/Version.h> + +namespace Audio { + +bool Person::is_artist() const +{ + return role == Person::Role::Artist + || role == Person::Role::Composer + || role == Person::Role::Conductor + || role == Person::Role::Lyricist + || role == Person::Role::Performer; +} + +Optional<StringView> Person::name_for_role() const +{ + switch (role) { + case Role::Artist: + case Role::Performer: + return {}; + case Role::Lyricist: + return "Lyricist"sv; + case Role::Conductor: + return "Conductor"sv; + case Role::Publisher: + return "Publisher"sv; + case Role::Engineer: + return "Engineer"sv; + case Role::Composer: + return "Composer"sv; + } + VERIFY_NOT_REACHED(); +} + +void Metadata::replace_encoder_with_serenity() +{ + auto version_or_error = Core::Version::read_long_version_string(); + // Unset the encoder field in this case; we definitely want to replace the existing encoder field. + if (version_or_error.is_error()) + encoder = {}; + auto encoder_string = String::formatted("SerenityOS LibAudio {}", version_or_error.release_value()); + if (encoder_string.is_error()) + encoder = {}; + encoder = encoder_string.release_value(); +} + +Optional<String> Metadata::first_artist() const +{ + auto artist = people.find_if([](auto const& person) { return person.is_artist(); }); + if (artist.is_end()) + return {}; + return artist->name; +} + +ErrorOr<Optional<String>> Metadata::all_artists(StringView concatenate_with) const +{ + // FIXME: This entire function could be similar to TRY(TRY(people.filter(...).try_map(...)).join(concatenate_with)) if these functional iterator transformers existed :^) + Vector<String> artist_texts; + TRY(artist_texts.try_ensure_capacity(people.size())); + for (auto const& person : people) { + if (!person.is_artist()) + continue; + if (auto role_name = person.name_for_role(); role_name.has_value()) + artist_texts.unchecked_append(TRY(String::formatted("{} ({})", person.name, role_name.release_value()))); + else + artist_texts.unchecked_append(person.name); + } + if (artist_texts.is_empty()) + return Optional<String> {}; + return String::join(concatenate_with, artist_texts); +} + +ErrorOr<void> Metadata::add_miscellaneous(String const& field, String value) +{ + // FIXME: Since try_ensure does not return a reference to the contained value, we have to retrieve it separately. + // This is a try_ensure bug that should be fixed. + (void)TRY(miscellaneous.try_ensure(field, []() { return Vector<String> {}; })); + auto& values_for_field = miscellaneous.get(field).release_value(); + return values_for_field.try_append(move(value)); +} + +ErrorOr<void> Metadata::add_person(Person::Role role, String name) +{ + return people.try_append(Person { role, move(name) }); +} + +} diff --git a/Userland/Libraries/LibAudio/Metadata.h b/Userland/Libraries/LibAudio/Metadata.h new file mode 100644 index 0000000000..78de65211e --- /dev/null +++ b/Userland/Libraries/LibAudio/Metadata.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/HashMap.h> +#include <AK/LexicalPath.h> +#include <AK/Optional.h> +#include <AK/String.h> +#include <AK/Time.h> +#include <AK/Variant.h> + +namespace Audio { + +struct Person { + enum class Role { + Artist, + Performer, + Lyricist, + Conductor, + Publisher, + Engineer, + Composer, + }; + Role role; + String name; + + // Whether this person has creative involvement with the song (so not only Role::Artist!). + // This list is subjective and is intended to keep the artist display text in applications relevant. + // It is used for first_artist and all_artists in Metadata. + bool is_artist() const; + + Optional<StringView> name_for_role() const; +}; + +// Audio metadata of the original format must be equivalently reconstructible from this struct. +// That means, (if the format allows it) fields can appear in a different order, but all fields must be present with the original values, +// including duplicate fields where allowed by the format. +struct Metadata { + using Year = unsigned; + + void replace_encoder_with_serenity(); + ErrorOr<void> add_miscellaneous(String const& field, String value); + ErrorOr<void> add_person(Person::Role role, String name); + Optional<String> first_artist() const; + ErrorOr<Optional<String>> all_artists(StringView concatenate_with = ", "sv) const; + + Optional<String> title; + Optional<String> subtitle; + Optional<unsigned> track_number; + Optional<String> album; + Optional<String> genre; + Optional<String> comment; + Optional<String> isrc; + Optional<String> encoder; + Optional<String> copyright; + Optional<float> bpm; + // FIXME: Until the time data structure situation is solved in a good way, we don't parse ISO 8601 time specifications. + Optional<String> unparsed_time; + Vector<Person> people; + + // Any other metadata, using the format-specific field names. This ensures reproducibility. + HashMap<String, Vector<String>> miscellaneous; +}; + +} |