Fixes and testing with files

This commit is contained in:
Jamie Hardt
2018-12-31 14:00:57 -08:00
parent e1a8c3a395
commit 37a9849b4e
4 changed files with 124 additions and 101 deletions

1
wavinfo/__init__.py Normal file
View File

@@ -0,0 +1 @@
from .wave_reader import WavInfoReader

View File

@@ -4,38 +4,40 @@ class WavIXMLFormat:
""" """
iXML recorder metadata, as defined by iXML 2.0 iXML recorder metadata, as defined by iXML 2.0
""" """
def __init__(xml): def __init__(self, xml):
self.source = xml self.source = xml
self.parsed = ET.fromstring(xml) self.parsed = ET.fromstring(xml)
def _get_text_value(xpath): def _get_text_value(self, xpath):
root = self.parsed.getroot() print("xpath",xpath)
e = root.find("//BWFXML/" + xpath) print("search", "./" + xpath)
if e: e = self.parsed.find("./" + xpath)
print("result was", e)
if e is not None:
return e.text return e.text
@property @property
def project(self): def project(self):
return _get_text_value("PROJECT") return self._get_text_value("PROJECT")
@property @property
def scene(self): def scene(self):
return _get_text_value("SCECE") return self._get_text_value("SCENE")
@property @property
def take(self): def take(self):
return _get_text_value("TAKE") return self._get_text_value("TAKE")
@property @property
def tape(self): def tape(self):
return _get_text_value("TAPE") return self._get_text_value("TAPE")
@property @property
def family_uid(self): def family_uid(self):
return _get_text_value("FILE_SET/FAMILY_UID") return self._get_text_value("FILE_SET/FAMILY_UID")
@property @property
def family_name(self): def family_name(self):
return _get_text_value("FILE_SET/FAMILY_NAME") return self._get_text_value("FILE_SET/FAMILY_NAME")

View File

