UMID Implementation

This commit is contained in:
Jamie Hardt
2020-01-06 08:27:34 -08:00
parent 1b9547e8c2
commit 5a30ce3afc
4 changed files with 185 additions and 203 deletions

Binary file not shown.

View File

@@ -1,5 +1,10 @@
import struct
from typing import Union from typing import Union
import binascii
def binary_to_string(binary_value):
return str(binascii.hexlify(binary_value), encoding='ascii')
class UMIDParser: class UMIDParser:
""" """
@@ -9,121 +14,111 @@ class UMIDParser:
""" """
def __init__(self, raw_umid: bytearray): def __init__(self, raw_umid: bytearray):
self.raw_umid = raw_umid self.raw_umid = raw_umid
#
@classmethod # @property
def binary_to_string(cls, binary_value): # def universal_label(self) -> bytearray:
result_str = '' # return self.raw_umid[0:12]
for n in range(len(binary_value)): #
result_str = '{:x}'.format(binary_value[n]) + result_str # @property
# def basic_umid(self):
return result_str # return self.raw_umid[0:32]
@property
def universal_label(self) -> bytearray:
return self.raw_umid[0:12]
@property
def basic_umid(self):
return self.raw_umid[0:32]
def basic_umid_to_str(self): def basic_umid_to_str(self):
return "%024x-%06x-%032x" % (self.binary_to_string(self.universal_label), return binary_to_string(self.raw_umid[0:13]) + '-' + binary_to_string(self.raw_umid[13:3])
self.binary_to_string(self.instance_number), #
self.binary_to_string(self.material_number)) # @property
# def universal_label_is_valid(self) -> bool:
@property # valid_preamble = b'\x06\x0a\x2b\x34\x01\x01\x01\x05\x01\x01'
def universal_label_is_valid(self) -> bool: # return self.universal_label[0:len(valid_preamble)] == valid_preamble
valid_preamble = b'\x06\x0a\x2b\x34\x01\x01\x01\x05\x01\x01' #
return self.universal_label[0:len(valid_preamble)] == valid_preamble # @property
# def material_type(self) -> str:
@property # material_byte = self.raw_umid[10]
def material_type(self) -> str: # if material_byte == 0x1:
material_byte = self.raw_umid[10] # return 'picture'
if material_byte == 0x1: # elif material_byte == 0x2:
return 'picture' # return 'audio'
elif material_byte == 0x2: # elif material_byte == 0x3:
return 'audio' # return 'data'
elif material_byte == 0x3: # elif material_byte == 0x4:
return 'data' # return 'other'
elif material_byte == 0x4: # elif material_byte == 0x5:
return 'other' # return 'picture_single_component'
elif material_byte == 0x5: # elif material_byte == 0x6:
return 'picture_single_component' # return 'picture_multiple_component'
elif material_byte == 0x6: # elif material_byte == 0x7:
return 'picture_multiple_component' # return 'audio_single_component'
elif material_byte == 0x7: # elif material_byte == 0x9:
return 'audio_single_component' # return 'audio_multiple_component'
elif material_byte == 0x9: # elif material_byte == 0xb:
return 'audio_multiple_component' # return 'auxiliary_single_component'
elif material_byte == 0xb: # elif material_byte == 0xc:
return 'auxiliary_single_component' # return 'auxiliary_multiple_component'
elif material_byte == 0xc: # elif material_byte == 0xd:
return 'auxiliary_multiple_component' # return 'mixed_components'
elif material_byte == 0xd: # elif material_byte == 0xf:
return 'mixed_components' # return 'not_identified'
elif material_byte == 0xf: # else:
return 'not_identified' # return 'not_recognized'
else: #
return 'not_recognized' # @property
# def material_number_creation_method(self) -> str:
@property # method_byte = self.raw_umid[11]
def material_number_creation_method(self) -> str: # method_byte = (method_byte << 4) & 0xf
method_byte = self.raw_umid[11] # if method_byte == 0x0:
method_byte = (method_byte << 4) & 0xf # return 'undefined'
if method_byte == 0x0: # elif method_byte == 0x1:
return 'undefined' # return 'smpte'
elif method_byte == 0x1: # elif method_byte == 0x2:
return 'smpte' # return 'uuid'
elif method_byte == 0x2: # elif method_byte == 0x3:
return 'uuid' # return 'masked'
elif method_byte == 0x3: # elif method_byte == 0x4:
return 'masked' # return 'ieee1394'
elif method_byte == 0x4: # elif 0x5 <= method_byte <= 0x7:
return 'ieee1394' # return 'reserved_undefined'
elif 0x5 <= method_byte <= 0x7: # else:
return 'reserved_undefined' # return 'unrecognized'
else: #
return 'unrecognized' # @property
# def instance_number_creation_method(self) -> str:
@property # method_byte = self.raw_umid[11]
def instance_number_creation_method(self) -> str: # method_byte = method_byte & 0xf
method_byte = self.raw_umid[11] # if method_byte == 0x0:
method_byte = method_byte & 0xf # return 'undefined'
if method_byte == 0x0: # elif method_byte == 0x01:
return 'undefined' # return 'local_registration'
elif method_byte == 0x01: # elif method_byte == 0x02:
return 'local_registration' # return '24_bit_prs'
elif method_byte == 0x02: # elif method_byte == 0x03:
return '24_bit_prs' # return 'copy_number_and_16_bit_prs'
elif method_byte == 0x03: # elif 0x04 <= method_byte <= 0x0e:
return 'copy_number_and_16_bit_prs' # return 'reserved_undefined'
elif 0x04 <= method_byte <= 0x0e: # elif method_byte == 0x0f:
return 'reserved_undefined' # return 'live_stream'
elif method_byte == 0x0f: # else:
return 'live_stream' # return 'unrecognized'
else: #
return 'unrecognized' # @property
# def indicated_length(self) -> str:
@property # if self.raw_umid[12] == 0x13:
def indicated_length(self) -> str: # return 'basic'
if self.raw_umid[12] == 0x13: # elif self.raw_umid[12] == 0x33:
return 'basic' # return 'extended'
elif self.raw_umid[12] == 0x33: #
return 'extended' # @property
# def instance_number(self) -> bytearray:
@property # return self.raw_umid[13:3]
def instance_number(self) -> bytearray: #
return self.raw_umid[13:3] # @property
# def material_number(self) -> bytearray:
@property # return self.raw_umid[16:16]
def material_number(self) -> bytearray: #
return self.raw_umid[16:16] # @property
# def source_pack(self) -> Union[bytearray, None]:
@property # if self.indicated_length == 'extended':
def source_pack(self) -> Union[bytearray, None]: # return self.raw_umid[32:32]
if self.indicated_length == 'extended': # else:
return self.raw_umid[32:32] # return None
else:
return None

