Source code for audio_metadata.formats.vorbiscomments
# https://xiph.org/vorbis/doc/v-comment.html
__all__ = [
'VorbisComment',
'VorbisComments',
]
import struct
from collections import defaultdict
from attr import (
attrib,
attrs,
)
from tbm_utils import datareader
from ..exceptions import (
FormatError,
TagError,
)
from ..models import (
Tag,
Tags,
)
[docs]@attrs(
repr=False,
kw_only=True,
)
class VorbisComment(Tag):
name = attrib(converter=lambda n: n.lower())
@staticmethod
def _validate_name(name):
return all(
(
char >= ' '
and char <= '}'
and char != '='
)
for char in name
)
@datareader
@classmethod
def parse(cls, data):
length = struct.unpack('I', data.read(4))[0]
comment = data.read(length).decode('utf-8', 'replace')
if '=' not in comment:
raise FormatError("Vorbis comment must contain an ``=``.")
name, value = comment.split('=', 1)
if not cls._validate_name(name):
raise TagError(f"Invalid character in Vorbis comment name: ``{name}``.")
return cls(
name=name,
value=value,
)
# TODO: Number frames.
[docs]class VorbisComments(Tags):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in self.keys():
if not VorbisComment._validate_name(key):
raise TagError(f"Invalid character in Vorbis comment name: ``{key}``.")
@datareader
@classmethod
def parse(cls, data):
vendor_length = struct.unpack('I', data.read(4))[0]
vendor = data.read(vendor_length).decode('utf-8', 'replace')
num_comments = struct.unpack('I', data.read(4))[0]
fields = defaultdict(list)
for _ in range(num_comments):
comment = VorbisComment.parse(data)
fields[comment.name].append(comment.value)
return cls(
fields,
_vendor=vendor,
)