def __render_freeform(self, key, value): if isinstance(value, text_type): value = value.encode('utf-8') dummy, mean, name = key.split(b":", 2) mean = struct_pack(">I4sI", len(mean) + 12, b"mean", 0) + mean name = struct_pack(">I4sI", len(name) + 12, b"name", 0) + name value = struct_pack(">I4s2I", len(value) + 16, b"data", 0x1, 0) + value return Atom.render("----", mean + name + value)
def render(self, asf): def render_text(name): value = asf.tags.get(name, []) if value: return value[0].encode("utf-16-le") + b"\x00\x00" else: return b"" texts = list(map(render_text, _standard_attribute_names)) data = struct_pack("<HHHHH", *map(len, texts)) + b"".join(texts) return self.GUID + struct_pack("<Q", 24 + len(data)) + data
def render(name, data): """Render raw atom data.""" # this raises OverflowError if Py_ssize_t can't handle the atom data size = len(data) + 8 if isinstance(name, text_type): name = name.encode('UTF-8') if size <= 0xFFFFFFFF: return struct_pack(">I4s", size, name) + data else: return struct_pack(">I4sQ", 1, name, size + 8) + data
def __render_freeform(self, key, value): if isinstance(key, text_type): key = key.encode() dummy, mean, name = key.split(b":", 2) mean = struct_pack(">I4sI", len(mean) + 12, b"mean", 0) + mean name = struct_pack(">I4sI", len(name) + 12, b"name", 0) + name if isinstance(value, byte_types): value = [value] return Atom.render(b"----", mean + name + bytearray().join([ struct_pack(">I4s2I", len(data) + 16, b"data", 1, 0) + data for data in value]))
def write(self): f = BytesIO() mime = self.mime.encode('UTF-8') f.write(struct_pack('>2I', self.type, len(mime))) f.write(mime) desc = self.desc.encode('UTF-8') f.write(struct_pack('>I', len(desc))) f.write(desc) f.write(struct_pack('>5I', self.width, self.height, self.depth, self.colors, len(self.data))) f.write(self.data) return f.getvalue()
def write(self): f = BytesIO() mime = self.mime.encode('UTF-8') f.write(struct_pack('>2I', self.type, len(mime))) f.write(mime) desc = self.desc.encode('UTF-8') f.write(struct_pack('>I', len(desc))) f.write(desc) f.write( struct_pack('>5I', self.width, self.height, self.depth, self.colors, len(self.data))) f.write(self.data) return f.getvalue()
def write(self): """Return a string encoding of the page header and data. A ValueError is raised if the data is too big to fit in a single page. """ data = [ struct_pack("<4sBBqIIi", b"OggS", self.version, self.__type_flags, self.position, self.serial, self.sequence, 0) ] lacing_data = [] for datum in self.packets: quot, rem = divmod(len(datum), 255) lacing_data.append(b"\xff" * quot + bytearray([rem])) lacing_data = bytearray().join(lacing_data) if not self.complete and lacing_data.endswith(b"\x00"): lacing_data = lacing_data[:-1] data.append(bytearray([len(lacing_data)])) data.append(lacing_data) data.extend(self.packets) data = bytearray().join(data) # Python's CRC is swapped relative to Ogg's needs. crc = zlib.crc32(buffer(data.translate(cdata.bitswap)), -1) & 0xffffffff crc = ((crc & 0x80000000) <<1) - crc - 1 # Although we're using to_int_be, this actually makes the CRC # a proper le integer, since Python's CRC is byteswapped. crc = cdata.to_int_be(crc).translate(cdata.bitswap) data = data[:22] + crc + data[26:] return data
def _inject(self, fileobj): """Write tag data into the FLAC Vorbis comment packet/page.""" # Ogg FLAC has no convenient data marker like Vorbis, but the # second packet - and second page - must be the comment data. fileobj.seek(0) page = OggPage(fileobj) while not page.packets[0].startswith(b"\x7FFLAC"): page = OggPage(fileobj) first_page = page while not (page.sequence == 1 and page.serial == first_page.serial): page = OggPage(fileobj) old_pages = [page] while not (old_pages[-1].complete or len(old_pages[-1].packets) > 1): page = OggPage(fileobj) if page.serial == first_page.serial: old_pages.append(page) packets = OggPage.to_packets(old_pages, strict=False) # Set the new comment block. data = self.write() data = bytearray([packets[0][0]]) + struct_pack(">I", len(data))[-3:] + data packets[0] = data new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def write(self): """Return a string encoding of the page header and data. A ValueError is raised if the data is too big to fit in a single page. """ data = [ struct_pack("<4sBBqIIi", b"OggS", self.version, self.__type_flags, self.position, self.serial, self.sequence, 0) ] lacing_data = [] for datum in self.packets: quot, rem = divmod(len(datum), 255) lacing_data.append(b"\xff" * quot + bytearray([rem])) lacing_data = bytearray().join(lacing_data) if not self.complete and lacing_data.endswith(b"\x00"): lacing_data = lacing_data[:-1] data.append(bytearray([len(lacing_data)])) data.append(lacing_data) data.extend(self.packets) data = bytearray().join(data) # Python's CRC is swapped relative to Ogg's needs. crc = zlib.crc32(buffer(data.translate(cdata.bitswap)), -1) & 0xffffffff crc = ((crc & 0x80000000) << 1) - crc - 1 # Although we're using to_int_be, this actually makes the CRC # a proper le integer, since Python's CRC is byteswapped. crc = cdata.to_int_be(crc).translate(cdata.bitswap) data = data[:22] + crc + data[26:] return data
def __render_cover(self, key, value): try: imageformat = value.imageformat except AttributeError: imageformat = M4ACover.FORMAT_JPEG if isinstance(value, text_type): value = value.encode('utf-8') data = Atom.render("data", struct_pack(">2I", imageformat, 0) + value) return Atom.render(key, data)
def __render_pair_no_trailing(self, key, value): track, total = value if 0 <= track < 1 << 16 and 0 <= total < 1 << 16: data = struct_pack(">3H", 0, track, total) return self.__render_data(key, 0, data) else: raise M4AMetadataValueError("invalid numeric pair %r" % (value,))
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 render_ml(self, name): name = name.encode("utf-16-le") + b"\x00\x00" if self.TYPE == 2: data = self._render(dword=False) else: data = self._render() return (struct_pack("<HHHHI", self.language or 0, self.stream or 0, len(name), self.TYPE, len(data)) + name + data)
def __render_cover(self, key, value): atom_data = [] for cover in value: try: imageformat = cover.imageformat except AttributeError: imageformat = MP4Cover.FORMAT_JPEG atom_data.append( Atom.render(b"data", struct_pack(">2I", imageformat, 0) + cover)) return Atom.render(key, b"".join(atom_data))
def save(self): # Move attributes to the right objects self.to_extended_content_description = {} self.to_metadata = {} self.to_metadata_library = [] for name, value in self.tags: if name in _standard_attribute_names: continue large_value = value.data_size() > 0xFFFF if (value.language is None and value.stream is None and name not in self.to_extended_content_description and not large_value): self.to_extended_content_description[name] = value elif (value.language is None and value.stream is not None and name not in self.to_metadata and not large_value): self.to_metadata[name] = value else: self.to_metadata_library.append((name, value)) # Add missing objects if not self.content_description_obj: self.content_description_obj = \ ContentDescriptionObject() self.objects.append(self.content_description_obj) if not self.extended_content_description_obj: self.extended_content_description_obj = \ ExtendedContentDescriptionObject() self.objects.append(self.extended_content_description_obj) if not self.header_extension_obj: self.header_extension_obj = \ HeaderExtensionObject() self.objects.append(self.header_extension_obj) if not self.metadata_obj: self.metadata_obj = \ MetadataObject() self.header_extension_obj.objects.append(self.metadata_obj) if not self.metadata_library_obj: self.metadata_library_obj = \ MetadataLibraryObject() self.header_extension_obj.objects.append(self.metadata_library_obj) # Render the header data = b"".join([obj.render(self) for obj in self.objects]) data = (HeaderObject.GUID + struct_pack("<QL", len(data) + 30, len(self.objects)) + b"\x01\x02" + data) fileobj = open(self.filename, "rb+") try: size = len(data) if size > self.size: insert_bytes(fileobj, size - self.size, self.size) if size < self.size: delete_bytes(fileobj, self.size - size, 0) fileobj.seek(0) fileobj.write(data) finally: fileobj.close()
def write(self): f = BytesIO() f.write(struct_pack(">I", self.min_blocksize)[-2:]) f.write(struct_pack(">I", self.max_blocksize)[-2:]) f.write(struct_pack(">I", self.min_framesize)[-3:]) f.write(struct_pack(">I", self.max_framesize)[-3:]) # first 16 bits of sample rate f.write(struct_pack(">I", self.sample_rate >> 4)[-2:]) # 4 bits sample, 3 channel, 1 bps byte = (self.sample_rate & 0xF) << 4 byte += ((self.channels - 1) & 7) << 1 byte += ((self.bits_per_sample - 1) >> 4) & 1 f.write(bytearray([byte])) # 4 bits of bps, 4 of sample count byte = ((self.bits_per_sample - 1) & 0xF) << 4 byte += (self.total_samples >> 32) & 0xF f.write(bytearray([byte])) # last 32 of sample count f.write(struct_pack(">I", self.total_samples & 0xFFFFFFFF)) # MD5 signature sig = self.md5_signature f.write( struct_pack(">4I", (sig >> 96) & 0xFFFFFFFF, (sig >> 64) & 0xFFFFFFFF, (sig >> 32) & 0xFFFFFFFF, sig & 0xFFFFFFFF)) return f.getvalue()
def write(self): f = BytesIO() f.write(struct_pack(">I", self.min_blocksize)[-2:]) f.write(struct_pack(">I", self.max_blocksize)[-2:]) f.write(struct_pack(">I", self.min_framesize)[-3:]) f.write(struct_pack(">I", self.max_framesize)[-3:]) # first 16 bits of sample rate f.write(struct_pack(">I", self.sample_rate >> 4)[-2:]) # 4 bits sample, 3 channel, 1 bps byte = (self.sample_rate & 0xF) << 4 byte += ((self.channels - 1) & 7) << 1 byte += ((self.bits_per_sample - 1) >> 4) & 1 f.write(bytearray([byte])) # 4 bits of bps, 4 of sample count byte = ((self.bits_per_sample - 1) & 0xF) << 4 byte += (self.total_samples >> 32) & 0xF f.write(bytearray([byte])) # last 32 of sample count f.write(struct_pack(">I", self.total_samples & 0xFFFFFFFF)) # MD5 signature sig = self.md5_signature f.write(struct_pack( ">4I", (sig >> 96) & 0xFFFFFFFF, (sig >> 64) & 0xFFFFFFFF, (sig >> 32) & 0xFFFFFFFF, sig & 0xFFFFFFFF)) return f.getvalue()
def __render_pair_no_trailing(self, key, value): data = [] for (track, total) in value: if 0 <= track < 1 << 16 and 0 <= total < 1 << 16: data.append(struct_pack(">3H", 0, track, total)) else: raise MP4MetadataValueError( "invalid numeric pair %r" % ((track, total),)) return self.__render_data(key, 0, data)
def save(self, filename=None): """Save changes to a file. If no filename is given, the one most recently loaded is used. Tags are always written at the end of the file, and include a header and a footer. """ filename = filename or self.filename try: fileobj = open(filename, "r+b") except IOError: fileobj = open(filename, "w+b") data = _APEv2Data(fileobj) if data.is_at_start: delete_bytes(fileobj, data.end - data.start, data.start) elif data.start is not None: fileobj.seek(data.start) # Delete an ID3v1 tag if present, too. fileobj.truncate() fileobj.seek(0, 2) # "APE tags items should be sorted ascending by size... This is # not a MUST, but STRONGLY recommended. Actually the items should # be sorted by importance/byte, but this is not feasible." tags = sorted((v._internal(k) for k, v in list(self.items())), key=len) num_tags = len(tags) tags = bytearray().join(tags) # tag string, version, tag size, item count, flags header = struct_pack("<8s 4I 8x", b"APETAGEX", 2000, len(tags)+32, num_tags, HAS_HEADER|IS_HEADER) fileobj.write(header) fileobj.write(tags) # tag string, version, tag size, item count, flags footer = struct_pack("<8s 4I 8x", b"APETAGEX", 2000, len(tags) + 32, num_tags, HAS_HEADER) fileobj.write(footer) fileobj.close()
def save(self, filename=None): """Save changes to a file. If no filename is given, the one most recently loaded is used. Tags are always written at the end of the file, and include a header and a footer. """ filename = filename or self.filename try: fileobj = open(filename, "r+b") except IOError: fileobj = open(filename, "w+b") data = _APEv2Data(fileobj) if data.is_at_start: delete_bytes(fileobj, data.end - data.start, data.start) elif data.start is not None: fileobj.seek(data.start) # Delete an ID3v1 tag if present, too. fileobj.truncate() fileobj.seek(0, 2) # "APE tags items should be sorted ascending by size... This is # not a MUST, but STRONGLY recommended. Actually the items should # be sorted by importance/byte, but this is not feasible." tags = sorted((v._internal(k) for k, v in list(self.items())), key=len) num_tags = len(tags) tags = bytearray().join(tags) # tag string, version, tag size, item count, flags header = struct_pack("<8s 4I 8x", b"APETAGEX", 2000, len(tags) + 32, num_tags, HAS_HEADER | IS_HEADER) fileobj.write(header) fileobj.write(tags) # tag string, version, tag size, item count, flags footer = struct_pack("<8s 4I 8x", b"APETAGEX", 2000, len(tags) + 32, num_tags, HAS_HEADER) fileobj.write(footer) fileobj.close()
def __update_offset_table(self, fileobj, fmt, atom, delta, offset): """Update offset table in the specified atom.""" if atom.offset > offset: atom.offset += delta fileobj.seek(atom.offset + 12) data = fileobj.read(atom.length - 12) fmt = fmt % cdata.uint_be(data[:4]) offsets = struct_unpack(fmt, data[4:]) offsets = [o + (0, delta)[offset < o] for o in offsets] fileobj.seek(atom.offset + 16) fileobj.write(struct_pack(fmt, *offsets))
def writeblocks(blocks): """Render metadata block as a byte string.""" data = [] codes = [[block.code, block.write()] for block in blocks] codes[-1][0] |= 128 for code, datum in codes: byte = bytearray([code]) if len(datum) > 2**24: raise error("block is too long to write") length = struct_pack(">I", len(datum))[-3:] data.append(byte + length + datum) return bytearray().join(data)
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 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 render(self, asf): attrs = list(asf.to_extended_content_description.items()) data = b"".join([attr.render(name) for (name, attr) in attrs]) data = struct_pack("<QH", 26 + len(data), len(attrs)) + data return self.GUID + data
def render(self, name): name = name.encode("utf-16-le") + b"\x00\x00" data = self._render() return (struct_pack("<H", len(name)) + name + struct_pack("<HH", self.TYPE, len(data)) + data)
def _internal(self, key): if isinstance(key, text_type): key = key.encode('utf-8') return struct_pack("<2I", len(self.value), self.kind << 1) + \ key + b"\0" + self.value
def __render_data(self, key, flags, value): return Atom.render(key, bytearray().join([ Atom.render(b"data", struct_pack(">2I", flags, 0) + data) for data in value]))
def _render(self, dword=True): if dword: return struct_pack("<I", int(self.value)) else: return struct_pack("<H", int(self.value))
def _render(self): return struct_pack("<H", self.value)
def render(self, asf): data = self.GUID + struct_pack("<Q", len(self.data) + 24) + self.data return data
def render(self, asf): data = b"".join([obj.render(asf) for obj in self.objects]) return (self.GUID + struct_pack("<Q", 24 + 16 + 6 + len(data)) + b"\x11\xD2\xD3\xAB\xBA\xA9\xcf\x11" + b"\x8E\xE6\x00\xC0\x0C\x20\x53\x65" + b"\x06\x00" + struct_pack("<I", len(data)) + data)
def render(self, asf): attrs = list(asf.to_metadata.items()) data = b"".join([attr.render_m(name) for (name, attr) in attrs]) return (self.GUID + struct_pack("<QH", 26 + len(data), len(attrs)) + data)
def render(self, asf): attrs = asf.to_metadata_library data = b"".join([attr.render_ml(name) for (name, attr) in attrs]) return (self.GUID + struct_pack("<QH", 26 + len(data), len(attrs)) + data)
def __render_data(self, key, flags, data): data = struct_pack(">2I", flags, 0) + data return Atom.render(key, Atom.render("data", data))