Skip to content

Commit a161810

Browse files
authored
oggopus: provide a bitrate property (#704)
Estimate the bitrate based on the file size when seeking after the tag pages. This makes sure the bitrate doesn't change when changing the tags. Fixes #475
1 parent 905e815 commit a161810

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

mutagen/oggopus.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ class OggOpusInfo(StreamInfo):
4141
Attributes:
4242
length (`float`): File length in seconds, as a float
4343
channels (`int`): Number of channels
44+
bitrate (`int`): Bitrate in bits per second, as an int
4445
"""
4546

4647
length = 0
4748
channels = 0
49+
bitrate = 0
4850

4951
def __init__(self, fileobj):
5052
page = OggPage(fileobj)
@@ -68,13 +70,20 @@ def __init__(self, fileobj):
6870
raise OggOpusHeaderError("version %r unsupported" % major)
6971

7072
def _post_tags(self, fileobj):
73+
audio_size = get_size(fileobj) - fileobj.tell()
74+
7175
page = OggPage.find_last(fileobj, self.serial, finishing=True)
7276
if page is None:
7377
raise OggOpusHeaderError
7478
self.length = (page.position - self.__pre_skip) / float(48000)
7579

80+
if self.length:
81+
self.bitrate = round(audio_size * 8 / self.length)
82+
else:
83+
self.bitrate = 0
84+
7685
def pprint(self):
77-
return u"Ogg Opus, %.2f seconds" % (self.length)
86+
return u"Ogg Opus, %.2f seconds, %d bps" % (self.length, self.bitrate)
7887

7988

8089
class OggOpusVComment(VCommentDict):

tests/test_oggopus.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,45 @@ def test_preserve_non_padding(self):
7979

8080
def test_init_padding(self):
8181
self.assertEqual(self.audio.tags._padding, 196)
82+
83+
def test_pprint(self):
84+
assert self.audio.info.pprint() == "Ogg Opus, 11.35 seconds, 45243 bps"
85+
86+
def test_bitrate(self):
87+
assert self.audio.info.bitrate == 45243
88+
89+
def test_bitrate_stable_after_tag_change(self):
90+
bitrate_before = self.audio.info.bitrate
91+
assert bitrate_before != 0
92+
93+
self.audio["ARTIST"] = ["Test Artist"]
94+
self.audio["ALBUM"] = ["Test Album"]
95+
self.audio["TITLE"] = ["Test Title"]
96+
self.audio.save(padding=lambda x: 0)
97+
98+
audio_after = self.Kind(self.filename)
99+
bitrate_after = audio_after.info.bitrate
100+
assert bitrate_before == bitrate_after
101+
102+
def test_bitrate_stable_with_large_metadata(self):
103+
bitrate_initial = self.audio.info.bitrate
104+
assert bitrate_initial != 0
105+
106+
large_comment = "x" * 50000
107+
self.audio["COMMENT"] = [large_comment]
108+
self.audio["DESCRIPTION"] = [large_comment]
109+
self.audio["CUSTOM"] = [large_comment]
110+
self.audio.save(padding=lambda x: 0)
111+
112+
audio_large = self.Kind(self.filename)
113+
bitrate_after_large = audio_large.info.bitrate
114+
assert bitrate_initial == bitrate_after_large
115+
116+
audio_large["COMMENT"] = ["small"]
117+
del audio_large["DESCRIPTION"]
118+
del audio_large["CUSTOM"]
119+
audio_large.save(padding=lambda x: 0)
120+
121+
audio_small = self.Kind(self.filename)
122+
bitrate_after_small = audio_small.info.bitrate
123+
assert bitrate_initial == bitrate_after_small

0 commit comments

Comments
 (0)