Class: Mp3Info

### Home Classes Methods

### In Files

- lib/mp3info.rb
- lib/mp3info/extension_modules.rb

### Parent

Object

### Methods

- ::hastag1?
- ::hastag2?
- ::new
- ::open
- ::removetag1
- ::removetag2
- #audio_content
- #close
- #each_frame
- #flush
- #get_frame_length
- #hastag1?
- #hastag2?
- #hastag?
- #reload
- #removetag1
- #removetag2
- #rename
- #to_s

### Files

- History.txt
- Manifest.txt

### Class/Module Index

Quicksearch

- ID3v2
- ID3v2Error
- Mp3Info
- Mp3InfoError
- Object
- String

No matching classes.

# Mp3Info

License | Ruby
Author | Guillaume Pierronnet (guillaume.pierronnet@gmail.com)

### Constants

BITRATE
CHANNEL_MODE
GENRES
LAYER
SAMPLERATE
SAMPLES_PER_FRAME

  www.codeproject.com/audio/MPEGAudioInfo.asp

TAG1_SIZE
TAG_MAPPING_2_2

  map to fill the "universal" tag (tag attribute) for id3v2.2

TAG_MAPPING_2_3

  for id3v2.3 and 2.4

VERSION

### Attributes

bitrate[R]

bitrate in kbps

channel_mode[R]

channel mode => "Stereo", "JStereo", "Dual Channel" or "Single Channel"

error_protection[R]

error protection => true or false

filename[R]

the original filename unless used with a StringIO

header[R]

Hash representing values in the MP3 frame header. Keys are one of the following:

- :private (boolean)

- :copyright (boolean)

- :original (boolean)

- :padding (boolean)

- :error_protection (boolean)

- :mode_extension (integer in the 0..3 range)

- :emphasis (integer in the 0..3 range)

detailled explanation can be found here: www.mp3-tech.org/programmer/frame_header.html

layer[R]

layer = 1, 2, or 3

length[R]

length in seconds as a Float

mpeg_version[R]

mpeg version = 1 or 2

samplerate[R]

samplerate in Hz

tag[R]

a sort of "universal" tag, regardless of the tag version, 1 or 2, with the same keys as @tag1 this tag has priority over @tag1 and @tag2 when writing the tag with close

tag1[RW]

id3v1 tag as a Hash. You can modify it, it will be written when calling "close" method.

tag2[RW]

id3v2 tag attribute as an ID3v2 object. You can modify it, it will be written when calling "close" method.

vbr[R]

variable bitrate => true or false

### Public Class Methods

hastag1?(filename_or_io) click to toggle source

Test the presence of an id3v1 tag in file or StringIO `filename_or_io`

    # File lib/mp3info.rb, line 161
    def self.hastag1?(filename_or_io)
      if filename_or_io.is_a?(StringIO)
        io = filename_or_io
        io.rewind
      else
        io = File.new(filename_or_io, "rb")
      end

      hastag1 = false
      begin
        io.seek(-TAG1_SIZE, File::SEEK_END)
        hastag1 = io.read(3) == "TAG"
      ensure
        io.close if io.is_a?(File)
      end
      hastag1
    end

hastag2?(filename_or_io) click to toggle source

Test the presence of an id3v2 tag in file or StringIO `filename_or_io`

    # File lib/mp3info.rb, line 180
    def self.hastag2?(filename_or_io)
      if filename_or_io.is_a?(StringIO)
        io = filename_or_io
        io.rewind
      else
        io = File.new(filename_or_io,"rb")
      end

      hastag2 = false

      begin
        hastag2 = io.read(3) == "ID3"
      ensure
        io.close if io.is_a?(File)
      end
      hastag2
    end

new(filename_or_io, options = {}) click to toggle source

Instantiate Mp3Info object with name `filename`. options hash is used for ID3v2#new. Specify :parse_tags => false to disable the processing of the tags (read and write). Specify :parse_mp3 => false to disable processing of the mp3

    # File lib/mp3info.rb, line 218
    def initialize(filename_or_io, options = {})
      warn("#{self.class}::new() does not take block; use #{self.class}::open() instead") if block_given?
      @filename_or_io = filename_or_io
      if @filename_or_io.nil?
        raise ArgumentError, "filename is nil"
      end
      options = {:parse_mp3 => true, :parse_tags => true}.update(options)
      @tag_parsing_enabled = options.delete(:parse_tags)
      @mp3_parsing_enabled = options.delete(:parse_mp3)
      @id3v2_options = options
      reload
    end

open(*params) click to toggle source

"block version" of Mp3Info::new()

    # File lib/mp3info.rb, line 295
    def self.open(*params)
      m = self.new(*params)
      ret = nil
      if block_given?
        begin
          ret = yield(m)
        ensure
          m.close
        end
      else
        ret = m
      end
      ret
    end

