Version 11 of Tagging MP3 files

Updated 2004-04-18 22:20:58

My son's new MP3 player is able to display some sound information such as title, artist etc. I wondered how this info is coded in the MP3 file. A short research led me to http://www.dv.co.yu/mpgscript/mpeghdr.htm . A simple 128 byte trailer is appended to the MP3 file. The following program can read, edit and (re)write this trailer.

Have fun tagging your MP3s. US

AK - Can something similar be done for Ogg/Vorbis files ?

US - Made a slight change in the binary scan/format lines, now it's exactly ID3v1.1 format. This is also used by some Ogg/Vorbis en-/decoders. It should be possible to use this program unchanged for both MP3 and Ogg/Vorbis. (ID3v2 is a different cup of tea)

NEM - Added variable to keep track of where you last looked for files. Makes my life a little easier.

EE - Note: I've encountered a number of files claiming to be mp3 files that had a huge pile of junk at the *beginning* of the file. This junk contained an extended amount of file identification data, sometimes even including an embedded thumbnail of an album cover image. Some of my mp3 players would complain about "junk at beginning of file", but not all. in particular, xmms would display the band-name and song-name from that data if present, although the "delete ID3 tag" function in xmms wouldn't delete that header junk. Unfortunately, I don't know the format of the junk blob, or how to recognize its start and end, and music starting point..

US - This is probably ID3v2, AFAIK XML based.

