id3 0.5.0 → 1.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,261 @@
1
+ # ==============================================================================
2
+ # Class AudioFile may call this ID3File
3
+ #
4
+ # reads and parses audio files for tags
5
+ # writes audio files and attaches dumped tags to it..
6
+ # revert feature would be nice to have..
7
+ #
8
+ # If we query and AudioFile object, we query what's currently associated with it
9
+ # e.g. we're not querying the file itself, but the Tag object which is perhaps modified.
10
+ # To query the file itself, use the ID3 module functions
11
+ #
12
+ # By default the audio portion of the file is not(!) read - to reduce memory footprint - the audioportion could be very long!
13
+ #
14
+ # BUG: (1) : when a id3v2 frame is deleted from a tag, e.g. 'PICTURE', then the raw tag is not updated
15
+ # BUG: (2) : when a AudioFile is written to file, the raw tag is not updated to reflect the new raw tag value
16
+ # BUG: (3) : FIXED. when a AufioFile is written, the order of frames is not the same as in the original file.. fixed using OrderedHash
17
+ # BUG: (5) : when a FrameType is set for a ID3v2 tag, e.g. 'TITLE', the underlying attributes are not automatically pre-filled..
18
+
19
+ class AudioFile
20
+ attr_reader :audioStartX , :audioEndX # begin and end indices of audio data in file
21
+
22
+ attr_reader :pwd, :filename # PWD and relative path/name how file was first referenced
23
+ attr_reader :dirname, :basename # absolute dirname and basename of the file (computed)
24
+
25
+ attr_accessor :tagID3v1, :tagID3v2 # should make aliases id3v1_tag , id3v2_tag
26
+
27
+ # ----------------------------------------------------------------------------
28
+ # initialize
29
+ #
30
+ # AudioFile.new does NOT keep the file open, but scans it and parses the info
31
+ # e.g.: ID3::AudioFile.new('mp3/a.mp3')
32
+
33
+ # this should take two parameters, either Filename or String, and an options hash, e.g. {:read_audio => false}
34
+
35
+ def initialize( filename )
36
+ @filename = filename # similar to path method from class File, which is a mis-nomer!
37
+ @pwd = ENV["PWD"]
38
+ @dirname = File.dirname( filename )
39
+ @basename = File.basename( filename )
40
+
41
+ @tagID3v1 = nil
42
+ @tagID3v2 = nil
43
+
44
+ @audio = nil # this doesn't get initialized with the actual audio during new(), so we don't waste memory
45
+
46
+ audioStartX = 0
47
+ audioEndX = File.size(filename) - 1 # points to the last index
48
+
49
+ if ID3.hasID3v1tag?(@filename)
50
+ @tagID3v1 = ID3::Tag1.new
51
+ @tagID3v1.read(@filename)
52
+
53
+ audioEndX -= ID3::ID3v1tagSize
54
+ end
55
+ if ID3.hasID3v2tag?(@filename)
56
+ @tagID3v2 = ID3::Tag2.new
57
+ @tagID3v2.read(@filename)
58
+
59
+ audioStartX = @tagID3v2.raw.size
60
+ end
61
+
62
+ # audioStartX audioEndX indices into the file need to be set
63
+ @audioStartX = audioStartX # first byte of audio data
64
+ @audioEndX = audioEndX # last byte of audio data
65
+
66
+ # user may compute the MD5sum of the audio content later..
67
+ # but we're only doing this if the user requests it..
68
+ # because MD5sum computation takes a little bit time.
69
+
70
+ @audioMD5sum = nil
71
+ @audioSHA1sum = nil
72
+ end
73
+
74
+ # ----------------------------------------------------------------------------
75
+ # version aka versions
76
+ # queries the tag objects and returns the version numbers of those tags
77
+ # NOTE: this does not reflect what's currently in the file, but what's
78
+ # currently in the AudioFile object
79
+
80
+ def id3_versions # returns Array of ID3 tag versions found
81
+ a = Array.new
82
+ a.push(@tagID3v1.version) if @tagID3v1
83
+ a.push(@tagID3v2.version) if @tagID3v2
84
+ return a
85
+ end
86
+ alias versions id3_versions
87
+ alias version id3_versions
88
+ # ----------------------------------------------------------------------------
89
+ def has_id3v1tag?
90
+ return @tagID3v1
91
+ end
92
+ # ----------------------------------------------------------------------------
93
+ def has_id3v2tag?
94
+ return @tagID3v2
95
+ end
96
+ # ----------------------------------------------------------------------------
97
+ def audioLength
98
+ @audioEndX - @audioStartX + 1
99
+ end
100
+ # ----------------------------------------------------------------------------
101
+ # write
102
+ # write the AudioFile to file, including any ID3-tags
103
+ # We keep backups if we write to a specific filename
104
+
105
+ def write(*filename)
106
+ backups = false
107
+
108
+ if filename.size == 0 # this is an Array!!
109
+ filename = @filename
110
+ backups = true # keep backups if we write to a specific filename
111
+ else
112
+ filename = filename[0]
113
+ backups = false
114
+ end
115
+
116
+ tf = Tempfile.new( @basename )
117
+ tmpname = tf.path
118
+
119
+ # write ID3v2 tag:
120
+
121
+ if @tagID3v2
122
+ tf.write( @tagID3v2.dump )
123
+ end
124
+
125
+ # write Audio Data:
126
+
127
+ tf.write( audio ) # reads audio from file if nil
128
+
129
+ # write ID3v1 tag:
130
+
131
+ if @tagID3v1
132
+ tf.write( @tagID3v1.dump )
133
+ end
134
+
135
+ tf.close
136
+
137
+ # now some logic about moving the tempfile and replacing the original
138
+
139
+ bakname = filename + '.bak'
140
+ move(filename, bakname) if backups && FileTest.exists?(filename) && ! FileTest.exists?(bakname)
141
+
142
+ move(tmpname, filename)
143
+ tf.close(true)
144
+
145
+ # write md5sum sha1sum files:
146
+ writeMD5sum if @audioMD5sum
147
+ writeSHA1sum if @audioSHA1sum
148
+ end
149
+
150
+ # ----------------------------------------------------------------------------
151
+ # readAudion
152
+ # read audio into @audio buffer either from String or from File
153
+ def audio
154
+ @audio ||= readAudio # read the audio portion of the file only once, the first time this is called.
155
+ end
156
+
157
+ def readAudio
158
+ File.open( File.join(@dirname, @basename) ) do |f|
159
+ f.seek(@audioStartX)
160
+ f.read(@audioEndX - @audioStartX + 1)
161
+ end
162
+ end
163
+ # ----------------------------------------------------------------------------
164
+ # writeAudio
165
+ # only for debugging, does not write any ID3-tags, but just the audio portion
166
+
167
+ def writeAudioOnly
168
+ tf = Tempfile.new( @basename )
169
+
170
+ File.open( @filename ) { |f|
171
+ f.seek(@audioStartX)
172
+ tf.write( audio ) # reads the audio from file if nil
173
+ }
174
+ tf.close
175
+ path = tf.path
176
+
177
+ tf.open
178
+ tf.close(true)
179
+ end
180
+
181
+
182
+ # ----------------------------------------------------------------------------
183
+ # NOTE on md5sum's:
184
+ # If you don't know what an md5sum is, you can think of it as a unique
185
+ # fingerprint of a file or some data. I added the md5sum computation to
186
+ # help users keep track of their converted songs - even if the ID3-tag of
187
+ # a file changes(!), the md5sum of the audio data does not change..
188
+ # The md5sum can help you ensure that the audio-portion of the file
189
+ # was not changed after modifying, adding or deleting ID3-tags.
190
+ # It can also help you identifying duplicates.
191
+
192
+ # ----------------------------------------------------------------------------
193
+ # audioMD5sum
194
+ # if the user tries to access @audioMD5sum, it will be computed for him,
195
+ # unless it was previously computed. We try to calculate that only once
196
+ # and on demand, because it's a bit expensive to compute..
197
+
198
+ def audioMD5sum
199
+ @audioMD5sum ||= MD5.hexdigest( audio )
200
+ end
201
+
202
+ def audioSHA1sum
203
+ @audioSHA1sum ||= SHA1.hexdigest( audio )
204
+ end
205
+ # ----------------------------------------------------------------------------
206
+ # writeMD5sum
207
+ # write the filename and MD5sum of the audio portion into an ascii file
208
+ # in the same location as the audio file, but with suffix .md5
209
+ #
210
+ # computes the @audioMD5sum, if it wasn't previously computed..
211
+
212
+ def writeMD5sum
213
+ base = @basename.sub( /(.)\.[^.]+$/ , '\1')
214
+ base += '.md5'
215
+ File.open( File.join(@dirname,base) ,"w") { |f|
216
+ f.printf("%s %s\n", File.join(@dirname,@basename), audioMD5sum ) # computes it if nil
217
+ }
218
+ @audioMD5sum
219
+ end
220
+
221
+ def writeSHA1sum
222
+ base = @basename.sub( /(.)\.[^.]+$/ , '\1')
223
+ base += '.sha1'
224
+ File.open( File.join(@dirname,base) ,"w") { |f|
225
+ f.printf("%s %s\n", File.join(@dirname,@basename), audioSHA1sum ) # computes it if nil
226
+ }
227
+ @audioSHA1sum
228
+ end
229
+ # ----------------------------------------------------------------------------
230
+ # verifyMD5sum
231
+ # compare the audioMD5sum against a previously stored md5sum file
232
+ # and returns boolean value of comparison
233
+ #
234
+ # If no md5sum file existed, we create one and return true.
235
+ #
236
+ # computes the @audioMD5sum, if it wasn't previously computed..
237
+
238
+ def verifyMD5sum
239
+
240
+ oldMD5sum = ''
241
+
242
+ self.audioMD5sum if ! @audioMD5sum # compute MD5sum if it's not computed yet
243
+
244
+ base = @basename.sub( /(.)\.[^.]+$/ , '\1') # remove suffix from audio-file
245
+ base += '.md5' # add new suffix .md5
246
+ md5name = File.join(@dirname,base)
247
+
248
+ # if a MD5-file doesn't exist, we should create one and return TRUE ...
249
+ if File.exists?(md5name)
250
+ File.open( md5name ,"r") { |f|
251
+ oldname,oldMD5sum = f.readline.split # read old MD5-sum
252
+ }
253
+ else
254
+ oldMD5sum = self.writeMD5sum # create MD5-file and return true..
255
+ end
256
+ @audioMD5sum == oldMD5sum
257
+
258
+ end
259
+ # ----------------------------------------------------------------------------
260
+
261
+ end # of class AudioFile
@@ -0,0 +1,292 @@
1
+ module ID3
2
+
3
+ # ----------------------------------------------------------------------------
4
+ # CONSTANTS
5
+ # ----------------------------------------------------------------------------
6
+ Version = '1.0.0_pre4'
7
+
8
+ ID3v1tagSize = 128 # ID3v1 and ID3v1.1 have fixed size tags
9
+ ID3v1versionbyte = 125
10
+
11
+ ID3v2headerSize = 10
12
+ ID3v2major = 3
13
+ ID3v2minor = 4
14
+ ID3v2flags = 5
15
+ ID3v2tagSize = 6
16
+
17
+
18
+ VERSIONS =
19
+ SUPPORTED_VERSIONS = ["1.0", "1.1", "2.2.0", "2.3.0", "2.4.0"]
20
+
21
+ SUPPORTED_SYMBOLS = {
22
+ "1.0" => {
23
+ "ARTIST"=>33..62 , "ALBUM"=>63..92 ,"TITLE"=>3..32,
24
+ "YEAR"=>93..96 , "COMMENT"=>97..126,"GENREID"=>127,
25
+ # "VERSION"=>"1.0"
26
+ } ,
27
+ "1.1" => {
28
+ "ARTIST"=>33..62 , "ALBUM"=>63..92 ,"TITLE"=>3..32,
29
+ "YEAR"=>93..96 , "COMMENT"=>97..124,
30
+ "TRACKNUM"=>126, "GENREID"=>127,
31
+ # "VERSION"=>"1.1"
32
+ } ,
33
+
34
+ "2.2.0" => {"CONTENTGROUP"=>"TT1", "TITLE"=>"TT2", "SUBTITLE"=>"TT3",
35
+ "ARTIST"=>"TP1", "BAND"=>"TP2", "CONDUCTOR"=>"TP3", "MIXARTIST"=>"TP4",
36
+ "COMPOSER"=>"TCM", "LYRICIST"=>"TXT", "LANGUAGE"=>"TLA", "CONTENTTYPE"=>"TCO",
37
+ "ALBUM"=>"TAL", "TRACKNUM"=>"TRK", "PARTINSET"=>"TPA", "ISRC"=>"TRC",
38
+ "DATE"=>"TDA", "YEAR"=>"TYE", "TIME"=>"TIM", "RECORDINGDATES"=>"TRD",
39
+ "ORIGYEAR"=>"TOR", "BPM"=>"TBP", "MEDIATYPE"=>"TMT", "FILETYPE"=>"TFT",
40
+ "COPYRIGHT"=>"TCR", "PUBLISHER"=>"TPB", "ENCODEDBY"=>"TEN",
41
+ "ENCODERSETTINGS"=>"TSS", "SONGLEN"=>"TLE", "SIZE"=>"TSI",
42
+ "PLAYLISTDELAY"=>"TDY", "INITIALKEY"=>"TKE", "ORIGALBUM"=>"TOT",
43
+ "ORIGFILENAME"=>"TOF", "ORIGARTIST"=>"TOA", "ORIGLYRICIST"=>"TOL",
44
+ "USERTEXT"=>"TXX",
45
+ "WWWAUDIOFILE"=>"WAF", "WWWARTIST"=>"WAR", "WWWAUDIOSOURCE"=>"WAS",
46
+ "WWWCOMMERCIALINFO"=>"WCM", "WWWCOPYRIGHT"=>"WCP", "WWWPUBLISHER"=>"WPB",
47
+ "WWWUSER"=>"WXX", "UNIQUEFILEID"=>"UFI",
48
+ "INVOLVEDPEOPLE"=>"IPL", "UNSYNCEDLYRICS"=>"ULT", "COMMENT"=>"COM",
49
+ "CDID"=>"MCI", "EVENTTIMING"=>"ETC", "MPEGLOOKUP"=>"MLL",
50
+ "SYNCEDTEMPO"=>"STC", "SYNCEDLYRICS"=>"SLT", "VOLUMEADJ"=>"RVA",
51
+ "EQUALIZATION"=>"EQU", "REVERB"=>"REV", "PICTURE"=>"PIC",
52
+ "GENERALOBJECT"=>"GEO", "PLAYCOUNTER"=>"CNT", "POPULARIMETER"=>"POP",
53
+ "BUFFERSIZE"=>"BUF", "CRYPTEDMETA"=>"CRM", "AUDIOCRYPTO"=>"CRA",
54
+ "LINKED"=>"LNK"
55
+ } ,
56
+
57
+ "2.3.0" => {"CONTENTGROUP"=>"TIT1", "TITLE"=>"TIT2", "SUBTITLE"=>"TIT3",
58
+ "ARTIST"=>"TPE1", "BAND"=>"TPE2", "CONDUCTOR"=>"TPE3", "MIXARTIST"=>"TPE4",
59
+ "COMPOSER"=>"TCOM", "LYRICIST"=>"TEXT", "LANGUAGE"=>"TLAN", "CONTENTTYPE"=>"TCON",
60
+ "ALBUM"=>"TALB", "TRACKNUM"=>"TRCK", "PARTINSET"=>"TPOS", "ISRC"=>"TSRC",
61
+ "DATE"=>"TDAT", "YEAR"=>"TYER", "TIME"=>"TIME", "RECORDINGDATES"=>"TRDA",
62
+ "ORIGYEAR"=>"TORY", "SIZE"=>"TSIZ",
63
+ "BPM"=>"TBPM", "MEDIATYPE"=>"TMED", "FILETYPE"=>"TFLT", "COPYRIGHT"=>"TCOP",
64
+ "PUBLISHER"=>"TPUB", "ENCODEDBY"=>"TENC", "ENCODERSETTINGS"=>"TSSE",
65
+ "SONGLEN"=>"TLEN", "PLAYLISTDELAY"=>"TDLY", "INITIALKEY"=>"TKEY",
66
+ "ORIGALBUM"=>"TOAL", "ORIGFILENAME"=>"TOFN", "ORIGARTIST"=>"TOPE",
67
+ "ORIGLYRICIST"=>"TOLY", "FILEOWNER"=>"TOWN", "NETRADIOSTATION"=>"TRSN",
68
+ "NETRADIOOWNER"=>"TRSO", "USERTEXT"=>"TXXX",
69
+ "WWWAUDIOFILE"=>"WOAF", "WWWARTIST"=>"WOAR", "WWWAUDIOSOURCE"=>"WOAS",
70
+ "WWWCOMMERCIALINFO"=>"WCOM", "WWWCOPYRIGHT"=>"WCOP", "WWWPUBLISHER"=>"WPUB",
71
+ "WWWRADIOPAGE"=>"WORS", "WWWPAYMENT"=>"WPAY", "WWWUSER"=>"WXXX", "UNIQUEFILEID"=>"UFID",
72
+ "INVOLVEDPEOPLE"=>"IPLS",
73
+ "UNSYNCEDLYRICS"=>"USLT", "COMMENT"=>"COMM", "TERMSOFUSE"=>"USER",
74
+ "CDID"=>"MCDI", "EVENTTIMING"=>"ETCO", "MPEGLOOKUP"=>"MLLT",
75
+ "SYNCEDTEMPO"=>"SYTC", "SYNCEDLYRICS"=>"SYLT",
76
+ "VOLUMEADJ"=>"RVAD", "EQUALIZATION"=>"EQUA",
77
+ "REVERB"=>"RVRB", "PICTURE"=>"APIC", "GENERALOBJECT"=>"GEOB",
78
+ "PLAYCOUNTER"=>"PCNT", "POPULARIMETER"=>"POPM", "BUFFERSIZE"=>"RBUF",
79
+ "AUDIOCRYPTO"=>"AENC", "LINKEDINFO"=>"LINK", "POSITIONSYNC"=>"POSS",
80
+ "COMMERCIAL"=>"COMR", "CRYPTOREG"=>"ENCR", "GROUPINGREG"=>"GRID",
81
+ "PRIVATE"=>"PRIV"
82
+ } ,
83
+
84
+ "2.4.0" => {"CONTENTGROUP"=>"TIT1", "TITLE"=>"TIT2", "SUBTITLE"=>"TIT3",
85
+ "ARTIST"=>"TPE1", "BAND"=>"TPE2", "CONDUCTOR"=>"TPE3", "MIXARTIST"=>"TPE4",
86
+ "COMPOSER"=>"TCOM", "LYRICIST"=>"TEXT", "LANGUAGE"=>"TLAN", "CONTENTTYPE"=>"TCON",
87
+ "ALBUM"=>"TALB", "TRACKNUM"=>"TRCK", "PARTINSET"=>"TPOS", "ISRC"=>"TSRC",
88
+ "RECORDINGTIME"=>"TDRC", "ORIGRELEASETIME"=>"TDOR",
89
+ "BPM"=>"TBPM", "MEDIATYPE"=>"TMED", "FILETYPE"=>"TFLT", "COPYRIGHT"=>"TCOP",
90
+ "PUBLISHER"=>"TPUB", "ENCODEDBY"=>"TENC", "ENCODERSETTINGS"=>"TSSE",
91
+ "SONGLEN"=>"TLEN", "PLAYLISTDELAY"=>"TDLY", "INITIALKEY"=>"TKEY",
92
+ "ORIGALBUM"=>"TOAL", "ORIGFILENAME"=>"TOFN", "ORIGARTIST"=>"TOPE",
93
+ "ORIGLYRICIST"=>"TOLY", "FILEOWNER"=>"TOWN", "NETRADIOSTATION"=>"TRSN",
94
+ "NETRADIOOWNER"=>"TRSO", "USERTEXT"=>"TXXX",
95
+ "SETSUBTITLE"=>"TSST", "MOOD"=>"TMOO", "PRODUCEDNOTICE"=>"TPRO",
96
+ "ENCODINGTIME"=>"TDEN", "RELEASETIME"=>"TDRL", "TAGGINGTIME"=>"TDTG",
97
+ "ALBUMSORTORDER"=>"TSOA", "PERFORMERSORTORDER"=>"TSOP", "TITLESORTORDER"=>"TSOT",
98
+ "WWWAUDIOFILE"=>"WOAF", "WWWARTIST"=>"WOAR", "WWWAUDIOSOURCE"=>"WOAS",
99
+ "WWWCOMMERCIALINFO"=>"WCOM", "WWWCOPYRIGHT"=>"WCOP", "WWWPUBLISHER"=>"WPUB",
100
+ "WWWRADIOPAGE"=>"WORS", "WWWPAYMENT"=>"WPAY", "WWWUSER"=>"WXXX", "UNIQUEFILEID"=>"UFID",
101
+ "MUSICIANCREDITLIST"=>"TMCL", "INVOLVEDPEOPLE2"=>"TIPL",
102
+ "UNSYNCEDLYRICS"=>"USLT", "COMMENT"=>"COMM", "TERMSOFUSE"=>"USER",
103
+ "CDID"=>"MCDI", "EVENTTIMING"=>"ETCO", "MPEGLOOKUP"=>"MLLT",
104
+ "SYNCEDTEMPO"=>"SYTC", "SYNCEDLYRICS"=>"SYLT",
105
+ "VOLUMEADJ2"=>"RVA2", "EQUALIZATION2"=>"EQU2",
106
+ "REVERB"=>"RVRB", "PICTURE"=>"APIC", "GENERALOBJECT"=>"GEOB",
107
+ "PLAYCOUNTER"=>"PCNT", "POPULARIMETER"=>"POPM", "BUFFERSIZE"=>"RBUF",
108
+ "AUDIOCRYPTO"=>"AENC", "LINKEDINFO"=>"LINK", "POSITIONSYNC"=>"POSS",
109
+ "COMMERCIAL"=>"COMR", "CRYPTOREG"=>"ENCR", "GROUPINGREG"=>"GRID",
110
+ "PRIVATE"=>"PRIV",
111
+ "OWNERSHIP"=>"OWNE", "SIGNATURE"=>"SIGN", "SEEKFRAME"=>"SEEK",
112
+ "AUDIOSEEKPOINT"=>"ASPI"
113
+ }
114
+ }
115
+
116
+
117
+ # ----------------------------------------------------------------------------
118
+ # Flags in the ID3-Tag Header:
119
+
120
+ TAG_HEADER_FLAG_MASK = { # the mask is inverse, for error detection
121
+ # those flags are supposed to be zero!
122
+ "2.2.0" => 0x3F, # 0xC0 ,
123
+ "2.3.0" => 0x1F, # 0xE0 ,
124
+ "2.4.0" => 0x0F # 0xF0
125
+ }
126
+
127
+ TAG_HEADER_FLAGS = {
128
+ "2.2.0" => {
129
+ "Unsynchronisation" => 0x80 ,
130
+ "Compression" => 0x40 ,
131
+ } ,
132
+ "2.3.0" => {
133
+ "Unsynchronisation" => 0x80 ,
134
+ "ExtendedHeader" => 0x40 ,
135
+ "Experimental" => 0x20 ,
136
+ } ,
137
+ "2.4.0" => {
138
+ "Unsynchronisation" => 0x80 ,
139
+ "ExtendedHeader" => 0x40 ,
140
+ "Experimental" => 0x20 ,
141
+ "Footer" => 0x10 ,
142
+ }
143
+ }
144
+
145
+ # ----------------------------------------------------------------------------
146
+ # Flags in the ID3-Frame Header:
147
+
148
+ FRAME_HEADER_FLAG_MASK = { # the mask is inverse, for error detection
149
+ # those flags are supposed to be zero!
150
+ "2.3.0" => 0x1F1F, # 0xD0D0 ,
151
+ "2.4.0" => 0x8FB0 # 0x704F ,
152
+ }
153
+
154
+ FRAME_HEADER_FLAGS = {
155
+ "2.3.0" => {
156
+ "TagAlterPreservation" => 0x8000 ,
157
+ "FileAlterPreservation" => 0x4000 ,
158
+ "ReadOnly" => 0x2000 ,
159
+
160
+ "Compression" => 0x0080 ,
161
+ "Encryption" => 0x0040 ,
162
+ "GroupIdentity" => 0x0020 ,
163
+ } ,
164
+ "2.4.0" => {
165
+ "TagAlterPreservation" => 0x4000 ,
166
+ "FileAlterPreservation" => 0x2000 ,
167
+ "ReadOnly" => 0x1000 ,
168
+
169
+ "GroupIdentity" => 0x0040 ,
170
+ "Compression" => 0x0008 ,
171
+ "Encryption" => 0x0004 ,
172
+ "Unsynchronisation" => 0x0002 ,
173
+ "DataLengthIndicator" => 0x0001 ,
174
+ }
175
+ }
176
+
177
+ # the FrameTypes are not visible to the user - they are just a mechanism
178
+ # to define only one parser for multiple FraneNames..
179
+ #
180
+
181
+ FRAMETYPE2FRAMENAME = {
182
+ "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),
183
+ "USERTEXT" => "USERTEXT",
184
+
185
+ "WEB" => %w(WWWAUDIOFILE WWWARTIST WWWAUDIOSOURCE WWWCOMMERCIALINFO WWWCOPYRIGHT WWWPUBLISHER WWWRADIOPAGE WWWPAYMENT) ,
186
+ "WWWUSER" => "WWWUSER",
187
+ "LTEXT" => "TERMSOFUSE" ,
188
+ "PICTURE" => "PICTURE" ,
189
+ "UNSYNCEDLYRICS" => "UNSYNCEDLYRICS" ,
190
+ "COMMENT" => "COMMENT" ,
191
+
192
+ "PLAYCOUNTER" => "PLAYCOUNTER" ,
193
+ "POPULARIMETER" => "POPULARIMETER",
194
+
195
+ "BINARY" => %w( CDID ) , # Cee Dee I Dee
196
+
197
+ # For the following Frames there are no parser stings defined .. the user has access to the raw data
198
+ # The following frames are good examples for completely useless junk which was put into the ID3-definitions.. what were they smoking?
199
+ #
200
+ "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)
201
+ }
202
+
203
+ VARS = 0
204
+ PACKING = 1
205
+
206
+ # ----------------------------------------------------------------------------
207
+ # String Encodings: See id3v2.4.0-structure document, at section 4.
208
+ # see also: http://en.wikipedia.org/wiki/ID3#ID3v2_Chapters
209
+ #
210
+ # Frames that allow different types of text encoding contains a text
211
+ # encoding description byte. Possible encodings:
212
+ #
213
+ # $00 ISO-8859-1 [ISO-8859-1]. Terminated with $00. (ASCII)
214
+ # $01 [UCS-2] in ID3v2.2,ID3v2.3 / UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM All in ID3v2.4
215
+ # strings in the same frame SHALL have the same byteorder.
216
+ # Terminated with $00 00.
217
+ # $02 UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM. (ID3v2.4 only)
218
+ # Terminated with $00 00.
219
+ # $03 UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00. (ID3v2.4 only)
220
+
221
+ TEXT_ENCODINGS = ["ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"]
222
+
223
+ # to get the BYTE-code for the encoding type: TEXT_ENCODINGS.index( string.encoding.to_s ).chr
224
+ # to read the string : .force_encoding( Encoding::whatever )
225
+ # in Ruby 1.9 : Encoding::UTF_8 , Encoding::UTF_16BE, Encoding::ISO_8859_1
226
+ # BOM: see: http://www.websina.com/bugzero/kb/unicode-bom.html
227
+ # ----------------------------------------------------------------------------
228
+
229
+ # not sure if it's Z* or A*
230
+ # A* does not append a \0 when writing!
231
+
232
+ # STILL NEED TO GET MORE TEST-CASES! e.g. Japanese ID3-Tags! or other encodings..
233
+ # seems like i have no version 2.4.x ID3-tags!! If you have some, send them my way!
234
+
235
+ # NOTE: please note that all the first array entries need to be hashes, in order for Ruby 1.9 to handle this correctly!
236
+
237
+ FRAME_PARSER = {
238
+ "TEXT" => [ %w(encoding text) , 'CZ*' ] ,
239
+ "USERTEXT" => [ %w(encoding description value) , 'CZ*Z*' ] ,
240
+
241
+ "PICTURE" => [ %w(encoding mime_type pict_type description picture) , 'CZ*CZ*a*' ] ,
242
+
243
+ "WEB" => [ %w(url) , 'Z*' ] ,
244
+ "WWWUSER" => [ %w(encoding description url) , 'CZ*Z*' ] ,
245
+
246
+ "LTEXT" => [ %w(encoding language text) , 'CZ*Z*' ] ,
247
+ "UNSYNCEDLYRICS" => [ %w(encoding language content text) , 'Ca3Z*Z*' ] ,
248
+ "COMMENT" => [ %w(encoding language short long) , 'Ca3Z*Z*' ] ,
249
+
250
+ "PLAYCOUNTER" => [%w(counter), 'C*'] ,
251
+ "POPULARIMETER" => [%w(email rating counter), 'Z*CC*'] ,
252
+
253
+ "BINARY" => [ %w(binary) , 'a*' ] ,
254
+ "UNPARSED" => [ %w(raw) , 'a*' ] # how would we do value checking for this?
255
+ }
256
+
257
+ # ----------------------------------------------------------------------------
258
+
259
+ Symbol2framename = ID3::SUPPORTED_SYMBOLS
260
+ Framename2symbol = Hash.new
261
+ Framename2symbol["1.0"] = ID3::SUPPORTED_SYMBOLS["1.0"].invert
262
+ Framename2symbol["1.1"] = ID3::SUPPORTED_SYMBOLS["1.1"].invert
263
+ Framename2symbol["2.2.0"] = ID3::SUPPORTED_SYMBOLS["2.2.0"].invert
264
+ Framename2symbol["2.3.0"] = ID3::SUPPORTED_SYMBOLS["2.3.0"].invert
265
+ Framename2symbol["2.4.0"] = ID3::SUPPORTED_SYMBOLS["2.4.0"].invert
266
+
267
+ FrameType2FrameName = ID3::FRAMETYPE2FRAMENAME
268
+
269
+ FrameName2FrameType = FrameType2FrameName.invert
270
+
271
+ # ----------------------------------------------------------------------------
272
+ # the following piece of code is just for debugging, to sanity-check that all
273
+ # the FrameSymbols map back to a FrameType -- otherwise the library code will
274
+ # break if we encounter a Frame which can't be mapped to a FrameType..
275
+ # ----------------------------------------------------------------------------
276
+ #
277
+ # ensure we have a FrameType defined for each FrameName, otherwise
278
+ # code might break later..
279
+ #
280
+
281
+ # print "\nMISSING SYMBOLS:\n"
282
+
283
+ (ID3::Framename2symbol["2.2.0"].values +
284
+ ID3::Framename2symbol["2.3.0"].values +
285
+ ID3::Framename2symbol["2.4.0"].values).uniq.each { |symbol|
286
+ # print "#{symbol} " if ! ID3::FrameName2FrameType[symbol]
287
+ print "SYMBOL: #{symbol} not defined!\n" if ! ID3::FrameName2FrameType[symbol]
288
+ }
289
+ # print "\n\n"
290
+
291
+ end
292
+