use crate::error::Result;
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
pub fn read_mp3(data: &[u8]) -> Result<Vec<Tag>> {
let mut tags = Vec::new();
let mut id3_len: usize = 0;
if data.len() >= 10 && data.starts_with(b"ID3") {
let tag_size = syncsafe_u32(&data[6..10]) as usize;
id3_len += tag_size + 10;
let id3v2_tags = read_id3v2(data)?;
tags.extend(id3v2_tags);
}
if data.len() >= 128 {
let v1_start = data.len() - 128;
if &data[v1_start..v1_start + 3] == b"TAG" {
id3_len += 128;
let id3v1_tags = read_id3v1(&data[v1_start..]);
for t in id3v1_tags {
if !tags.iter().any(|existing| existing.name == t.name) {
tags.push(t);
}
}
}
}
if id3_len > 0 {
tags.push(mk("ID3Size", "ID3 Size", Value::U32(id3_len as u32)));
}
let audio_start = if data.starts_with(b"ID3") && data.len() >= 10 {
let size = syncsafe_u32(&data[6..10]);
10 + size as usize
} else {
0
};
if let Some(mpeg_tags) = parse_mpeg_header(data, audio_start) {
tags.extend(mpeg_tags);
}
{
let audio_bitrate_bps = tags
.iter()
.find(|t| t.name == "AudioBitrate")
.and_then(|t| {
t.print_value
.split_whitespace()
.next()
.and_then(|s| s.parse::<u64>().ok())
.map(|kbps| kbps * 1000)
});
if let Some(bps) = audio_bitrate_bps {
if bps > 0 {
let audio_bytes = (data.len() as u64).saturating_sub(id3_len as u64);
let duration_secs = (8 * audio_bytes) as f64 / bps as f64;
let print = format!("{:.2} s (approx)", duration_secs);
tags.push(mk("Duration", "Duration", Value::String(print)));
}
}
}
if !tags.iter().any(|t| t.name == "DateTimeOriginal") {
if let Some(year) = tags
.iter()
.find(|t| t.name == "Year")
.map(|t| t.print_value.clone())
{
if !year.is_empty() {
tags.push(mk(
"DateTimeOriginal",
"Date/Time Original",
Value::String(year),
));
}
}
}
Ok(tags)
}
fn read_id3v2(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 10 || !data.starts_with(b"ID3") {
return Ok(Vec::new());
}
let version = data[3];
let _revision = data[4];
let _flags = data[5];
let tag_size = syncsafe_u32(&data[6..10]) as usize;
let mut tags = Vec::new();
let end = (10 + tag_size).min(data.len());
let mut pos = 10;
if _flags & 0x40 != 0 && pos + 4 <= end {
let ext_size = if version == 4 {
syncsafe_u32(&data[pos..pos + 4]) as usize
} else {
u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize
};
pos += ext_size;
}
while pos < end {
if version == 2 {
if pos + 6 > end {
break;
}
let frame_id = &data[pos..pos + 3];
if frame_id[0] == 0 {
break;
}
let frame_size = ((data[pos + 3] as usize) << 16)
| ((data[pos + 4] as usize) << 8)
| data[pos + 5] as usize;
pos += 6;
if frame_size == 0 || pos + frame_size > end {
break;
}
let frame_data = &data[pos..pos + frame_size];
let new_tags = decode_id3v2_frame_22(frame_id, frame_data);
tags.extend(new_tags);
pos += frame_size;
} else {
if pos + 10 > end {
break;
}
let frame_id = &data[pos..pos + 4];
if frame_id[0] == 0 {
break;
}
let frame_size = if version == 4 {
syncsafe_u32(&data[pos + 4..pos + 8]) as usize
} else {
u32::from_be_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
as usize
};
let _flags = u16::from_be_bytes([data[pos + 8], data[pos + 9]]);
pos += 10;
if frame_size == 0 || pos + frame_size > end {
break;
}
let frame_data = &data[pos..pos + frame_size];
if let Some(tag) = decode_id3v2_frame(frame_id, frame_data) {
tags.push(tag);
}
pos += frame_size;
}
}
Ok(tags)
}
fn syncsafe_u32(data: &[u8]) -> u32 {
((data[0] as u32) << 21) | ((data[1] as u32) << 14) | ((data[2] as u32) << 7) | data[3] as u32
}
fn decode_id3v2_frame_22(frame_id: &[u8], data: &[u8]) -> Vec<Tag> {
let id = match std::str::from_utf8(frame_id) {
Ok(s) => s,
Err(_) => return Vec::new(),
};
match id {
"PIC" => return decode_pic_frame(data),
"ULT" => {
if let Some(tag) = decode_comment_frame("Lyrics", "Lyrics", data) {
return vec![tag];
}
return Vec::new();
}
"COM" => {
if let Some(tag) = decode_comment_frame("Comment", "Comment", data) {
return vec![tag];
}
return Vec::new();
}
"RVA" => {
if let Some(tag) = decode_rva_frame(data) {
return vec![tag];
}
return Vec::new();
}
_ => {}
}
let (name, description) = match id {
"TT2" => ("Title", "Title"),
"TP1" => ("Artist", "Artist"),
"TAL" => ("Album", "Album"),
"TRK" => ("Track", "Track"),
"TYE" => ("Year", "Year"),
"TCO" => ("Genre", "Genre"),
"TEN" => ("EncodedBy", "Encoded By"),
"TCM" => ("Composer", "Composer"),
"TT1" => ("Grouping", "Grouping"),
"TP2" => ("Band", "Band"),
"TPA" => ("PartOfSet", "Part of Set"),
"TCP" => ("Compilation", "Compilation"),
_ => return Vec::new(),
};
let text = match decode_id3_text(data) {
Some(t) => t,
None => return Vec::new(),
};
if name == "Genre" {
let resolved = resolve_genre(&text);
return vec![mk(name, description, Value::String(resolved))];
}
if name == "Compilation" {
let val = match text.trim() {
"0" => "No".to_string(),
"1" => "Yes".to_string(),
other => other.to_string(),
};
return vec![mk(name, description, Value::String(val))];
}
vec![mk(name, description, Value::String(text))]
}
fn decode_id3v2_frame(frame_id: &[u8], data: &[u8]) -> Option<Tag> {
let id = std::str::from_utf8(frame_id).ok()?;
let (name, description) = match id {
"TIT1" => ("Grouping", "Grouping"),
"TIT2" => ("Title", "Title"),
"TIT3" => ("Subtitle", "Subtitle"),
"TPE1" => ("Artist", "Artist"),
"TPE2" => ("Band", "Band"),
"TPE3" => ("Conductor", "Conductor"),
"TPE4" => ("InterpretedBy", "Interpreted By"),
"TALB" => ("Album", "Album"),
"TRCK" => ("Track", "Track"),
"TPOS" => ("PartOfSet", "Part of Set"),
"TYER" | "TDRC" => ("Year", "Year"),
"TCON" => ("Genre", "Genre"),
"TCOM" => ("Composer", "Composer"),
"TENC" => ("EncodedBy", "Encoded By"),
"TBPM" => ("BeatsPerMinute", "Beats Per Minute"),
"TLEN" => ("Length", "Length"),
"TPUB" => ("Publisher", "Publisher"),
"TLAN" => ("Language", "Language"),
"TCOP" => ("Copyright", "Copyright"),
"TSSE" => ("EncoderSettings", "Encoder Settings"),
"TSRC" => ("ISRC", "ISRC"),
"TCMP" => ("Compilation", "Compilation"),
"COMM" => return decode_comment_frame("Comment", "Comment", data),
"USLT" => return decode_comment_frame("Lyrics", "Lyrics", data),
"APIC" => {
return decode_apic_primary(data);
}
"TXXX" => return decode_txxx_frame(data),
"WOAR" => {
let url = crate::encoding::decode_utf8_or_latin1(data)
.trim_end_matches('\0')
.to_string();
return Some(mk("ArtistURL", "Artist URL", Value::String(url)));
}
"WXXX" => return decode_wxxx_frame(data),
"PCNT" => {
if data.len() >= 4 {
let count = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
return Some(mk("PlayCounter", "Play Counter", Value::U32(count)));
}
return None;
}
"POPM" => return decode_popularity_frame(data),
_ => return None,
};
let text = decode_id3_text(data)?;
if name == "Genre" {
let resolved = resolve_genre(&text);
return Some(mk(name, description, Value::String(resolved)));
}
if name == "Compilation" {
let val = match text.trim() {
"0" => "No".to_string(),
"1" => "Yes".to_string(),
other => other.to_string(),
};
return Some(mk(name, description, Value::String(val)));
}
Some(mk(name, description, Value::String(text)))
}
fn decode_id3_text(data: &[u8]) -> Option<String> {
if data.is_empty() {
return None;
}
let encoding = data[0];
let text_data = &data[1..];
let text = match encoding {
0 => {
text_data.iter().map(|&b| b as char).collect::<String>()
}
1 => {
decode_utf16(text_data)
}
2 => {
decode_utf16_be(text_data)
}
3 => {
crate::encoding::decode_utf8_or_latin1(text_data).to_string()
}
_ => crate::encoding::decode_utf8_or_latin1(text_data).to_string(),
};
let trimmed = text.trim_end_matches('\0').to_string();
if trimmed.is_empty() {
None
} else {
Some(trimmed)
}
}
fn decode_utf16(data: &[u8]) -> String {
if data.len() < 2 {
return String::new();
}
let is_le = data[0] == 0xFF && data[1] == 0xFE;
let text_data = &data[2..];
if is_le {
decode_utf16_le(text_data)
} else {
decode_utf16_be(text_data)
}
}
fn decode_utf16_le(data: &[u8]) -> String {
let units: Vec<u16> = data
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect();
String::from_utf16_lossy(&units)
}
fn decode_utf16_be(data: &[u8]) -> String {
let units: Vec<u16> = data
.chunks_exact(2)
.map(|c| u16::from_be_bytes([c[0], c[1]]))
.collect();
String::from_utf16_lossy(&units)
}
fn decode_comment_frame(name: &str, description: &str, data: &[u8]) -> Option<Tag> {
if data.len() < 5 {
return None;
}
let encoding = data[0];
let _language = &data[1..4]; let rest = &data[4..];
let (_, text_part) = split_encoded_string(rest, encoding);
let text = decode_raw_text(text_part, encoding);
let trimmed = text.trim_end_matches('\0').to_string();
if trimmed.is_empty() {
None
} else {
Some(mk(name, description, Value::String(trimmed)))
}
}
fn decode_pic_frame(data: &[u8]) -> Vec<Tag> {
if data.len() < 6 {
return Vec::new();
}
let encoding = data[0];
let image_format = crate::encoding::decode_utf8_or_latin1(&data[1..4]).to_string();
let pic_type = data[4];
let rest = &data[5..];
let (desc_bytes, image_data) = split_encoded_string(rest, encoding);
let description = decode_raw_text(desc_bytes, encoding);
let description = description.trim_end_matches('\0').to_string();
let pic_type_str = picture_type_str(pic_type);
let tags = vec![
mk(
"PictureFormat",
"Picture Format",
Value::String(image_format),
),
mk(
"PictureType",
"Picture Type",
Value::String(pic_type_str.to_string()),
),
mk(
"PictureDescription",
"Picture Description",
Value::String(description),
),
mk("Picture", "Picture", Value::Binary(image_data.to_vec())),
];
tags
}
fn decode_apic_primary(data: &[u8]) -> Option<Tag> {
if data.len() < 4 {
return None;
}
let encoding = data[0];
let rest = &data[1..];
let null_pos = rest.iter().position(|&b| b == 0)?;
let rest = &rest[null_pos + 1..];
if rest.is_empty() {
return None;
}
let _pic_type = rest[0];
let rest = &rest[1..];
let (_, image_data) = split_encoded_string(rest, encoding);
Some(mk("Picture", "Picture", Value::Binary(image_data.to_vec())))
}
fn decode_rva_frame(data: &[u8]) -> Option<Tag> {
if data.len() < 2 {
return None;
}
let mut dat: Vec<u8> = data.to_vec();
let flag = dat.remove(0) as u32;
if dat.is_empty() {
return None;
}
let bits = dat.remove(0) as u32;
if bits == 0 {
return None;
}
let bytes = ((bits + 7) / 8) as usize;
let channels: &[(&str, usize, usize, u32)] = &[
("Right", 0, 2, 0x01),
("Left", 1, 3, 0x02),
("Back-right", 4, 6, 0x04),
("Back-left", 5, 7, 0x08),
("Center", 8, 9, 0x10),
("Bass", 10, 11, 0x20),
];
let mut parts = Vec::new();
for &(name, vol_idx, peak_idx, flag_bit) in channels {
let j = peak_idx * bytes;
if dat.len() < j + bytes {
break;
}
let i = vol_idx * bytes;
let mut rel: i64 = 0;
for b in 0..bytes {
rel = rel * 256 + dat[i + b] as i64;
}
if flag & flag_bit == 0 {
rel = -rel;
}
let max_val = ((1i64 << bits) - 1) as f64;
let pct = 100.0 * rel as f64 / max_val;
parts.push(format!("{:+.1}% {}", pct, name));
}
if parts.is_empty() {
return None;
}
Some(mk(
"RelativeVolumeAdjustment",
"Relative Volume Adjustment",
Value::String(parts.join(", ")),
))
}
fn decode_txxx_frame(data: &[u8]) -> Option<Tag> {
if data.len() < 2 {
return None;
}
let encoding = data[0];
let rest = &data[1..];
let (desc_bytes, value_bytes) = split_encoded_string(rest, encoding);
let desc = decode_raw_text(desc_bytes, encoding);
let value = decode_raw_text(value_bytes, encoding);
let desc = desc.trim_end_matches('\0');
let value = value.trim_end_matches('\0');
if desc.is_empty() {
return None;
}
Some(mk(desc, desc, Value::String(value.to_string())))
}
fn decode_wxxx_frame(data: &[u8]) -> Option<Tag> {
if data.len() < 2 {
return None;
}
let encoding = data[0];
let rest = &data[1..];
let (desc_bytes, url_bytes) = split_encoded_string(rest, encoding);
let desc = decode_raw_text(desc_bytes, encoding);
let url = crate::encoding::decode_utf8_or_latin1(url_bytes)
.trim_end_matches('\0')
.to_string();
let desc = desc.trim_end_matches('\0');
let name = if desc.is_empty() { "UserURL" } else { desc };
Some(mk(name, name, Value::String(url)))
}
fn decode_popularity_frame(data: &[u8]) -> Option<Tag> {
let null_pos = data.iter().position(|&b| b == 0)?;
let email = crate::encoding::decode_utf8_or_latin1(&data[..null_pos]).to_string();
let rest = &data[null_pos + 1..];
if rest.is_empty() {
return None;
}
let rating = rest[0];
let cnt: u32 = if rest.len() >= 5 {
u32::from_be_bytes([rest[1], rest[2], rest[3], rest[4]])
} else {
0
};
Some(mk(
"Popularimeter",
"Popularimeter",
Value::String(format!("{} {} {}", email, rating, cnt)),
))
}
fn split_encoded_string(data: &[u8], encoding: u8) -> (&[u8], &[u8]) {
if encoding == 1 || encoding == 2 {
let mut i = 0;
while i + 1 < data.len() {
if data[i] == 0 && data[i + 1] == 0 {
return (&data[..i], &data[i + 2..]);
}
i += 2;
}
(data, &[])
} else {
match data.iter().position(|&b| b == 0) {
Some(pos) => (&data[..pos], &data[pos + 1..]),
None => (data, &[]),
}
}
}
fn decode_raw_text(data: &[u8], encoding: u8) -> String {
match encoding {
0 => data.iter().map(|&b| b as char).collect(),
1 => decode_utf16(data),
2 => decode_utf16_be(data),
3 => crate::encoding::decode_utf8_or_latin1(data).to_string(),
_ => crate::encoding::decode_utf8_or_latin1(data).to_string(),
}
}
fn resolve_genre(text: &str) -> String {
let text = text.trim();
let inner = if text.starts_with('(') && text.ends_with(')') {
&text[1..text.len() - 1]
} else {
text
};
if let Ok(idx) = inner.parse::<usize>() {
if idx < GENRES.len() {
return GENRES[idx].to_string();
}
}
text.to_string()
}
fn picture_type_str(pic_type: u8) -> &'static str {
match pic_type {
0 => "Other",
1 => "32x32 PNG Icon",
2 => "Other Icon",
3 => "Front Cover",
4 => "Back Cover",
5 => "Leaflet",
6 => "Media",
7 => "Lead Artist",
8 => "Artist",
9 => "Conductor",
10 => "Band",
11 => "Composer",
12 => "Lyricist",
13 => "Recording Studio or Location",
14 => "Recording Session",
15 => "Performance",
16 => "Capture from Movie or Video",
17 => "Bright(ly) Colored Fish",
18 => "Illustration",
19 => "Band Logo",
20 => "Publisher Logo",
_ => "Unknown",
}
}
fn read_id3v1(data: &[u8]) -> Vec<Tag> {
let mut tags = Vec::new();
if data.len() < 128 || &data[0..3] != b"TAG" {
return tags;
}
let title = latin1_string(&data[3..33]);
let artist = latin1_string(&data[33..63]);
let album = latin1_string(&data[63..93]);
let year = latin1_string(&data[93..97]);
let comment = latin1_string(&data[97..127]);
let genre_idx = data[127] as usize;
if !title.is_empty() {
tags.push(mk("Title", "Title", Value::String(title)));
}
if !artist.is_empty() {
tags.push(mk("Artist", "Artist", Value::String(artist)));
}
if !album.is_empty() {
tags.push(mk("Album", "Album", Value::String(album)));
}
if !year.is_empty() {
tags.push(mk("Year", "Year", Value::String(year)));
}
if data[125] == 0 && data[126] != 0 {
tags.push(mk("Track", "Track", Value::U8(data[126])));
let short_comment = latin1_string(&data[97..125]);
if !short_comment.is_empty() {
tags.push(mk("Comment", "Comment", Value::String(short_comment)));
}
} else if !comment.is_empty() {
tags.push(mk("Comment", "Comment", Value::String(comment)));
}
if genre_idx < GENRES.len() {
tags.push(mk(
"Genre",
"Genre",
Value::String(GENRES[genre_idx].to_string()),
));
}
tags
}
fn latin1_string(data: &[u8]) -> String {
data.iter()
.map(|&b| b as char)
.collect::<String>()
.trim()
.trim_end_matches('\0')
.to_string()
}
fn parse_mpeg_header(data: &[u8], start: usize) -> Option<Vec<Tag>> {
let mut pos = start;
while pos + 4 <= data.len() {
if data[pos] == 0xFF && (data[pos + 1] & 0xE0) == 0xE0 {
break;
}
pos += 1;
if pos > start + 4096 {
return None; }
}
if pos + 4 > data.len() {
return None;
}
let header = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
let version = (header >> 19) & 3;
let layer = (header >> 17) & 3;
let bitrate_idx = ((header >> 12) & 0xF) as usize;
let samplerate_idx = ((header >> 10) & 3) as usize;
let channel_mode = (header >> 6) & 3;
let ms_stereo = (header >> 5) & 1;
let intensity_stereo = (header >> 4) & 1;
let copyright_flag = (header >> 3) & 1;
let original_media = (header >> 2) & 1;
let emphasis = header & 3;
let version_str = match version {
0 => "2.5",
2 => "2",
3 => "1",
_ => return None,
};
let layer_num = match layer {
1 => 3u32,
2 => 2,
3 => 1,
_ => return None,
};
let audio_version_str = version_str.to_string();
let audio_layer = layer_num;
let bitrate = if version == 3 && layer == 3 {
[
0u32, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000,
352000, 384000, 416000, 448000, 0,
]
.get(bitrate_idx)
.copied()?
} else if version == 3 && layer == 2 {
[
0u32, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000,
256000, 320000, 384000, 0,
]
.get(bitrate_idx)
.copied()?
} else if version == 3 && layer == 1 {
[
0u32, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000,
224000, 256000, 320000, 0,
]
.get(bitrate_idx)
.copied()?
} else if (version == 0 || version == 2) && layer == 3 {
[
0u32, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000,
192000, 224000, 256000, 0,
]
.get(bitrate_idx)
.copied()?
} else if (version == 0 || version == 2) && (layer == 1 || layer == 2) {
[
0u32, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000,
128000, 144000, 160000, 0,
]
.get(bitrate_idx)
.copied()?
} else {
0
};
let sample_rate = if version == 3 {
[44100u32, 48000, 32000, 0].get(samplerate_idx).copied()?
} else if version == 2 {
[22050u32, 24000, 16000, 0].get(samplerate_idx).copied()?
} else {
[11025u32, 12000, 8000, 0].get(samplerate_idx).copied()?
};
let channel_str = match channel_mode {
0 => "Stereo",
1 => "Joint Stereo",
2 => "Dual Channel",
3 => "Single Channel",
_ => "Unknown",
};
let mut tags = Vec::new();
tags.push(mk(
"MPEGAudioVersion",
"MPEG Audio Version",
Value::String(audio_version_str),
));
tags.push(mk("AudioLayer", "Audio Layer", Value::U32(audio_layer)));
if bitrate > 0 {
tags.push(mk(
"AudioBitrate",
"Audio Bitrate",
Value::String(format!("{} kbps", bitrate / 1000)),
));
}
if sample_rate > 0 {
tags.push(mk("SampleRate", "Sample Rate", Value::U32(sample_rate)));
}
tags.push(mk(
"ChannelMode",
"Channel Mode",
Value::String(channel_str.into()),
));
if layer_num == 3 {
tags.push(mk(
"MSStereo",
"MS Stereo",
Value::String(if ms_stereo != 0 { "On" } else { "Off" }.into()),
));
tags.push(mk(
"IntensityStereo",
"Intensity Stereo",
Value::String(if intensity_stereo != 0 { "On" } else { "Off" }.into()),
));
}
tags.push(mk(
"CopyrightFlag",
"Copyright Flag",
Value::String(if copyright_flag != 0 { "True" } else { "False" }.into()),
));
tags.push(mk(
"OriginalMedia",
"Original Media",
Value::String(if original_media != 0 { "True" } else { "False" }.into()),
));
let emphasis_str = match emphasis {
0 => "None",
1 => "50/15 ms",
2 => "reserved",
3 => "CCIT J.17",
_ => "None",
};
tags.push(mk(
"Emphasis",
"Emphasis",
Value::String(emphasis_str.into()),
));
Some(tags)
}
fn mk(name: &str, description: &str, value: Value) -> Tag {
let print_value = value.to_display_string();
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: description.to_string(),
group: TagGroup {
family0: "ID3".into(),
family1: "ID3".into(),
family2: "Audio".into(),
},
raw_value: value,
print_value,
priority: 0,
}
}
static GENRES: &[&str] = &[
"Blues",
"Classic Rock",
"Country",
"Dance",
"Disco",
"Funk",
"Grunge",
"Hip-Hop",
"Jazz",
"Metal",
"New Age",
"Oldies",
"Other",
"Pop",
"R&B",
"Rap",
"Reggae",
"Rock",
"Techno",
"Industrial",
"Alternative",
"Ska",
"Death Metal",
"Pranks",
"Soundtrack",
"Euro-Techno",
"Ambient",
"Trip-Hop",
"Vocal",
"Jazz+Funk",
"Fusion",
"Trance",
"Classical",
"Instrumental",
"Acid",
"House",
"Game",
"Sound Clip",
"Gospel",
"Noise",
"Alt. Rock",
"Bass",
"Soul",
"Punk",
"Space",
"Meditative",
"Instrumental Pop",
"Instrumental Rock",
"Ethnic",
"Gothic",
"Darkwave",
"Techno-Industrial",
"Electronic",
"Pop-Folk",
"Eurodance",
"Dream",
"Southern Rock",
"Comedy",
"Cult",
"Gangsta Rap",
"Top 40",
"Christian Rap",
"Pop/Funk",
"Jungle",
"Native American",
"Cabaret",
"New Wave",
"Psychedelic",
"Rave",
"Showtunes",
"Trailer",
"Lo-Fi",
"Tribal",
"Acid Punk",
"Acid Jazz",
"Polka",
"Retro",
"Musical",
"Rock & Roll",
"Hard Rock",
"Folk",
"Folk-Rock",
"National Folk",
"Swing",
"Fast-Fusion",
"Bebop",
"Latin",
"Revival",
"Celtic",
"Bluegrass",
"Avantgarde",
"Gothic Rock",
"Progressive Rock",
"Psychedelic Rock",
"Symphonic Rock",
"Slow Rock",
"Big Band",
"Chorus",
"Easy Listening",
"Acoustic",
"Humour",
"Speech",
"Chanson",
"Opera",
"Chamber Music",
"Sonata",
"Symphony",
"Booty Bass",
"Primus",
"Porn Groove",
"Satire",
"Slow Jam",
"Club",
"Tango",
"Samba",
"Folklore",
"Ballad",
"Power Ballad",
"Rhythmic Soul",
"Freestyle",
"Duet",
"Punk Rock",
"Drum Solo",
"A Cappella",
"Euro-House",
"Dance Hall",
"Goa",
"Drum & Bass",
"Club-House",
"Hardcore",
"Terror",
"Indie",
"BritPop",
"Afro-Punk",
"Polsk Punk",
"Beat",
"Christian Gangsta Rap",
"Heavy Metal",
"Black Metal",
"Crossover",
"Contemporary Christian",
"Christian Rock",
"Merengue",
"Salsa",
"Thrash Metal",
"Anime",
"JPop",
"Synthpop",
"Abstract",
"Art Rock",
"Baroque",
"Bhangra",
"Big Beat",
"Breakbeat",
"Chillout",
"Downtempo",
"Dub",
"EBM",
"Eclectic",
"Electro",
"Electroclash",
"Emo",
"Experimental",
"Garage",
"Global",
"IDM",
"Illbient",
"Industro-Goth",
"Jam Band",
"Krautrock",
"Leftfield",
"Lounge",
"Math Rock",
"New Romantic",
"Nu-Breakz",
"Post-Punk",
"Post-Rock",
"Psytrance",
"Shoegaze",
"Space Rock",
"Trop Rock",
"World Music",
"Neoclassical",
"Audiobook",
"Audio Theatre",
"Neue Deutsche Welle",
"Podcast",
"Indie Rock",
"G-Funk",
"Dubstep",
"Garage Rock",
"Psybient",
];