ID3 v1.1 Tag Class

dwarvenassassin 0 Tallied Votes 205 Views Share

Again, for anyone working with ID3 version 1 or 1.1 tags.

This class is used to store the tags associated with a given file.
There is a default constructor in case you need to instantiate an 'empty' instance, but in 99% of cases you pass the filename of the audio file to the constuctor and it'll return an instance with the correct tags and filename all neatly packaged up for you.

Enjoy!

using System.IO;
using System.Text;

namespace DGRP
{
    public class MP3File
    {
        #region Private fields
        private string artist = "";
        private string title = "";
        private string album = "";
        private string year = "0000";
        private string comment = "";
        private byte track = 0;
        private byte genre = 255;
        private string fname = "";
        #endregion

        #region Accessors
        public string Filename
        {
            get
            {
                return fname;
            }
            set
            {
                fname = value;
            }
        }

        public string Artist
        {
            get
            {
                return artist;
            }
            set
            {
                artist = value.Trim();
             }
        }

        public string Title
        {
            get
            {
                return title;
            }
            set
            {
                title = value.Trim();
            }
        }

        public string Album
        {
            get
            {
                return album;
            }
            set
            {
                album = value.Trim();
            }
        }

        public string Year
        {
            get
            {
                return year;
            }
            set
            {
                string tmpStr = value.Trim();
                if (tmpStr == "") tmpStr = "0000";
                year = tmpStr;
            }
        }

        public string Comment
        {
            get 
            {
                return comment;
            }
            set
            {
                comment = value.Trim();
            }
        }

        public byte Track
        {
            get
            {
                return track;
            }
            set
            {
                track = value;
            }
        }

        public byte GenreIndex
        {
            get
            {
                return genre;
            }
            set
            {
                genre = (value>148)?(byte)255:value;
            }
        }

        public string GenreName
        {
            get
            {
                return Genre.Type[genre];
            }
            set
            {
                genre = Genre.Type[value];
            }
        }
        #endregion

        #region Constructors
        public MP3File() {}

        public MP3File(string fName)
        {
            ReadTags(fName);
        }
        #endregion

        public void ReadTags(string fName)
        {
            FileInfo fi = new FileInfo(fName);

            if (fi.Length < 129)
            {
                this.Filename = fi.FullName;
                return;
            }

            // Test for version 1 Tags
            FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read);
            fs.Seek(-128, SeekOrigin.End);
            byte[] tagTest = new byte[3];
            tagTest[0] = (byte)fs.ReadByte();
            tagTest[1] = (byte)fs.ReadByte();
            tagTest[2] = (byte)fs.ReadByte();

            if (Encoding.ASCII.GetString(tagTest) == "TAG")
            {
                // Found version 1 tags
                byte[] tag = new byte[125];
                fs.Read(tag, 0, 125);
                byte[] artistTag = new byte[30];
                byte[] titleTag = new byte[30];
                byte[] albumTag = new byte[30];
                byte[] yearTag = new byte[4];
                byte[] commentTag = new byte[30];

                for (int i = 0; i < 30; i++)
                {
                    titleTag[i] = tag[i];
                    artistTag[i] = tag[i + 30];
                    albumTag[i] = tag[i + 60];
                    if (i < 29) commentTag[i] = tag[i + 94];
                    if (i < 4) yearTag[i] = tag[i + 90];
                }

                // if this is a version 1.0 tag, convert to version 1.1 and set the track number to 0 .
                if (tag[122] != 0)
                {
                    tag[122] = 0;
                    tag[123] = 0;
                }

                byte trackTag = tag[123];
                byte genreTag = tag[124];

                this.Artist = (Encoding.ASCII.GetString(artistTag)).Replace("\0", "");
                this.Title = (Encoding.ASCII.GetString(titleTag)).Replace("\0", "");
                this.Album = (Encoding.ASCII.GetString(albumTag)).Replace("\0", "");
                this.Year = (Encoding.ASCII.GetString(yearTag)).Replace("\0", "");
                this.Comment = (Encoding.ASCII.GetString(commentTag)).Replace("\0", "");
                this.Track = trackTag;
                this.GenreIndex = genreTag;
            }
            this.Filename = fi.FullName;
        }
    }
}

Dani AI

Generated

Thanks — a compact, useful ID3v1 reader. A few practical correctness and robustness notes follow that complement the original implementation and help avoid data loss or misreads when this code is used against real-world MP3s.

The critical correctness issue is comment/track handling. ID3v1.1 uses the last two comment bytes specially: if the penultimate comment byte (comment[28] of the 30-byte comment) is zero, the final byte is the track number and the stored comment is the first 28 bytes; otherwise the whole 30 bytes are the comment and there is no track byte. The current copy loop effectively drops the final comment byte unconditionally, which truncates v1.0 comments. Correct parsing: read all 30 comment bytes, test comment[28]==0 — if true set comment = first 28 bytes and track = comment[29]; else set comment = all 30 bytes and track = 0.

I/O and encoding improvements: always dispose file handles (use a using block or ReadAllBytes) and wrap file access in try/catch to handle IO/permission errors. Avoid casting ReadByte() directly to byte without checking for -1; prefer Read(buffer, offset, count) and check the returned byte count. For text decoding use ISO-8859-1 (Latin1) rather than ASCII so accented/high-bit characters are preserved.

Other useful refinements: validate Year as a 4-digit field (truncate or pad as needed), avoid magic genre limits (use the actual Genre.Type length to validate indices instead of hardcoding 148), and add basic unit tests (v1.0 vs v1.1, non-ASCII text, files with no tags, tiny files). For full, future-proof tag handling (ID3v1 + ID3v2, nonstandard encodings, write support) consider using a dedicated tagging library such as TagLib# rather than hand-rolling all cases.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.