Module: ID3

Defined in:
lib/id3.rb

Overview

Defined Under Namespace

Classes: AudioFile, Frame, GenericTag, RestrictedOrderedHash, Tag1, Tag2

Constant Summary collapse

ID3v1tagSize =

ID3v1 and ID3v1.1 have fixed size tags

128
ID3v1versionbyte =
125
ID3v2headerSize =
10
SUPPORTED_SYMBOLS =
{
"1.0"   => {"ARTIST"=>33..62 , "ALBUM"=>63..92 ,"TITLE"=>3..32,
            "YEAR"=>93..96 , "COMMENT"=>97..126,"GENREID"=>127,
#               "VERSION"=>"1.0"
           }  ,
"1.1"   => {"ARTIST"=>33..62 , "ALBUM"=>63..92 ,"TITLE"=>3..32,
            "YEAR"=>93..96 , "COMMENT"=>97..124,
            "TRACKNUM"=>126, "GENREID"=>127,
#                "VERSION"=>"1.1"
           }  ,

"2.2.0" => {"CONTENTGROUP"=>"TT1", "TITLE"=>"TT2", "SUBTITLE"=>"TT3",
            "ARTIST"=>"TP1", "BAND"=>"TP2", "CONDUCTOR"=>"TP3", "MIXARTIST"=>"TP4",
            "COMPOSER"=>"TCM", "LYRICIST"=>"TXT", "LANGUAGE"=>"TLA", "CONTENTTYPE"=>"TCO",
            "ALBUM"=>"TAL", "TRACKNUM"=>"TRK", "PARTINSET"=>"TPA", "ISRC"=>"TRC", 
            "DATE"=>"TDA", "YEAR"=>"TYE", "TIME"=>"TIM", "RECORDINGDATES"=>"TRD",
            "ORIGYEAR"=>"TOR", "BPM"=>"TBP", "MEDIATYPE"=>"TMT", "FILETYPE"=>"TFT", 
            "COPYRIGHT"=>"TCR", "PUBLISHER"=>"TPB", "ENCODEDBY"=>"TEN", 
            "ENCODERSETTINGS"=>"TSS", "SONGLEN"=>"TLE", "SIZE"=>"TSI",
            "PLAYLISTDELAY"=>"TDY", "INITIALKEY"=>"TKE", "ORIGALBUM"=>"TOT",
            "ORIGFILENAME"=>"TOF", "ORIGARTIST"=>"TOA", "ORIGLYRICIST"=>"TOL",
            "USERTEXT"=>"TXX", 
            "WWWAUDIOFILE"=>"WAF", "WWWARTIST"=>"WAR", "WWWAUDIOSOURCE"=>"WAS",
            "WWWCOMMERCIALINFO"=>"WCM", "WWWCOPYRIGHT"=>"WCP", "WWWPUBLISHER"=>"WPB",
            "WWWUSER"=>"WXX", "UNIQUEFILEID"=>"UFI",
            "INVOLVEDPEOPLE"=>"IPL", "UNSYNCEDLYRICS"=>"ULT", "COMMENT"=>"COM",
            "CDID"=>"MCI", "EVENTTIMING"=>"ETC", "MPEGLOOKUP"=>"MLL",
            "SYNCEDTEMPO"=>"STC", "SYNCEDLYRICS"=>"SLT", "VOLUMEADJ"=>"RVA",
            "EQUALIZATION"=>"EQU", "REVERB"=>"REV", "PICTURE"=>"PIC",
            "GENERALOBJECT"=>"GEO", "PLAYCOUNTER"=>"CNT", "POPULARIMETER"=>"POP",
            "BUFFERSIZE"=>"BUF", "CRYPTEDMETA"=>"CRM", "AUDIOCRYPTO"=>"CRA",
            "LINKED"=>"LNK"
           } ,

"2.3.0" => {"CONTENTGROUP"=>"TIT1", "TITLE"=>"TIT2", "SUBTITLE"=>"TIT3",
            "ARTIST"=>"TPE1", "BAND"=>"TPE2", "CONDUCTOR"=>"TPE3", "MIXARTIST"=>"TPE4",
            "COMPOSER"=>"TCOM", "LYRICIST"=>"TEXT", "LANGUAGE"=>"TLAN", "CONTENTTYPE"=>"TCON",
            "ALBUM"=>"TALB", "TRACKNUM"=>"TRCK", "PARTINSET"=>"TPOS", "ISRC"=>"TSRC",
            "DATE"=>"TDAT", "YEAR"=>"TYER", "TIME"=>"TIME", "RECORDINGDATES"=>"TRDA",
            "ORIGYEAR"=>"TORY", "SIZE"=>"TSIZ", 
            "BPM"=>"TBPM", "MEDIATYPE"=>"TMED", "FILETYPE"=>"TFLT", "COPYRIGHT"=>"TCOP",
            "PUBLISHER"=>"TPUB", "ENCODEDBY"=>"TENC", "ENCODERSETTINGS"=>"TSSE",
            "SONGLEN"=>"TLEN", "PLAYLISTDELAY"=>"TDLY", "INITIALKEY"=>"TKEY",
            "ORIGALBUM"=>"TOAL", "ORIGFILENAME"=>"TOFN", "ORIGARTIST"=>"TOPE",
            "ORIGLYRICIST"=>"TOLY", "FILEOWNER"=>"TOWN", "NETRADIOSTATION"=>"TRSN",
            "NETRADIOOWNER"=>"TRSO", "USERTEXT"=>"TXXX",
            "WWWAUDIOFILE"=>"WOAF", "WWWARTIST"=>"WOAR", "WWWAUDIOSOURCE"=>"WOAS",
            "WWWCOMMERCIALINFO"=>"WCOM", "WWWCOPYRIGHT"=>"WCOP", "WWWPUBLISHER"=>"WPUB",
            "WWWRADIOPAGE"=>"WORS", "WWWPAYMENT"=>"WPAY", "WWWUSER"=>"WXXX", "UNIQUEFILEID"=>"UFID",
            "INVOLVEDPEOPLE"=>"IPLS", 
            "UNSYNCEDLYRICS"=>"USLT", "COMMENT"=>"COMM", "TERMSOFUSE"=>"USER",
            "CDID"=>"MCDI", "EVENTTIMING"=>"ETCO", "MPEGLOOKUP"=>"MLLT",
            "SYNCEDTEMPO"=>"SYTC", "SYNCEDLYRICS"=>"SYLT", 
            "VOLUMEADJ"=>"RVAD", "EQUALIZATION"=>"EQUA", 
            "REVERB"=>"RVRB", "PICTURE"=>"APIC", "GENERALOBJECT"=>"GEOB",
            "PLAYCOUNTER"=>"PCNT", "POPULARIMETER"=>"POPM", "BUFFERSIZE"=>"RBUF",
            "AUDIOCRYPTO"=>"AENC", "LINKEDINFO"=>"LINK", "POSITIONSYNC"=>"POSS",
            "COMMERCIAL"=>"COMR", "CRYPTOREG"=>"ENCR", "GROUPINGREG"=>"GRID", 
            "PRIVATE"=>"PRIV"
           } ,

"2.4.0" => {"CONTENTGROUP"=>"TIT1", "TITLE"=>"TIT2", "SUBTITLE"=>"TIT3",
            "ARTIST"=>"TPE1", "BAND"=>"TPE2", "CONDUCTOR"=>"TPE3", "MIXARTIST"=>"TPE4",
            "COMPOSER"=>"TCOM", "LYRICIST"=>"TEXT", "LANGUAGE"=>"TLAN", "CONTENTTYPE"=>"TCON",
            "ALBUM"=>"TALB", "TRACKNUM"=>"TRCK", "PARTINSET"=>"TPOS", "ISRC"=>"TSRC",
            "RECORDINGTIME"=>"TDRC", "ORIGRELEASETIME"=>"TDOR",
            "BPM"=>"TBPM", "MEDIATYPE"=>"TMED", "FILETYPE"=>"TFLT", "COPYRIGHT"=>"TCOP",
            "PUBLISHER"=>"TPUB", "ENCODEDBY"=>"TENC", "ENCODERSETTINGS"=>"TSSE",
            "SONGLEN"=>"TLEN", "PLAYLISTDELAY"=>"TDLY", "INITIALKEY"=>"TKEY",
            "ORIGALBUM"=>"TOAL", "ORIGFILENAME"=>"TOFN", "ORIGARTIST"=>"TOPE",
            "ORIGLYRICIST"=>"TOLY", "FILEOWNER"=>"TOWN", "NETRADIOSTATION"=>"TRSN",
            "NETRADIOOWNER"=>"TRSO", "USERTEXT"=>"TXXX",
            "SETSUBTITLE"=>"TSST", "MOOD"=>"TMOO", "PRODUCEDNOTICE"=>"TPRO",
            "ENCODINGTIME"=>"TDEN", "RELEASETIME"=>"TDRL", "TAGGINGTIME"=>"TDTG",
            "ALBUMSORTORDER"=>"TSOA", "PERFORMERSORTORDER"=>"TSOP", "TITLESORTORDER"=>"TSOT",
            "WWWAUDIOFILE"=>"WOAF", "WWWARTIST"=>"WOAR", "WWWAUDIOSOURCE"=>"WOAS",
            "WWWCOMMERCIALINFO"=>"WCOM", "WWWCOPYRIGHT"=>"WCOP", "WWWPUBLISHER"=>"WPUB",
            "WWWRADIOPAGE"=>"WORS", "WWWPAYMENT"=>"WPAY", "WWWUSER"=>"WXXX", "UNIQUEFILEID"=>"UFID",
            "MUSICIANCREDITLIST"=>"TMCL", "INVOLVEDPEOPLE2"=>"TIPL",
            "UNSYNCEDLYRICS"=>"USLT", "COMMENT"=>"COMM", "TERMSOFUSE"=>"USER",
            "CDID"=>"MCDI", "EVENTTIMING"=>"ETCO", "MPEGLOOKUP"=>"MLLT",
            "SYNCEDTEMPO"=>"SYTC", "SYNCEDLYRICS"=>"SYLT", 
            "VOLUMEADJ2"=>"RVA2", "EQUALIZATION2"=>"EQU2",
            "REVERB"=>"RVRB", "PICTURE"=>"APIC", "GENERALOBJECT"=>"GEOB",
            "PLAYCOUNTER"=>"PCNT", "POPULARIMETER"=>"POPM", "BUFFERSIZE"=>"RBUF",
            "AUDIOCRYPTO"=>"AENC", "LINKEDINFO"=>"LINK", "POSITIONSYNC"=>"POSS",
            "COMMERCIAL"=>"COMR", "CRYPTOREG"=>"ENCR", "GROUPINGREG"=>"GRID", 
            "PRIVATE"=>"PRIV",
            "OWNERSHIP"=>"OWNE", "SIGNATURE"=>"SIGN", "SEEKFRAME"=>"SEEK",
            "AUDIOSEEKPOINT"=>"ASPI"
           }
}
TAG_HEADER_FLAG_MASK =

