summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorkleines Filmröllchen <filmroellchen@serenityos.org>2023-03-07 16:50:32 +0100
committerTim Flynn <trflynn89@pm.me>2023-03-13 12:35:17 -0400
commitd8e8ddedf37e16b41186c2b8dd0b247e1676c4af (patch)
tree145a2f0e35d1b064664d0dc9629ea72adc84a0c7 /Userland
parentd1dd753a95540f8b573f9bcf6313e28d01e369dd (diff)
downloadserenity-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.txt1
-rw-r--r--Userland/Libraries/LibAudio/Loader.h4
-rw-r--r--Userland/Libraries/LibAudio/Metadata.cpp94
-rw-r--r--Userland/Libraries/LibAudio/Metadata.h69
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;
+};
+
+}