Class: ID3v2
- Inherits:
-
Hash
- Object
- Hash
- ID3v2
- Includes:
- Kernel, Mp3Info::HashKeys
- Defined in:
- lib/mp3info/id3v2.rb
Overview
This class can be used to decode id3v2 tags from files, like .mp3 or .ape for example. It works like a hash, where key represents the tag name as 3 or 4 upper case letters (respectively related to 2.2 and 2.3+ tag) and value represented as array or raw value. Written version is always 2.3.
Constant Summary collapse
- TAGS =
{ "AENC" => "Audio encryption", "APIC" => "Attached picture", "COMM" => "Comments", "COMR" => "Commercial frame", "ENCR" => "Encryption method registration", "EQUA" => "Equalization", "ETCO" => "Event timing codes", "GEOB" => "General encapsulated object", "GRID" => "Group identification registration", "IPLS" => "Involved people list", "LINK" => "Linked information", "MCDI" => "Music CD identifier", "MLLT" => "MPEG location lookup table", "OWNE" => "Ownership frame", "PRIV" => "Private frame", "PCNT" => "Play counter", "POPM" => "Popularimeter", "POSS" => "Position synchronisation frame", "RBUF" => "Recommended buffer size", "RVAD" => "Relative volume adjustment", "RVRB" => "Reverb", "SYLT" => "Synchronized lyric/text", "SYTC" => "Synchronized tempo codes", "TALB" => "Album/Movie/Show title", "TBPM" => "BPM (beats per minute)", "TCOM" => "Composer", "TCON" => "Content type", "TCOP" => "Copyright message", "TDAT" => "Date", "TDLY" => "Playlist delay", "TENC" => "Encoded by", "TEXT" => "Lyricist/Text writer", "TFLT" => "File type", "TIME" => "Time", "TIT1" => "Content group description", "TIT2" => "Title/songname/content description", "TIT3" => "Subtitle/Description refinement", "TKEY" => "Initial key", "TLAN" => "Language(s)", "TLEN" => "Length", "TMED" => "Media type", "TOAL" => "Original album/movie/show title", "TOFN" => "Original filename", "TOLY" => "Original lyricist(s)/text writer(s)", "TOPE" => "Original artist(s)/performer(s)", "TORY" => "Original release year", "TOWN" => "File owner/licensee", "TPE1" => "Lead performer(s)/Soloist(s)", "TPE2" => "Band/orchestra/accompaniment", "TPE3" => "Conductor/performer refinement", "TPE4" => "Interpreted, remixed, or otherwise modified by", "TPOS" => "Part of a set", "TPUB" => "Publisher", "TRCK" => "Track number/Position in set", "TRDA" => "Recording dates", "TRSN" => "Internet radio station name", "TRSO" => "Internet radio station owner", "TSIZ" => "Size", "TSRC" => "ISRC (international standard recording code)", "TSSE" => "Software/Hardware and settings used for encoding", "TYER" => "Year", "TXXX" => "User defined text information frame", "UFID" => "Unique file identifier", "USER" => "Terms of use", "USLT" => "Unsychronized lyric/text transcription", "WCOM" => "Commercial information", "WCOP" => "Copyright/Legal information", "WOAF" => "Official audio file webpage", "WOAR" => "Official artist/performer webpage", "WOAS" => "Official audio source webpage", "WORS" => "Official internet radio station homepage", "WPAY" => "Payment", "WPUB" => "Publishers official webpage", "WXXX" => "User defined URL link frame" }
- TAG_MAPPING_2_2_to_2_3 =
Translate V2 to V3 tags
{ "BUF" => "RBUF", "COM" => "COMM", "CRA" => "AENC", "EQU" => "EQUA", "ETC" => "ETCO", "GEO" => "GEOB", "MCI" => "MCDI", "MLL" => "MLLT", "PIC" => "APIC", "POP" => "POPM", "REV" => "RVRB", "RVA" => "RVAD", "SLT" => "SYLT", "STC" => "SYTC", "TAL" => "TALB", "TBP" => "TBPM", "TCM" => "TCOM", "TCO" => "TCON", "TCR" => "TCOP", "TDA" => "TDAT", "TDY" => "TDLY", "TEN" => "TENC", "TFT" => "TFLT", "TIM" => "TIME", "TKE" => "TKEY", "TLA" => "TLAN", "TLE" => "TLEN", "TMT" => "TMED", "TOA" => "TOPE", "TOF" => "TOFN", "TOL" => "TOLY", "TOR" => "TORY", "TOT" => "TOAL", "TP1" => "TPE1", "TP2" => "TPE2", "TP3" => "TPE3", "TP4" => "TPE4", "TPA" => "TPOS", "TPB" => "TPUB", "TRC" => "TSRC", "TRD" => "TRDA", "TRK" => "TRCK", "TSI" => "TSIZ", "TSS" => "TSSE", "TT1" => "TIT1", "TT2" => "TIT2", "TT3" => "TIT3", "TXT" => "TEXT", "TXX" => "TXXX", "TYE" => "TYER", "UFI" => "UFID", "ULT" => "USLT", "WAF" => "WOAF", "WAR" => "WOAR", "WAS" => "WOAS", "WCM" => "WCOM", "WCP" => "WCOP", "WPB" => "WPB", "WXX" => "WXXX", }
- TEXT_ENCODINGS =
See id3v2.4.0-structure document, at section 4.
["iso-8859-1", "utf-16", "utf-16be", "utf-8"]
Instance Attribute Summary collapse
-
#io_position ⇒ Object
readonly
this is the position in the file where the tag really ends.
-
#options ⇒ Object
readonly
:
lang
: for writing comments.
Instance Method Summary collapse
-
#add_picture(data, opts = {}) ⇒ Object
ID3V2::add_picture Takes an image string as input and writes it with header.
-
#changed? ⇒ Boolean
does this tag has been changed ?.
-
#from_io(io) ⇒ Object
gets id3v2 tag information from io object (must support #seek() method).
-
#initialize(options = {}) ⇒ ID3v2
constructor
possible options are described above (‘options’ attribute) you can access this object like an hash, with [] and []= methods special cases are [“disc_number”] and [“disc_total”] mirroring TPOS attribute.
- #inspect ⇒ Object
-
#parsed? ⇒ Boolean
does this tag has been correctly read ?.
-
#pictures ⇒ Object
Returns an array of images: [ [“01_.jpg”, “Image Data in Binary String”], [“02_.png”, “Another Image in a String”] ].
- #remove_pictures ⇒ Object
-
#to_bin ⇒ Object
dump tag for writing.
-
#to_inspect_hash ⇒ Object
cuts out long tag values from hash for display on screen.
-
#version ⇒ Object
full version of this tag (like “2.3.0”) or nil if tag was not correctly read.
Methods included from Mp3Info::HashKeys
Constructor Details
#initialize(options = {}) ⇒ ID3v2
possible options are described above (‘options’ attribute) you can access this object like an hash, with [] and []= methods special cases are [“disc_number”] and [“disc_total”] mirroring TPOS attribute
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/mp3info/id3v2.rb', line 185 def initialize( = {}) @options = { :lang => "ENG" } if @options[:encoding] warn("use of :encoding parameter is DEPRECATED. In ruby 1.8, use utf-8 encoded strings for tags.\n" + "In ruby >= 1.9, strings are automatically transcoded from their original encoding.") end @options.update() @hash = {} #TAGS.keys.each { |k| @hash[k] = nil } @hash_orig = {} super(@hash) @parsed = false @version_maj = @version_min = nil end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class Mp3Info::HashKeys
Instance Attribute Details
#io_position ⇒ Object (readonly)
this is the position in the file where the tag really ends
173 174 175 |
# File 'lib/mp3info/id3v2.rb', line 173 def io_position @io_position end |
#options ⇒ Object (readonly)
:lang
: for writing comments
- DEPRECATION
-
:
encoding
: one of the string ofTEXT_ENCODINGS
,
use of :encoding parameter is DEPRECATED. In ruby 1.8, use utf-8 encoded strings for tags. In ruby >= 1.9, strings are automatically transcoded from their originaloriginal encoding.
180 181 182 |
# File 'lib/mp3info/id3v2.rb', line 180 def @options end |
Instance Method Details
#add_picture(data, opts = {}) ⇒ Object
ID3V2::add_picture Takes an image string as input and writes it with header. Mime type is automatically guessed by default. It is possible but not necessary to include:
:pic_type => 0 - 14 (see http://id3.org/id3v2.3.0#Attached_picture)
:mime => 'gif'
:description => "Image description"
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/mp3info/id3v2.rb', line 246 def add_picture(data, opts = {}) = { :pic_type => 0, :mime => nil, :description => "image" } .update(opts) jpg = Regexp.new( "^\xFF".force_encoding("BINARY"), Regexp::FIXEDENCODING ) png = Regexp.new( "^\x89PNG".force_encoding("BINARY"), Regexp::FIXEDENCODING ) gif = Regexp.new( "^\x89GIF".force_encoding("BINARY"), Regexp::FIXEDENCODING ) mime = [:mime] mime ||= "jpg" if data.match jpg mime ||= "png" if data.match png mime ||= "gif" if data.match gif pic_type = [:pic_type] pic_type = ["%02i" % pic_type].pack('H*') desc = "#{[:description]}" header = "\x00image/#{mime}\x00#{pic_type}#{desc}\x00" self["APIC"] = header + data.force_encoding('BINARY') end |
#changed? ⇒ Boolean
does this tag has been changed ?
208 209 210 |
# File 'lib/mp3info/id3v2.rb', line 208 def changed? @hash_orig != @hash end |
#from_io(io) ⇒ Object
gets id3v2 tag information from io object (must support #seek() method)
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/mp3info/id3v2.rb', line 356 def from_io(io) @io = io original_pos = @io.pos @io.extend(Mp3Info::Mp3FileMethods) version_maj, version_min, flags = @io.read(3).unpack("CCB4") @unsync, ext_header, _, _ = (0..3).collect { |i| flags[i].chr == '1' } # _, _ = experimental, footer raise(ID3v2Error, "can't find version_maj ('#{version_maj}')") unless [2, 3, 4].include?(version_maj) @version_maj, @version_min = version_maj, version_min @tag_length = @io.get_syncsafe @parsed = true begin case @version_maj when 2 read_id3v2_2_frames when 3, 4 # seek past extended header if present @io.seek(@io.get_syncsafe - 4, IO::SEEK_CUR) if ext_header read_id3v2_3_frames end rescue ID3v2Error => e warn("warning: id3v2 tag not fully parsed: #{e.}") end @io_position = @io.pos @tag_length = @io_position - original_pos @hash_orig = @hash.dup #no more reading @io = nil end |
#inspect ⇒ Object
346 347 348 |
# File 'lib/mp3info/id3v2.rb', line 346 def inspect self.to_inspect_hash end |
#parsed? ⇒ Boolean
does this tag has been correctly read ?
203 204 205 |
# File 'lib/mp3info/id3v2.rb', line 203 def parsed? @parsed end |
#pictures ⇒ Object
Returns an array of images:
[ ["01_.jpg", "Image Data in Binary String"],
["02_.png", "Another Image in a String"] ]
e.g. to write all images: mp3.tag2.pictures.each do |image|
File.open(img[0], 'wb'){|f| f.write img[1])
end
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/mp3info/id3v2.rb', line 279 def pictures apic_images = [self["APIC"]].flatten.dup result = [] apic_images.each_index do |index| pic = apic_images[index] next if !pic.is_a?(String) or pic == "" pic.force_encoding 'BINARY' picture = [] jpg_regexp = Regexp.new("jpg|JPG|jpeg|JPEG".force_encoding("BINARY"), Regexp::FIXEDENCODING ) png_regexp = Regexp.new("png|PNG".force_encoding("BINARY"), Regexp::FIXEDENCODING ) header = pic.unpack('a120').first.force_encoding "BINARY" mime_pos = 0 # safest way to correctly extract jpg and png is finding mime if header.match jpg_regexp and not header.match png_regexp mime = "jpg" mime_pos = header =~ jpg_regexp start = Regexp.new("\xFF\xD8".force_encoding("BINARY"), Regexp::FIXEDENCODING ) start_with_anchor = Regexp.new("^\xFF\xD8".force_encoding("BINARY"), Regexp::FIXEDENCODING ) end if header.match png_regexp and not header.match jpg_regexp mime = "png" mime_pos = header =~ png_regexp start = Regexp.new("\x89PNG".force_encoding("BINARY"), Regexp::FIXEDENCODING ) start_with_anchor = Regexp.new("^\x89PNG".force_encoding("BINARY"), Regexp::FIXEDENCODING ) end puts "analysing image: #{header.inspect}..." if $DEBUG _, _, desc, data = pic[mime_pos, pic.length].unpack('Z*hZ*a*') if mime if !data.match(start_with_anchor) or data.nil? real_start = pic =~ start data = pic[real_start, pic.length] end if mime == "jpg" # inspect jpg image header (first 10 chars) for "\xFF\x00" (expect "\xFF") trailing_null_byte = Regexp.new("(\377)(\000)".force_encoding('BINARY'), Regexp::FIXEDENCODING) md = data =~ trailing_null_byte if !md.nil? and md < 10 data.gsub!(trailing_null_byte, "\xff".force_encoding('BINARY')) end end else mime = "dat" # if no jpg or png, extract data anyway e.g. gif mime, desc, data = pic.unpack('h Z* h Z* a*').values_at(1,3,4) end filename = ("%02i_#{desc[0,25]}" % (index + 1)).gsub('/','') picture[0] = filename picture[1] = data result << picture end result end |
#remove_pictures ⇒ Object
350 351 352 353 |
# File 'lib/mp3info/id3v2.rb', line 350 def remove_pictures self["APIC"] = "" self["PIC"] = "" end |
#to_bin ⇒ Object
dump tag for writing. Version is always 2.3.0
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'lib/mp3info/id3v2.rb', line 388 def to_bin #TODO handle of @tag2[TLEN"] #TODO add of crc #TODO add restrictions tag tag = "" @hash.each do |k, v| next unless v next if v.respond_to?("empty?") and v.empty? # Automagically translate V2 to V3 tags k = TAG_MAPPING_2_2_to_2_3[k] if TAG_MAPPING_2_2_to_2_3.has_key?(k) # doesn't encode id3v2.2 tags, which have 3 characters next if k.size != 4 # Output one flag for each array element, or one only if it's not an array [v].flatten.each do |value| data = encode_tag(k, value.to_s) #data << "\x00"*2 #End of tag tag << k[0,4] #4 characte max for a tag's key #tag << to_syncsafe(data.size) #+1 because of the language encoding byte size = data.size unless RUBY_1_8 size = data.dup.force_encoding("binary").size end tag << [size].pack("N") #+1 because of the language encoding byte tag << "\x00"*2 #flags tag << data end end tag_str = "ID3" #version_maj, version_min, unsync, ext_header, experimental, footer tag_str << [ 3, 0, "0000" ].pack("CCB4") tag_str << [to_syncsafe(tag.size)].pack("N") tag_str << tag puts "tag in binary format: #{tag_str.inspect}" if $DEBUG tag_str end |
#to_inspect_hash ⇒ Object
cuts out long tag values from hash for display on screen
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/mp3info/id3v2.rb', line 223 def to_inspect_hash result = Marshal.load(Marshal.dump(self.to_hash)) result.each do |k,v| if v.is_a? Array v.each_index do |i, item| if (v[i].is_a? String and v[i].length > 128) result[k][i] = pretty_header(v[i]) end end elsif v.is_a? String and v.length > 128 result[k] = pretty_header(v) # this method 'snips' long data end end result end |
#version ⇒ Object
full version of this tag (like “2.3.0”) or nil if tag was not correctly read
214 215 216 217 218 219 220 |
# File 'lib/mp3info/id3v2.rb', line 214 def version if @version_maj && @version_min "2.#{@version_maj}.#{@version_min}" else nil end end |