def test_theora_bad_version(self): page = OggPage(open(self.filename, "rb")) packet = page.packets[0] packet = packet[:7] + b"\x03\x00" + packet[9:] page.packets = [packet] fileobj = io.BytesIO(page.write()) self.failUnlessRaises(IOError, OggTheoraInfo, fileobj)
def test_to_packets(self): self.failUnlessEqual( [b"foo", b"bar", b"baz"], OggPage.to_packets(self.pages)) self.pages[0].complete = False self.pages[1].continued = True self.failUnlessEqual( [b"foobar", b"baz"], OggPage.to_packets(self.pages))
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 = bytes((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 _inject(self, fileobj): """Write tag data into the Speex comment packet/page.""" fileobj.seek(0) # Find the first header page, with the stream info. # Use it to get the serial number. page = OggPage(fileobj) while not page.packets[0].startswith(b"Speex "): page = OggPage(fileobj) # Look for the next page with that serial number, it'll start # the comment packet. serial = page.serial page = OggPage(fileobj) while page.serial != serial: page = OggPage(fileobj) # Then find all the pages with the comment packet. old_pages = [page] while not (old_pages[-1].complete or len(old_pages[-1].packets) > 1): page = OggPage(fileobj) if page.serial == old_pages[0].serial: old_pages.append(page) packets = OggPage.to_packets(old_pages, strict=False) # Set the new comment packet. packets[0] = self.write(framing=False) new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"\x01vorbis"): page = OggPage(fileobj) if not page.first: raise OggVorbisHeaderError( "page has ID header, but doesn't start a stream") (self.channels, self.sample_rate, max_bitrate, nominal_bitrate, min_bitrate) = struct.unpack("<B4i", page.packets[0][11:28]) self.serial = page.serial max_bitrate = max(0, max_bitrate) min_bitrate = max(0, min_bitrate) nominal_bitrate = max(0, nominal_bitrate) if nominal_bitrate == 0: self.bitrate = (max_bitrate + min_bitrate) // 2 elif max_bitrate and max_bitrate < nominal_bitrate: # If the max bitrate is less than the nominal, we know # the nominal is wrong. self.bitrate = max_bitrate elif min_bitrate > nominal_bitrate: self.bitrate = min_bitrate else: self.bitrate = nominal_bitrate
def test_too_many_packets(self): packets = [b"1"] * 3000 pages = OggPage.from_packets(packets) for p in pages: OggPage.write(p) self.failUnless(len(pages) > 3000//255)
def test_underestimated_bitrate(self): page = OggPage(open(self.filename, "rb")) packet = page.packets[0] packet = (packet[:16] + b"\x00\x00\x01\x00" + b"\x01\x00\x00\x00" + b"\x00\x00\x01\x00" + packet[28:]) page.packets[0] = packet info = OggVorbisInfo(io.BytesIO(page.write())) self.failUnlessEqual(info.bitrate, 65536)
def test_negative_bitrate(self): page = OggPage(open(self.filename, "rb")) packet = page.packets[0] packet = (packet[:16] + b"\xff\xff\xff\xff" + b"\xff\xff\xff\xff" + b"\xff\xff\xff\xff" + packet[28:]) page.packets[0] = packet info = OggVorbisInfo(io.BytesIO(page.write())) self.failUnlessEqual(info.bitrate, 0)
def _inject(self, fileobj): fileobj.seek(0) info = OggOpusInfo(fileobj) old_pages = self.__get_comment_pages(fileobj, info) packets = OggPage.to_packets(old_pages) packets[0] = b"OpusTags" + self.write(framing=False) new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def __init__(self, fileobj, info): pages = [] complete = False while not complete: page = OggPage(fileobj) if page.serial == info.serial: pages.append(page) complete = page.complete or (len(page.packets) > 1) data = OggPage.to_packets(pages)[0][7:] # Strip off "\x03vorbis". super(OggVCommentDict, self).__init__(data)
def __init__(self, fileobj, info): pages = [] complete = False while not complete: page = OggPage(fileobj) if page.serial == info.serial: pages.append(page) complete = page.complete or (len(page.packets) > 1) data = OggPage.to_packets(pages)[0] + b"\x01" super(OggSpeexVComment, self).__init__(data, framing=False)
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"Speex "): page = OggPage(fileobj) if not page.first: raise OggSpeexHeaderError( "page has ID header, but doesn't start a stream") self.sample_rate = cdata.uint_le(page.packets[0][36:40]) self.channels = cdata.uint_le(page.packets[0][48:52]) self.bitrate = max(0, cdata.int_le(page.packets[0][52:56])) self.serial = page.serial
def test_unsupported_version(self): page = OggPage(open(self.filename, "rb")) data = bytearray(page.packets[0]) data[8] = 0x03 page.packets[0] = bytes(data) OggOpusInfo(io.BytesIO(page.write())) data[8] = 0x10 page.packets[0] = bytes(data) self.failUnlessRaises(IOError, OggOpusInfo, io.BytesIO(page.write()))
def load(self, data, info, errors='replace'): # data should be pointing at the start of an Ogg page, after # the first FLAC page. pages = [] complete = False while not complete: page = OggPage(data) if page.serial == info.serial: pages.append(page) complete = page.complete or (len(page.packets) > 1) comment = BytesIO(OggPage.to_packets(pages)[0][4:]) super(OggFLACVComment, self).load(comment, errors=errors)
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 = bytes( (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 test_random_data_roundtrip(self): try: random_file = open("/dev/urandom", "rb") except (IOError, OSError): print("WARNING: Random data round trip test disabled.") return for i in range(10): num_packets = random.randrange(2, 100) lengths = [random.randrange(10, 10000) for i in range(num_packets)] packets = [random_file.read(l) for l in lengths] self.failUnlessEqual( packets, OggPage.to_packets(OggPage.from_packets(packets)))
def test_renumber_muxed(self): pages = [OggPage() for i in range(10)] for seq, page in enumerate(pages[0:1] + pages[2:]): page.serial = 0 page.sequence = seq pages[1].serial = 2 pages[1].sequence = 100 data = io.BytesIO(b"".join([page.write() for page in pages])) OggPage.renumber(data, 0, 20) data.seek(0) pages = [OggPage(data) for i in range(10)] self.failUnlessEqual(pages[1].serial, 2) self.failUnlessEqual(pages[1].sequence, 100) pages.pop(1) self.failUnlessEqual([page.sequence for page in pages], list(range(20, 29)))
def __get_comment_pages(self, fileobj, info): # find the first tags page with the right serial page = OggPage(fileobj) while ((info.serial != page.serial) or not page.packets[0].startswith(b"OpusTags")): page = OggPage(fileobj) # get all comment pages pages = [page] while not (pages[-1].complete or len(pages[-1].packets) > 1): page = OggPage(fileobj) if page.serial == pages[0].serial: pages.append(page) return pages
def test_from_packets_position(self): packets = [b"1" * 100000] pages = OggPage.from_packets(packets) self.failUnless(len(pages) > 1) for page in pages[:-1]: self.failUnlessEqual(-1, page.position) self.failUnlessEqual(0, pages[-1].position)
def test_read_max_size(self): page = OggPage() page.packets = [b"1" * 255 * 255] page.complete = False page2 = OggPage() page2.packets = [b"", b"foo"] page2.sequence = 1 page2.continued = True data = page.write() + page2.write() fileobj = io.BytesIO(data) self.failUnlessEqual(OggPage(fileobj), page) self.failUnlessEqual(OggPage(fileobj), page2) self.failUnlessRaises(EOFError, OggPage, fileobj)
def test_renumber_reread(self): try: fd, filename = tempfile.mkstemp(suffix=".ogg") os.close(fd) shutil.copy(os.path.join("tests", "data", "multipagecomment.ogg"), filename) fileobj = open(filename, "rb+") OggPage.renumber(fileobj, 1002429366, 20) fileobj.close() fileobj = open(filename, "rb+") OggPage.renumber(fileobj, 1002429366, 0) fileobj.close() finally: try: os.remove(filename) except OSError: pass
def test_renumber(self): self.failUnlessEqual( [page.sequence for page in self.pages], [0, 1, 2]) fileobj = io.BytesIO() for page in self.pages: fileobj.write(page.write()) fileobj.seek(0) OggPage.renumber(fileobj, 1, 10) fileobj.seek(0) pages = [OggPage(fileobj) for i in range(3)] self.failUnlessEqual([page.sequence for page in pages], [10, 11, 12]) fileobj.seek(0) OggPage.renumber(fileobj, 1, 20) fileobj.seek(0) pages = [OggPage(fileobj) for i in range(3)] self.failUnlessEqual([page.sequence for page in pages], [20, 21, 22])
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"\x80theora"): page = OggPage(fileobj) if not page.first: raise OggTheoraHeaderError( "page has ID header, but doesn't start a stream") data = page.packets[0] vmaj, vmin = struct.unpack("2B", data[7:9]) if (vmaj, vmin) != (3, 2): raise OggTheoraHeaderError( "found Theora version %d.%d != 3.2" % (vmaj, vmin)) fps_num, fps_den = struct.unpack(">2I", data[22:30]) self.fps = fps_num / float(fps_den) self.bitrate = cdata.uint_be(b"\x00" + data[37:40]) self.granule_shift = (cdata.ushort_be(data[40:42]) >> 5) & 0x1F self.serial = page.serial
def test_find_last_no_serial(self): pages = [OggPage() for i in range(10)] for i, page in enumerate(pages): page.sequence = i data = io.BytesIO(b"".join([page.write() for page in pages])) self.failUnless(OggPage.find_last(data, pages[0].serial + 1) is None)
def test_page_max_size(self): page = OggPage() page.packets = [b"1" * 255 * 255] page.complete = False page2 = OggPage() page2.packets = [b""] page2.sequence = 1 page2.continued = True self.failUnlessEqual( [b"1" * 255 * 255], OggPage.to_packets([page, page2]))
def test_find_last_really_last(self): pages = [OggPage() for i in range(10)] pages[-1].last = True for i, page in enumerate(pages): page.sequence = i data = io.BytesIO(b"".join([page.write() for page in pages])) self.failUnlessEqual( OggPage.find_last(data, pages[0].serial), pages[-1])
def __init__(self, fileobj, info): pages = [] complete = False while not complete: page = OggPage(fileobj) if page.serial == info.serial: pages.append(page) complete = page.complete or (len(page.packets) > 1) data = OggPage.to_packets(pages)[0][7:] super(OggTheoraCommentDict, self).__init__(data + b"\x01")
def _inject(self, fileobj): """Write tag data into the Theora comment packet/page.""" fileobj.seek(0) page = OggPage(fileobj) while not page.packets[0].startswith(b"\x81theora"): 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 == old_pages[0].serial: old_pages.append(page) packets = OggPage.to_packets(old_pages, strict=False) packets[0] = b"\x81theora" + self.write(framing=False) new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def __init__(self, fileobj): page = OggPage(fileobj) while not page.packets[0].startswith(b"OpusHead"): page = OggPage(fileobj) self.serial = page.serial if not page.first: raise OggOpusHeaderError( "page has ID header, but doesn't start a stream") (version, self.channels, pre_skip, orig_sample_rate, output_gain, channel_map) = struct.unpack("<BBHIhB", page.packets[0][8:19]) self.__pre_skip = pre_skip # only the higher 4 bits change on incombatible changes major, minor = version >> 4, version & 0xF if major != 0: raise OggOpusHeaderError("version %r unsupported" % major)
def load(self, data): # Ogg expects file objects that don't raise on read if isinstance(data, StrictFileObject): data = data._fileobj page = OggPage(data) while not page.packets[0].startswith(b"\x7FFLAC"): page = OggPage(data) major, minor, self.packets, flac = struct.unpack( ">BBH4s", page.packets[0][5:13]) if flac != b"fLaC": raise OggFLACHeaderError("invalid FLAC marker (%r)" % flac) elif (major, minor) != (1, 0): raise OggFLACHeaderError("unknown mapping version: %d.%d" % (major, minor)) self.serial = page.serial # Skip over the block header. stringobj = StrictFileObject(BytesIO(page.packets[0][17:])) super(OggFLACStreamInfo, self).load(stringobj)
def load(self, data, info, errors='replace'): # data should be pointing at the start of an Ogg page, after # the first FLAC page. pages = [] complete = False while not complete: page = OggPage(data) if page.serial == info.serial: pages.append(page) complete = page.complete or (len(page.packets) > 1) comment = io.BytesIO(OggPage.to_packets(pages)[0][4:]) super(OggFLACVComment, self).load(comment, errors=errors)
def setUp(self): self.fileobj = open(os.path.join("tests", "data", "empty.ogg"), "rb") self.page = OggPage(self.fileobj) pages = [OggPage(), OggPage(), OggPage()] pages[0].packets = [b"foo"] pages[1].packets = [b"bar"] pages[2].packets = [b"baz"] for i in range(len(pages)): pages[i].sequence = i for page in pages: page.serial = 1 self.pages = pages
def _inject(self, fileobj): """Write tag data into the Vorbis comment packet/page.""" # Find the old pages in the file; we'll need to remove them, # plus grab any stray setup packet data out of them. fileobj.seek(0) page = OggPage(fileobj) while not page.packets[0].startswith(b"\x03vorbis"): 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 == old_pages[0].serial: old_pages.append(page) packets = OggPage.to_packets(old_pages, strict=False) # Set the new comment packet. packets[0] = b"\x03vorbis" + self.write() new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def test_crc_py25(self): # Make sure page.write can handle both signed/unsigned int # return values of crc32. # http://code.google.com/p/mutagen/issues/detail?id=63 # http://docs.python.org/library/zlib.html#zlib.crc32 import zlib old_crc = zlib.crc32 def zlib_uint(*args): return (old_crc(*args) & 0xffffffff) def zlib_int(*args): return cdata.int_be(cdata.to_uint_be(old_crc(*args) & 0xffffffff)) try: page = OggPage() page.packets = [b"abc"] zlib.crc32 = zlib_uint uint_data = page.write() zlib.crc32 = zlib_int int_data = page.write() finally: zlib.crc32 = old_crc self.failUnlessEqual(uint_data, int_data)
def _post_tags(self, fileobj): if self.length: return page = OggPage.find_last(fileobj, self.serial) self.length = page.position / self.sample_rate
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) self.length = page.position / self.sample_rate
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) self.length = (page.position - self.__pre_skip) / 48000
def test_invalid_not_first(self): page = OggPage(open(self.filename, "rb")) page.first = False self.failUnlessRaises(IOError, OggVorbisInfo, io.BytesIO(page.write()))
def test_theora_not_first_page(self): page = OggPage(open(self.filename, "rb")) page.first = False fileobj = io.BytesIO(page.write()) self.failUnlessRaises(IOError, OggTheoraInfo, fileobj)
def __init__(self, fileobj, info): pages = self.__get_comment_pages(fileobj, info) data = OggPage.to_packets(pages)[0][8:] # Strip OpusTags super(OggOpusVComment, self).__init__(data, framing=False)
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) position = page.position mask = (1 << self.granule_shift) - 1 frames = (position >> self.granule_shift) + (position & mask) self.length = frames / self.fps
def test_one_packet_per_wiggle(self): packets = [b"1" * 511, b"2" * 511, b"3" * 511] pages = OggPage.from_packets( packets, default_size=1000, wiggle_room=1000000) self.failUnlessEqual(len(pages), 2) self.failUnlessEqual(OggPage.to_packets(pages), packets)
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) self.length = page.position / float(self.sample_rate)
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) position = page.position mask = (1 << self.granule_shift) - 1 frames = (position >> self.granule_shift) + (position & mask) self.length = frames / float(self.fps)