miriam - See http://www.id3.org/ for full specifications of the ID3v2 tag. It is quite a lot more complex than ID3v1 but much more flexible. Being at the head of the file it is picked up at the beginning of a streaming file. It can contain descriptions longer than 30 characters. Fields can contain anything, even images if desired. It is a expandable set of chunks like IFF files or PNG files where a reader can skip parts it doesn't understand. The downside of course is the complexity.


 #! /usr/local/bin/tclsh8.3

 package require msgcat
 namespace import msgcat::*

 if {[info exists ::env(HOME)]} {
     set initial_dir $::env(HOME)
 } else {
     set initial_dir [pwd]
 }


 #
 # Read tag (if there is one)
 #
 proc rd_tag {} {
      global title artist album year comment track genre hastag fnam
      set fd [open $fnam r]
      fconfigure $fd -encoding binary -translation binary
      if {[catch {seek $fd -128 end}]} {
         too_short
         .m.file entryconfigure [mc Schreiben] -state disabled
         return
         }
      set tag [read $fd]
      close $fd
      binary scan $tag A3 id
      if {[string equal $id TAG]} {
         set hastag 1
         set genre 12
         binary scan $tag A3A30A30A30A4A28ccc id title artist album  year comment zero track genre
         } else {
         set hastag 0
         set title ""
         set artist ""
         set album ""
         set year ""
         set comment ""
         set track 0
         set genre 12
         }
      .f3.lbgen selection clear 0 end
      .f3.lbgen selection set $genre
      .f3.lbgen see $genre
      }


 #
 # Choose a file to read
 #
 proc rd_file {} {
      global fnam initial_dir
      set ftypes {{MP3 .mp3} {All *}}
      set fnam [tk_getOpenFile -filetypes $ftypes -defaultextension .mp3 \
         -initialdir $initial_dir]
      if {[string length $fnam]} {
         set initial_dir [file dirname $fnam]
         .m.file entryconfigure [mc Schreiben] -state normal
         rd_tag
         } else {
         .m.file entryconfigure [mc Schreiben] -state disabled
         }
      }


 #
 # Write (back) tag to file
 #
 proc wr_file {} {
      global title artist album year comment track hastag fnam
      set genre [.f3.lbgen curselection]
      set tag [binary format a3a30a30a30a4a28ccc TAG $title $artist $album $year $comment 0 [string trimleft $track 0] $genre]
      set fd [open $fnam a]
      fconfigure $fd -encoding binary -translation binary
      if {$hastag} {
         seek $fd -128 end
         }
      puts -nonewline $fd $tag
      close $fd
      }


 #
 # seek fails, file too short
 #
 proc too_short {} {
      global fnam
      tk_messageBox -type ok -icon warning \
         -message "[mc \"Die Datei\"] $fnam [mc \"ist keine MP3-Datei.\"]"
      }


 #
 # Message catalogs:
 #

 # Deutsch
 mcset de Lesen
 mcset de Schreiben
 mcset de Datei
 mcset de Ende
 mcset de Titel
 mcset de Interpret
 mcset de Album
 mcset de Kommentar
 mcset de Jahr
 mcset de Track
 mcset de Genre
 mcset de "Die Datei"
 mcset de "ist keine MP3-Datei."

 # English
 mcset en Lesen Read
 mcset en Schreiben Write
 mcset en Datei File
 mcset en Ende Exit
 mcset en Titel Title
 mcset en Interpret Artist
 mcset en Album
 mcset en Kommentar Comment
 mcset en Jahr Year
 mcset en Track
 mcset en Genre
 mcset en "Die Datei" "The file"
 mcset en "ist keine MP3-Datei." "is not an MP3 file."

 # Add your preferred language here


 #
 # Build GUI
 #
 menu .m -type menubar
 . configure -menu .m
 .m add cascade -label [mc Datei] -menu .m.file
 menu .m.file -tearoff 0
 .m.file add command -label [mc Lesen] -command rd_file
 .m.file add command -label [mc Schreiben] -command wr_file
 .m.file add separator
 .m.file add command -label [mc Ende] -command {destroy .}
 frame .f1
 frame .f2
 frame .f3
 label .f1.lfil -text [mc Datei]:
 entry .f1.efil -textvariable fnam -width 30
 label .f1.ltit -text [mc Titel]:
 entry .f1.etit -textvariable title -width 30
 label .f1.lart -text [mc Interpret]:
 entry .f1.eart -textvariable artist -width 30
 label .f1.lalb -text [mc Album]:
 entry .f1.ealb -textvariable album -width 30
 label .f1.lcom -text [mc Kommentar]:
 entry .f1.ecom -textvariable comment -width 30
 label .f2.lyea -text [mc Jahr]:
 entry .f2.eyea -textvariable year -width 4
 label .f2.ltrk -text [mc Track]:
 entry .f2.etrk -textvariable track -width 2
 label .f3.lgen -text [mc Genre]:
 listbox .f3.lbgen -listvar lgenre -width 40 -height 5 \
                   -yscrollcommand {.f3.sbgen set} \
                   -exportselection 0
 scrollbar .f3.sbgen -orient vert -command {.f3.lbgen yview}

 pack .f1 .f2 .f3 -padx 1m -pady 1m
 grid .f1.lfil -row 0 -column 0 -sticky e
 grid .f1.efil -row 0 -column 1
 grid .f1.ltit -row 1 -column 0 -sticky e
 grid .f1.etit -row 1 -column 1
 grid .f1.lart -row 2 -column 0 -sticky e
 grid .f1.eart -row 2 -column 1
 grid .f1.lalb -row 3 -column 0 -sticky e
 grid .f1.ealb -row 3 -column 1
 grid .f1.lcom -row 4 -column 0 -sticky e
 grid .f1.ecom -row 4 -column 1
 pack .f2.lyea .f2.eyea .f2.ltrk .f2.etrk -side left
 pack .f3.lgen
 pack .f3.lbgen .f3.sbgen -side left -fill y

 .m.file entryconfigure [mc Schreiben] -state disabled

 #
 # Some variables
 #
 set hastag 0
 set fnam ""

 set lgenre {
 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}
 Folk                        Folk-Rock                {National Folk}                Swing
 {Fast Fusion}                Bebob                        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 Brass}
 Primus                        {Porn Groove}                Satire                        {Slow Jam}
 Club                        Tango                        Samba                        Folklore
 Ballad                        {Power Ballad}                {Rhytmic Soul}                Freestyle
 Duet                        {Punk Rock}                {Drum Solo}                {A Capela}
 Euro-House                {Dance Hall}
 }

 #
 # Set defaults
 #
 .f3.lbgen selection clear 0 end
 .f3.lbgen selection set 12
 .f3.lbgen see 12

Category Multimedia