/src/serenity/Userland/Libraries/LibAudio/VorbisComment.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023, kleines Filmröllchen <filmroellchen@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include "VorbisComment.h" |
8 | | #include <AK/Endian.h> |
9 | | #include <AK/MemoryStream.h> |
10 | | #include <AK/Span.h> |
11 | | #include <AK/String.h> |
12 | | |
13 | | namespace Audio { |
14 | | |
15 | | static StringView vorbis_field_for_role(Person::Role role) |
16 | 23.0k | { |
17 | 23.0k | static HashMap<Person::Role, StringView> role_map { |
18 | 23.0k | { Person::Role::Artist, "ARTIST"sv }, |
19 | 23.0k | { Person::Role::Performer, "PERFORMER"sv }, |
20 | 23.0k | { Person::Role::Lyricist, "LYRICIST"sv }, |
21 | 23.0k | { Person::Role::Conductor, "CONDUCTOR"sv }, |
22 | 23.0k | { Person::Role::Publisher, "PUBLISHER"sv }, |
23 | 23.0k | { Person::Role::Engineer, "ENCODED-BY"sv }, |
24 | 23.0k | { Person::Role::Composer, "COMPOSER"sv }, |
25 | 23.0k | }; |
26 | 23.0k | return role_map.get(role).value(); |
27 | 23.0k | } |
28 | | |
29 | | // "Content vector format" |
30 | | static ErrorOr<void> read_vorbis_field(Metadata& metadata_to_write_into, String const& unparsed_user_comment) |
31 | 3.88k | { |
32 | | // Technically the field name has to be ASCII, but we just accept all UTF-8. |
33 | 3.88k | auto field_name_and_contents = TRY(unparsed_user_comment.split_limit('=', 2)); |
34 | | |
35 | 3.88k | if (field_name_and_contents.size() != 2) |
36 | 550 | return Error::from_string_literal("User comment does not contain '='"); |
37 | 3.33k | auto contents = field_name_and_contents.take_last(); |
38 | 3.33k | auto field_name = TRY(field_name_and_contents.take_first().to_uppercase()); |
39 | | |
40 | | // Some of these are taken from https://age.hobba.nl/audio/tag_frame_reference.html |
41 | 3.33k | if (field_name == "TITLE"sv) { |
42 | 12 | if (metadata_to_write_into.title.has_value()) |
43 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
44 | 12 | else |
45 | 12 | metadata_to_write_into.title = contents; |
46 | 3.32k | } else if (field_name == "VERSION"sv) { |
47 | 0 | if (metadata_to_write_into.subtitle.has_value()) |
48 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
49 | 0 | else |
50 | 0 | metadata_to_write_into.subtitle = contents; |
51 | 3.32k | } else if (field_name == "ALBUM"sv) { |
52 | 5 | if (metadata_to_write_into.album.has_value()) |
53 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
54 | 5 | else |
55 | 5 | metadata_to_write_into.album = contents; |
56 | 3.31k | } else if (field_name == "COPYRIGHT"sv) { |
57 | 0 | if (metadata_to_write_into.copyright.has_value()) |
58 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
59 | 0 | else |
60 | 0 | metadata_to_write_into.copyright = contents; |
61 | 3.31k | } else if (field_name == "ISRC"sv) { |
62 | 0 | if (metadata_to_write_into.isrc.has_value()) |
63 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
64 | 0 | else |
65 | 0 | metadata_to_write_into.isrc = contents; |
66 | 3.31k | } else if (field_name == "GENRE"sv) { |
67 | 10 | if (metadata_to_write_into.genre.has_value()) |
68 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
69 | 10 | else |
70 | 10 | metadata_to_write_into.genre = contents; |
71 | 3.30k | } else if (field_name == "COMMENT"sv) { |
72 | 0 | if (metadata_to_write_into.comment.has_value()) |
73 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
74 | 0 | else |
75 | 0 | metadata_to_write_into.comment = contents; |
76 | 3.30k | } else if (field_name == "TRACKNUMBER"sv) { |
77 | 11 | if (metadata_to_write_into.track_number.has_value()) |
78 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
79 | 11 | else if (auto maybe_number = contents.to_number<unsigned>(); maybe_number.has_value()) |
80 | 2 | metadata_to_write_into.track_number = maybe_number.release_value(); |
81 | 9 | else |
82 | 9 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
83 | 3.29k | } else if (field_name == "DATE"sv) { |
84 | 4 | if (metadata_to_write_into.unparsed_time.has_value()) |
85 | 0 | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
86 | 4 | else |
87 | 4 | metadata_to_write_into.unparsed_time = contents; |
88 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Performer)) { |
89 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Performer, contents)); |
90 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Artist)) { |
91 | 1 | TRY(metadata_to_write_into.add_person(Person::Role::Artist, contents)); |
92 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Composer)) { |
93 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Composer, contents)); |
94 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Conductor)) { |
95 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Conductor, contents)); |
96 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Lyricist)) { |
97 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Lyricist, contents)); |
98 | 3.29k | } else if (field_name == "ORGANIZATION"sv) { |
99 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Publisher, contents)); |
100 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Publisher)) { |
101 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Publisher, contents)); |
102 | 3.29k | } else if (field_name == vorbis_field_for_role(Person::Role::Engineer)) { |
103 | 0 | TRY(metadata_to_write_into.add_person(Person::Role::Engineer, contents)); |
104 | 3.29k | } else { |
105 | 3.29k | TRY(metadata_to_write_into.add_miscellaneous(field_name, contents)); |
106 | 3.29k | } |
107 | | |
108 | 3.33k | return {}; |
109 | 3.33k | } |
110 | | |
111 | | ErrorOr<Metadata, LoaderError> load_vorbis_comment(ByteBuffer const& vorbis_comment) |
112 | 5.41k | { |
113 | 5.41k | FixedMemoryStream stream { vorbis_comment }; |
114 | 5.41k | auto vendor_length = TRY(stream.read_value<LittleEndian<u32>>()); |
115 | 5.38k | Vector<u8> raw_vendor_string; |
116 | 5.38k | TRY(raw_vendor_string.try_resize(vendor_length)); |
117 | 5.38k | TRY(stream.read_until_filled(raw_vendor_string)); |
118 | 5.03k | auto vendor_string = TRY(String::from_utf8(StringView { raw_vendor_string.span() })); |
119 | | |
120 | 4.90k | Metadata metadata; |
121 | 4.90k | metadata.encoder = move(vendor_string); |
122 | | |
123 | 4.90k | auto user_comment_count = TRY(stream.read_value<LittleEndian<u32>>()); |
124 | 8.11k | for (size_t i = 0; i < user_comment_count; ++i) { |
125 | 7.06k | auto user_comment_length = TRY(stream.read_value<LittleEndian<u32>>()); |
126 | 4.62k | Vector<u8> raw_user_comment; |
127 | 4.62k | TRY(raw_user_comment.try_resize(user_comment_length)); |
128 | 4.62k | TRY(stream.read_until_filled(raw_user_comment)); |
129 | 4.45k | auto unparsed_user_comment = TRY(String::from_utf8(StringView { raw_user_comment.span() })); |
130 | 3.88k | TRY(read_vorbis_field(metadata, unparsed_user_comment)); |
131 | 3.33k | } |
132 | | |
133 | 4.77k | return metadata; |
134 | 4.77k | } |
135 | | |
136 | | struct VorbisCommentPair { |
137 | | String field_name; |
138 | | String contents; |
139 | | }; |
140 | | |
141 | | static ErrorOr<Vector<VorbisCommentPair>> make_vorbis_user_comments(Metadata const& metadata) |
142 | 0 | { |
143 | 0 | Vector<VorbisCommentPair> user_comments; |
144 | |
|
145 | 0 | auto add_if_present = [&](auto field_name, auto const& value) -> ErrorOr<void> { |
146 | 0 | if (value.has_value()) |
147 | 0 | TRY(user_comments.try_append(VorbisCommentPair { field_name, TRY(String::formatted("{}", value.value())) })); |
148 | 0 | return {}; |
149 | 0 | }; Unexecuted instantiation: VorbisComment.cpp:AK::ErrorOr<void, AK::Error> Audio::make_vorbis_user_comments(Audio::Metadata const&)::$_0::operator()<AK::String, AK::Optional<AK::String> >(AK::String, AK::Optional<AK::String> const&) const Unexecuted instantiation: VorbisComment.cpp:AK::ErrorOr<void, AK::Error> Audio::make_vorbis_user_comments(Audio::Metadata const&)::$_0::operator()<AK::String, AK::Optional<unsigned int> >(AK::String, AK::Optional<unsigned int> const&) const |
150 | |
|
151 | 0 | TRY(add_if_present("TITLE"_string, metadata.title)); |
152 | 0 | TRY(add_if_present("VERSION"_string, metadata.subtitle)); |
153 | 0 | TRY(add_if_present("ALBUM"_string, metadata.album)); |
154 | 0 | TRY(add_if_present("COPYRIGHT"_string, metadata.copyright)); |
155 | 0 | TRY(add_if_present("ISRC"_string, metadata.isrc)); |
156 | 0 | TRY(add_if_present("GENRE"_string, metadata.genre)); |
157 | 0 | TRY(add_if_present("COMMENT"_string, metadata.comment)); |
158 | 0 | TRY(add_if_present("TRACKNUMBER"_string, metadata.track_number)); |
159 | 0 | TRY(add_if_present("DATE"_string, metadata.unparsed_time)); |
160 | |
|
161 | 0 | for (auto const& person : metadata.people) |
162 | 0 | TRY(user_comments.try_append(VorbisCommentPair { TRY(String::from_utf8(vorbis_field_for_role(person.role))), person.name })); |
163 | |
|
164 | 0 | for (auto const& field : metadata.miscellaneous) { |
165 | 0 | for (auto const& value : field.value) |
166 | 0 | TRY(user_comments.try_append(VorbisCommentPair { field.key, value })); |
167 | 0 | } |
168 | |
|
169 | 0 | return user_comments; |
170 | 0 | } |
171 | | |
172 | | ErrorOr<void> write_vorbis_comment(Metadata const& metadata, Stream& target) |
173 | 0 | { |
174 | 0 | auto encoder = metadata.encoder.value_or({}).bytes(); |
175 | 0 | TRY(target.write_value<LittleEndian<u32>>(encoder.size())); |
176 | 0 | TRY(target.write_until_depleted(encoder)); |
177 | |
|
178 | 0 | auto vorbis_user_comments = TRY(make_vorbis_user_comments(metadata)); |
179 | 0 | TRY(target.write_value<LittleEndian<u32>>(vorbis_user_comments.size())); |
180 | 0 | for (auto const& field : vorbis_user_comments) { |
181 | 0 | auto const serialized_field = TRY(String::formatted("{}={}", field.field_name, field.contents)); |
182 | 0 | TRY(target.write_value<LittleEndian<u32>>(serialized_field.bytes().size())); |
183 | 0 | TRY(target.write_until_depleted(serialized_field.bytes())); |
184 | 0 | } |
185 | |
|
186 | 0 | return {}; |
187 | 0 | } |
188 | | |
189 | | } |