Flags in the ID3-Tag Header:

{  # the mask is inverse, for error detection
                          # those flags are supposed to be zero!
  "2.2.0" =>  0x3F,   # 0xC0 , 
  "2.3.0" =>  0x1F,   # 0xE0 , 
  "2.4.0" =>  0x0F    # 0xF0 
}
TAG_HEADER_FLAGS =
{
  "2.2.0" => { "Unsynchronisation"      => 0x80 ,
               "Compression"            => 0x40 ,
             } ,
  "2.3.0" => { "Unsynchronisation"      => 0x80 ,
               "ExtendedHeader"         => 0x40 ,
               "Experimental"           => 0x20 ,
             } ,
  "2.4.0" => { "Unsynchronisation"      => 0x80 ,
               "ExtendedHeader"         => 0x40 ,
               "Experimental"           => 0x20 ,
               "Footer"                 => 0x10 , 
             }
}
FRAME_HEADER_FLAG_MASK =

Flags in the ID3-Frame Header:

{ # the mask is inverse, for error detection
                           # those flags are supposed to be zero!
  "2.3.0" =>  0x1F1F,   # 0xD0D0 ,
  "2.4.0" =>  0x8FB0    # 0x704F ,
}
FRAME_HEADER_FLAGS =
{
  "2.3.0" => { "TagAlterPreservation"   => 0x8000 ,
               "FileAlterPreservation"  => 0x4000 ,
               "ReadOnly"               => 0x2000 ,

               "Compression"            => 0x0080 ,
               "Encryption"             => 0x0040 ,
               "GroupIdentity"          => 0x0020 ,
             } ,
  "2.4.0" => { "TagAlterPreservation"   => 0x4000 , 
               "FileAlterPreservation"  => 0x2000 ,
               "ReadOnly"               => 0x1000 ,

               "GroupIdentity"          => 0x0040 ,
               "Compression"            => 0x0008 ,
               "Encryption"             => 0x0004 ,
               "Unsynchronisation"      => 0x0002 ,
               "DataLengthIndicator"    => 0x0001 ,
             }
}
FRAMETYPE2FRAMENAME =

