Module: ID3

Defined in:
lib/id3/id3.rb,
lib/id3/tag1.rb,
lib/id3/tag2.rb,
lib/id3/frame.rb,
lib/id3/version.rb,
lib/id3/constants.rb,
lib/id3/frame_array.rb,
lib/id3/generic_tag.rb,
lib/id3/module_methods.rb

Overview


Module ID3 - MODULE METHODS


Defined Under Namespace

Classes: Frame, FrameArray, GenericTag, Tag1, Tag2

Constant Summary collapse

VERSION =
'1.0.0'
Version =

CONSTANTS

VERSION
ID3v1tagSize =

ID3v1 and ID3v1.1 have fixed size tags

128
ID3v1versionbyte =
125
ID3v2headerSize =
10
ID3v2major =
3
ID3v2minor =
4
ID3v2flags =
5
ID3v2tagSize =
6
VERSIONS =
SUPPORTED_VERSIONS = ["1.0", "1.1", "2.2.0", "2.3.0", "2.4.0"]
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" , 

  "PLAYCOUNTER" => "PLAYCOUNTER" , 
  "POPULARIMETER" => "POPULARIMETER", 

  "BINARY"   => %w( CDID ) , # Cee Dee I Dee

  # 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 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
TEXT_ENCODINGS =

String Encodings: See id3v2.4.0-structure document, at section 4.

                 see also: http://en.wikipedia.org/wiki/ID3#ID3v2_Chapters

Frames that allow different types of text encoding contains a text
encoding description byte. Possible encodings:

  $00   ISO-8859-1 [ISO-8859-1]. Terminated with $00. (ASCII)
  $01   [UCS-2] in ID3v2.2,ID3v2.3  / UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM All in ID3v2.4
        strings in the same frame SHALL have the same byteorder.
        Terminated with $00 00.
  $02   UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM. (ID3v2.4 only)
        Terminated with $00 00.
  $03   UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.  (ID3v2.4 only)
["ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"]
FRAME_PARSER =

NOTE: please note that all the first array entries need to be hashes, in order for Ruby 1.9 to handle this correctly!

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

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

  "WEB"       => [ %w(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*' ] ,

  "PLAYCOUNTER"  =>  [%w(counter), 'C*'] ,
  "POPULARIMETER" => [%w(email rating counter), 'Z*CC*'] ,

  "BINARY"    => [ %w(binary) , 'a*' ] ,
  "UNPARSED"  => [ %w(raw) , 'a*' ]       # how would we do value checking for this?
}
Symbol2framename =

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

Class Method Summary collapse

Class Method Details

.hasID3tag?(filename) ⇒ Boolean


hasID3tag?

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

Returns:

  • (Boolean)


66
67
68
69
70
71
72
73
74
# File 'lib/id3/module_methods.rb', line 66

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)


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/id3/module_methods.rb', line 21

def ID3.hasID3v1tag?(filename)
  hasID3v1tag     = false
  
  # be careful with empty or corrupt files..
  return false if File.size(filename) < ID3v1tagSize
  
  f = File.open(filename, 'rb:binary')
  f.seek(-ID3v1tagSize, IO::SEEK_END)
  if (f.read(3) == "TAG")
    f.seek(-ID3v1tagSize + ID3v1versionbyte, IO::SEEK_END)
    c = f.get_byte                         # this is character 125 of the tag
    if (c == 0) 
      hasID3v1tag = "1.0"
    else
      hasID3v1tag = "1.1"
    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)


47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/id3/module_methods.rb', line 47

def ID3.hasID3v2tag?(filename)
  hasID3v2tag     = false
  
  f = File.open(filename, 'rb:binary')
  if (f.read(3) == "ID3")
    major = f.get_byte
    minor = f.get_byte
    version   = "2." + major.to_s + '.' + minor.to_s
    hasID3v2tag = version
  end
  f.close
  return hasID3v2tag
end

.id3_versionsObject


id3_versions



12
13
14
# File 'lib/id3/module_methods.rb', line 12

def ID3.id3_versions
  [ hasID3v1tag?(filename) ,hasID3v2tag?(filename) ].compact    # returns Array of ID3 tag versions found
end

.mungeSize(size) ⇒ Object


convert the size into 4 bytes to be written into an id3v2 header



114
115
116
117
118
119
120
121
122
123
# File 'lib/id3/module_methods.rb', line 114

def ID3.mungeSize(size)
  bytes = Array.new(4,0)
  j = 0;  i = 3
  while i >= 0
    bytes[j],size = size.divmod(128**i)
    j += 1
    i -= 1
  end
  return bytes
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


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/id3/module_methods.rb', line 84

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

.unmungeSize(bytes) ⇒ Object


convert the 4 bytes found in the id3v2 header and return the size



102
103
104
105
106
107
108
109
110
111
# File 'lib/id3/module_methods.rb', line 102

def ID3.unmungeSize(bytes)
  size = 0
  j = 0; i = 3 
  while i >= 0
    size += 128**i * (bytes.getbyte(j) & 0x7f)
    j += 1
    i -= 1
  end
  return size
end