View File

@@ -1,21 +1,22 @@
import struct import struct
import binascii import binascii
from .umid_parser import UMIDParser
class WavBextReader: class WavBextReader:
def __init__(self,bext_data,encoding): def __init__(self, bext_data, encoding):
""" """
Read Broadcast-WAV extended metadata. Read Broadcast-WAV extended metadata.
:param best_data: The bytes-like data. :param best_data: The bytes-like data.
"param encoding: The encoding to use when decoding the text fields of the "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. BEXT metadata scope. According to EBU Rec 3285 this shall be ASCII.
""" """
packstring = "<256s"+ "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s" packstring = "<256s" + "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s"
rest_starts = struct.calcsize(packstring) rest_starts = struct.calcsize(packstring)
unpacked = struct.unpack(packstring, bext_data[:rest_starts]) unpacked = struct.unpack(packstring, bext_data[:rest_starts])
def sanatize_bytes(bytes): def sanatize_bytes(bytes):
first_null = next( (index for index, byte in enumerate(bytes) if byte == 0 ), None ) first_null = next((index for index, byte in enumerate(bytes) if byte == 0), None)
if first_null is not None: if first_null is not None:
trimmed = bytes[:first_null] trimmed = bytes[:first_null]
else: else:
@@ -67,13 +68,13 @@ class WavBextReader:
self.max_momentary_loudness = unpacked[11] / 100.0 self.max_momentary_loudness = unpacked[11] / 100.0
self.max_shortterm_loudness = unpacked[12] / 100.0 self.max_shortterm_loudness = unpacked[12] / 100.0
def umid_to_str(self):
if self.umid:
return str(binascii.hexlify(self.umid), encoding='ascii')
else:
return None
def to_dict(self): 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, return {'description': self.description,
'originator': self.originator, 'originator': self.originator,
'originator_ref': self.originator_ref, 'originator_ref': self.originator_ref,
@@ -81,7 +82,7 @@ class WavBextReader:
'originator_time': self.originator_time, 'originator_time': self.originator_time,
'time_reference': self.time_reference, 'time_reference': self.time_reference,
'version': self.version, 'version': self.version,
'umid': self.umid_to_str(), 'umid': umid_str,
'coding_history': self.coding_history, 'coding_history': self.coding_history,
'loudness_value': self.loudness_value, 'loudness_value': self.loudness_value,
'loudness_range': self.loudness_range, 'loudness_range': self.loudness_range,
@@ -89,4 +90,3 @@ class WavBextReader:
'max_momentary_loudness': self.max_momentary_loudness, 'max_momentary_loudness': self.max_momentary_loudness,
'max_shortterm_loudness': self.max_shortterm_loudness 'max_shortterm_loudness': self.max_shortterm_loudness
} }

View File

