1
0
mirror of https://github.com/SineVector241/VoiceCraft-MCBE_Proximity_Chat.git synced 2024-11-20 10:27:45 +00:00
VoiceCraft-MCBE_Proximity_Chat/ATL/AudioData/IO/ID3v1.cs
2024-07-13 11:16:08 +10:00

396 lines
11 KiB
C#

using Commons;
using System;
using System.Collections.Generic;
using System.IO;
using static ATL.TagData;
namespace ATL.AudioData.IO
{
/// <summary>
/// Class for ID3v1.0-1.1 tags manipulation
/// </summary>
public class ID3v1 : MetaDataIO
{
/// <summary>
/// Max. number of music genres
/// </summary>
public const int MAX_MUSIC_GENRES = 192;
/// <summary>
/// Standard size of an ID3v1 tag
/// </summary>
public const int ID3V1_TAG_SIZE = 128;
/// <summary>
/// Magic number of an ID3v1 tag
/// </summary>
public const string ID3V1_ID = "TAG";
/// <summary>
/// Index for ID3v1.0 tag
/// </summary>
private const byte TAG_VERSION_1_0 = 1;
/// <summary>
/// Index for ID3v1.1 tag
/// </summary>
private const byte TAG_VERSION_1_1 = 2;
#region music genres
/// <summary>
/// Codified music genres, ordered by numeric code
/// </summary>
public static readonly string[] MusicGenre = { // Standard genres
"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",
"AlternRock",
"Bass",
"Soul",
"Punk",
"Space",
"Meditative",
"Instrumental Pop",
"Instrumental Rock",
"Ethnic",
"Gothic",
"Darkwave",
"Techno-Industrial",
"Electronic",
"Pop-Folk",
"Eurodance",
"Dream",
"Southern Rock",
"Comedy",
"Cult",
"Gangsta",
"Top 40",
"Christian Rap",
"Pop/Funk",
"Jungle",
"Native American",
"Cabaret",
"New Wave",
"Psychadelic",
"Rave",
"Showtunes",
"Trailer",
"Lo-Fi",
"Tribal",
"Acid Punk",
"Acid Jazz",
"Polka",
"Retro",
"Musical",
"Rock & Roll",
"Hard Rock",
// Extended genres
"Folk",
"Folk-Rock",
"National Folk",
"Swing",
"Fast Fusion",
"Bebob",
"Latin",
"Revival",
"Celtic",
"Bluegrass",
"Avantgarde",
"Gothic Rock",
"Progessive 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 capella",
"Euro-House",
"Dance Hall",
"Goa",
"Drum & Bass",
"Club-House",
"Hardcore",
"Terror",
"Indie",
"BritPop",
"Negerpunk",
"Polsk Punk",
"Beat",
"Christian Gangsta Rap",
"Heavy Metal",
"Black Metal",
"Crossover",
"Contemporary Christian",
"Christian Rock",
"Merengue",
"Salsa",
"Trash 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"
};
#endregion
// --------------- OPTIONAL INFORMATIVE OVERRIDES
/// <inheritdoc/>
public override IList<Format> MetadataFormats
{
get
{
Format format = new Format(MetaDataIOFactory.GetInstance().getFormatsFromPath("id3v1")[0]);
format.Name = format.Name + "." + (m_tagVersion - 1);
format.ID += m_tagVersion - 1;
return new List<Format>(new[] { format });
}
}
// --------------- MANDATORY INFORMATIVE OVERRIDES
/// <inheritdoc/>
protected override int getDefaultTagOffset()
{
return TO_EOF;
}
/// <inheritdoc/>
protected override MetaDataIOFactory.TagType getImplementedTagType()
{
return MetaDataIOFactory.TagType.ID3V1;
}
/// <inheritdoc/>
protected override Field getFrameMapping(string zone, string ID, byte tagVersion)
{
throw new NotImplementedException();
}
// ********************* Auxiliary functions & voids ********************
private bool ReadTag(BufferedBinaryReader source)
{
if (source.Length < ID3V1_TAG_SIZE) return false;
// Read tag
source.Seek(-ID3V1_TAG_SIZE, SeekOrigin.End);
// ID3v1 tags are C-String(null-terminated)-based tags encoded in ASCII
byte[] data = new byte[ID3V1_TAG_SIZE];
source.Read(data, 0, ID3V1_TAG_SIZE);
string header = Utils.Latin1Encoding.GetString(data, 0, 3);
if (!header.Equals(ID3V1_ID)) return false;
byte[] endComment = new byte[2];
structureHelper.AddZone(source.Position - ID3V1_TAG_SIZE, ID3V1_TAG_SIZE);
setMetaField(Field.TITLE, Utils.Latin1Encoding.GetString(data, 3, 30).Replace("\0", ""));
setMetaField(Field.ARTIST, Utils.Latin1Encoding.GetString(data, 33, 30).Replace("\0", ""));
setMetaField(Field.ALBUM, Utils.Latin1Encoding.GetString(data, 63, 30).Replace("\0", ""));
setMetaField(Field.RECORDING_YEAR, Utils.Latin1Encoding.GetString(data, 93, 4).Replace("\0", ""));
string comment = Utils.Latin1Encoding.GetString(data, 97, 28).Replace("\0", "");
Array.Copy(data, 125, endComment, 0, 2);
m_tagVersion = GetTagVersion(endComment);
// Fill properties using tag data
if (TAG_VERSION_1_0 == m_tagVersion)
{
comment += Utils.Latin1Encoding.GetString(endComment, 0, 2).Replace("\0", "");
}
else
{
setMetaField(Field.TRACK_NUMBER, endComment[1].ToString());
}
setMetaField(Field.COMMENT, comment);
setMetaField(Field.GENRE, (data[127] < MAX_MUSIC_GENRES) ? MusicGenre[data[127]] : "");
return true;
}
private static byte GetTagVersion(byte[] endComment)
{
byte result = TAG_VERSION_1_0;
// Terms for ID3v1.1
if ((0 == endComment[0] && 0 != endComment[1]) ||
(32 == endComment[0] && 32 != endComment[1]))
result = TAG_VERSION_1_1;
return result;
}
// ********************** Public functions & voids **********************
/// <summary>
/// Constructor
/// </summary>
public ID3v1()
{
ResetData();
}
/// <inheritdoc/>
protected override bool read(Stream source, ReadTagParams readTagParams)
{
BufferedBinaryReader reader = new BufferedBinaryReader(source);
// Reset and load tag data from file to variable
ResetData();
m_tagVersion = TAG_VERSION_1_0;
bool result = ReadTag(reader);
// Process data if loaded successfuly
if (result) tagExists = true;
else ResetData();
return result;
}
/// <inheritdoc/>
protected override int write(TagData tag, Stream s, string zone)
{
// ID3v1 tags are C-String(null-terminated)-based tags
// they are not unicode-encoded, hence the use of ReadOneByteChars
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(ID3V1_ID));
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(Utils.BuildStrictLengthString(tag[Field.TITLE], 30, '\0')));
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(Utils.BuildStrictLengthString(tag[Field.ARTIST], 30, '\0')));
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(Utils.BuildStrictLengthString(tag[Field.ALBUM], 30, '\0')));
// ID3v1 standard requires the year
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(Utils.BuildStrictLengthString(TrackUtils.ExtractStrYear(tag[Field.RECORDING_YEAR]), 4, '\0')));
StreamUtils.WriteBytes(s, Utils.Latin1Encoding.GetBytes(Utils.BuildStrictLengthString(tag[Field.COMMENT], 28, '\0')));
// ID3v1.1 standard
s.WriteByte(0);
s.WriteByte((byte)Math.Min(TrackUtils.ExtractTrackNumber(tag[Field.TRACK_NUMBER]), Byte.MaxValue));
byte genre = byte.MaxValue;
if (tag[Field.GENRE] != null)
{
string genreStr = tag[Field.GENRE];
for (byte i = 0; i < MAX_MUSIC_GENRES; i++)
{
if (genreStr.Equals(MusicGenre[i], StringComparison.OrdinalIgnoreCase))
{
genre = i;
break;
}
}
}
s.WriteByte(genre);
return 7;
}
}
}