the FrameTypes are not visible to the user - they are just a mechanism to define only one parser for multiple FraneNames..

{
   "TEXT" => %w(TENTGROUP TITLE SUBTITLE ARTIST BAND CONDUCTOR MIXARTIST COMPOSER LYRICIST LANGUAGE CONTENTTYPE ALBUM TRACKNUM PARTINSET ISRC DATE YEAR TIME RECORDINGDATES ORIGYEAR BPM MEDIATYPE FILETYPE COPYRIGHT PUBLISHER ENCODEDBY ENCODERSETTINGS SONGLEN SIZE PLAYLISTDELAY INITIALKEY ORIGALBUM ORIGFILENAME ORIGARTIST ORIGLYRICIST FILEOWNER NETRADIOSTATION NETRADIOOWNER SETSUBTITLE MOOD PRODUCEDNOTICE ALBUMSORTORDER PERFORMERSORTORDER TITLESORTORDER INVOLVEDPEOPLE), 
   "USERTEXT" => "USERTEXT",
   
   "WEB"      => %w(WWWAUDIOFILE WWWARTIST WWWAUDIOSOURCE WWWCOMMERCIALINFO WWWCOPYRIGHT WWWPUBLISHER WWWRADIOPAGE WWWPAYMENT) , 
   "WWWUSER"  => "WWWUSER",
   "LTEXT"    => "TERMSOFUSE" ,
   "PICTURE"  => "PICTURE" , 
   "UNSYNCEDLYRICS"  => "UNSYNCEDLYRICS" , 
   "COMMENT"  => "COMMENT" , 
   "BINARY"   => %w(PLAYCOUNTER CDID) ,

   # For the following Frames there are no parser stings defined .. the user has access to the raw data
   # The following frames are good examples for completely useless junk which was put into the ID3-definitions.. what were they smoking?
   #
   "UNPARSED"  => %w(UNIQUEFILEID OWNERSHIP SYNCEDTEMPO MPEGLOOKUP REVERB SYNCEDLYRICS CONTENTGROUP POPULARIMETER GENERALOBJECT VOLUMEADJ AUDIOCRYPTO CRYPTEDMETA BUFFERSIZE EVENTTIMING EQUALIZATION LINKED PRIVATE LINKEDINFO POSITIONSYNC GROUPINGREG CRYPTOREG COMMERCIAL SEEKFRAME AUDIOSEEKPOINT SIGNATURE EQUALIZATION2 VOLUMEADJ2 MUSICIANCREDITLIST INVOLVEDPEOPLE2 RECORDINGTIME ORIGRELEASETIME ENCODINGTIME RELEASETIME TAGGINGTIME)
}
VARS =
0
PACKING =
1
FRAME_PARSER =

