mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
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.
This commit is contained in:
parent
d1dd753a95
commit
d8e8ddedf3
Notes:
sideshowbarker
2024-07-17 11:30:54 +09:00
Author: https://github.com/kleinesfilmroellchen Commit: https://github.com/SerenityOS/serenity/commit/d8e8ddedf3 Pull-request: https://github.com/SerenityOS/serenity/pull/17749 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/LucasChollet Reviewed-by: https://github.com/trflynn89 ✅
4 changed files with 168 additions and 0 deletions
|
@ -4,6 +4,7 @@ set(SOURCES
|
|||
WavLoader.cpp
|
||||
FlacLoader.cpp
|
||||
WavWriter.cpp
|
||||
Metadata.cpp
|
||||
MP3Loader.cpp
|
||||
QOALoader.cpp
|
||||
QOATypes.cpp
|
||||
|
|
|
@ -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:
|
||||
|
|
94
Userland/Libraries/LibAudio/Metadata.cpp
Normal file
94
Userland/Libraries/LibAudio/Metadata.cpp
Normal file
|
@ -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) });
|
||||
}
|
||||
|
||||
}
|
69
Userland/Libraries/LibAudio/Metadata.h
Normal file
69
Userland/Libraries/LibAudio/Metadata.h
Normal file
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in a new issue