mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 08:50:41 +00:00
Fixes and testing with files
This commit is contained in:
1
wavinfo/__init__.py
Normal file
1
wavinfo/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .wave_reader import WavInfoReader
|
||||||
@@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,92 +53,9 @@ 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
100
wavinfo/wave_reader.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user