STILL NEED TO GET MORE TEST-CASES! e.g. Japanese ID3-Tags! or other encodings.. seems like i have no version 2.4.x ID3-tags!! If you have some, send them my way!

{
  "TEXT"      => [ %w(encoding text) , 'CZ*' ] ,
  "USERTEXT"  => [ %w(encoding description value) , 'CZ*Z*' ] ,

  "PICTURE"   => [ %w(encoding mimeType pictType description picture) , 'CZ*CZ*a*' ] ,

  "WEB"       => [ "url" , 'Z*' ] ,
  "WWWUSER"   => [ %w(encoding description url) , 'CZ*Z*' ] ,

  "LTEXT"     => [ %w(encoding language text) , 'CZ*Z*' ] ,
  "UNSYNCEDLYRICS"    => [ %w(encoding language content text) , 'Ca3Z*Z*' ] ,
  "COMMENT"   => [ %w(encoding language short long) , 'Ca3Z*Z*' ] ,
  "BINARY"    => [ "binary" , 'a*' ] ,
  "UNPARSED"  => [ "raw" , 'a*' ]       # how would we do value checking for this?
}
Symbol2framename =

MODULE VARIABLES


ID3::SUPPORTED_SYMBOLS
Framename2symbol =
Hash.new
FrameType2FrameName =
ID3::FRAMETYPE2FRAMENAME
FrameName2FrameType =
FrameType2FrameName.invert
@@RCSid =

