mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2026-10-01 17:27:42 +00:00
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
import struct
|
|
|
|
# from .umid_parser import UMIDParser
|
|
from typing import Optional
|
|
|
|
|
|
class WavBextReader:
|
|
def __init__(self, bext_data, encoding):
|
|
"""
|
|
Read Broadcast-WAV extended metadata.
|
|
:param bext_data: The bytes-like data.
|
|
:param encoding: The encoding to use when decoding the text fields of
|
|
the BEXT metadata scope. According to EBU Rec 3285 this shall be
|
|
ASCII.
|
|
"""
|
|
packstring = (
|
|
"<256s" + "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s"
|
|
)
|
|
|
|
rest_starts = struct.calcsize(packstring)
|
|
unpacked = struct.unpack(packstring, bext_data[:rest_starts])
|
|
|
|
def sanitize_bytes(b: bytes) -> str:
|
|
# honestly can't remember why I'm stripping nulls this way
|
|
first_null = next(
|
|
(index for index, byte in enumerate(b) if byte == 0), None
|
|
)
|
|
trimmed = b if first_null is None else b[:first_null]
|
|
decoded = trimmed.decode(encoding)
|
|
return decoded
|
|
|
|
#: Description. A free-text field up to 256 characters long.
|
|
self.description: str = sanitize_bytes(unpacked[0])
|
|
|
|
#: Originator. Usually the name of the encoding application, sometimes
|
|
#: an artist name.
|
|
self.originator: str = sanitize_bytes(unpacked[1])
|
|
|
|
#: A unique identifier for the file, a serial number.
|
|
self.originator_ref: str = sanitize_bytes(unpacked[2])
|
|
|
|
#: Date of the recording, in the format YYYY-MM-DD.
|
|
self.originator_date: str = sanitize_bytes(unpacked[3])
|
|
|
|
#: Time of the recording, in the format HH:MM:SS.
|
|
self.originator_time: str = sanitize_bytes(unpacked[4])
|
|
|
|
#: The sample offset of the start, usually relative
|
|
#: to midnight.
|
|
self.time_reference: int = unpacked[5]
|
|
|
|
#: A variable-length text field containing a list of processes and
|
|
#: and conversions performed on the file.
|
|
self.coding_history: str = sanitize_bytes(bext_data[rest_starts:])
|
|
|
|
#: BEXT version.
|
|
self.version: int = unpacked[6]
|
|
|
|
#: SMPTE 330M UMID of this audio file, 64 bytes are allocated though
|
|
#: the UMID may only be 32 bytes long.
|
|
self.umid: Optional[bytes] = None
|
|
|
|
#: EBU R128 Integrated loudness, in LUFS.
|
|
self.loudness_value: Optional[float] = None
|
|
|
|
#: EBU R128 Loudness range, in LUFS.
|
|
self.loudness_range: Optional[float] = None
|
|
|
|
#: True peak level, in dBFS TP
|
|
self.max_true_peak: Optional[float] = None
|
|
|
|
#: EBU R128 Maximum momentary loudness, in LUFS
|
|
self.max_momentary_loudness: Optional[float] = None
|
|
|
|
#: EBU R128 Maximum short-term loudness, in LUFS.
|
|
self.max_shortterm_loudness: Optional[float] = None
|
|
|
|
if self.version > 0:
|
|
self.umid = unpacked[7]
|
|
|
|
if self.version > 1:
|
|
self.loudness_value = unpacked[8] / 100.0
|
|
self.loudness_range = unpacked[9] / 100.0
|
|
self.max_true_peak = unpacked[10] / 100.0
|
|
self.max_momentary_loudness = unpacked[11] / 100.0
|
|
self.max_shortterm_loudness = unpacked[12] / 100.0
|
|
|
|
def to_dict(self):
|
|
# if self.umid is not None:
|
|
# umid_parsed = UMIDParser(self.umid)
|
|
# umid_str = umid_parsed.basic_umid_to_str()
|
|
# else:
|
|
|
|
# umid_str = None
|
|
|
|
return {
|
|
"description": self.description,
|
|
"originator": self.originator,
|
|
"originator_ref": self.originator_ref,
|
|
"originator_date": self.originator_date,
|
|
"originator_time": self.originator_time,
|
|
"time_reference": self.time_reference,
|
|
"version": self.version,
|
|
"umid": self.umid,
|
|
"coding_history": self.coding_history,
|
|
"loudness_value": self.loudness_value,
|
|
"loudness_range": self.loudness_range,
|
|
"max_true_peak": self.max_true_peak,
|
|
"max_momentary_loudness": self.max_momentary_loudness,
|
|
"max_shortterm_loudness": self.max_shortterm_loudness,
|
|
}
|