--- /dev/null
+# Ogg Vorbis support.
+#
+# Copyright 2006 Joe Wreschnig <piman@sacredchao.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# $Id: oggvorbis.py 4275 2008-06-01 06:32:37Z piman $
+
+"""Read and write Ogg Vorbis comments.
+
+This module handles Vorbis files wrapped in an Ogg bitstream. The
+first Vorbis stream found is used.
+
+Read more about Ogg Vorbis at http://vorbis.com/. This module is based
+on the specification at http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html.
+"""
+
+__all__ = ["OggVorbis", "Open", "delete"]
+
+import struct
+
+from mutagen._vorbis import VCommentDict
+from mutagen.ogg import OggPage, OggFileType, error as OggError
+
+class error(OggError): pass
+class OggVorbisHeaderError(error): pass
+
+class OggVorbisInfo(object):
+ """Ogg Vorbis stream information.
+
+ Attributes:
+ length - file length in seconds, as a float
+ bitrate - nominal ('average') bitrate in bits per second, as an int
+ """
+
+ length = 0
+
+ def __init__(self, fileobj):
+ page = OggPage(fileobj)
+ while not page.packets[0].startswith("\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
+
+ 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 pprint(self):
+ return "Ogg Vorbis, %.2f seconds, %d bps" % (self.length, self.bitrate)
+
+class OggVCommentDict(VCommentDict):
+ """Vorbis comments embedded in an Ogg bitstream."""
+
+ 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 _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("\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] = "\x03vorbis" + self.write()
+
+ new_pages = OggPage.from_packets(packets, old_pages[0].sequence)
+ OggPage.replace(fileobj, old_pages, new_pages)
+
+class OggVorbis(OggFileType):
+ """An Ogg Vorbis file."""
+
+ _Info = OggVorbisInfo
+ _Tags = OggVCommentDict
+ _Error = OggVorbisHeaderError
+ _mimes = ["audio/vorbis", "audio/x-vorbis"]
+
+ def score(filename, fileobj, header):
+ return (header.startswith("OggS") * ("\x01vorbis" in header))
+ score = staticmethod(score)
+
+Open = OggVorbis
+
+def delete(filename):
+ """Remove tags from a file."""
+ OggVorbis(filename).delete()