symphonia_metadata/
id3v1.rs

1// Symphonia
2// Copyright (c) 2019-2022 The Project Symphonia Developers.
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8//! An ID3v1 metadata reader.
9
10use symphonia_core::errors::{unsupported_error, Result};
11use symphonia_core::io::ReadBytes;
12use symphonia_core::meta::{MetadataBuilder, StandardTagKey, Tag, Value};
13
14const GENRES: &[&str] = &[
15    // Standard Genres as per ID3v1 specificaation
16    "Blues",
17    "Classic rock",
18    "Country",
19    "Dance",
20    "Disco",
21    "Funk",
22    "Grunge",
23    "Hip-Hop",
24    "Jazz",
25    "Metal",
26    "New Age",
27    "Oldies",
28    "Other",
29    "Pop",
30    "Rhythm and Blues",
31    "Rap",
32    "Reggae",
33    "Rock",
34    "Techno",
35    "Industrial",
36    "Alternative",
37    "Ska",
38    "Death metal",
39    "Pranks",
40    "Soundtrack",
41    "Euro-Techno",
42    "Ambient",
43    "Trip-Hop",
44    "Vocal",
45    "Jazz & Funk",
46    "Fusion",
47    "Trance",
48    "Classical",
49    "Instrumental",
50    "Acid",
51    "House",
52    "Game",
53    "Sound clip",
54    "Gospel",
55    "Noise",
56    "Alternative Rock",
57    "Bass",
58    "Soul",
59    "Punk",
60    "Space",
61    "Meditative",
62    "Instrumental Pop",
63    "Instrumental Rock",
64    "Ethnic",
65    "Gothic",
66    "Darkwave",
67    "Techno-Industrial",
68    "Electronic",
69    "Pop-Folk",
70    "Eurodance",
71    "Dream",
72    "Southern Rock",
73    "Comedy",
74    "Cult",
75    "Gangsta",
76    "Top 40",
77    "Christian Rap",
78    "Pop/Funk",
79    "Jungle",
80    "Native US",
81    "Cabaret",
82    "New Wave",
83    "Psychedelic",
84    "Rave",
85    "Show tunes",
86    "Trailer",
87    "Lo-Fi",
88    "Tribal",
89    "Acid Punk",
90    "Acid Jazz",
91    "Polka",
92    "Retro",
93    "Musical",
94    "Rock 'n Roll",
95    "Hard Rock",
96    // Winamp 1.91+ Extended Genres
97    "Folk",
98    "Folk-Rock",
99    "National Folk",
100    "Swing",
101    "Fast Fusion",
102    "Bebop",
103    "Latin",
104    "Revival",
105    "Celtic",
106    "Bluegrass",
107    "Avantgarde",
108    "Gothic Rock",
109    "Progressive Rock",
110    "Psychedelic Rock",
111    "Symphonic Rock",
112    "Slow rock",
113    "Big Band",
114    "Chorus",
115    "Easy Listening",
116    "Acoustic",
117    "Humour",
118    "Speech",
119    "Chanson",
120    "Opera",
121    "Chamber music",
122    "Symphonia",
123    "Symphony",
124    "Booty bass",
125    "Primus",
126    "Porn groove",
127    "Satire",
128    "Slow jam",
129    "Club",
130    "Tango",
131    "Samba",
132    "Folklore",
133    "Ballad",
134    "Power ballad",
135    "Rhythmic Soul",
136    "Freestyle",
137    "Duet",
138    "Punk Rock",
139    "Drum solo",
140    "A cappella",
141    "Euro-House",
142    "Dance Hall",
143    "Goa",
144    "Drum & Bass",
145    "Club-House",
146    "Hardcore Techno",
147    "Terror",
148    "Indie",
149    "BritPop",
150    "(133)",
151    "Polsk Punk",
152    "Beat",
153    "Christian Gangsta Rap",
154    "Heavy Metal",
155    "Black Metal",
156    "Crossover",
157    "Contemporary Christian",
158    "Christian rock",
159    "Merengue",
160    "Salsa",
161    "Thrash Metal",
162    "Anime",
163    "Jpop",
164    "Synthpop",
165    // Winamp 5.0+ Extended Genres
166    "Abstract",
167    "Art Rock",
168    "Baroque",
169    "Bhangra",
170    "Big beat",
171    "Breakbeat",
172    "Chillout",
173    "Downtempo",
174    "Dub",
175    "EBM",
176    "Eclectic",
177    "Electro",
178    "Electroclash",
179    "Emo",
180    "Experimental",
181    "Garage",
182    "Global",
183    "IDM",
184    "Illbient",
185    "Industro-Goth",
186    "Jam Band",
187    "Krautrock",
188    "Leftfield",
189    "Lounge",
190    "Math Rock",
191    "New Romantic",
192    "Nu-Breakz",
193    "Post-Punk",
194    "Post-Rock",
195    "Psytrance",
196    "Shoegaze",
197    "Space Rock",
198    "Trop Rock",
199    "World Music",
200    "Neoclassical",
201    "Audiobook",
202    "Audio theatre",
203    "Neue Deutsche Welle",
204    "Podcast",
205    "Indie-Rock",
206    "G-Funk",
207    "Dubstep",
208    "Garage Rock",
209    "Psybient",
210];
211
212pub fn read_id3v1<B: ReadBytes>(reader: &mut B, metadata: &mut MetadataBuilder) -> Result<()> {
213    // Read the "TAG" header.
214    let marker = reader.read_triple_bytes()?;
215
216    if marker != *b"TAG" {
217        return unsupported_error("id3v1: Not an ID3v1 tag");
218    }
219
220    let buf = reader.read_boxed_slice_exact(125)?;
221
222    let title = decode_iso8859_text(&buf[0..30]);
223    if !title.is_empty() {
224        metadata.add_tag(Tag::new(Some(StandardTagKey::TrackTitle), "TITLE", Value::from(title)));
225    }
226
227    let artist = decode_iso8859_text(&buf[30..60]);
228    if !artist.is_empty() {
229        metadata.add_tag(Tag::new(Some(StandardTagKey::Artist), "ARTIST", Value::from(artist)));
230    }
231
232    let album = decode_iso8859_text(&buf[60..90]);
233    if !album.is_empty() {
234        metadata.add_tag(Tag::new(Some(StandardTagKey::Album), "ALBUM", Value::from(album)));
235    }
236
237    let year = decode_iso8859_text(&buf[90..94]);
238    if !year.is_empty() {
239        metadata.add_tag(Tag::new(Some(StandardTagKey::Date), "DATE", Value::from(year)));
240    }
241
242    let comment = if buf[122] == 0 {
243        let track = buf[123];
244
245        metadata.add_tag(Tag::new(Some(StandardTagKey::TrackNumber), "TRACK", Value::from(track)));
246
247        decode_iso8859_text(&buf[94..122])
248    }
249    else {
250        decode_iso8859_text(&buf[94..124])
251    };
252
253    if !comment.is_empty() {
254        metadata.add_tag(Tag::new(Some(StandardTagKey::Comment), "COMMENT", Value::from(comment)));
255    }
256
257    let genre_idx = buf[124] as usize;
258
259    // Convert the genre index to an actual genre name using the GENRES lookup table. Genre #133 is
260    // an offensive term and is excluded from Symphonia.
261    if genre_idx < GENRES.len() && genre_idx != 133 {
262        metadata.add_tag(Tag::new(
263            Some(StandardTagKey::Genre),
264            "GENRE",
265            Value::from(GENRES[genre_idx]),
266        ));
267    }
268
269    Ok(())
270}
271
272fn decode_iso8859_text(data: &[u8]) -> String {
273    data.iter().filter(|&b| *b > 0x1f).map(|&b| b as char).collect()
274}
275
276pub mod util {
277    use super::GENRES;
278
279    /// Try to get the genre name for the ID3v1 genre index.
280    pub fn genre_name(index: u8) -> Option<&'static &'static str> {
281        GENRES.get(usize::from(index))
282    }
283}