removetag1(filename) click to toggle source

Remove id3v1 tag from `filename`

    # File lib/mp3info.rb, line 199
    def self.removetag1(filename)
      if self.hastag1?(filename)
        newsize = File.size(filename) - TAG1_SIZE
        File.open(filename, "rb+") { |f| f.truncate(newsize) }
      end
    end

removetag2(filename) click to toggle source

Remove id3v2 tag from `filename`

    # File lib/mp3info.rb, line 207
    def self.removetag2(filename)
      self.open(filename) do |mp3|
        mp3.tag2.clear
      end
    end

### Public Instance Methods

audio_content() click to toggle source

this method returns the "audio-only" data boundaries of the file, i.e. content stripped form tags. Useful to compare 2 files with the same audio content but with differents tags. Returned value is an array

position_in_the_file, length_of_the_data

    # File lib/mp3info.rb, line 347
    def audio_content
      pos = 0
      length = @io_size
      if hastag1?
        length -= TAG1_SIZE
      end
      if hastag2?
        pos = @tag2.io_position
        length -= @tag2.io_position
      end
      [pos, length]
    end

close() click to toggle source

Flush pending modifications to tags and close the file not used when source IO is a StringIO

    # File lib/mp3info.rb, line 367
    def close
      puts "close" if $DEBUG
      return unless @io_is_a_file
      if !@tag_parsing_enabled
        return
      end
      if @tag != @tag_orig
        puts "@tag has changed" if $DEBUG

        # @tag1 has precedence over @tag
        if @tag1 == @tag1_orig
          @tag.each do |k, v|
            @tag1[k] = v
          end
        end

        # ruby-mp3info can only write v2.3 tags
        TAG_MAPPING_2_3.each do |key, tag2_name|
          @tag2.delete(TAG_MAPPING_2_2[key])
          @tag2[tag2_name] = @tag[key] if @tag[key]
        end
      end

      if @tag1 != @tag1_orig
        puts "@tag1 has changed" if $DEBUG
        raise(Mp3InfoError, "file is not writable") unless File.writable?(@filename_or_io)
        #@tag1_orig.update(@tag1)
        @tag1_orig = @tag1.dup
        File.open(@filename_or_io, 'rb+') do |file|
          if @tag1_orig.empty?
            newsize = @io_size - TAG1_SIZE
            file.truncate(newsize)
          else
            file.seek(-TAG1_SIZE, File::SEEK_END)
            t = file.read(3)
            if t != 'TAG'
              #append new tag
              file.seek(0, File::SEEK_END)
              file.write('TAG')
            end
            str = [
              @tag1_orig["title"]||"",
              @tag1_orig["artist"]||"",
              @tag1_orig["album"]||"",
              ((@tag1_orig["year"] != 0) ? ("%04d" % @tag1_orig["year"].to_i) : "\00\\00\\00\\00""),
              @tag1_orig["comments"]||"",
              0,
              @tag1_orig["tracknum"]||0,
              @tag1_orig["genre"]||255
              ].pack("Z30Z30Z30Z4Z28CCC")
            file.write(str)
          end
        end
      end

      if @tag2.changed?
        puts "@tag2 has changed" if $DEBUG
        raise(Mp3InfoError, "file is not writable") unless File.writable?(@filename_or_io)
        tempfile_name = nil
        File.open(@filename_or_io, 'rb+') do |file|
          #if tag2 already exists, seek to end of it
          if @tag2.parsed?
            file.seek(@tag2.io_position)
          end
    #      if @io.read(3) == "ID3"
    #        version_maj, version_min, flags = @io.read(3).unpack("CCB4")
    #        unsync, ext_header, experimental, footer = (0..3).collect { |i| flags[i].chr == '1' }
    #        tag2_len = @io.get_syncsafe
    #        @io.seek(@io.get_syncsafe - 4, IO::SEEK_CUR) if ext_header
    #        @io.seek(tag2_len, IO::SEEK_CUR)
    #      end
          filename_splitted = File.split(@filename_or_io)
          filename_splitted[-1] = ".#{filename_splitted[-1]}.tmp"
          tempfile_name = File.join(filename_splitted)
          File.open(tempfile_name, "wb") do |tempfile|
            unless @tag2.empty?
              tempfile.write(@tag2.to_bin)
            end

            bufsiz = file.stat.blksize || 4096
            while buf = file.read(bufsiz)
              tempfile.write(buf)
            end
          end
        end
        File.rename(tempfile_name, @filename_or_io)
      end
    end

each_frame() click to toggle source

iterates over each mpeg frame over the file, allowing you to write some funny things, like an mpeg lossless cutter, or frame counter, or whatever you like ;) `frame` is a hash with the following keys: :layer, :bitrate, :samplerate, :mpeg_version, :padding and :size (in bytes)

    # File lib/mp3info.rb, line 476
    def each_frame
      @io.seek(@first_frame_pos, File::SEEK_SET)
      loop do
        frame = find_next_frame
        yield frame
        @io.seek(frame[:size] -4, File::SEEK_CUR)
        #puts "frame #{frame_count} len #{frame[:length]} br #{frame[:bitrate]} @io.pos #{@io.pos}"
        break if @io.eof?
      end
    end

