Source code for eyed3.mp3.headers

import deprecation
from math import log10

from . import Mp3Exception
from ..utils.binfuncs import bytes2bin, bytes2dec, bin2dec
from ..utils.log import getLogger
from ..__about__ import __version__

log = getLogger(__name__)


[docs]def isValidHeader(header): """Determine if ``header`` (an integer, 4 bytes compared) is a valid mp3 frame header.""" # Test for the mp3 frame sync: 11 set bits. sync = (header >> 16) if sync & 0xffe0 != 0xffe0: # ffe0 is 11 sync bits, 12 are not used in order to support identifying # mpeg v2.5 (bits 20,19) return False # All the remaining tests are not entirely required, but do help in # finding false syncs version = (header >> 19) & 0x3 if version == 1: # This is a "reserved" version log.debug("invalid mpeg version") return False layer = (header >> 17) & 0x3 if layer == 0: # This is a "reserved" layer log.debug("invalid mpeg layer") return False bitrate = (header >> 12) & 0xf if bitrate in (0, 0xf): # free and bad bitrate values log.debug("invalid mpeg bitrate") return False sample_rate = (header >> 10) & 0x3 if sample_rate == 0x3: # this is a "reserved" sample rate log.debug("invalid mpeg sample rate") return False return True
[docs]def findHeader(fp, start_pos=0): """Locate the first mp3 header in file stream ``fp`` starting a offset ``start_pos`` (defaults to 0). Returned is a 3-tuple containing the offset where the header was found, the header as an integer, and the header as 4 bytes. If no header is found header_int will equal 0. """ def isBOM(buffer, pos): """Check for unicode BOM""" try: if pos - 1 >= 0: if buffer[pos - 1] == 254: return True return buffer[pos + 1] == 254 except IndexError: return False def find_sync(_fp, _pos=0): chunk_sz = 8192 # Measured as optimal _fp.seek(_pos) data = _fp.read(chunk_sz) while data: pos = 0 while 0 <= pos < chunk_sz: pos = data.find(b"\xff", pos) if pos == -1: break if not isBOM(data, pos): h = data[pos:pos + 4] if len(h) == 4: return tuple([_pos + pos, h]) pos += 1 _pos += len(data) data = _fp.read(chunk_sz) return None, None sync_pos, header_bytes = find_sync(fp, start_pos) while sync_pos is not None: header = bytes2dec(header_bytes) if isValidHeader(header): return tuple([sync_pos, header, header_bytes]) sync_pos, header_bytes = find_sync(fp, start_pos + sync_pos + 2) return None, None, None
[docs]def timePerFrame(mp3_header, vbr): """Computes the number of seconds per mp3 frame. It can be used to compute overall playtime and bitrate. The mp3 layer and sample rate from ``mp3_header`` are used to compute the number of seconds (fractional float point value) per mp3 frame. Be sure to set ``vbr`` True when dealing with VBR, otherwise playtimes may be incorrect.""" # https://bitbucket.org/nicfit/eyed3/issue/32/mp3audioinfotime_secs-incorrect-for-mpeg2 if mp3_header.version >= 2.0 and vbr: row = _mp3VersionKey(mp3_header.version) else: row = 0 return (float(SAMPLES_PER_FRAME_TABLE[row][mp3_header.layer]) / float(mp3_header.sample_freq))
[docs]@deprecation.deprecated(deprecated_in="0.9a2", removed_in="1.0", current_version=__version__, details="Use timePerFrame instead") def compute_time_per_frame(mp3_header): if mp3_header is not None: return timePerFrame(mp3_header, False)
[docs]class Mp3Header: """Header container for MP3 frames.""" def __init__(self, header_data=None): self.version = None self.layer = None self.error_protection = None self.bit_rate = None self.sample_freq = None self.padding = None self.private_bit = None self.copyright = None self.original = None self.emphasis = None self.mode = None # This value is left as is: 0<=mode_extension<=3. # See http://www.dv.co.yu/mpgscript/mpeghdr.htm for how to interpret self.mode_extension = None self.frame_length = None if header_data: self.decode(header_data) # This may throw an Mp3Exception if the header is malformed.
[docs] def decode(self, header): if not isValidHeader(header): raise Mp3Exception("Invalid MPEG header") # MPEG audio version from bits 19 and 20. version = (header >> 19) & 0x3 self.version = [2.5, None, 2.0, 1.0][version] if self.version is None: raise Mp3Exception("Illegal MPEG version") # MPEG layer self.layer = 4 - ((header >> 17) & 0x3) if self.layer == 4: raise Mp3Exception("Illegal MPEG layer") # Decode some simple values. self.error_protection = not (header >> 16) & 0x1 self.padding = (header >> 9) & 0x1 self.private_bit = (header >> 8) & 0x1 self.copyright = (header >> 3) & 0x1 self.original = (header >> 2) & 0x1 # Obtain sampling frequency. sample_bits = (header >> 10) & 0x3 self.sample_freq = \ SAMPLE_FREQ_TABLE[sample_bits][_mp3VersionKey(self.version)] if not self.sample_freq: raise Mp3Exception("Illegal MPEG sampling frequency") # Compute bitrate. bit_rate_row = (header >> 12) & 0xf if int(self.version) == 1 and self.layer == 1: bit_rate_col = 0 elif int(self.version) == 1 and self.layer == 2: bit_rate_col = 1 elif int(self.version) == 1 and self.layer == 3: bit_rate_col = 2 elif int(self.version) == 2 and self.layer == 1: bit_rate_col = 3 elif int(self.version) == 2 and (self.layer == 2 or self.layer == 3): bit_rate_col = 4 else: raise Mp3Exception("Mp3 version %f and layer %d is an invalid " "combination" % (self.version, self.layer)) self.bit_rate = BIT_RATE_TABLE[bit_rate_row][bit_rate_col] if self.bit_rate is None: raise Mp3Exception("Invalid bit rate") # We know know the bit rate specified in this frame, but if the file # is VBR we need to obtain the average from the Xing header. # This is done by the caller since right now all we have is the frame # header. # Emphasis; whatever that means?? emph = header & 0x3 if emph == 0: self.emphasis = EMPHASIS_NONE elif emph == 1: self.emphasis = EMPHASIS_5015 elif emph == 2: self.emphasis = EMPHASIS_CCIT else: raise Mp3Exception("Illegal mp3 emphasis value: %d" % emph) # Channel mode. mode_bits = (header >> 6) & 0x3 if mode_bits == 0: self.mode = MODE_STEREO elif mode_bits == 1: self.mode = MODE_JOINT_STEREO elif mode_bits == 2: self.mode = MODE_DUAL_CHANNEL_STEREO else: self.mode = MODE_MONO self.mode_extension = (header >> 4) & 0x3 # Layer II has restrictions wrt to mode and bit rate. This code # enforces them. if self.layer == 2: m = self.mode br = self.bit_rate if (br in [32, 48, 56, 80] and (m != MODE_MONO)): raise Mp3Exception("Invalid mode/bitrate combination for layer " "II") if (br in [224, 256, 320, 384] and (m == MODE_MONO)): raise Mp3Exception("Invalid mode/bitrate combination for layer " "II") br = self.bit_rate * 1000 sf = self.sample_freq p = self.padding if self.layer == 1: # Layer 1 uses 32 bit slots for padding. p = self.padding * 4 self.frame_length = int((((12 * br) / sf) + p) * 4) else: # Layer 2 and 3 uses 8 bit slots for padding. p = self.padding * 1 self.frame_length = int(((144 * br) / sf) + p) # Dump the state. log.debug("MPEG audio version: " + str(self.version)) log.debug("MPEG audio layer: " + ("I" * self.layer)) log.debug("MPEG sampling frequency: " + str(self.sample_freq)) log.debug("MPEG bit rate: " + str(self.bit_rate)) log.debug("MPEG channel mode: " + self.mode) log.debug("MPEG channel mode extension: " + str(self.mode_extension)) log.debug("MPEG CRC error protection: " + str(self.error_protection)) log.debug("MPEG original: " + str(self.original)) log.debug("MPEG copyright: " + str(self.copyright)) log.debug("MPEG private bit: " + str(self.private_bit)) log.debug("MPEG padding: " + str(self.padding)) log.debug("MPEG emphasis: " + str(self.emphasis)) log.debug("MPEG frame length: " + str(self.frame_length))
[docs]class VbriHeader(object): def __init__(self): self.vbr = True self.version = None ## # \brief Decode the VBRI info from \a frame. # http://www.codeproject.com/audio/MPEGAudioInfo.asp#VBRIHeader
[docs] def decode(self, frame): # The header is 32 bytes after the end of the first MPEG audio header, # therefore 4 + 32 = 36 offset = 36 head = frame[offset:offset + 4] if head != 'VBRI': return False log.debug("VBRI header detected @ %x" % (offset)) offset += 4 self.version = bin2dec(bytes2bin(frame[offset:offset + 2])) offset += 2 self.delay = bin2dec(bytes2bin(frame[offset:offset + 2])) offset += 2 self.quality = bin2dec(bytes2bin(frame[offset:offset + 2])) offset += 2 self.num_bytes = bin2dec(bytes2bin(frame[offset:offset + 4])) offset += 4 self.num_frames = bin2dec(bytes2bin(frame[offset:offset + 4])) offset += 4 return True
[docs]class XingHeader: """Header class for the Xing header extensions.""" def __init__(self): self.numFrames = int() self.numBytes = int() self.toc = [0] * 100 self.vbrScale = int() # Pass in the first mp3 frame from the file as a byte string. # If an Xing header is present in the file it'll be in the first mp3 # frame. This method returns true if the Xing header is found in the # frame, and false otherwise.
[docs] def decode(self, frame): # mp3 version version = (frame[1] >> 3) & 0x1 # channel mode. mode = (frame[3] >> 6) & 0x3 # Find the start of the Xing header. if version: # +4 in all of these to skip initial mp3 frame header. if mode != 3: pos = 32 + 4 else: pos = 17 + 4 else: if mode != 3: pos = 17 + 4 else: pos = 9 + 4 head = frame[pos:pos + 4] self.vbr = (head == b'Xing') and True or False if head not in [b'Xing', b'Info']: return False log.debug("%s header detected @ %x" % (head, pos)) pos += 4 # Read Xing flags. headFlags = bin2dec(bytes2bin(frame[pos:pos + 4])) pos += 4 log.debug("%s header flags: 0x%x" % (head, headFlags)) # Read frames header flag and value if present if headFlags & FRAMES_FLAG: self.numFrames = bin2dec(bytes2bin(frame[pos:pos + 4])) pos += 4 log.debug("%s numFrames: %d" % (head, self.numFrames)) # Read bytes header flag and value if present if headFlags & BYTES_FLAG: self.numBytes = bin2dec(bytes2bin(frame[pos:pos + 4])) pos += 4 log.debug("%s numBytes: %d" % (head, self.numBytes)) # Read TOC header flag and value if present if headFlags & TOC_FLAG: self.toc = frame[pos:pos + 100] pos += 100 log.debug("%s TOC (100 bytes): PRESENT" % head) else: log.debug("%s TOC (100 bytes): NOT PRESENT" % head) # Read vbr scale header flag and value if present if headFlags & VBR_SCALE_FLAG and head == b'Xing': self.vbrScale = bin2dec(bytes2bin(frame[pos:pos + 4])) pos += 4 log.debug("%s vbrScale: %d" % (head, self.vbrScale)) return True
[docs]class LameHeader(dict): r""" Mp3 Info tag (AKA LAME Tag) Lame (and some other encoders) write a tag containing various bits of info about the options used at encode time. If available, the following are parsed and stored in the LameHeader dict: encoder_version: short encoder version [str] tag_revision: revision number of the tag [int] vbr_method: VBR method used for encoding [str] lowpass_filter: lowpass filter frequency in Hz [int] replaygain: if available, radio and audiofile gain (see below) [dict] encoding_flags: encoding flags used [list] nogap: location of gaps when --nogap was used [list] ath_type: ATH type [int] bitrate: bitrate and type (Constant, Target, Minimum) [tuple] encoder_delay: samples added at the start of the mp3 [int] encoder_padding: samples added at the end of the mp3 [int] noise_shaping: noise shaping method [int] stereo_mode: stereo mode used [str] unwise_settings: whether unwise settings were used [boolean] sample_freq: source sample frequency [str] mp3_gain: mp3 gain adjustment (rarely used) [float] preset: preset used [str] surround_info: surround information [str] music_length: length in bytes of original mp3 [int] music_crc: CRC-16 of the mp3 music data [int] infotag_crc: CRC-16 of the info tag [int] Prior to ~3.90, Lame simply stored the encoder version in the first frame. If the infotag_crc is invalid, then we try to read this version string. A simple way to tell if the LAME Tag is complete is to check for the infotag_crc key. Replay Gain data is only available since Lame version 3.94b. If set, the replaygain dict has the following structure: \code peak_amplitude: peak signal amplitude [float] radio: name: name of the gain adjustment [str] adjustment: gain adjustment [float] originator: originator of the gain adjustment [str] audiofile: [same as radio] \endcode Note that as of 3.95.1, Lame uses 89dB as a reference level instead of the 83dB that is specified in the Replay Gain spec. This is not automatically compensated for. You can do something like this if you want: \code import eyeD3 af = eyeD3.mp3.Mp3AudioFile('/path/to/some.mp3') lamever = af.lameTag['encoder_version'] name, ver = lamever[:4], lamever[4:] gain = af.lameTag['replaygain']['radio']['adjustment'] if name == 'LAME' and eyeD3.mp3.lamevercmp(ver, '3.95') > 0: gain -= 6 \endcode Radio and Audiofile Replay Gain are often referrered to as Track and Album gain, respectively. See http://replaygain.hydrogenaudio.org/ for futher details on Replay Gain. See http://gabriel.mp3-tech.org/mp3infotag.html for the gory details of the LAME Tag. """ # from the LAME source: # http://lame.cvs.sourceforge.net/*checkout*/lame/lame/libmp3lame/VbrTag.c _crc16_table = [ 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040] ENCODER_FLAGS = { 'NSPSYTUNE': 0x0001, 'NSSAFEJOINT': 0x0002, 'NOGAP_NEXT': 0x0004, 'NOGAP_PREV': 0x0008} PRESETS = { 0: 'Unknown', # 8 to 320 are reserved for ABR bitrates 410: 'V9', 420: 'V8', 430: 'V7', 440: 'V6', 450: 'V5', 460: 'V4', 470: 'V3', 480: 'V2', 490: 'V1', 500: 'V0', 1000: 'r3mix', 1001: 'standard', 1002: 'extreme', 1003: 'insane', 1004: 'standard/fast', 1005: 'extreme/fast', 1006: 'medium', 1007: 'medium/fast'} REPLAYGAIN_NAME = { 0: 'Not set', 1: 'Radio', 2: 'Audiofile'} REPLAYGAIN_ORIGINATOR = { 0: 'Not set', 1: 'Set by artist', 2: 'Set by user', 3: 'Set automatically', 100: 'Set by simple RMS average'} SAMPLE_FREQUENCIES = { 0: '<= 32 kHz', 1: '44.1 kHz', 2: '48 kHz', 3: '> 48 kHz'} STEREO_MODES = { 0: 'Mono', 1: 'Stereo', 2: 'Dual', 3: 'Joint', 4: 'Force', 5: 'Auto', 6: 'Intensity', 7: 'Undefined'} SURROUND_INFO = { 0: 'None', 1: 'DPL encoding', 2: 'DPL2 encoding', 3: 'Ambisonic encoding', 8: 'Reserved'} VBR_METHODS = { 0: 'Unknown', 1: 'Constant Bitrate', 2: 'Average Bitrate', 3: 'Variable Bitrate method1 (old/rh)', 4: 'Variable Bitrate method2 (mtrh)', 5: 'Variable Bitrate method3 (mt)', 6: 'Variable Bitrate method4', 8: 'Constant Bitrate (2 pass)', 9: 'Average Bitrate (2 pass)', 15: 'Reserved'} def __init__(self, frame): """Read the LAME info tag. frame should be the first frame of an mp3. """ super().__init__() self.decode(frame) def _crc16(self, data, val=0): """Compute a CRC-16 checksum on a data stream.""" for c in [bytes([b]) for b in data]: val = self._crc16_table[ord(c) ^ (val & 0xff)] ^ (val >> 8) return val
[docs] def decode(self, frame): """Decode the LAME info tag.""" try: pos = frame.index(b"LAME") except: # noqa: B901 return log.debug('Lame info tag found at position %d' % pos) # check the info tag crc.Iif it's not valid, no point parsing much more. lamecrc = bin2dec(bytes2bin(frame[190:192])) if self._crc16(frame[:190]) != lamecrc: log.warning('Lame tag CRC check failed') try: # Encoder short VersionString, 9 bytes self['encoder_version'] = str(frame[pos:pos + 9].rstrip(), "latin1") log.debug('Lame Encoder Version: %s' % self['encoder_version']) pos += 9 # Info Tag revision + VBR method, 1 byte self['tag_revision'] = bin2dec(bytes2bin(frame[pos:pos + 1])[:5]) vbr_method = bin2dec(bytes2bin(frame[pos:pos + 1])[5:]) self['vbr_method'] = self.VBR_METHODS.get(vbr_method, 'Unknown') log.debug('Lame info tag version: %s' % self['tag_revision']) log.debug('Lame VBR method: %s' % self['vbr_method']) pos += 1 # Lowpass filter value, 1 byte self['lowpass_filter'] = bin2dec( bytes2bin(frame[pos:pos + 1])) * 100 log.debug('Lame Lowpass filter value: %s Hz' % self['lowpass_filter']) pos += 1 # Replay Gain, 8 bytes total replaygain = {} # Peak signal amplitude, 4 bytes peak = bin2dec(bytes2bin(frame[pos:pos + 4])) << 5 if peak > 0: peak /= float(1 << 28) db = 20 * log10(peak) replaygain['peak_amplitude'] = peak log.debug('Lame Peak signal amplitude: %.8f (%+.1f dB)' % (peak, db)) pos += 4 # Radio and Audiofile Gain, AKA track and album, 2 bytes each for gaintype in ['radio', 'audiofile']: name = bin2dec(bytes2bin(frame[pos:pos + 2])[:3]) orig = bin2dec(bytes2bin(frame[pos:pos + 2])[3:6]) sign = bin2dec(bytes2bin(frame[pos:pos + 2])[6:7]) adj = bin2dec(bytes2bin(frame[pos:pos + 2])[7:]) / 10.0 if sign: adj *= -1 # Lame 3.95.1 and above use 89dB as a reference instead of 83dB as defined by the # Replay Gain spec. This will be compensated for with `adj -= 6` lamever = self['encoder_version'] if lamever[:4] == 'LAME' and lamevercmp(lamever[4:], "3.95") > 0: adj -= 6 if orig: name = self.REPLAYGAIN_NAME.get(name, 'Unknown') orig = self.REPLAYGAIN_ORIGINATOR.get(orig, 'Unknown') replaygain[gaintype] = {'name': name, 'adjustment': adj, 'originator': orig} log.debug('Lame %s Replay Gain: %s dB (%s)' % (name, adj, orig)) pos += 2 if replaygain: self['replaygain'] = replaygain # Encoding flags + ATH Type, 1 byte encflags = bin2dec(bytes2bin(frame[pos:pos + 1])[:4]) (self['encoding_flags'], self['nogap']) = self._parse_encflags(encflags) self['ath_type'] = bin2dec(bytes2bin(frame[pos:pos + 1])[4:]) log.debug('Lame Encoding flags: %s' % ' '.join(self['encoding_flags'])) if self['nogap']: log.debug('Lame No gap: %s' % ' and '.join(self['nogap'])) log.debug('Lame ATH type: %s' % self['ath_type']) pos += 1 # if ABR {specified bitrate} else {minimal bitrate}, 1 byte btype = 'Constant' if 'Average' in self['vbr_method']: btype = 'Target' elif 'Variable' in self['vbr_method']: btype = 'Minimum' # bitrate may be modified below after preset is read self['bitrate'] = (bin2dec(bytes2bin(frame[pos:pos + 1])), btype) log.debug('Lame Bitrate (%s): %s' % (btype, self['bitrate'][0])) pos += 1 # Encoder delays, 3 bytes self['encoder_delay'] = bin2dec(bytes2bin(frame[pos:pos + 3])[:12]) self['encoder_padding'] = bin2dec( bytes2bin(frame[pos:pos + 3])[12:]) log.debug('Lame Encoder delay: %s samples' % self['encoder_delay']) log.debug('Lame Encoder padding: %s samples' % self['encoder_padding']) pos += 3 # Misc, 1 byte sample_freq = bin2dec(bytes2bin(frame[pos:pos + 1])[:2]) unwise_settings = bin2dec(bytes2bin(frame[pos:pos + 1])[2:3]) stereo_mode = bin2dec(bytes2bin(frame[pos:pos + 1])[3:6]) self['noise_shaping'] = bin2dec(bytes2bin(frame[pos:pos + 1])[6:]) self['sample_freq'] = self.SAMPLE_FREQUENCIES.get(sample_freq, 'Unknown') self['unwise_settings'] = bool(unwise_settings) self['stereo_mode'] = self.STEREO_MODES.get(stereo_mode, 'Unknown') log.debug('Lame Source Sample Frequency: %s' % self['sample_freq']) log.debug('Lame Unwise settings used: %s' % self['unwise_settings']) log.debug('Lame Stereo mode: %s' % self['stereo_mode']) log.debug('Lame Noise Shaping: %s' % self['noise_shaping']) pos += 1 # MP3 Gain, 1 byte sign = bytes2bin(frame[pos:pos + 1])[0] gain = bin2dec(bytes2bin(frame[pos:pos + 1])[1:]) if sign: gain *= -1 self['mp3_gain'] = gain db = gain * 1.5 log.debug('Lame MP3 Gain: %s (%+.1f dB)' % (self['mp3_gain'], db)) pos += 1 # Preset and surround info, 2 bytes surround = bin2dec(bytes2bin(frame[pos:pos + 2])[2:5]) preset = bin2dec(bytes2bin(frame[pos:pos + 2])[5:]) if preset in range(8, 321): if self['bitrate'][0] >= 255: # the value from preset is better in this case self['bitrate'] = (preset, btype) log.debug('Lame Bitrate (%s): %s' % (btype, self['bitrate'][0])) if 'Average' in self['vbr_method']: preset = 'ABR %s' % preset else: preset = 'CBR %s' % preset else: preset = self.PRESETS.get(preset, preset) self['surround_info'] = self.SURROUND_INFO.get(surround, surround) self['preset'] = preset log.debug('Lame Surround Info: %s' % self['surround_info']) log.debug('Lame Preset: %s' % self['preset']) pos += 2 # MusicLength, 4 bytes self['music_length'] = bin2dec(bytes2bin(frame[pos:pos + 4])) log.debug('Lame Music Length: %s bytes' % self['music_length']) pos += 4 # MusicCRC, 2 bytes self['music_crc'] = bin2dec(bytes2bin(frame[pos:pos + 2])) log.debug('Lame Music CRC: %04X' % self['music_crc']) pos += 2 # CRC-16 of Info Tag, 2 bytes self['infotag_crc'] = lamecrc # we read this earlier log.debug('Lame Info Tag CRC: %04X' % self['infotag_crc']) pos += 2 except IndexError: log.warning("Truncated LAME info header, values incomplete.")
def _parse_encflags(self, flags): """Parse encoder flags. Returns a tuple containing lists of encoder flags and nogap data in human readable format. """ encoder_flags, nogap = [], [] if not flags: return encoder_flags, nogap if flags & self.ENCODER_FLAGS['NSPSYTUNE']: encoder_flags.append('--nspsytune') if flags & self.ENCODER_FLAGS['NSSAFEJOINT']: encoder_flags.append('--nssafejoint') NEXT = self.ENCODER_FLAGS['NOGAP_NEXT'] PREV = self.ENCODER_FLAGS['NOGAP_PREV'] if flags & (NEXT | PREV): encoder_flags.append('--nogap') if flags & PREV: nogap.append('before') if flags & NEXT: nogap.append('after') return encoder_flags, nogap
[docs]def lamevercmp(x, y): """Compare LAME version strings. alpha and beta versions are considered older. Versions with sub minor parts or end with 'r' are considered newer. :param x: The first version to compare. :param y: The second version to compare. :returns: Return negative if x<y, zero if x==y, positive if x>y. """ def cmp(a, b): # This is Python2's built-in `cmp`, which was removed from Python3 # And depends on bool - bool yielding the integer -1, 0, 1 return (a > b) - (a < b) x = x.ljust(5) y = y.ljust(5) if x[:5] == y[:5]: return 0 ret = cmp(x[:4], y[:4]) if ret: return ret xmaj, xmin = x.split('.')[:2] ymaj, ymin = y.split('.')[:2] minparts = ['.'] # lame 3.96.1 added the use of r in the very short version for post releases if (xmaj == '3' and xmin >= '96') or (ymaj == '3' and ymin >= '96'): minparts.append('r') if x[4] in minparts: return 1 if y[4] in minparts: return -1 if x[4] == ' ': return 1 if y[4] == ' ': return -1 return cmp(x[4], y[4])
# MPEG1 MPEG2 MPEG2.5 SAMPLE_FREQ_TABLE = ((44100, 22050, 11025), (48000, 24000, 12000), (32000, 16000, 8000), (None, None, None)) # V1/L1 V1/L2 V1/L3 V2/L1 V2/L2&L3 BIT_RATE_TABLE = ((0, 0, 0, 0, 0), # noqa (32, 32, 32, 32, 8), # noqa (64, 48, 40, 48, 16), # noqa (96, 56, 48, 56, 24), # noqa (128, 64, 56, 64, 32), # noqa (160, 80, 64, 80, 40), # noqa (192, 96, 80, 96, 48), # noqa (224, 112, 96, 112, 56), # noqa (256, 128, 112, 128, 64), # noqa (288, 160, 128, 144, 80), # noqa (320, 192, 160, 160, 96), # noqa (352, 224, 192, 176, 112), # noqa (384, 256, 224, 192, 128), # noqa (416, 320, 256, 224, 144), # noqa (448, 384, 320, 256, 160), # noqa (None, None, None, None, None)) # Rows 1 and 2 (mpeg 2.x) are only used for those versions *and* VBR. # L1 L2 L3 SAMPLES_PER_FRAME_TABLE = ((None, 384, 1152, 1152), # MPEG 1 (None, 384, 1152, 576), # MPEG 2 (None, 384, 1152, 576), # MPEG 2.5 ) # Emphasis constants EMPHASIS_NONE = "None" EMPHASIS_5015 = "50/15 ms" EMPHASIS_CCIT = "CCIT J.17" # Mode constants MODE_STEREO = "Stereo" MODE_JOINT_STEREO = "Joint stereo" MODE_DUAL_CHANNEL_STEREO = "Dual channel stereo" MODE_MONO = "Mono" # Xing flag bits FRAMES_FLAG = 0x0001 BYTES_FLAG = 0x0002 TOC_FLAG = 0x0004 VBR_SCALE_FLAG = 0x0008 def _mp3VersionKey(version): """Map mp3 version float to a data structure index. 1 -> 0, 2 -> 1, 2.5 -> 2 """ key = None if version == 2.5: key = 2 else: key = int(version - 1) assert(0 <= key <= 2) return key