CONSTANTS

'$Id: id3.rb,v 1.2 2004/11/29 05:18:44 tilo Exp tilo $'

Class Method Summary collapse

Class Method Details

.hasID3tag?(filename) ⇒ Boolean


hasID3tag?

returns string with all versions found, space separated
returns false  otherwise

Returns:

  • (Boolean)


400
401
402
403
404
405
406
407
408
# File 'lib/id3.rb', line 400

def ID3.hasID3tag?(filename)
  v1 = ID3.hasID3v1tag?(filename)
  v2 = ID3.hasID3v2tag?(filename)

  return false if !v1 && !v2 
  return v1    if !v2
  return v2    if !v1
  return "#{v1} #{v2}"
end

.hasID3v1tag?(filename) ⇒ Boolean


hasID3v1tag?

returns string with version 1.0 or 1.1 if tag was found 
returns false  otherwise

Returns:

  • (Boolean)


355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/id3.rb', line 355

def ID3.hasID3v1tag?(filename)
  hasID3v1tag     = false

  # be careful with empty or corrupt files..
  return false if File.size(filename) < ID3v1tagSize

  f = File.open(filename, 'r')
  f.seek(-ID3v1tagSize, IO::SEEK_END)
  if (f.read(3) == "TAG")
    f.seek(-ID3v1tagSize + ID3v1versionbyte, IO::SEEK_END)
    c = f.getc;                         # this is character 125 of the tag
    if (c == 0) 
       hasID3v1tag = "1.1"
    else
       hasID3v1tag = "1.0"
    end
  end
  f.close
  return hasID3v1tag
end

.hasID3v2tag?(filename) ⇒ Boolean


hasID3v2tag?

returns string with version 2.2.0, 2.3.0 or 2.4.0 if tag found
returns false  otherwise

Returns:

  • (Boolean)


381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/id3.rb', line 381

def ID3.hasID3v2tag?(filename)
  hasID3v2tag     = false

  f = File.open(filename, 'r')
  if (f.read(3) == "ID3")
     major = f.getc
     minor = f.getc
     version   = "2." + major.to_s + '.' + minor.to_s
     hasID3v2tag = version
  end
  f.close
  return hasID3v2tag
end

.removeID3v1tag(filename) ⇒ Object


removeID3v1tag

returns  nil  if no v1 tag was found, or it couldn't be removed
returns  true if v1 tag found and it was removed..

in the future:

returns  ID3.Tag1  object if a v1 tag was found and removed


418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/id3.rb', line 418

def ID3.removeID3v1tag(filename)
  stat = File.stat(filename)
  if stat.file? && stat.writable? && ID3.hasID3v1tag?(filename)
     
     # CAREFUL: this does not check if there really is a valid tag,
     #          that's why we need to check above!!
     
     newsize = stat.size - ID3v1tagSize
     File.open(filename, "r+") { |f| f.truncate(newsize) }

     return true
  else
     return nil
  end
end