def __init__(self, atoms, fileobj): hdlr = atoms["moov.trak.mdia.hdlr"] fileobj.seek(hdlr.offset) if b"soun" not in fileobj.read(hdlr.length): raise M4AStreamInfoError("track has no audio data") mdhd = atoms["moov.trak.mdia.mdhd"] fileobj.seek(mdhd.offset) data = bytearray(fileobj.read(mdhd.length)) if data[8] == 0: offset = 20 fmt = ">2I" else: offset = 28 fmt = ">IQ" end = offset + struct_calcsize(fmt) unit, length = struct_unpack(fmt, data[offset:end]) self.length = float(length) / unit try: atom = atoms["moov.trak.mdia.minf.stbl.stsd"] fileobj.seek(atom.offset) data = fileobj.read(atom.length) self.bitrate = cdata.uint_be(data[-17:-13]) except (ValueError, KeyError): # Bitrate values are optional. pass
def __init__(self, atoms, fileobj): for trak in list(atoms["moov"].findall(b"trak")): hdlr = trak["mdia", "hdlr"] fileobj.seek(hdlr.offset) data = fileobj.read(hdlr.length) if data[16:20] == b"soun": break else: raise MP4StreamInfoError("track has no audio data") mdhd = trak["mdia", "mdhd"] fileobj.seek(mdhd.offset) data = bytearray(fileobj.read(mdhd.length)) if data[8] == 0: offset = 20 fmt = ">2I" else: offset = 28 fmt = ">IQ" end = offset + struct_calcsize(fmt) unit, length = struct_unpack(fmt, data[offset:end]) self.length = float(length) / unit try: atom = trak["mdia", "minf", "stbl", "stsd"] fileobj.seek(atom.offset) data = bytearray(fileobj.read(atom.length)) if data[20:24] == b"mp4a": length = cdata.uint_be(data[16:20]) (self.channels, self.bits_per_sample, _, self.sample_rate) = struct_unpack(">3HI", data[40:50]) # ES descriptor type if data[56:60] == b"esds" and ord(data[64:65]) == 0x03: pos = 65 # skip extended descriptor type tag, length, ES ID # and stream priority if data[pos:pos+3] == b"\x80\x80\x80": pos += 3 pos += 4 # decoder config descriptor type if data[pos] == 0x04: pos += 1 # skip extended descriptor type tag, length, # object type ID, stream type, buffer size # and maximum bitrate if data[pos:pos+3] == b"\x80\x80\x80": pos += 3 pos += 10 # average bitrate self.bitrate = cdata.uint_be(data[pos:pos+4]) except (ValueError, KeyError): # stsd atoms are optional pass
class SeekTable(MetadataBlock): """Read and write FLAC seek tables. Attributes: seekpoints -- list of SeekPoint objects """ __SEEKPOINT_FORMAT = '>QQH' __SEEKPOINT_SIZE = struct_calcsize(__SEEKPOINT_FORMAT) code = 3 def __init__(self, data): self.seekpoints = [] super(SeekTable, self).__init__(data) def __eq__(self, other): try: return (self.seekpoints == other.seekpoints) except (AttributeError, TypeError): return False __hash__ = MetadataBlock.__hash__ def load(self, data): self.seekpoints = [] sp = data.read(self.__SEEKPOINT_SIZE) while len(sp) == self.__SEEKPOINT_SIZE: self.seekpoints.append( SeekPoint(*struct_unpack(self.__SEEKPOINT_FORMAT, sp))) sp = data.read(self.__SEEKPOINT_SIZE) def write(self): f = BytesIO() for seekpoint in self.seekpoints: packed = struct_pack(self.__SEEKPOINT_FORMAT, seekpoint.first_sample, seekpoint.byte_offset, seekpoint.num_samples) f.write(packed) return f.getvalue() def __repr__(self): return "<%s seekpoints=%r>" % (type(self).__name__, self.seekpoints)
class CueSheet(MetadataBlock): """Read and write FLAC embedded cue sheets. Number of tracks should be from 1 to 100. There should always be exactly one lead-out track and that track must be the last track in the cue sheet. Attributes: media_catalog_number -- media catalog number in ASCII lead_in_samples -- number of lead-in samples compact_disc -- true if the cuesheet corresponds to a compact disc tracks -- list of CueSheetTrack objects lead_out -- lead-out as CueSheetTrack or None if lead-out was not found """ __CUESHEET_FORMAT = '>128sQB258xB' __CUESHEET_SIZE = struct_calcsize(__CUESHEET_FORMAT) __CUESHEET_TRACK_FORMAT = '>QB12sB13xB' __CUESHEET_TRACK_SIZE = struct_calcsize(__CUESHEET_TRACK_FORMAT) __CUESHEET_TRACKINDEX_FORMAT = '>QB3x' __CUESHEET_TRACKINDEX_SIZE = struct_calcsize(__CUESHEET_TRACKINDEX_FORMAT) code = 5 media_catalog_number = b'' lead_in_samples = 88200 compact_disc = True def __init__(self, data): self.tracks = [] super(CueSheet, self).__init__(data) def __eq__(self, other): try: return (self.media_catalog_number == other.media_catalog_number and self.lead_in_samples == other.lead_in_samples and self.compact_disc == other.compact_disc and self.tracks == other.tracks) except (AttributeError, TypeError): return False __hash__ = MetadataBlock.__hash__ def load(self, data): header = data.read(self.__CUESHEET_SIZE) media_catalog_number, lead_in_samples, flags, num_tracks = \ struct_unpack(self.__CUESHEET_FORMAT, header) self.media_catalog_number = media_catalog_number.rstrip(b'\0') self.lead_in_samples = lead_in_samples self.compact_disc = bool(flags & 0x80) self.tracks = [] for i in range(num_tracks): track = data.read(self.__CUESHEET_TRACK_SIZE) start_offset, track_number, isrc_padded, flags, num_indexes = \ struct_unpack(self.__CUESHEET_TRACK_FORMAT, track) isrc = isrc_padded.rstrip(b'\0') type_ = (flags & 0x80) >> 7 pre_emphasis = bool(flags & 0x40) val = CueSheetTrack(track_number, start_offset, isrc, type_, pre_emphasis) for j in range(num_indexes): index = data.read(self.__CUESHEET_TRACKINDEX_SIZE) index_offset, index_number = struct_unpack( self.__CUESHEET_TRACKINDEX_FORMAT, index) val.indexes.append( CueSheetTrackIndex(index_number, index_offset)) self.tracks.append(val) def write(self): f = BytesIO() flags = 0 if self.compact_disc: flags |= 0x80 packed = struct_pack(self.__CUESHEET_FORMAT, self.media_catalog_number, self.lead_in_samples, flags, len(self.tracks)) f.write(packed) for track in self.tracks: track_flags = 0 track_flags |= (track.type & 1) << 7 if track.pre_emphasis: track_flags |= 0x40 track_packed = struct_pack(self.__CUESHEET_TRACK_FORMAT, track.start_offset, track.track_number, track.isrc, track_flags, len(track.indexes)) f.write(track_packed) for index in track.indexes: index_packed = struct_pack(self.__CUESHEET_TRACKINDEX_FORMAT, index.index_offset, index.index_number) f.write(index_packed) return f.getvalue() def __repr__(self): return ("<%s media_catalog_number=%r, lead_in=%r, compact_disc=%r, " "tracks=%r>") % ( type(self).__name__, self.media_catalog_number, self.lead_in_samples, self.compact_disc, self.tracks)