mp3info 0.6.18 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,46 +0,0 @@
1
- # coding:utf-8
2
- # License:: Ruby
3
- # Author:: Guillaume Pierronnet (mailto:moumar_AT__rubyforge_DOT_org)
4
- # Website:: http://ruby-mp3info.rubyforge.org/
5
-
6
- class Mp3Info
7
- module HashKeys #:nodoc:
8
- ### lets you specify hash["key"] as hash.key
9
- ### this came from CodingInRuby on RubyGarden
10
- ### http://www.rubygarden.org/ruby?CodingInRuby
11
- def method_missing(meth,*args)
12
- m = meth.id2name
13
- if /=$/ =~ m
14
- self[m.chop] = (args.length<2 ? args[0] : args)
15
- else
16
- self[m]
17
- end
18
- end
19
- end
20
-
21
- class ::String
22
- if RUBY_VERSION < "1.9.0"
23
- alias getbyte []
24
- else
25
- def getbyte(i)
26
- self[i].ord unless self[i].nil?
27
- end
28
- end
29
- end
30
-
31
- module Mp3FileMethods #:nodoc:
32
- if RUBY_VERSION < "1.9.0"
33
- def getbyte
34
- getc
35
- end
36
- end
37
-
38
- def get32bits
39
- (getbyte << 24) + (getbyte << 16) + (getbyte << 8) + getbyte
40
- end
41
-
42
- def get_syncsafe
43
- (getbyte << 21) + (getbyte << 14) + (getbyte << 7) + getbyte
44
- end
45
- end
46
- end
@@ -1,437 +0,0 @@
1
- # coding:utf-8
2
- # License:: Ruby
3
- # Author:: Guillaume Pierronnet (mailto:moumar_AT__rubyforge_DOT_org)
4
- # Website:: http://ruby-mp3info.rubyforge.org/
5
-
6
- require "delegate"
7
- require "iconv"
8
-
9
- require "mp3info/extension_modules"
10
-
11
- class ID3v2Error < StandardError ; end
12
-
13
- # This class can be used to decode id3v2 tags from files, like .mp3 or .ape for example.
14
- # It works like a hash, where key represents the tag name as 3 or 4 upper case letters
15
- # (respectively related to 2.2 and 2.3+ tag) and value represented as array or raw value.
16
- # Written version is always 2.3.
17
- class ID3v2 < DelegateClass(Hash)
18
-
19
- # Major version used when writing tags
20
- WRITE_VERSION = 3
21
-
22
- TAGS = {
23
- "AENC" => "Audio encryption",
24
- "APIC" => "Attached picture",
25
- "COMM" => "Comments",
26
- "COMR" => "Commercial frame",
27
- "ENCR" => "Encryption method registration",
28
- "EQUA" => "Equalization",
29
- "ETCO" => "Event timing codes",
30
- "GEOB" => "General encapsulated object",
31
- "GRID" => "Group identification registration",
32
- "IPLS" => "Involved people list",
33
- "LINK" => "Linked information",
34
- "MCDI" => "Music CD identifier",
35
- "MLLT" => "MPEG location lookup table",
36
- "OWNE" => "Ownership frame",
37
- "PRIV" => "Private frame",
38
- "PCNT" => "Play counter",
39
- "POPM" => "Popularimeter",
40
- "POSS" => "Position synchronisation frame",
41
- "RBUF" => "Recommended buffer size",
42
- "RVAD" => "Relative volume adjustment",
43
- "RVRB" => "Reverb",
44
- "SYLT" => "Synchronized lyric/text",
45
- "SYTC" => "Synchronized tempo codes",
46
- "TALB" => "Album/Movie/Show title",
47
- "TBPM" => "BPM (beats per minute)",
48
- "TCOM" => "Composer",
49
- "TCON" => "Content type",
50
- "TCOP" => "Copyright message",
51
- "TDAT" => "Date",
52
- "TDLY" => "Playlist delay",
53
- "TENC" => "Encoded by",
54
- "TEXT" => "Lyricist/Text writer",
55
- "TFLT" => "File type",
56
- "TIME" => "Time",
57
- "TIT1" => "Content group description",
58
- "TIT2" => "Title/songname/content description",
59
- "TIT3" => "Subtitle/Description refinement",
60
- "TKEY" => "Initial key",
61
- "TLAN" => "Language(s)",
62
- "TLEN" => "Length",
63
- "TMED" => "Media type",
64
- "TOAL" => "Original album/movie/show title",
65
- "TOFN" => "Original filename",
66
- "TOLY" => "Original lyricist(s)/text writer(s)",
67
- "TOPE" => "Original artist(s)/performer(s)",
68
- "TORY" => "Original release year",
69
- "TOWN" => "File owner/licensee",
70
- "TPE1" => "Lead performer(s)/Soloist(s)",
71
- "TPE2" => "Band/orchestra/accompaniment",
72
- "TPE3" => "Conductor/performer refinement",
73
- "TPE4" => "Interpreted, remixed, or otherwise modified by",
74
- "TPOS" => "Part of a set",
75
- "TPUB" => "Publisher",
76
- "TRCK" => "Track number/Position in set",
77
- "TRDA" => "Recording dates",
78
- "TRSN" => "Internet radio station name",
79
- "TRSO" => "Internet radio station owner",
80
- "TSIZ" => "Size",
81
- "TSRC" => "ISRC (international standard recording code)",
82
- "TSSE" => "Software/Hardware and settings used for encoding",
83
- "TYER" => "Year",
84
- "TXXX" => "User defined text information frame",
85
- "UFID" => "Unique file identifier",
86
- "USER" => "Terms of use",
87
- "USLT" => "Unsychronized lyric/text transcription",
88
- "WCOM" => "Commercial information",
89
- "WCOP" => "Copyright/Legal information",
90
- "WOAF" => "Official audio file webpage",
91
- "WOAR" => "Official artist/performer webpage",
92
- "WOAS" => "Official audio source webpage",
93
- "WORS" => "Official internet radio station homepage",
94
- "WPAY" => "Payment",
95
- "WPUB" => "Publishers official webpage",
96
- "WXXX" => "User defined URL link frame"
97
- }
98
-
99
- # Translate V2 to V3 tags
100
- TAG_MAPPING_2_2_to_2_3 = {
101
- "BUF" => "RBUF",
102
- "COM" => "COMM",
103
- "CRA" => "AENC",
104
- "EQU" => "EQUA",
105
- "ETC" => "ETCO",
106
- "GEO" => "GEOB",
107
- "MCI" => "MCDI",
108
- "MLL" => "MLLT",
109
- "PIC" => "APIC",
110
- "POP" => "POPM",
111
- "REV" => "RVRB",
112
- "RVA" => "RVAD",
113
- "SLT" => "SYLT",
114
- "STC" => "SYTC",
115
- "TAL" => "TALB",
116
- "TBP" => "TBPM",
117
- "TCM" => "TCOM",
118
- "TCO" => "TCON",
119
- "TCR" => "TCOP",
120
- "TDA" => "TDAT",
121
- "TDY" => "TDLY",
122
- "TEN" => "TENC",
123
- "TFT" => "TFLT",
124
- "TIM" => "TIME",
125
- "TKE" => "TKEY",
126
- "TLA" => "TLAN",
127
- "TLE" => "TLEN",
128
- "TMT" => "TMED",
129
- "TOA" => "TOPE",
130
- "TOF" => "TOFN",
131
- "TOL" => "TOLY",
132
- "TOR" => "TORY",
133
- "TOT" => "TOAL",
134
- "TP1" => "TPE1",
135
- "TP2" => "TPE2",
136
- "TP3" => "TPE3",
137
- "TP4" => "TPE4",
138
- "TPA" => "TPOS",
139
- "TPB" => "TPUB",
140
- "TRC" => "TSRC",
141
- "TRD" => "TRDA",
142
- "TRK" => "TRCK",
143
- "TSI" => "TSIZ",
144
- "TSS" => "TSSE",
145
- "TT1" => "TIT1",
146
- "TT2" => "TIT2",
147
- "TT3" => "TIT3",
148
- "TXT" => "TEXT",
149
- "TXX" => "TXXX",
150
- "TYE" => "TYER",
151
- "UFI" => "UFID",
152
- "ULT" => "USLT",
153
- "WAF" => "WOAF",
154
- "WAR" => "WOAR",
155
- "WAS" => "WOAS",
156
- "WCM" => "WCOM",
157
- "WCP" => "WCOP",
158
- "WPB" => "WPB",
159
- "WXX" => "WXXX",
160
- }
161
-
162
- # See id3v2.4.0-structure document, at section 4.
163
- TEXT_ENCODINGS = ["iso-8859-1", "utf-16", "utf-16be", "utf-8"]
164
-
165
- include Mp3Info::HashKeys
166
-
167
- # this is the position in the file where the tag really ends
168
- attr_reader :io_position
169
-
170
- # :+lang+: for writing comments
171
- #
172
- # :+encoding+: one of the string of +TEXT_ENCODINGS+,
173
- # used as a source and destination encoding respectively
174
- # for read and write tag2 values.
175
- attr_reader :options
176
-
177
- # possible options are described above ('options' attribute)
178
- # you can access this object like an hash, with [] and []= methods
179
- # special cases are ["disc_number"] and ["disc_total"] mirroring TPOS attribute
180
- def initialize(options = {})
181
- @options = {
182
- :lang => "ENG",
183
- :encoding => 'utf-8'
184
- }
185
-
186
- @options.update(options)
187
- @text_encoding_index = TEXT_ENCODINGS.index(@options[:encoding])
188
-
189
- unless @text_encoding_index
190
- raise(ArgumentError, "bad id3v2 text encoding specified")
191
- end
192
-
193
- @hash = {}
194
- #TAGS.keys.each { |k| @hash[k] = nil }
195
- @hash_orig = {}
196
- super(@hash)
197
- @parsed = false
198
- @version_maj = @version_min = nil
199
- end
200
-
201
- # does this tag has been correctly read ?
202
- def parsed?
203
- @parsed
204
- end
205
-
206
- # does this tag has been changed ?
207
- def changed?
208
- @hash_orig != @hash
209
- end
210
-
211
- # full version of this tag (like "2.3.0") or nil
212
- # if tag was not correctly read
213
- def version
214
- if @version_maj && @version_min
215
- "2.#{@version_maj}.#{@version_min}"
216
- else
217
- nil
218
- end
219
- end
220
-
221
- ### gets id3v2 tag information from io object (must support #seek() method)
222
- def from_io(io)
223
- @io = io
224
- original_pos = @io.pos
225
- @io.extend(Mp3Info::Mp3FileMethods)
226
- version_maj, version_min, flags = @io.read(3).unpack("CCB4")
227
- @unsync, ext_header, experimental, footer = (0..3).collect { |i| flags[i].chr == '1' }
228
- raise(ID3v2Error, "can't find version_maj ('#{version_maj}')") unless [2, 3, 4].include?(version_maj)
229
- @version_maj, @version_min = version_maj, version_min
230
- @tag_length = @io.get_syncsafe
231
-
232
- @parsed = true
233
- begin
234
- case @version_maj
235
- when 2
236
- read_id3v2_2_frames
237
- when 3, 4
238
- # seek past extended header if present
239
- @io.seek(@io.get_syncsafe - 4, IO::SEEK_CUR) if ext_header
240
- read_id3v2_3_frames
241
- end
242
- rescue ID3v2Error => e
243
- warn("warning: id3v2 tag not fully parsed: #{e.message}")
244
- end
245
- @io_position = @io.pos
246
- @tag_length = @io_position - original_pos
247
-
248
- @hash_orig = @hash.dup
249
- #no more reading
250
- @io = nil
251
- end
252
-
253
- # dump tag for writing. Version is always 2.#{WRITE_VERSION}.0.
254
- def to_bin
255
- #TODO handle of @tag2[TLEN"]
256
- #TODO add of crc
257
- #TODO add restrictions tag
258
-
259
- tag = ""
260
- @hash.each do |k, v|
261
- next unless v
262
- next if v.respond_to?("empty?") and v.empty?
263
-
264
- # Automagically translate V2 to V3 tags
265
- k = TAG_MAPPING_2_2_to_2_3[k] if TAG_MAPPING_2_2_to_2_3.has_key?(k)
266
-
267
- # doesn't encode id3v2.2 tags, which have 3 characters
268
- next if k.size != 4
269
-
270
- # Output one flag for each array element, or one only if it's not an array
271
- [v].flatten.each do |value|
272
- data = encode_tag(k, value.to_s, WRITE_VERSION)
273
- #data << "\x00"*2 #End of tag
274
-
275
- tag << k[0,4] #4 characte max for a tag's key
276
- #tag << to_syncsafe(data.size) #+1 because of the language encoding byte
277
- size = data.size
278
- if RUBY_VERSION >= "1.9.0"
279
- size = data.dup.force_encoding("binary").size
280
- end
281
- tag << [size].pack("N") #+1 because of the language encoding byte
282
- tag << "\x00"*2 #flags
283
- tag << data
284
- end
285
- end
286
-
287
- tag_str = "ID3"
288
- #version_maj, version_min, unsync, ext_header, experimental, footer
289
- tag_str << [ WRITE_VERSION, 0, "0000" ].pack("CCB4")
290
- tag_str << [to_syncsafe(tag.size)].pack("N")
291
- tag_str << tag
292
- puts "tag in binary format: #{tag_str.inspect}" if $DEBUG
293
- tag_str
294
- end
295
-
296
- private
297
-
298
- def encode_tag(name, value, version)
299
- puts "encode_tag(#{name.inspect}, #{value.inspect}, #{version})" if $DEBUG
300
-
301
- text_encoding_index = @text_encoding_index
302
- if (name.index("T") == 0 || name == "COMM" ) && version == 3
303
- # in id3v2.3 tags, there is only 2 encodings possible
304
- transcoded_value = value
305
- if text_encoding_index >= 2
306
- begin
307
- transcoded_value = Iconv.iconv(TEXT_ENCODINGS[1],
308
- TEXT_ENCODINGS[text_encoding_index],
309
- value).first
310
- rescue Iconv::Failure
311
- transcoded_value = value
312
- end
313
- text_encoding_index = 1
314
- end
315
- end
316
-
317
- case name
318
- when "COMM"
319
- puts "encode COMM: enc: #{text_encoding_index}, lang: #{@options[:lang]}, str: #{transcoded_value.dump}" if $DEBUG
320
- [ text_encoding_index , @options[:lang], 0, transcoded_value ].pack("ca3ca*")
321
- when /^T/
322
- text_encoding_index.chr + transcoded_value
323
- else
324
- value
325
- end
326
- end
327
-
328
- ### Read a tag from file and perform UNICODE translation if needed
329
- def decode_tag(name, raw_value)
330
- puts("decode_tag(#{name.inspect}, #{raw_value.inspect})") if $DEBUG
331
- case name
332
- when /^COM/
333
- #FIXME improve this
334
- encoding, lang, str = raw_value.unpack("ca3a*")
335
- out = raw_value.split(0.chr).last
336
- when /^T/
337
- encoding = raw_value.getbyte(0) # language encoding (see TEXT_ENCODINGS constant)
338
- out = raw_value[1..-1]
339
- # we need to convert the string in order to match
340
- # the requested encoding
341
- if encoding && TEXT_ENCODINGS[encoding] && out && encoding != @text_encoding_index
342
- begin
343
- out = Iconv.iconv("#{@options[:encoding]}//IGNORE", TEXT_ENCODINGS[encoding], out).first
344
- rescue Iconv::Failure
345
- end
346
- end
347
- # remove padding zeros for textual tags
348
- out.sub!(/\0*$/, '') unless out.nil?
349
- return out
350
- else
351
- return raw_value
352
- end
353
- end
354
-
355
- ### reads id3 ver 2.3.x/2.4.x frames and adds the contents to @tag2 hash
356
- ### NOTE: the id3v2 header does not take padding zero's into consideration
357
- def read_id3v2_3_frames
358
- loop do # there are 2 ways to end the loop
359
- name = @io.read(4)
360
- if name.nil? || name.getbyte(0) == 0 || name == "MP3e" #bug caused by old tagging application "mp3ext" ( http://www.mutschler.de/mp3ext/ )
361
- @io.seek(-4, IO::SEEK_CUR) # 1. find a padding zero,
362
- seek_to_v2_end
363
- break
364
- else
365
- if @version_maj == 4
366
- size = @io.get_syncsafe
367
- else
368
- size = @io.get32bits
369
- end
370
- @io.seek(2, IO::SEEK_CUR) # skip flags
371
- puts "name '#{name}' size #{size}" if $DEBUG
372
- add_value_to_tag2(name, size)
373
- end
374
- break if @io.pos >= @tag_length # 2. reach length from header
375
- end
376
- end
377
-
378
- ### reads id3 ver 2.2.x frames and adds the contents to @tag2 hash
379
- ### NOTE: the id3v2 header does not take padding zero's into consideration
380
- def read_id3v2_2_frames
381
- loop do
382
- name = @io.read(3)
383
- if name.nil? || name.getbyte(0) == 0
384
- @io.seek(-3, IO::SEEK_CUR)
385
- seek_to_v2_end
386
- break
387
- else
388
- size = (@io.getbyte << 16) + (@io.getbyte << 8) + @io.getbyte
389
- add_value_to_tag2(name, size)
390
- break if @io.pos >= @tag_length
391
- end
392
- end
393
- end
394
-
395
- ### Add data to tag2["name"]
396
- ### read lang_encoding, decode data if unicode and
397
- ### create an array if the key already exists in the tag
398
- def add_value_to_tag2(name, size)
399
- puts "add_value_to_tag2" if $DEBUG
400
-
401
- if size > 50_000_000
402
- raise ID3v2Error, "tag size is > 50_000_000"
403
- end
404
-
405
- data_io = @io.read(size)
406
- data = decode_tag(name, data_io)
407
-
408
- if self["TPOS"] =~ /(\d+)\s*\/\s*(\d+)/
409
- self["disc_number"] = $1.to_i
410
- self["disc_total"] = $2.to_i
411
- end
412
-
413
- if self.keys.include?(name)
414
- unless self[name].is_a?(Array)
415
- self[name] = [ self[name] ]
416
- end
417
- self[name] << data
418
- else
419
- self[name] = data
420
- end
421
- p data if $DEBUG
422
- end
423
-
424
- ### runs thru @file one char at a time looking for best guess of first MPEG
425
- ### frame, which should be first 0xff byte after id3v2 padding zero's
426
- def seek_to_v2_end
427
- until @io.getbyte == 0xff
428
- raise ID3v2Error, "got EOF before finding id3v2 end" if @io.eof?
429
- end
430
- @io.seek(-1, IO::SEEK_CUR)
431
- end
432
-
433
- ### convert an 32 integer to a syncsafe string
434
- def to_syncsafe(num)
435
- ( (num<<3) & 0x7f000000 ) + ( (num<<2) & 0x7f0000 ) + ( (num<<1) & 0x7f00 ) + ( num & 0x7f )
436
- end
437
- end