def parse(cls, data): stream_info_block_data = bitstruct.unpack( 'u16 u16 u24 u24 u20 u3 u5 u36 r128', data.read(34), ) min_block_size = stream_info_block_data[0] max_block_size = stream_info_block_data[1] min_frame_size = stream_info_block_data[2] max_frame_size = stream_info_block_data[3] sample_rate = stream_info_block_data[4] channels = stream_info_block_data[5] + 1 bit_depth = stream_info_block_data[6] + 1 total_samples = stream_info_block_data[7] md5sum = binascii.hexlify(stream_info_block_data[8]).decode( 'ascii', 'replace') duration = total_samples / sample_rate return cls( start=None, size=None, min_block_size=min_block_size, max_block_size=max_block_size, min_frame_size=min_frame_size, max_frame_size=max_frame_size, bit_depth=bit_depth, bitrate=None, channels=channels, duration=duration, md5=md5sum, sample_rate=sample_rate, )
def find_mpeg_frames(data): frames = [] cached_frames = None buffer_size = 128 buffer = data.peek(buffer_size) while len(buffer) >= buffer_size: sync_start = buffer.find(b'\xFF') if sync_start >= 0: data.seek(sync_start, os.SEEK_CUR) if bitstruct.unpack('u11', data.peek(2))[0] == 2047: for _ in range(4): try: frame = MPEGFrameHeader.parse(data) frames.append(frame) if frame._xing and frame._xing.num_frames: break data.seek(frame._start + frame._size, os.SEEK_SET) except (FormatError, *bitstruct.Error): data.seek(1, os.SEEK_CUR) break else: data.seek(sync_start + 1, os.SEEK_CUR) if frames and (len(frames) >= 4 or frames[0]._xing): break if len(frames) >= 2 and cached_frames is None: cached_frames = frames.copy() del frames[:] else: data.seek(buffer_size, os.SEEK_CUR) buffer = data.peek(buffer_size) # I actually found a PNG file that had multiple consecutive MPEG frames parsed. # The all_equal check combats this false positive by # making sure certain attributes don't change between frames. if not frames: if ( cached_frames and more_itertools.all_equal( [ frame.channel_mode, frame.channels, frame.layer, frame.sample_rate, frame.version, ] for frame in cached_frames ) ): frames = cached_frames else: raise FormatError("No XING header and insufficient MPEG frames.") return frames
def parse(cls, data): catalog_number = data.read(128).rstrip(b'\0').decode( 'ascii', 'replace') lead_in_samples = struct.unpack( '>Q', data.read(8), )[0] compact_disc = bitstruct.unpack( 'b1', data.read(1), )[0] data.read(258) num_tracks = struct.unpack( 'B', data.read(1), )[0] tracks = [] for _ in range(num_tracks): tracks.append(FLACCueSheetTrack.parse(data)) return cls( tracks, catalog_number=catalog_number, lead_in_samples=lead_in_samples, compact_disc=compact_disc, )
def parse(cls, data): offset = struct.unpack( '>Q', data.read(8), )[0] track_number = struct.unpack( '>B', data.read(1), )[0] isrc = data.read(12).rstrip(b'\x00').decode('ascii', 'replace') type_, pre_emphasis = bitstruct.unpack( 'u1 b1', data.read(1), ) data.read(13) num_indexes = struct.unpack( '>B', data.read(1), )[0] indexes = [] for _ in range(num_indexes): indexes.append(FLACCueSheetIndex.parse(data)) return cls( track_number=track_number, offset=offset, isrc=isrc, type=type_, pre_emphasis=pre_emphasis, indexes=indexes, )
def parse(cls, data): peak_data = struct.unpack('>I', data.read(4))[0] if peak_data == 0: gain_peak = None else: gain_peak = peak_data / 2 ** 23 track_gain_type_, track_gain_origin_, track_gain_sign, track_gain_adjustment_ = bitstruct.unpack( 'u3 u3 b1 u9', data.read(2), ) track_gain_type = LAMEReplayGainType(track_gain_type_) track_gain_origin = LAMEReplayGainOrigin(track_gain_origin_) track_gain_adjustment = track_gain_adjustment_ / 10.0 if track_gain_sign: track_gain_adjustment *= -1 album_gain_type_, album_gain_origin_, album_gain_sign, album_gain_adjustment_ = bitstruct.unpack( 'u3 u3 b1 u9', data.read(2), ) album_gain_type = LAMEReplayGainType(album_gain_type_) album_gain_origin = LAMEReplayGainOrigin(album_gain_origin_) album_gain_adjustment = album_gain_adjustment_ / 10.0 if album_gain_sign: album_gain_adjustment *= -1 return cls( peak=gain_peak, track_type=track_gain_type, track_origin=track_gain_origin, track_adjustment=track_gain_adjustment, album_type=album_gain_type, album_origin=album_gain_origin, album_adjustment=album_gain_adjustment, )
def _parse_metadata_block(data): is_last_block, block_type, block_size = bitstruct.unpack( 'b1 u7 u24', data.read(4), ) if block_size == 0: raise FormatError( "FLAC metadata block size must be greater than 0.") # There are examples of tools writing incorrect block sizes. # The FLAC reference implementation unintentionally (I hope?) parses them. # I've chosen not to add special handling for these invalid files. # If needed, mutagen (https://github.com/quodlibet/mutagen) may support them. metadata_block_data = data.read(block_size) if block_type == FLACMetadataBlockType.STREAMINFO: metadata_block = FLACStreamInfo.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.PADDING: metadata_block = FLACPadding.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.APPLICATION: metadata_block = FLACApplication.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.SEEKTABLE: metadata_block = FLACSeekTable.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.VORBIS_COMMENT: metadata_block = FLACVorbisComments.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.CUESHEET: metadata_block = FLACCueSheet.parse(metadata_block_data) elif block_type == FLACMetadataBlockType.PICTURE: metadata_block = FLACPicture.parse(metadata_block_data) elif block_type >= 127: raise FormatError( f"{block_type} is not a valid FLAC metadata block type.") else: metadata_block = FLACMetadataBlock( type=block_type, data=metadata_block_data, ) return metadata_block, is_last_block
def parse(cls, data): frame_start = data.tell() sync, version_id, layer_index, protection = bitstruct.unpack( 'u11 u2 u2 b1', data.read(2), ) if sync != 2047: raise FormatError("Invalid MPEG frame sync.") version = [2.5, None, 2, 1][version_id] layer = 4 - layer_index protected = not protection bitrate_index, sample_rate_index, padded = bitstruct.unpack( 'u4 u2 b1', data.read(1), ) if ( version_id == 1 or layer_index == 0 or bitrate_index == 0 or bitrate_index == 15 or sample_rate_index == 3 ): raise FormatError("Invalid MPEG audio frame.") channel_mode = MP3ChannelMode(bitstruct.unpack('u2', data.read(1))[0]) channels = 1 if channel_mode == 3 else 2 bitrate = MP3Bitrates[(version, layer)][bitrate_index] * 1000 sample_rate = MP3SampleRates[version][sample_rate_index] samples_per_frame, slot_size = MP3SamplesPerFrame[(version, layer)] frame_size = (((samples_per_frame // 8 * bitrate) // sample_rate) + padded) * slot_size vbri_header = None xing_header = None if layer == 3: # pragma: nobranch if version == 1: if channel_mode != 3: xing_header_start = 36 else: xing_header_start = 21 elif channel_mode != 3: xing_header_start = 21 else: xing_header_start = 13 data.seek(frame_start + xing_header_start, os.SEEK_SET) if data.peek(4) in [b'Xing', b'Info']: xing_header = XingHeader.parse(data.read(frame_size)) data.seek(frame_start + 36, os.SEEK_SET) if data.peek(4) == b'VBRI': vbri_header = VBRIHeader.parse(data) return cls( start=frame_start, size=frame_size, vbri=vbri_header, xing=xing_header, version=version, layer=layer, protected=protected, padded=padded, bitrate=bitrate, channel_mode=channel_mode, channels=channels, sample_rate=sample_rate, )
def parse(cls, data, xing_quality): encoder = data.read(9) if not encoder.startswith(b'LAME'): raise FormatError("Valid LAME header not found.") version = None version_match = re.search(rb'LAME(\d+)\.(\d+)', encoder) if version_match: # pragma: nobranch version = tuple(int(part) for part in version_match.groups()) revision, bitrate_mode_ = bitstruct.unpack( 'u4 u4', data.read(1), ) bitrate_mode = LAMEBitrateMode(bitrate_mode_) # TODO: Decide what, if anything, to do with the different meanings in LAME. # quality = (100 - xing_quality) % 10 # vbr_quality = (100 - xing_quality) // 10 lowpass_filter = struct.unpack( 'B', data.read(1), )[0] * 100 replay_gain = LAMEReplayGain.parse(data) flags_ath = bitstruct.unpack_dict( 'b1 b1 b1 b1 u4', [ 'nogap_continuation', 'nogap_continued', 'nssafejoint', 'nspsytune', 'ath_type', ], data.read(1), ) ath_type = flags_ath.pop('ath_type') encoding_flags = LAMEEncodingFlags(**flags_ath) # TODO: Different representation for VBR minimum bitrate vs CBR/ABR specified bitrate? # Can only go up to 255. bitrate = struct.unpack( 'B', data.read(1), )[0] * 1000 delay, padding = bitstruct.unpack( 'u12 u12', data.read(3), ) source_sample_rate, unwise_settings_used, channel_mode_, noise_shaping = bitstruct.unpack( 'u2 b1 u3 u2', data.read(1), ) channel_mode = LAMEChannelMode(channel_mode_) mp3_gain = bitstruct.unpack( 's8', data.read(1), )[0] surround_info_, preset_used_ = bitstruct.unpack( 'p2 u3 u11', data.read(2), ) surround_info = LAMESurroundInfo(surround_info_) preset = LAMEPreset(preset_used_) audio_size, audio_crc, lame_crc = struct.unpack( '>I2s2s', data.read(8), ) return cls( crc=lame_crc, version=version, revision=revision, ath_type=ath_type, audio_crc=audio_crc, audio_size=audio_size, bitrate=bitrate, bitrate_mode=bitrate_mode, channel_mode=channel_mode, delay=delay, encoding_flags=encoding_flags, lowpass_filter=lowpass_filter, mp3_gain=mp3_gain, noise_shaping=noise_shaping, padding=padding, preset=preset, replay_gain=replay_gain, source_sample_rate=source_sample_rate, surround_info=surround_info, unwise_settings_used=unwise_settings_used, )