ruby-mp3info 0.4 → 0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ class Mp3Info
2
+ module HashKeys #:nodoc:
3
+ ### lets you specify hash["key"] as hash.key
4
+ ### this came from CodingInRuby on RubyGarden
5
+ ### http://www.rubygarden.org/ruby?CodingInRuby
6
+ def method_missing(meth,*args)
7
+ m = meth.id2name
8
+ if /=$/ =~ m
9
+ self[m.chop] = (args.length<2 ? args[0] : args)
10
+ else
11
+ self[m]
12
+ end
13
+ end
14
+ end
15
+
16
+ module NumericBits #:nodoc:
17
+ ### returns the selected bit range (b, a) as a number
18
+ ### NOTE: b > a if not, returns 0
19
+ def bits(b, a)
20
+ t = 0
21
+ b.downto(a) { |i| t += t + self[i] }
22
+ t
23
+ end
24
+ end
25
+
26
+ module Mp3FileMethods #:nodoc:
27
+ def get32bits
28
+ (getc << 24) + (getc << 16) + (getc << 8) + getc
29
+ end
30
+ def get_syncsafe
31
+ (getc << 21) + (getc << 14) + (getc << 7) + getc
32
+ end
33
+ end
34
+
35
+ end
36
+
@@ -0,0 +1,311 @@
1
+ require "mp3info/extension_modules"
2
+
3
+ # This class is not intended to be used directly
4
+ class ID3v2 < DelegateClass(Hash)
5
+ VERSION_MAJ = 3
6
+
7
+ TAGS = {
8
+ "AENC" => "Audio encryption",
9
+ "APIC" => "Attached picture",
10
+ "COMM" => "Comments",
11
+ "COMR" => "Commercial frame",
12
+ "ENCR" => "Encryption method registration",
13
+ "EQUA" => "Equalization",
14
+ "ETCO" => "Event timing codes",
15
+ "GEOB" => "General encapsulated object",
16
+ "GRID" => "Group identification registration",
17
+ "IPLS" => "Involved people list",
18
+ "LINK" => "Linked information",
19
+ "MCDI" => "Music CD identifier",
20
+ "MLLT" => "MPEG location lookup table",
21
+ "OWNE" => "Ownership frame",
22
+ "PRIV" => "Private frame",
23
+ "PCNT" => "Play counter",
24
+ "POPM" => "Popularimeter",
25
+ "POSS" => "Position synchronisation frame",
26
+ "RBUF" => "Recommended buffer size",
27
+ "RVAD" => "Relative volume adjustment",
28
+ "RVRB" => "Reverb",
29
+ "SYLT" => "Synchronized lyric/text",
30
+ "SYTC" => "Synchronized tempo codes",
31
+ "TALB" => "Album/Movie/Show title",
32
+ "TBPM" => "BPM (beats per minute)",
33
+ "TCOM" => "Composer",
34
+ "TCON" => "Content type",
35
+ "TCOP" => "Copyright message",
36
+ "TDAT" => "Date",
37
+ "TDLY" => "Playlist delay",
38
+ "TENC" => "Encoded by",
39
+ "TEXT" => "Lyricist/Text writer",
40
+ "TFLT" => "File type",
41
+ "TIME" => "Time",
42
+ "TIT1" => "Content group description",
43
+ "TIT2" => "Title/songname/content description",
44
+ "TIT3" => "Subtitle/Description refinement",
45
+ "TKEY" => "Initial key",
46
+ "TLAN" => "Language(s)",
47
+ "TLEN" => "Length",
48
+ "TMED" => "Media type",
49
+ "TOAL" => "Original album/movie/show title",
50
+ "TOFN" => "Original filename",
51
+ "TOLY" => "Original lyricist(s)/text writer(s)",
52
+ "TOPE" => "Original artist(s)/performer(s)",
53
+ "TORY" => "Original release year",
54
+ "TOWN" => "File owner/licensee",
55
+ "TPE1" => "Lead performer(s)/Soloist(s)",
56
+ "TPE2" => "Band/orchestra/accompaniment",
57
+ "TPE3" => "Conductor/performer refinement",
58
+ "TPE4" => "Interpreted, remixed, or otherwise modified by",
59
+ "TPOS" => "Part of a set",
60
+ "TPUB" => "Publisher",
61
+ "TRCK" => "Track number/Position in set",
62
+ "TRDA" => "Recording dates",
63
+ "TRSN" => "Internet radio station name",
64
+ "TRSO" => "Internet radio station owner",
65
+ "TSIZ" => "Size",
66
+ "TSRC" => "ISRC (international standard recording code)",
67
+ "TSSE" => "Software/Hardware and settings used for encoding",
68
+ "TYER" => "Year",
69
+ "TXXX" => "User defined text information frame",
70
+ "UFID" => "Unique file identifier",
71
+ "USER" => "Terms of use",
72
+ "USLT" => "Unsychronized lyric/text transcription",
73
+ "WCOM" => "Commercial information",
74
+ "WCOP" => "Copyright/Legal information",
75
+ "WOAF" => "Official audio file webpage",
76
+ "WOAR" => "Official artist/performer webpage",
77
+ "WOAS" => "Official audio source webpage",
78
+ "WORS" => "Official internet radio station homepage",
79
+ "WPAY" => "Payment",
80
+ "WPUB" => "Publishers official webpage",
81
+ "WXXX" => "User defined URL link frame"
82
+ }
83
+
84
+ include Mp3Info::HashKeys
85
+
86
+ attr_reader :io_position
87
+
88
+ # possibles keys:
89
+ # :+lang+ for writing comments
90
+ # :+encoding+: :+iso+ or :+unicode+
91
+ attr_reader :options
92
+
93
+ def initialize(options = {})
94
+ @options = {
95
+ :lang => "ENG",
96
+ :encoding => :iso #language encoding bit 0 for iso_8859_1, 1 for unicode
97
+ }
98
+ @options.update(options)
99
+
100
+ @hash = {}
101
+ #TAGS.keys.each { |k| @hash[k] = nil }
102
+ @hash_orig = {}
103
+ super(@hash)
104
+ @valid = false
105
+ @version_maj = @version_min = nil
106
+ end
107
+
108
+ def valid?
109
+ @valid
110
+ end
111
+
112
+ def changed?
113
+ @hash_orig != @hash
114
+ end
115
+
116
+ def version
117
+ "2.#{@version_maj}.#{@version_min}"
118
+ end
119
+
120
+ ### gets id3v2 tag information from io
121
+ def from_io(io)
122
+ @io = io
123
+ version_maj, version_min, flags = @io.read(3).unpack("CCB4")
124
+ unsync, ext_header, experimental, footer = (0..3).collect { |i| flags[i].chr == '1' }
125
+ raise("can't find version_maj ('#{version_maj}')") unless [2, 3, 4].include?(version_maj)
126
+ @version_maj, @version_min = version_maj, version_min
127
+ @valid = true
128
+ tag2_len = @io.get_syncsafe
129
+ case @version_maj
130
+ when 2
131
+ read_id3v2_2_frames(tag2_len)
132
+ when 3,4
133
+ # seek past extended header if present
134
+ @io.seek(@io.get_syncsafe - 4, IO::SEEK_CUR) if ext_header
135
+ read_id3v2_3_frames(tag2_len)
136
+ end
137
+ @io_position = @io.pos
138
+
139
+ @hash_orig = @hash.dup
140
+ #no more reading
141
+ @io = nil
142
+ # we should now have io sitting at the first MPEG frame
143
+ end
144
+
145
+ def to_bin
146
+ #TODO handle of @tag2[TLEN"]
147
+ #TODO add of crc
148
+ #TODO add restrictions tag
149
+
150
+ tag = ""
151
+ @hash.each do |k, v|
152
+ next unless v
153
+ next if v.respond_to?("empty?") and v.empty?
154
+ data = encode_tag(k, v.to_s)
155
+ #data << "\x00"*2 #End of tag
156
+
157
+ tag << k[0,4] #4 characte max for a tag's key
158
+ #tag << to_syncsafe(data.size) #+1 because of the language encoding byte
159
+ tag << [data.size].pack("N") #+1 because of the language encoding byte
160
+ tag << "\x00"*2 #flags
161
+ tag << data
162
+ end
163
+
164
+ tag_str = ""
165
+ #version_maj, version_min, unsync, ext_header, experimental, footer
166
+ tag_str << [ VERSION_MAJ, 0, "0000" ].pack("CCB4")
167
+ tag_str << to_syncsafe(tag.size)
168
+ tag_str << tag
169
+ p tag_str if $DEBUG
170
+ tag_str
171
+ end
172
+
173
+ private
174
+
175
+
176
+ def encode_tag(name, value)
177
+ puts "encode_tag(#{name.inspect}, #{value.inspect})" if $DEBUG
178
+ case name
179
+ when "COMM"
180
+ [ @options[:encoding] == :iso ? 0 : 1, @options[:lang], 0, value ].pack("ca3ca*")
181
+ else
182
+ if @options[:encoding] == :iso
183
+ "\x00"+value
184
+ else
185
+ "\x01"+value #Iconv.iconv("UNICODE", "ISO-8859-1", value)[0]
186
+ end
187
+ #data << "\x00"
188
+ end
189
+ end
190
+
191
+ ### Read a tag from file and perform UNICODE translation if needed
192
+ def decode_tag(name, value)
193
+ case name
194
+ when "COMM"
195
+ #FIXME improve this
196
+ encoding, lang, str = value.unpack("ca3a*")
197
+ out = value.split(0.chr).last
198
+ else
199
+ encoding = value[0] # language encoding bit 0 for iso_8859_1, 1 for unicode
200
+ out = value[1..-1]
201
+ end
202
+
203
+ if encoding == 1 #and name[0] == ?T
204
+ require "iconv"
205
+
206
+ #strip byte-order bytes at the beginning of the unicode string if they exists
207
+ out[0..3] =~ /^[\xff\xfe]+$/ and out = out[2..-1]
208
+ begin
209
+ out = Iconv.iconv("ISO-8859-1", "UNICODE", out)[0]
210
+ rescue Iconv::IllegalSequence, Iconv::InvalidCharacter
211
+ end
212
+ end
213
+ out
214
+ end
215
+
216
+ ### reads id3 ver 2.3.x/2.4.x frames and adds the contents to @tag2 hash
217
+ ### tag2_len (fixnum) = length of entire id3v2 data, as reported in header
218
+ ### NOTE: the id3v2 header does not take padding zero's into consideration
219
+ def read_id3v2_3_frames(tag2_len)
220
+ loop do # there are 2 ways to end the loop
221
+ name = @io.read(4)
222
+ if name[0] == 0 or name == "MP3e" #bug caused by old tagging application "mp3ext" ( http://www.mutschler.de/mp3ext/ )
223
+ @io.seek(-4, IO::SEEK_CUR) # 1. find a padding zero,
224
+ seek_to_v2_end
225
+ break
226
+ else
227
+ #size = @file.get_syncsafe #this seems to be a bug
228
+ size = @io.get32bits
229
+ puts "name '#{name}' size #{size} " if $DEBUG
230
+ @io.seek(2, IO::SEEK_CUR) # skip flags
231
+ add_value_to_tag2(name, size)
232
+ # case name
233
+ # when /^T/
234
+ # puts "tag is text. reading" if $DEBUG
235
+ ## data = read_id3_string(size-1)
236
+ ## add_value_to_tag2(name, data)
237
+ # else
238
+ # decode_tag(
239
+ # #@file.seek(size-1, IO::SEEK_CUR)
240
+ # puts "tag is binary, skipping" if $DEBUG
241
+ # @io.seek(size, IO::SEEK_CUR)
242
+ # end
243
+
244
+ # case name
245
+ # #FIXME DRY
246
+ # when "COMM"
247
+ # data = read_id3v2_frame(size)
248
+ # lang = data[0,3]
249
+ # data = data[3,-1]
250
+ # else
251
+ # end
252
+ end
253
+ break if @io.pos >= tag2_len # 2. reach length from header
254
+ end
255
+ end
256
+
257
+ ### reads id3 ver 2.2.x frames and adds the contents to @tag2 hash
258
+ ### tag2_len (fixnum) = length of entire id3v2 data, as reported in header
259
+ ### NOTE: the id3v2 header does not take padding zero's into consideration
260
+ def read_id3v2_2_frames(tag2_len)
261
+ loop do
262
+ name = @io.read(3)
263
+ if name[0] == 0
264
+ @io.seek(-3, IO::SEEK_CUR)
265
+ seek_to_v2_end
266
+ break
267
+ else
268
+ size = (@io.getc << 16) + (@io.getc << 8) + @io.getc
269
+ add_value_to_tag2(name, size)
270
+ break if @io.pos >= tag2_len
271
+ end
272
+ end
273
+ end
274
+
275
+ ### Add data to tag2["name"]
276
+ ### read lang_encoding, decode data if unicode and
277
+ ### create an array if the key already exists in the tag
278
+ def add_value_to_tag2(name, size)
279
+ puts "add_value_to_tag2" if $DEBUG
280
+ data = decode_tag(name, @io.read(size))
281
+ if self.keys.include?(name)
282
+ unless self[name].is_a?(Array)
283
+ self[name] = self[name].to_a
284
+ end
285
+ self[name] << data
286
+ else
287
+ self[name] = data
288
+ end
289
+ end
290
+
291
+ ### runs thru @file one char at a time looking for best guess of first MPEG
292
+ ### frame, which should be first 0xff byte after id3v2 padding zero's
293
+ def seek_to_v2_end
294
+ until @io.getc == 0xff
295
+ end
296
+ @io.seek(-1, IO::SEEK_CUR)
297
+ end
298
+
299
+ ### convert an 32 integer to a syncsafe string
300
+ def to_syncsafe(num)
301
+ n = ( (num<<3) & 0x7f000000 ) + ( (num<<2) & 0x7f0000 ) + ( (num<<1) & 0x7f00 ) + ( num & 0x7f )
302
+ [n].pack("N")
303
+ end
304
+
305
+ # def method_missing(meth, *args)
306
+ # m = meth.id2name
307
+ # return nil if TAGS.has_key?(m) and self[m].nil?
308
+ # super
309
+ # end
310
+ end
311
+
data/test.rb ADDED
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $:.unshift("lib/")
4
+
5
+ require "test/unit"
6
+ require "base64"
7
+ require "mp3info"
8
+ require "fileutils"
9
+
10
+ class Mp3InfoTest < Test::Unit::TestCase
11
+
12
+ TEMP_FILE = File.join(File.dirname($0), "test_mp3info.mp3")
13
+ BASIC_TAG2 = {
14
+ "COMM" => "comments",
15
+ #"TCON" => "genre_s"
16
+ "TIT2" => "title",
17
+ "TPE1" => "artist",
18
+ "TALB" => "album",
19
+ "TYER" => "year",
20
+ "TRCK" => "tracknum"
21
+ }
22
+
23
+ # aliasing to allow testing with old versions of Test::Unit
24
+ alias set_up setup
25
+ alias tear_down teardown
26
+
27
+ def setup
28
+ # Command to create a dummy MP3
29
+ # dd if=/dev/zero bs=1024 count=15 | lame --preset cbr 128 -r -s 44.1 --bitwidth 16 - - | ruby -rbase64 -e 'print Base64.encode64($stdin.read)'
30
+ @valid_mp3 = Base64.decode64 <<EOF
31
+ //uQZAAAAAAAaQYAAAAAAA0gwAAAAAABpBwAAAAAADSDgAAATEFNRTMuOTNV
32
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
33
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
34
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
35
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
36
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
37
+ VVVVVVVVVVVVVVVVVVVVVVVVTEFNRTMuOTNVVVVVVVVVVVVVVVVVVVVVVVVV
38
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
39
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
40
+ VVVVVVVVVVVVVVVV//uSZL6P8AAAaQAAAAAAAA0gAAAAAAABpAAAAAAAADSA
41
+ AAAAVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
42
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
43
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
44
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
45
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
46
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUxBTUUzLjkzVVVVVVVV
47
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
48
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
49
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/7kmT/j/AAAGkAAAAAAAANIAAA
50
+ AAAAAaQAAAAAAAA0gAAAAFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
51
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
52
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
53
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
54
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
55
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVM
56
+ QU1FMy45M1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
57
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
58
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVX/+5Jk/4/w
59
+ AABpAAAAAAAADSAAAAAAAAGkAAAAAAAANIAAAABVVVVVVVVVVVVVVVVVVVVV
60
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
61
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
62
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
63
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
64
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
65
+ VVVVVVVVVVVVVVVVTEFNRTMuOTNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
66
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
67
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
68
+ VVVVVVVV//uSZP+P8AAAaQAAAAAAAA0gAAAAAAABpAAAAAAAADSAAAAAVVVV
69
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
70
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
71
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
72
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
73
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
74
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
75
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
76
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
77
+ VVVVVVVVVVVVVVVVVVVVVVVVVQ==
78
+ EOF
79
+ @tag = {
80
+ "title" => "title",
81
+ "artist" => "artist",
82
+ "album" => "album",
83
+ "year" => 1921,
84
+ "comments" => "comments",
85
+ "genre" => 0,
86
+ "genre_s" => "Blues",
87
+ "tracknum" => 36
88
+ }
89
+ File.open(TEMP_FILE, "w") { |f| f.write(@valid_mp3) }
90
+ end
91
+
92
+ def teardown
93
+ FileUtils.rm_f(TEMP_FILE)
94
+ end
95
+
96
+
97
+ def test_to_s
98
+ Mp3Info.open(TEMP_FILE) { |info| assert(info.to_s.is_a?(String)) }
99
+ end
100
+
101
+ def test_not_an_mp3
102
+ File.open(TEMP_FILE, "w") do |f|
103
+ str = "0"*1024*1024
104
+ f.write(str)
105
+ end
106
+ assert_raises(Mp3InfoError) {
107
+ mp3 = Mp3Info.new(TEMP_FILE)
108
+ }
109
+ end
110
+
111
+ def test_is_an_mp3
112
+ assert_nothing_raised {
113
+ Mp3Info.new(TEMP_FILE).close
114
+ }
115
+ end
116
+
117
+ def test_detected_info
118
+ Mp3Info.open(TEMP_FILE) do |info|
119
+ assert_equal(info.mpeg_version, 1)
120
+ assert_equal(info.layer, 3)
121
+ assert_equal(info.vbr, false)
122
+ assert_equal(info.bitrate, 128)
123
+ assert_equal(info.channel_mode, "JStereo")
124
+ assert_equal(info.samplerate, 44100)
125
+ assert_equal(info.error_protection, false)
126
+ assert_equal(info.length, 0.1305625)
127
+ end
128
+ end
129
+
130
+ def test_removetag1
131
+ Mp3Info.open(TEMP_FILE) { |info| info.tag1 = @tag }
132
+ assert(Mp3Info.hastag1?(TEMP_FILE))
133
+ Mp3Info.removetag1(TEMP_FILE)
134
+ assert(! Mp3Info.hastag1?(TEMP_FILE))
135
+ end
136
+
137
+ def test_writetag1
138
+ Mp3Info.open(TEMP_FILE) { |info| info.tag1 = @tag }
139
+ Mp3Info.open(TEMP_FILE) { |info| assert(info.tag1 == @tag) }
140
+ end
141
+
142
+ def test_valid_tag1_1
143
+ tag = [ "title", "artist", "album", "1921", "comments", 36, 0].pack('A30A30A30A4a29CC')
144
+ valid_tag = {
145
+ "title" => "title",
146
+ "artist" => "artist",
147
+ "album" => "album",
148
+ "year" => 1921,
149
+ "comments" => "comments",
150
+ "genre" => "Blues",
151
+ #"version" => "1",
152
+ "tracknum" => 36
153
+ }
154
+ id3_test(tag, valid_tag)
155
+ end
156
+
157
+ def test_valid_tag1_0
158
+ tag = [ "title", "artist", "album", "1921", "comments", 0].pack('A30A30A30A4A30C')
159
+ valid_tag = {
160
+ "title" => "title",
161
+ "artist" => "artist",
162
+ "album" => "album",
163
+ "year" => 1921,
164
+ "comments" => "comments",
165
+ "genre" => "Blues",
166
+ #"version" => "0"
167
+ }
168
+ id3_test(tag, valid_tag)
169
+ end
170
+
171
+ def id3_test(tag_str, valid_tag)
172
+ tag = "TAG" + tag_str
173
+ File.open(TEMP_FILE, "w") do |f|
174
+ f.write(@valid_mp3)
175
+ f.write(tag)
176
+ end
177
+ assert(Mp3Info.hastag1?(TEMP_FILE))
178
+ #info = Mp3Info.new(TEMP_FILE)
179
+ #FIXME validate this test
180
+ #assert_equal(info.tag1, valid_tag)
181
+ end
182
+
183
+ def test_removetag2
184
+ w = write_temp_file({"TIT2" => "sdfqdsf"})
185
+
186
+ assert( Mp3Info.hastag2?(TEMP_FILE) )
187
+ Mp3Info.removetag2(TEMP_FILE)
188
+ assert( ! Mp3Info.hastag2?(TEMP_FILE) )
189
+ end
190
+
191
+ def test_universal_tag
192
+ 2.times do
193
+ tag = {"title" => "title"}
194
+ Mp3Info.open(TEMP_FILE) do |mp3|
195
+ tag.each { |k,v| mp3.tag[k] = v }
196
+ end
197
+ w = Mp3Info.open(TEMP_FILE) { |m| m.tag }
198
+ assert_equal(tag, w)
199
+ end
200
+ end
201
+
202
+ def test_id3v2_universal_tag
203
+ tag = {}
204
+ %w{comments title artist album}.each { |k| tag[k] = k }
205
+ tag["tracknum"] = 34
206
+ Mp3Info.open(TEMP_FILE) do |mp3|
207
+ tag.each { |k,v| mp3.tag[k] = v }
208
+ end
209
+ w = Mp3Info.open(TEMP_FILE) { |m| m.tag }
210
+ w.delete("genre")
211
+ w.delete("genre_s")
212
+ assert_equal(tag, w)
213
+ # id3v2_prog_test(tag, w)
214
+ end
215
+
216
+ def test_id3v2_version
217
+ written_tag = write_temp_file(BASIC_TAG2)
218
+ assert_equal( "2.3.0", written_tag.version )
219
+ end
220
+
221
+ def test_id3v2_methods
222
+ tag = { "TIT2" => "tit2", "TPE1" => "tpe1" }
223
+ Mp3Info.open(TEMP_FILE) do |mp3|
224
+ tag.each do |k, v|
225
+ mp3.tag2.send("#{k}=".to_sym, v)
226
+ end
227
+ assert_equal(tag, mp3.tag2)
228
+ end
229
+ end
230
+
231
+ def test_id3v2_basic
232
+ w = write_temp_file(BASIC_TAG2)
233
+ assert_equal(BASIC_TAG2, w)
234
+ id3v2_prog_test(BASIC_TAG2, w)
235
+ end
236
+
237
+ def test_id3v2_trash
238
+ end
239
+
240
+ def test_id3v2_complex
241
+ tag = {}
242
+ #ID3v2::TAGS.keys.each do |k|
243
+ ["PRIV", "APIC"].each do |k|
244
+ tag[k] = random_string(50)
245
+ end
246
+ assert_equal(tag, write_temp_file(tag))
247
+ end
248
+
249
+ def test_id3v2_bigtag
250
+ tag = {"APIC" => random_string(1024) }
251
+ assert_equal(tag, write_temp_file(tag))
252
+ end
253
+
254
+ #test the tag with php getid3
255
+ # prog = %{
256
+ # <?php
257
+ # require("/var/www/root/netjuke/lib/getid3/getid3.php");
258
+ # $mp3info = GetAllFileInfo('#{TEMP_FILE}');
259
+ # echo $mp3info;
260
+ # ?>
261
+ # }
262
+ #
263
+ # open("|php", "r+") do |io|
264
+ # io.puts(prog)
265
+ # io.close_write
266
+ # p io.read
267
+ # end
268
+
269
+ #test the tag with the "id3v2" program
270
+ def id3v2_prog_test(tag, written_tag)
271
+ return if PLATFORM =~ /win32/
272
+ return if `which id3v2`.empty?
273
+ start = false
274
+ id3v2_output = {}
275
+ `id3v2 -l #{TEMP_FILE}`.each do |line|
276
+ if line =~ /^id3v2 tag info/
277
+ start = true
278
+ next
279
+ end
280
+ next unless start
281
+ k, v = /^(.{4}) \(.+\): (.+)$/.match(line)[1,2]
282
+ case k
283
+ #COMM (Comments): ()[spa]: fmg
284
+ when "COMM"
285
+ v.sub!(/\(\)\[.{3}\]: (.+)/, '\1')
286
+ end
287
+ id3v2_output[k] = v
288
+ end
289
+
290
+ assert_equal( id3v2_output, written_tag, "id3v2 program output doesn't match")
291
+ end
292
+
293
+ def write_temp_file(tag)
294
+ Mp3Info.open(TEMP_FILE) do |mp3|
295
+ mp3.tag2.update(tag)
296
+ end
297
+ return Mp3Info.open(TEMP_FILE) { |m| m.tag2 }
298
+ #system("cp -v #{TEMP_FILE} #{TEMP_FILE}.test")
299
+ end
300
+
301
+ def random_string(size)
302
+ out = ""
303
+ size.times { out << rand(256).chr }
304
+ out
305
+ end
306
+
307
+ =begin
308
+
309
+ def test_encoder
310
+ write_to_temp
311
+ info = Mp3Info.new(TEMP_FILE)
312
+ assert(info.encoder == "Lame 3.93")
313
+ end
314
+
315
+ def test_vbr
316
+ mp3_vbr = Base64.decode64 <<EOF
317
+
318
+ EOF
319
+ File.open(TEMP_FILE, "w") { |f| f.write(mp3_vbr) }
320
+ info = Mp3Info.new(TEMP_FILE)
321
+ assert_equal(info.vbr, true)
322
+ assert_equal(info.bitrate, 128)
323
+ end
324
+ =end
325
+ end