@@ -1,16 +1,19 @@
import struct import struct
from .wave_ixml_reader import WavIXMLFormat
from collections import namedtuple from collections import namedtuple
ListChunkDescriptor = namedtuple('ListChunk' , 'signature children') ListChunkDescriptor = namedtuple('ListChunk' , 'signature children')
class ChunkDescriptor(namedtuple('Chunk', 'ident start length'): class ChunkDescriptor(namedtuple('Chunk', 'ident start length') ):
def read_data(from_stream): def read_data(self, from_stream):
from_stream.seek(start) from_stream.seek(self.start)
return from_stream.read(length) return from_stream.read(self.length)
def parse_list_chunk(stream, length): def parse_list_chunk(stream, length):
@@ -50,95 +53,12 @@ def parse_chunk(stream):
WavInfoFormat = namedtuple("WavInfoFormat",'audio_format channel_count sample_rate byte_rate block_align bits_per_sample') WavInfoFormat = namedtuple("WavInfoFormat",'audio_format channel_count sample_rate byte_rate block_align bits_per_sample')
WavBextFormat = namedtuple("WavBextFormat",'description originator originator_ref ' + WavBextFormat = namedtuple("WavBextFormat",'description originator originator_ref ' +
'originator_date originator_time time_reference version umid loudness_value ' + 'originator_date originator_time time_reference version umid ' +
'loudness_range max_true_peak max_momentary_loudness max_shortterm_loudness coding_history') 'loudness_value loudness_range max_true_peak max_momentary_loudness max_shortterm_loudness ' +
'coding_history')
class WavInfoReader( namedtuple("_WavInfoReaderImpl", "format bext ixml") ):
"""
format : WAV format
bext : The Broadcast-WAV extension as definied by EBU Tech 3285 v2 (2011)
"""
def __init__(self, path):
with open(path, 'rb') as f:
chunks = parse_chunk(f)
f.seek(0)
self.format = _get_format(chunks,f)
self.info = _get_bext(chunks,f)
self.ixml = _get_ixml(chunks,f)
def _get_format(chunks,f):
fmt_chunk = next(chunk for chunk in chunks if chunk.ident == b'fmt ')
fmt_data = chunk.read_data(from_stream=f)
# The format chunk is
# audio_format U16
# channel_count U16
# sample_rate U32 Note an integer
# byte_rate U32 == SampleRate * NumChannels * BitsPerSample/8
# block_align U16 == NumChannels * BitsPerSample/8
# bits_per_sampl U16
unpacked = struct.unpack("<HHIIHH", fmt_data)
return WavInfoFormat(audio_format = unpacked[0],
channel_count = unpacked[1],
sample_rate = unpacked[2],
byte_rate = unpacked[3],
block_align = unpacked[4],
bits_per_sample = unpacked[5]
)
def _get_bext(chunks,f):
fmt_chunk = next(chunk for chunk in chunks if chunk.ident == b'bext')
fmt_data = chunk.read_data(from_stream=f)
# description[256]
# originator[32]
# originatorref[32]
# originatordate[10] "YYYY:MM:DD"
# originatortime[8] "HH:MM:SS"
# lowtimeref U32
# hightimeref U32
# version U16
# umid[64]
# loudnessvalue S16 (in LUFS*100)
# loudnessrange S16 (in LUFS*100)
# maxtruepeak S16 (in dbTB*100)
# maxmomentaryloudness S16 (LUFS*100)
# maxshorttermloudness S16 (LUFS*100)
# reserved[180]
# codinghistory []
packstring = "<256s"+ "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s"
unpacked = struct.unpack(packstring, chunk)
rest_starts = struct.calcsize(packstring)
return WavBextFormat(description=unpacked[0],
originator = unpacked[1],
originator_ref = unpacked[2],
originator_date = unpacked[3],
originator_time = unpacked[4],
time_reference = unpacked[5],
version = unpacked[6],
umid = unpacked[7],
loudness_value = unpacked[8],
loudness_range = unpacked[9],
max_true_peak = unpacked[10],
max_momentary_loudness = unpacked[11],
max_shortterm_loudness = unpacked[12],
coding_history = fmt_data[rest_starts:]
)

100
wavinfo/wave_reader.py Normal file
View File

@@ -0,0 +1,100 @@
import struct
class WavInfoReader():
"""
format : WAV format
bext : The Broadcast-WAV extension as definied by EBU Tech 3285 v2 (2011)
"""
def __init__(self, path):
with open(path, 'rb') as f:
chunks = parse_chunk(f)
main_list = chunks.children
f.seek(0)
self.fmt = self._get_format(main_list,f)
self.info = self._get_bext(main_list,f)
self.ixml = self._get_ixml(main_list,f)
def _get_format(self,chunks,f):
fmt_chunk = next(chunk for chunk in chunks if chunk.ident == b'fmt ')
fmt_data = fmt_chunk.read_data(f)
# The format chunk is
# audio_format U16
# channel_count U16
# sample_rate U32 Note an integer
# byte_rate U32 == SampleRate * NumChannels * BitsPerSample/8
# block_align U16 == NumChannels * BitsPerSample/8
# bits_per_sampl U16
unpacked = struct.unpack("<HHIIHH", fmt_data)
return WavInfoFormat(audio_format = unpacked[0],
channel_count = unpacked[1],
sample_rate = unpacked[2],
byte_rate = unpacked[3],
block_align = unpacked[4],
bits_per_sample = unpacked[5]
)
def _get_bext(self,chunks,f):
bext_chunk = next((chunk for chunk in chunks if chunk.ident == b'bext'),None)
bext_data = bext_chunk.read_data(from_stream=f)
# description[256]
# originator[32]
# originatorref[32]
# originatordate[10] "YYYY:MM:DD"
# originatortime[8] "HH:MM:SS"
# lowtimeref U32
# hightimeref U32
# version U16
# umid[64]
#
# EBU 3285 fields
# loudnessvalue S16 (in LUFS*100)
# loudnessrange S16 (in LUFS*100)
# maxtruepeak S16 (in dbTB*100)
# maxmomentaryloudness S16 (LUFS*100)
# maxshorttermloudness S16 (LUFS*100)
# reserved[180]
# codinghistory []
print(len(bext_data))
packstring = "<256s"+ "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s"
rest_starts = struct.calcsize(packstring)
unpacked = struct.unpack(packstring, bext_data[:rest_starts])
return WavBextFormat(description=unpacked[0].decode('ascii').rstrip(' \t\r\n\0'),
originator = unpacked[1].decode('ascii').rstrip(' \t\r\n\0'),
originator_ref = unpacked[2].decode('ascii').rstrip(' \t\r\n\0'),
originator_date = unpacked[3].decode('ascii').rstrip(' \t\r\n\0'),
originator_time = unpacked[4].decode('ascii').rstrip(' \t\r\n\0'),
time_reference = unpacked[5],
version = unpacked[6],
umid = unpacked[7],
loudness_value = unpacked[8],
loudness_range = unpacked[9],
max_true_peak = unpacked[10],
max_momentary_loudness = unpacked[11],
max_shortterm_loudness = unpacked[12],
coding_history = bext_data[rest_starts:].decode('ascii').rstrip(' \t\r\n\0')
)
def _get_ixml(self,chunks,f):
ixml_chunk = next((chunk for chunk in chunks if chunk.ident == b'iXML'),None)
ixml_data = ixml_chunk.read_data(from_stream=f)
ixml_string = ixml_data.decode('utf-8')
return WavIXMLFormat(ixml_string)