flush() click to toggle source

close and reopen the file, i.e. commit changes to disk and reload it (only works with "true" files, not StringIO ones)

    # File lib/mp3info.rb, line 458
    def flush
      return unless @io_is_a_file
      close
      reload
    end

get_frame_length() click to toggle source

return the length in seconds of one frame

    # File lib/mp3info.rb, line 361
    def get_frame_length
      SAMPLES_PER_FRAME[@layer][@mpeg_version] / Float(@samplerate)
    end

hastag1?() click to toggle source

Does the file has an id3v1 tag?

    # File lib/mp3info.rb, line 328
    def hastag1?
      !@tag1.empty?
    end

hastag2?() click to toggle source

Does the file has an id3v2 tag?

    # File lib/mp3info.rb, line 333
    def hastag2?
      @tag2.parsed?
    end

hastag?() click to toggle source

Does the file has an id3v1 or v2 tag?

    # File lib/mp3info.rb, line 323
    def hastag?
      hastag1? || hastag2?
    end

reload() click to toggle source

reload (or load for the first time) the file from disk

    # File lib/mp3info.rb, line 232
    def reload
      @header = {}

      if @filename_or_io.is_a?(StringIO) || @filename_or_io.is_a?(IO)
        @io_is_a_file = false
        @io = @filename_or_io
        @io_size = @io.size
        @filename = nil
      else
        @io_is_a_file = true
        @io = File.new(@filename_or_io, "rb")
        @io_size = @io.stat.size
        @filename = @filename_or_io
      end

      if @io_size == 0
        raise(Mp3InfoError, "empty file or IO")
      end

      @io.extend(Mp3FileMethods)
      @tag1 = @tag = @tag1_orig = @tag_orig = {}
      @tag1.extend(HashKeys)
      @tag2 = ID3v2.new(@id3v2_options)

      if @tag_parsing_enabled
        parse_tags
        @tag1_orig = @tag1.dup

        if hastag1?
          @tag = @tag1.dup
        end

        if hastag2?
          @tag = {}
          # creation of a sort of "universal" tag, regardless of the tag version
          tag2_mapping = @tag2.version =~ /^2\.2/ ? TAG_MAPPING_2_2 : TAG_MAPPING_2_3
          tag2_mapping.each do |key, tag2_name|
            tag_value = (@tag2[tag2_name].is_a?(Array) ? @tag2[tag2_name].first : @tag2[tag2_name])
            next unless tag_value
            @tag[key] = tag_value.is_a?(Array) ? tag_value.first : tag_value

            if %{year tracknum}.include?(key)
              @tag[key] = tag_value.to_i
            end
            # this is a special case with id3v2.2, which uses
            # old fashionned id3v1 genres
            if tag2_name == "TCO" && tag_value =~ /^\((\d+)\)$/
              @tag["genre_s"] = GENRES[$1.to_i]
            end
          end
        end

        @tag.extend(HashKeys)
        @tag_orig = @tag.dup
      end

      if @mp3_parsing_enabled
        parse_mp3
      end

    end

removetag1() click to toggle source

Remove id3v1 from mp3

    # File lib/mp3info.rb, line 311
    def removetag1
      @tag1.clear
      self
    end

removetag2() click to toggle source

Remove id3v2 from mp3

    # File lib/mp3info.rb, line 317
    def removetag2
      @tag2.clear
      self
    end

rename(new_filename) click to toggle source

write to another filename at close()

    # File lib/mp3info.rb, line 338
    def rename(new_filename)
      raise(Mp3InfoError, "cannot rename an IO") unless @io_is_a_file
      @filename = new_filename
    end

to_s() click to toggle source

inspect inside Mp3Info

    # File lib/mp3info.rb, line 465
    def to_s
      s = "MPEG #{@mpeg_version} Layer #{@layer} #{@vbr ? "VBR" : "CBR"} #{@bitrate} Kbps #{@channel_mode} #{@samplerate} Hz length #{@length} sec. header #{@header.inspect} "
      s << "tag1: "+@tag1.to_hash.inspect+"\n" if hastag1?
      s << "tag2: "+@tag2.to_inspect_hash.inspect+"\n" if hastag2?
      s
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.