def _inject(self, fileobj, padding_func): """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) content_size = get_size(fileobj) - len(packets[0]) # approx vcomment_data = b"\x03vorbis" + self.write() padding_left = len(packets[0]) - len(vcomment_data) info = PaddingInfo(padding_left, content_size) new_padding = info._get_padding(padding_func) # Set the new comment packet. packets[0] = vcomment_data + b"\x00" * new_padding new_pages = OggPage._from_packets_try_preserve(packets, old_pages) OggPage.replace(fileobj, old_pages, new_pages)
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 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 _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("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 _inject(self, fileobj, padding_func): """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) content_size = get_size(fileobj) - len(packets[0]) # approx vcomment_data = b"\x81theora" + self.write(framing=False) padding_left = len(packets[0]) - len(vcomment_data) info = PaddingInfo(padding_left, content_size) new_padding = info._get_padding(padding_func) packets[0] = vcomment_data + b"\x00" * new_padding new_pages = OggPage._from_packets_try_preserve(packets, old_pages) OggPage.replace(fileobj, old_pages, new_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 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 = cBytesIO(page.write()) self.failUnlessRaises(IOError, OggTheoraInfo, fileobj)
def test_find_last_single_muxed(self): page1 = OggPage() page1.last = True page2 = OggPage() page2.serial = page1.serial + 1 pages = [page1, page2] data = BytesIO(b"".join([page.write() for page in pages])) assert OggPage.find_last(data, page2.serial).serial == page2.serial
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(cBytesIO(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(cBytesIO(page.write())) self.failUnlessEqual(info.bitrate, 0)
def test_avg_bitrate(self): page = OggPage(open(self.filename, "rb")) packet = page.packets[0] packet = (packet[:16] + "\x00\x00\x01\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + packet[28:]) page.packets[0] = packet info = OggVorbisInfo(StringIO(page.write())) self.failUnlessEqual(info.bitrate, 32768)
def test_overestimated_bitrate(self): page = OggPage(file(self.filename, "rb")) packet = page.packets[0] packet = (packet[:16] + "\x00\x00\x01\x00" + "\x00\x00\x00\x01" + "\x00\x00\x00\x00" + packet[28:]) page.packets[0] = packet info = OggVorbisInfo(StringIO(page.write())) self.failUnlessEqual(info.bitrate, 65536)
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] = "OpusTags" + self.write(framing=False) new_pages = OggPage.from_packets(packets, old_pages[0].sequence) OggPage.replace(fileobj, old_pages, new_pages)
def test_renumber_reread(self): try: filename = get_temp_copy( os.path.join(DATA_DIR, "multipagecomment.ogg")) with open(filename, "rb+") as fileobj: OggPage.renumber(fileobj, 1002429366, 20) with open(filename, "rb+") as fileobj: OggPage.renumber(fileobj, 1002429366, 0) finally: os.unlink(filename)
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 = map(random_file.read, lengths) self.failUnlessEqual(packets, OggPage.to_packets(OggPage.from_packets(packets)))
def test_unsupported_version(self): page = OggPage(open(self.filename, "rb")) data = list(page.packets[0]) data[8] = "\x03" page.packets[0] = "".join(data) OggOpusInfo(StringIO(page.write())) data[8] = "\x10" page.packets[0] = "".join(data) self.failUnlessRaises(IOError, OggOpusInfo, StringIO(page.write()))
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(BytesIO(page.write())) data[8] = 0x10 page.packets[0] = bytes(data) self.failUnlessRaises(IOError, OggOpusInfo, BytesIO(page.write()))
def main(argv): from mutagen.ogg import OggPage parser = OptionParser( usage="%prog [options] filename.ogg ...", description="Split Ogg logical streams using Mutagen.", version="Mutagen %s" % ".".join(map(str, mutagen.version)) ) parser.add_option( "--extension", dest="extension", default="ogg", metavar='ext', help="use this extension (default 'ogg')") parser.add_option( "--pattern", dest="pattern", default="%(base)s-%(stream)d.%(ext)s", metavar='pattern', help="name files using this pattern") parser.add_option( "--m3u", dest="m3u", action="store_true", default=False, help="generate an m3u (playlist) file") (options, args) = parser.parse_args(argv[1:]) if not args: raise SystemExit(parser.print_help() or 1) format = {'ext': options.extension} for filename in args: with _sig.block(): fileobjs = {} format["base"] = os.path.splitext(os.path.basename(filename))[0] fileobj = open(filename, "rb") if options.m3u: m3u = open(format["base"] + ".m3u", "w") fileobjs["m3u"] = m3u else: m3u = None while True: try: page = OggPage(fileobj) except EOFError: break else: format["stream"] = page.serial if page.serial not in fileobjs: new_filename = options.pattern % format new_fileobj = open(new_filename, "wb") fileobjs[page.serial] = new_fileobj if m3u: m3u.write(new_filename + "\r\n") fileobjs[page.serial].write(page.write()) for f in fileobjs.values(): f.close()
def test_renumber_reread(self): try: fd, filename = 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.unlink(filename) except OSError: pass
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 = BytesIO(bytearray().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 yield_pages(track_derived): fh = track_derived.open() yield OggPage(fh) tmp = OggPage(fh) # replace outgoing tag tmp.packets[0] = create_metadata_packet([ ('title', track_derived.original.title), ('artist', track_derived.original.artist), ('x-ireul-id', unicode(track_derived.id))]) yield tmp try: while True: yield OggPage(fh) except EOFError: pass
def test_find_last_last_empty(self): # https://github.com/quodlibet/mutagen/issues/308 pages = [OggPage() for i in xrange(10)] for i, page in enumerate(pages): page.sequence = i page.position = i pages[-1].last = True pages[-1].position = -1 data = BytesIO(b"".join([page.write() for page in pages])) page = OggPage.find_last(data, pages[-1].serial, finishing=True) assert page is not None assert page.position == 8 page = OggPage.find_last(data, pages[-1].serial, finishing=False) assert page is not None assert page.position == -1
def test_find_last(self): pages = [OggPage() for i in xrange(10)] for i, page in enumerate(pages): page.sequence = i data = BytesIO(b"".join([page.write() for page in pages])) self.failUnlessEqual( OggPage.find_last(data, pages[0].serial), pages[-1])
def test_renumber(self): self.failUnlessEqual([page.sequence for page in self.pages], [0, 1, 2]) fileobj = StringIO() 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 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 _post_tags(self, fileobj): if self.length: return page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggFLACHeaderError self.length = page.position / float(self.sample_rate)
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 = BytesIO(bytearray().join([page.write() for page in pages])) self.failUnlessEqual( OggPage.find_last(data, pages[0].serial), pages[-1])
def _post_tags(self, fileobj): """Raises ogg.error""" page = OggPage.find_last(fileobj, self.serial) if page is None: raise OggVorbisHeaderError self.length = page.position / float(self.sample_rate)
def test_page_max_size(self): page = OggPage() page.packets = ["1" * 255 * 255] page.complete = False page2 = OggPage() page2.packets = [""] page2.sequence = 1 page2.continued = True self.failUnlessEqual(["1" * 255 * 255], OggPage.to_packets([page, page2]))
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial) self.length = (page.position - self.__pre_skip) / float(48000)
def test_not_equal(self): self.failIfEqual(OggPage(), 12)
def test_find_last_no_serial(self): pages = [OggPage() for i in xrange(10)] for i, page in enumerate(pages): page.sequence = i data = BytesIO(b"".join([page.write() for page in pages])) self.failUnless(OggPage.find_last(data, pages[0].serial + 1) is None)
def _post_tags(self, fileobj): if self.length: return page = OggPage.find_last(fileobj, self.serial) self.length = page.position / float(self.sample_rate)
def test_at_least_one_audio_page(self): page = OggPage(self.fileobj) while not page.last: page = OggPage(self.fileobj) self.failUnless(page.last)
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 test_invalid_not_first(self): with open(self.filename, "rb") as h: page = OggPage(h) page.first = False self.failUnlessRaises(error, OggSpeexInfo, BytesIO(page.write()))
class TOggPage(TestCase): def setUp(self): self.fileobj = open(os.path.join(DATA_DIR, "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 test_to_packets_empty_page(self): pages = [OggPage(), OggPage()] for i in range(len(pages)): pages[i].sequence = i assert OggPage.to_packets(pages, True) == [] assert OggPage.to_packets(pages, False) == [] pages = [OggPage(), OggPage(), OggPage()] pages[0].packets = [b"foo"] pages[0].complete = False pages[1].continued = True pages[1].complete = False pages[2].packets = [b"bar"] pages[2].continued = True for i in range(len(pages)): pages[i].sequence = i assert OggPage.to_packets(pages, True) == [b'foobar'] def test_flags(self): self.failUnless(self.page.first) self.failIf(self.page.continued) self.failIf(self.page.last) self.failUnless(self.page.complete) for first in [True, False]: self.page.first = first for last in [True, False]: self.page.last = last for continued in [True, False]: self.page.continued = continued self.failUnlessEqual(self.page.first, first) self.failUnlessEqual(self.page.last, last) self.failUnlessEqual(self.page.continued, continued) def test_flags_next_page(self): page = OggPage(self.fileobj) self.failIf(page.first) self.failIf(page.continued) self.failIf(page.last) def test_length(self): # Always true for Ogg Vorbis files self.failUnlessEqual(self.page.size, 58) self.failUnlessEqual(len(self.page.write()), 58) def test_first_metadata_page_is_separate(self): self.failIf(OggPage(self.fileobj).continued) def test_single_page_roundtrip(self): self.failUnlessEqual(self.page, OggPage(BytesIO(self.page.write()))) def test_at_least_one_audio_page(self): page = OggPage(self.fileobj) while not page.last: page = OggPage(self.fileobj) self.failUnless(page.last) def test_crappy_fragmentation(self): packets = [b"1" * 511, b"2" * 511, b"3" * 511] pages = OggPage.from_packets(packets, default_size=510, wiggle_room=0) self.failUnless(len(pages) > 3) self.failUnlessEqual(OggPage.to_packets(pages), packets) def test_wiggle_room(self): packets = [b"1" * 511, b"2" * 511, b"3" * 511] pages = OggPage.from_packets(packets, default_size=510, wiggle_room=100) self.failUnlessEqual(len(pages), 3) self.failUnlessEqual(OggPage.to_packets(pages), packets) 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 test_replace(self): # create interleaved pages fileobj = BytesIO() pages = [OggPage(), OggPage(), OggPage()] pages[0].serial = 42 pages[0].sequence = 0 pages[0].packets = [b"foo"] pages[1].serial = 24 pages[1].sequence = 0 pages[1].packets = [b"bar"] pages[2].serial = 42 pages[2].sequence = 1 pages[2].packets = [b"baz"] for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] old_pages = [pages_from_file[0], pages_from_file[2]] packets = OggPage.to_packets(old_pages, strict=True) self.assertEqual(packets, [b"foo", b"baz"]) new_packets = [b"1111", b"2222"] new_pages = OggPage.from_packets(new_packets, sequence=old_pages[0].sequence) self.assertEqual(len(new_pages), 1) OggPage.replace(fileobj, old_pages, new_pages) fileobj.seek(0, 0) first = OggPage(fileobj) self.assertEqual(first.serial, 42) self.assertEqual(OggPage.to_packets([first], strict=True), [b"1111", b"2222"]) second = OggPage(fileobj) self.assertEqual(second.serial, 24) self.assertEqual(OggPage.to_packets([second], strict=True), [b"bar"]) def test_replace_fast_path(self): # create interleaved pages fileobj = BytesIO() pages = [OggPage(), OggPage(), OggPage()] pages[0].serial = 42 pages[0].sequence = 0 pages[0].packets = [b"foo"] pages[1].serial = 24 pages[1].sequence = 0 pages[1].packets = [b"bar"] pages[2].serial = 42 pages[2].sequence = 1 pages[2].packets = [b"baz"] for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] old_pages = [pages_from_file[0], pages_from_file[2]] packets = OggPage.to_packets(old_pages, strict=True) self.assertEqual(packets, [b"foo", b"baz"]) new_packets = [b"111", b"222"] new_pages = OggPage._from_packets_try_preserve(new_packets, old_pages) self.assertEqual(len(new_pages), 2) # remove insert_bytes, so we can be sure the fast path was taken old_insert_bytes = _util.insert_bytes _util.insert_bytes = None try: OggPage.replace(fileobj, old_pages, new_pages) finally: _util.insert_bytes = old_insert_bytes # validate that the new data was written and the other pages # are untouched fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] packets = OggPage.to_packets([pages_from_file[0], pages_from_file[2]], strict=True) self.assertEqual(packets, [b"111", b"222"]) packets = OggPage.to_packets([pages_from_file[1]], strict=True) self.assertEqual(packets, [b"bar"]) def test_replace_continued(self): # take a partial packet and replace it with a new page # replace() should make it spanning again fileobj = BytesIO() pages = [OggPage(), OggPage()] pages[0].serial = 1 pages[0].sequence = 0 pages[0].complete = False pages[0].packets = [b"foo"] pages[1].serial = 1 pages[1].sequence = 1 pages[1].continued = True pages[1].packets = [b"bar"] fileobj = BytesIO() for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [OggPage(fileobj), OggPage(fileobj)] self.assertEqual(OggPage.to_packets(pages_from_file), [b"foobar"]) packets_part = OggPage.to_packets([pages_from_file[0]]) self.assertEqual(packets_part, [b"foo"]) new_pages = OggPage.from_packets([b"quuux"]) OggPage.replace(fileobj, [pages_from_file[0]], new_pages) fileobj.seek(0, 0) written = OggPage.to_packets([OggPage(fileobj), OggPage(fileobj)]) self.assertEquals(written, [b"quuuxbar"]) def test_renumber(self): self.failUnlessEqual([page.sequence for page in self.pages], [0, 1, 2]) fileobj = 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 test_renumber_extradata(self): fileobj = BytesIO() for page in self.pages: fileobj.write(page.write()) fileobj.write(b"left over data") fileobj.seek(0) # Trying to rewrite should raise an error... self.failUnlessRaises(Exception, OggPage.renumber, fileobj, 1, 10) fileobj.seek(0) # But the already written data should remain valid, pages = [OggPage(fileobj) for i in range(3)] self.failUnlessEqual([page.sequence for page in pages], [10, 11, 12]) # And the garbage that caused the error should be okay too. self.failUnlessEqual(fileobj.read(), b"left over data") def test_renumber_reread(self): try: filename = get_temp_copy( os.path.join(DATA_DIR, "multipagecomment.ogg")) with open(filename, "rb+") as fileobj: OggPage.renumber(fileobj, 1002429366, 20) with open(filename, "rb+") as fileobj: OggPage.renumber(fileobj, 1002429366, 0) finally: os.unlink(filename) 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 = 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 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 test_to_packets_mixed_stream(self): self.pages[0].serial = 3 self.failUnlessRaises(ValueError, OggPage.to_packets, self.pages) def test_to_packets_missing_sequence(self): self.pages[0].sequence = 3 self.failUnlessRaises(ValueError, OggPage.to_packets, self.pages) def test_to_packets_continued(self): self.pages[0].continued = True self.failUnlessEqual(OggPage.to_packets(self.pages), [b"foo", b"bar", b"baz"]) def test_to_packets_continued_strict(self): self.pages[0].continued = True self.failUnlessRaises(ValueError, OggPage.to_packets, self.pages, strict=True) def test_to_packets_strict(self): for page in self.pages: page.complete = False self.failUnlessRaises(ValueError, OggPage.to_packets, self.pages, strict=True) def test_from_packets_short_enough(self): packets = [b"1" * 200, b"2" * 200, b"3" * 200] pages = OggPage.from_packets(packets) self.failUnlessEqual(OggPage.to_packets(pages), packets) 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_from_packets_long(self): packets = [b"1" * 100000, b"2" * 100000, b"3" * 100000] pages = OggPage.from_packets(packets) self.failIf(pages[0].complete) self.failUnless(pages[1].continued) self.failUnlessEqual(OggPage.to_packets(pages), packets) def test__from_packets_try_preserve(self): # if the packet layout matches, just create pages with # the same layout and copy things over packets = [b"1" * 100000, b"2" * 100000, b"3" * 100000] pages = OggPage.from_packets(packets, sequence=42, default_size=977) new_pages = OggPage._from_packets_try_preserve(packets, pages) self.assertEqual(pages, new_pages) # zero case new_pages = OggPage._from_packets_try_preserve([], pages) self.assertEqual(new_pages, []) # if the layout doesn't match we should fall back to creating new # pages starting with the sequence of the first given page other_packets = list(packets) other_packets[1] += b"\xff" other_pages = OggPage.from_packets(other_packets, 42) new_pages = OggPage._from_packets_try_preserve(other_packets, pages) self.assertEqual(new_pages, other_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 try: for i in range(10): num_packets = random.randrange(2, 100) lengths = [ random.randrange(10, 10000) for i in range(num_packets) ] packets = list(map(random_file.read, lengths)) self.failUnlessEqual( packets, OggPage.to_packets(OggPage.from_packets(packets))) finally: random_file.close() def test_packet_exactly_255(self): page = OggPage() page.packets = [b"1" * 255] page.complete = False page2 = OggPage() page2.packets = [b""] page2.sequence = 1 page2.continued = True self.failUnlessEqual([b"1" * 255], OggPage.to_packets([page, page2])) def test_page_max_size_alone_too_big(self): page = OggPage() page.packets = [b"1" * 255 * 255] page.complete = True self.failUnlessRaises(ValueError, page.write) 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_complete_zero_length(self): packets = [b""] * 20 page = OggPage.from_packets(packets)[0] new_page = OggPage(BytesIO(page.write())) self.failUnlessEqual(new_page, page) self.failUnlessEqual(OggPage.to_packets([new_page]), packets) def test_too_many_packets(self): packets = [b"1"] * 3000 pages = OggPage.from_packets(packets) map(OggPage.write, pages) self.failUnless(len(pages) > 3000 // 255) 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 = BytesIO(data) self.failUnlessEqual(OggPage(fileobj), page) self.failUnlessEqual(OggPage(fileobj), page2) self.failUnlessRaises(EOFError, OggPage, fileobj) def test_invalid_version(self): page = OggPage() OggPage(BytesIO(page.write())) page.version = 1 self.failUnlessRaises(OggError, OggPage, BytesIO(page.write())) def test_not_enough_lacing(self): data = OggPage().write()[:-1] + b"\x10" self.failUnlessRaises(OggError, OggPage, BytesIO(data)) def test_not_enough_data(self): data = OggPage().write()[:-1] + b"\x01\x10" self.failUnlessRaises(OggError, OggPage, BytesIO(data)) def test_not_equal(self): self.failIfEqual(OggPage(), 12) def test_find_last(self): pages = [OggPage() for i in range(10)] for i, page in enumerate(pages): page.sequence = i data = BytesIO(b"".join([page.write() for page in pages])) self.failUnlessEqual(OggPage.find_last(data, pages[0].serial), pages[-1]) def test_find_last_none_finishing(self): page = OggPage() page.position = -1 data = BytesIO(page.write()) assert OggPage.find_last(data, page.serial, finishing=True) is None def test_find_last_none_finishing_mux(self): page1 = OggPage() page1.last = True page1.position = -1 page2 = OggPage() page2.serial = page1.serial + 1 pages = [page1, page2] data = BytesIO(b"".join([page.write() for page in pages])) assert OggPage.find_last(data, page1.serial, finishing=True) is None assert OggPage.find_last(data, page2.serial, finishing=True) == page2 def test_find_last_last_empty(self): # https://github.com/quodlibet/mutagen/issues/308 pages = [OggPage() for i in range(10)] for i, page in enumerate(pages): page.sequence = i page.position = i pages[-1].last = True pages[-1].position = -1 data = BytesIO(b"".join([page.write() for page in pages])) page = OggPage.find_last(data, pages[-1].serial, finishing=True) assert page is not None assert page.position == 8 page = OggPage.find_last(data, pages[-1].serial, finishing=False) assert page is not None assert page.position == -1 def test_find_last_single_muxed(self): page1 = OggPage() page1.last = True page2 = OggPage() page2.serial = page1.serial + 1 pages = [page1, page2] data = BytesIO(b"".join([page.write() for page in pages])) assert OggPage.find_last(data, page2.serial).serial == page2.serial 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 = BytesIO(b"".join([page.write() for page in pages])) self.failUnlessEqual(OggPage.find_last(data, pages[0].serial), pages[-1]) def test_find_last_muxed(self): pages = [OggPage() for i in range(10)] for i, page in enumerate(pages): page.sequence = i pages[-2].last = True pages[-1].serial = pages[0].serial + 1 data = BytesIO(b"".join([page.write() for page in pages])) self.failUnlessEqual(OggPage.find_last(data, pages[0].serial), pages[-2]) def test_find_last_no_serial(self): pages = [OggPage() for i in range(10)] for i, page in enumerate(pages): page.sequence = i data = BytesIO(b"".join([page.write() for page in pages])) self.failUnless(OggPage.find_last(data, pages[0].serial + 1) is None) def test_find_last_invalid(self): data = BytesIO(b"if you think this is an Ogg, you're crazy") self.failUnlessRaises(OggError, OggPage.find_last, data, 0) # Disabled because GStreamer will write Oggs with bad data, # which we need to make a best guess for. # # def test_find_last_invalid_sync(self): # data = BytesIO("if you think this is an OggS, you're crazy") # self.failUnlessRaises(OggError, OggPage.find_last, data, 0) def test_find_last_invalid_sync(self): data = BytesIO(b"if you think this is an OggS, you're crazy") page = OggPage.find_last(data, 0) self.failIf(page) def test_crc_py25(self): # Make sure page.write can handle both signed/unsigned int # return values of crc32. # https://github.com/quodlibet/mutagen/issues/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 tearDown(self): self.fileobj.close()
def test_replace(self): # create interleaved pages fileobj = BytesIO() pages = [OggPage(), OggPage(), OggPage()] pages[0].serial = 42 pages[0].sequence = 0 pages[0].packets = [b"foo"] pages[1].serial = 24 pages[1].sequence = 0 pages[1].packets = [b"bar"] pages[2].serial = 42 pages[2].sequence = 1 pages[2].packets = [b"baz"] for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] old_pages = [pages_from_file[0], pages_from_file[2]] packets = OggPage.to_packets(old_pages, strict=True) self.assertEqual(packets, [b"foo", b"baz"]) new_packets = [b"1111", b"2222"] new_pages = OggPage.from_packets(new_packets, sequence=old_pages[0].sequence) self.assertEqual(len(new_pages), 1) OggPage.replace(fileobj, old_pages, new_pages) fileobj.seek(0, 0) first = OggPage(fileobj) self.assertEqual(first.serial, 42) self.assertEqual(OggPage.to_packets([first], strict=True), [b"1111", b"2222"]) second = OggPage(fileobj) self.assertEqual(second.serial, 24) self.assertEqual(OggPage.to_packets([second], strict=True), [b"bar"])
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggSpeexHeaderError self.length = page.position / float(self.sample_rate)
def test_theora_not_first_page(self): with open(self.filename, "rb") as h: page = OggPage(h) page.first = False fileobj = BytesIO(page.write()) self.failUnlessRaises(error, OggTheoraInfo, fileobj)
def test_invalid_version(self): page = OggPage() OggPage(BytesIO(page.write())) page.version = 1 self.failUnlessRaises(OggError, OggPage, BytesIO(page.write()))
def test_not_enough_data(self): data = OggPage().write()[:-1] + b"\x01\x10" self.failUnlessRaises(OggError, OggPage, BytesIO(data))
def test_to_packets_continued(self): self.pages[0].continued = True self.failUnlessEqual(OggPage.to_packets(self.pages), [b"foo", b"bar", b"baz"])
def test_crappy_fragmentation(self): packets = [b"1" * 511, b"2" * 511, b"3" * 511] pages = OggPage.from_packets(packets, default_size=510, wiggle_room=0) self.failUnless(len(pages) > 3) self.failUnlessEqual(OggPage.to_packets(pages), packets)
def test_from_packets_short_enough(self): packets = [b"1" * 200, b"2" * 200, b"3" * 200] pages = OggPage.from_packets(packets) self.failUnlessEqual(OggPage.to_packets(pages), packets)
def test_single_page_roundtrip(self): self.failUnlessEqual( self.page, OggPage(BytesIO(self.page.write())))
def test_first_metadata_page_is_separate(self): self.failIf(OggPage(self.fileobj).continued)
def test_from_packets_long(self): packets = [b"1" * 100000, b"2" * 100000, b"3" * 100000] pages = OggPage.from_packets(packets) self.failIf(pages[0].complete) self.failUnless(pages[1].continued) self.failUnlessEqual(OggPage.to_packets(pages), packets)
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 test_page_max_size_alone_too_big(self): page = OggPage() page.packets = [b"1" * 255 * 255] page.complete = True self.failUnlessRaises(ValueError, page.write)
def test_complete_zero_length(self): packets = [b""] * 20 page = OggPage.from_packets(packets)[0] new_page = OggPage(BytesIO(page.write())) self.failUnlessEqual(new_page, page) self.failUnlessEqual(OggPage.to_packets([new_page]), packets)
def test_find_last_invalid_sync(self): data = BytesIO(b"if you think this is an OggS, you're crazy") page = OggPage.find_last(data, 0) self.failIf(page)
def test_too_many_packets(self): packets = [b"1"] * 3000 pages = OggPage.from_packets(packets) map(OggPage.write, pages) self.failUnless(len(pages) > 3000 // 255)
def test_flags_next_page(self): page = OggPage(self.fileobj) self.failIf(page.first) self.failIf(page.continued) self.failIf(page.last)
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 = BytesIO(data) self.failUnlessEqual(OggPage(fileobj), page) self.failUnlessEqual(OggPage(fileobj), page2) self.failUnlessRaises(EOFError, OggPage, fileobj)
def test_replace_fast_path(self): # create interleaved pages fileobj = BytesIO() pages = [OggPage(), OggPage(), OggPage()] pages[0].serial = 42 pages[0].sequence = 0 pages[0].packets = [b"foo"] pages[1].serial = 24 pages[1].sequence = 0 pages[1].packets = [b"bar"] pages[2].serial = 42 pages[2].sequence = 1 pages[2].packets = [b"baz"] for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] old_pages = [pages_from_file[0], pages_from_file[2]] packets = OggPage.to_packets(old_pages, strict=True) self.assertEqual(packets, [b"foo", b"baz"]) new_packets = [b"111", b"222"] new_pages = OggPage._from_packets_try_preserve(new_packets, old_pages) self.assertEqual(len(new_pages), 2) # remove insert_bytes, so we can be sure the fast path was taken old_insert_bytes = _util.insert_bytes _util.insert_bytes = None try: OggPage.replace(fileobj, old_pages, new_pages) finally: _util.insert_bytes = old_insert_bytes # validate that the new data was written and the other pages # are untouched fileobj.seek(0, 0) pages_from_file = [ OggPage(fileobj), OggPage(fileobj), OggPage(fileobj) ] packets = OggPage.to_packets([pages_from_file[0], pages_from_file[2]], strict=True) self.assertEqual(packets, [b"111", b"222"]) packets = OggPage.to_packets([pages_from_file[1]], strict=True) self.assertEqual(packets, [b"bar"])
def test_theora_not_first_page(self): page = OggPage(open(self.filename, "rb")) page.first = False fileobj = cBytesIO(page.write()) self.failUnlessRaises(IOError, OggTheoraInfo, fileobj)
def _post_tags(self, fileobj): page = OggPage.find_last(fileobj, self.serial, finishing=True) if page is None: raise OggOpusHeaderError self.length = (page.position - self.__pre_skip) / float(48000)
def test_replace_continued(self): # take a partial packet and replace it with a new page # replace() should make it spanning again fileobj = BytesIO() pages = [OggPage(), OggPage()] pages[0].serial = 1 pages[0].sequence = 0 pages[0].complete = False pages[0].packets = [b"foo"] pages[1].serial = 1 pages[1].sequence = 1 pages[1].continued = True pages[1].packets = [b"bar"] fileobj = BytesIO() for page in pages: fileobj.write(page.write()) fileobj.seek(0, 0) pages_from_file = [OggPage(fileobj), OggPage(fileobj)] self.assertEqual(OggPage.to_packets(pages_from_file), [b"foobar"]) packets_part = OggPage.to_packets([pages_from_file[0]]) self.assertEqual(packets_part, [b"foo"]) new_pages = OggPage.from_packets([b"quuux"]) OggPage.replace(fileobj, [pages_from_file[0]], new_pages) fileobj.seek(0, 0) written = OggPage.to_packets([OggPage(fileobj), OggPage(fileobj)]) self.assertEquals(written, [b"quuuxbar"])