class MpcInfo

Constants

FREQUENCIES
PROFILES_NAMES
SV4_6_HEADER

Attributes

id3v2_tag[R]
infos[R]

Public Class Methods

new(filename) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 39
def initialize(filename)
  @file = File.open(filename, 'rb')

  @infos = {}
  @infos['raw'] = {}
  parse_infos
end

Private Instance Methods

encoder_version(encoderversion) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 183
def encoder_version(encoderversion)
  # Encoder version * 100  (106 = 1.06)
  # EncoderVersion % 10 == 0        Release (1.0)
  # EncoderVersion %  2 == 0        Beta (1.06)
  # EncoderVersion %  2 == 1        Alpha (1.05a...z)

  if encoderversion.zero?
    # very old version, not known exactly which
    'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05'
  elsif (encoderversion % 10).zero?
    # release version
    format('%.2f', encoderversion / 100.0)
  elsif encoderversion.even?
    format('%.2f beta', encoderversion / 100.0)
  else
    format('%.2f alpha', encoderversion / 100.0)
  end
end
parse_infos() click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 49
def parse_infos
  mpc_header = @file.read(3)

  case mpc_header
  when 'MP+'
    # this is SV7+

    header = StringIO.new(@file.read(25))
    header_size = 28
    # stream_version_byte = header.read(4).unpack("V").first
    stream_version_byte = header.read(1)[0].ord # .unpack("c").first

    @infos['stream_major_version'] = (stream_version_byte & 0x0F)
    @infos['stream_minor_version'] = (stream_version_byte & 0xF0) >> 4

    @infos['frame_count']          = read32(header)
    raise(MpcInfoError, 'Only Musepack SV7 supported') if @infos['stream_major_version'] != 7

    flags_dword1 = read32(header)

    @infos['intensity_stereo']       = ((flags_dword1 & 0x80000000) >> 31) == 1
    @infos['mid_side_stereo']        = ((flags_dword1 & 0x40000000) >> 30) == 1
    @infos['max_subband']            = (flags_dword1 & 0x3F000000) >> 24
    @infos['raw']['profile']         = (flags_dword1 & 0x00F00000) >> 20
    @infos['begin_loud']             = ((flags_dword1 & 0x00080000) >> 19) == 1
    @infos['end_loud']               = ((flags_dword1 & 0x00040000) >> 18) == 1
    @infos['raw']['sample_rate']     = (flags_dword1 & 0x00030000) >> 16
    @infos['max_level']              = (flags_dword1 & 0x0000FFFF)

    @infos['raw']['title_peak']      = read16(header)
    @infos['raw']['title_gain']      = read16(header)

    @infos['raw']['album_peak']      = read16(header)
    @infos['raw']['album_gain']      = read16(header)

    flags_dword2 = read32(header)
    @infos['true_gapless']           = ((flags_dword2 & 0x80000000) >> 31) == 1
    @infos['last_frame_length']      = (flags_dword2 & 0x7FF00000) >> 20

    not_sure_what = read32(header, 3)
    @infos['raw']['encoder_version'] = read8(header)

    @infos['profile']     = PROFILES_NAMES[@infos['raw']['profile']] || 'invalid'
    @infos['sample_rate'] = FREQUENCIES[@infos['raw']['sample_rate']]

    raise(MpcInfoError, 'Corrupt MPC file: frequency == zero') if (@infos['sample_rate']).zero?

    sample_rate = @infos['sample_rate']
    channels = 2 # appears to be hardcoded
    @infos['samples'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels
    @infos['length'] = (((@infos['frame_count'] - 1) * 1152) + @infos['last_frame_length']) * channels

    @infos['length'] = (@infos['samples'] / channels) / @infos['sample_rate'].to_f
    raise(MpcInfoError, 'Corrupt MPC file: playtime_seconds == zero') if (@infos['length']).zero?

    # add size of file header to avdataoffset - calc bitrate correctly + MD5 data
    avdataoffset = header_size

    # FIXME: is $ThisFileInfo['avdataend']  == File.size ????
    @infos['bitrate'] = ((@file.stat.size - avdataoffset) * 8) / @infos['length']

    @infos['title_peak'] = @infos['raw']['title_peak']
    @infos['title_peak_db'] = @infos['title_peak'].zero? ? 0 : peak_db(@infos['title_peak'])
    @infos['title_gain_db'] = if (@infos['raw']['title_gain']).negative?
                                (32_768 + @infos['raw']['title_gain']) / -100.0
                              else
                                @infos['raw']['title_gain'] / 100.0
                              end

    @infos['album_peak']        = @infos['raw']['album_peak']
    @infos['album_peak_db']     = @infos['album_peak'].zero? ? 0 : peak_db(@infos['album_peak'])

    @infos['album_gain_db'] = if (@infos['raw']['album_gain']).negative?
                                (32_768 + @infos['raw']['album_gain']) / -100.0
                              else
                                @infos['raw']['album_gain'] / 100.0
                              end
    @infos['encoder_version'] = encoder_version(@infos['raw']['encoder_version'])

  #       #FIXME
  #       $ThisFileInfo['replay_gain']['track']['adjustment'] = @infos['title_gain_db'];
  #       $ThisFileInfo['replay_gain']['album']['adjustment'] = @infos['album_gain_db'];
  #       if @infos['title_peak'] > 0
  #         #$ThisFileInfo['replay_gain']['track']['peak'] = @infos['title_peak']
  #       elsif round(@infos['max_level'] * 1.18) > 0)
  #         // why? I don't know - see mppdec.c
  #         # ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round(@infos['max_level'] * 1.18));
  #       end
  #
  #       if @infos['album_peak'] > 0
  #         #$ThisFileInfo['replay_gain']['album']['peak'] = @infos['album_peak'];
  #       end
  #
  #       #ThisFileInfo['audio']['encoder'] =
  #       #  'SV'.@infos['stream_major_version'].'.'.@infos['stream_minor_version'].', '.@infos['encoder_version'];
  #       #$ThisFileInfo['audio']['encoder'] = @infos['encoder_version'];
  #       #$ThisFileInfo['audio']['encoder_options'] = @infos['profile'];
  when SV4_6_HEADER
    # this is SV4 - SV6, handle seperately
    header_size = 8
  when 'ID3'
    @id3v2_tag = ID3v2.new
    @id3v2_tag.from_io(@file)
    @file.seek(@id3v2_tag.io_position)
    # very dirty hack to allow parsing of mpc infos after id3v2 tag
    while @file.read(1) != 'M'; end
    if @file.read(2) == 'P+'
      @file.seek(-3, IO::SEEK_CUR)
      # we need to reparse the tag, since we have the beggining of the mpc file
      parse_infos
    else
      raise(MpcInfoError, 'cannot find MPC header after id3 tag')
    end
  else
    raise(MpcInfoError, 'cannot find MPC header')
  end
end
peak_db(i) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 179
def peak_db(i)
  ((Math.log10(i) / Math.log10(2)) - 15) * 6
end
read16(io) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 171
def read16(io)
  io.read(2).unpack1('v')
end
read32(io, size = 4) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 175
def read32(io, size = 4)
  io.read(size).unpack1('V')
end
read8(io) click to toggle source
# File lib/audioinfo/mpcinfo.rb, line 167
def read8(io)
  io.read(1)[0].ord
end