@@ -1,6 +1,6 @@
from .riff_parser import parse_chunk, ListChunkDescriptor from .riff_parser import parse_chunk, ListChunkDescriptor
class WavInfoChunkReader: class WavInfoChunkReader:
def __init__(self, f, encoding): def __init__(self, f, encoding):
@@ -9,53 +9,48 @@ class WavInfoChunkReader:
f.seek(0) f.seek(0)
parsed_chunks = parse_chunk(f) parsed_chunks = parse_chunk(f)
list_chunks = [chunk for chunk in parsed_chunks.children \ list_chunks = [chunk for chunk in parsed_chunks.children if type(chunk) is ListChunkDescriptor]
if type(chunk) is ListChunkDescriptor]
self.info_chunk = next((chunk for chunk in list_chunks \ self.info_chunk = next((chunk for chunk in list_chunks if chunk.signature == b'INFO'), None)
if chunk.signature == b'INFO'), None)
#: 'ICOP' Copyright #: 'ICOP' Copyright
self.copyright = self._get_field(f,b'ICOP') self.copyright = self._get_field(f, b'ICOP')
#: 'IPRD' Product #: 'IPRD' Product
self.product = self._get_field(f,b'IPRD') self.product = self._get_field(f, b'IPRD')
#: 'IGNR' Genre #: 'IGNR' Genre
self.genre = self._get_field(f,b'IGNR') self.genre = self._get_field(f, b'IGNR')
#: 'ISBJ' Supject #: 'ISBJ' Supject
self.subject = self._get_field(f,b'ISBJ') self.subject = self._get_field(f, b'ISBJ')
#: 'IART' Artist, composer, author #: 'IART' Artist, composer, author
self.artist = self._get_field(f,b'IART') self.artist = self._get_field(f, b'IART')
#: 'ICMT' Comment #: 'ICMT' Comment
self.comment = self._get_field(f,b'ICMT') self.comment = self._get_field(f, b'ICMT')
#: 'ISFT' Software, encoding application #: 'ISFT' Software, encoding application
self.software = self._get_field(f,b'ISFT') self.software = self._get_field(f, b'ISFT')
#: 'ICRD' Created date #: 'ICRD' Created date
self.created_date = self._get_field(f,b'ICRD') self.created_date = self._get_field(f, b'ICRD')
#: 'IENG' Engineer #: 'IENG' Engineer
self.engineer = self._get_field(f,b'IENG') self.engineer = self._get_field(f, b'IENG')
#: 'ITCH' Technician #: 'ITCH' Technician
self.technician = self._get_field(f,b'ITCH') self.technician = self._get_field(f, b'ITCH')
#: 'IKEY' Keywords, keyword list #: 'IKEY' Keywords, keyword list
self.keywords = self._get_field(f,b'IKEY') self.keywords = self._get_field(f, b'IKEY')
#: 'INAM' Name, title #: 'INAM' Name, title
self.title = self._get_field(f,b'INAM') self.title = self._get_field(f, b'INAM')
#: 'ISRC' Source #: 'ISRC' Source
self.source = self._get_field(f,b'ISRC') self.source = self._get_field(f, b'ISRC')
#: 'TAPE' Tape #: 'TAPE' Tape
self.tape = self._get_field(f,b'TAPE') self.tape = self._get_field(f, b'TAPE')
#: 'IARL' Archival Location #: 'IARL' Archival Location
self.archival_location = self._get_field(f,b'IARL') self.archival_location = self._get_field(f, b'IARL')
#: 'ISFT' Software #: 'ISFT' Software
self.software = self._get_field(f,b'ISFT') self.software = self._get_field(f, b'ISFT')
#: 'ICSM' Commissioned #: 'ICSM' Commissioned
self.commissioned = self._get_field(f,b'ICMS') self.commissioned = self._get_field(f, b'ICMS')
def _get_field(self, f, field_ident): def _get_field(self, f, field_ident):
search = next(((chunk.start, chunk.length) for chunk in self.info_chunk.children if chunk.ident == field_ident),
search = next( ( (chunk.start, chunk.length) for chunk in self.info_chunk.children \ None)
if chunk.ident == field_ident ), None)
if search is not None: if search is not None:
f.seek(search[0]) f.seek(search[0])
@@ -64,7 +59,6 @@ class WavInfoChunkReader:
else: else:
return None return None
def to_dict(self): def to_dict(self):
""" """
A dictionary with all of the key/values read from the INFO scope. A dictionary with all of the key/values read from the INFO scope.
@@ -82,14 +76,7 @@ class WavInfoChunkReader:
'source': self.source, 'source': self.source,
'tape': self.tape, 'tape': self.tape,
'commissioned': self.commissioned, 'commissioned': self.commissioned,
'software': self.software, 'archival_location': self.archival_location,
'archival_location':self.archival_location,
'subject': self.subject, 'subject': self.subject,
'technician':self.technician 'technician': self.technician
} }