mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 17:00:41 +00:00
ADM support
This commit is contained in:
@@ -14,9 +14,10 @@ The `wavinfo` package allows you to probe WAVE and [RF64/WAVE files][eburf64] an
|
|||||||
* Most of the common __RIFF INFO__<sup>[4][info-tags]</sup> metadata fields.
|
* Most of the common __RIFF INFO__<sup>[4][info-tags]</sup> metadata fields.
|
||||||
* The __wav format__ is also parsed, so you can access the basic sample rate and channel count
|
* The __wav format__ is also parsed, so you can access the basic sample rate and channel count
|
||||||
information.
|
information.
|
||||||
|
* EBU ADM track metadata<sup>[5][adm]</sup>, including track, channel and pack formats, object and content names.
|
||||||
|
|
||||||
In progress:
|
In progress:
|
||||||
* ADM metadata consilient with the output of the [__Dolby RMU__][dolby], and [EBU Tech 3285 Supplement 6][ebu3285s6].
|
* [Dolby RMU][dolby] metadata and [EBU Tech 3285 Supplement 6][ebu3285s6].
|
||||||
* iXML `STEINBERG` sound library attributes.
|
* iXML `STEINBERG` sound library attributes.
|
||||||
* __NetMix__ library attributes.
|
* __NetMix__ library attributes.
|
||||||
* Pro Tools __embedded regions__.
|
* Pro Tools __embedded regions__.
|
||||||
|
|||||||
@@ -17,8 +17,24 @@ class TestADMWave(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(len(adm.channel_uids), 14)
|
self.assertEqual(len(adm.channel_uids), 14)
|
||||||
|
|
||||||
# def test_to_dict(self):
|
def test_to_dict(self):
|
||||||
# info = wavinfo.WavInfoReader(self.protools_adm_wav)
|
info = wavinfo.WavInfoReader(self.protools_adm_wav)
|
||||||
# adm = info.adm
|
adm = info.adm
|
||||||
# dict = adm.to_dict()
|
dict = adm.to_dict()
|
||||||
|
self.assertIsNotNone(dict)
|
||||||
|
|
||||||
|
def test_track_info(self):
|
||||||
|
info = wavinfo.WavInfoReader(self.protools_adm_wav)
|
||||||
|
adm = info.adm
|
||||||
|
|
||||||
|
t1 = adm.track_info(0)
|
||||||
|
self.assertTrue("channel_format_name" in t1.keys())
|
||||||
|
self.assertEqual("RoomCentricLeft", t1["channel_format_name"])
|
||||||
|
|
||||||
|
self.assertTrue("pack_format_name" in t1.keys())
|
||||||
|
self.assertEqual("AtmosCustomPackFormat1", t1["pack_format_name"])
|
||||||
|
|
||||||
|
t10 = adm.track_info(10)
|
||||||
|
self.assertTrue("content_name" in t10.keys())
|
||||||
|
self.assertEqual("Dialog", t10["content_name"])
|
||||||
|
|
||||||
|
|||||||
@@ -3,35 +3,19 @@ ADM Reader
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from struct import unpack, unpack_from, calcsize
|
from struct import unpack, unpack_from, calcsize
|
||||||
from dataclasses import dataclass
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from collections import namedtuple
|
||||||
|
from typing import Iterable, Tuple
|
||||||
|
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ChannelEntry:
|
|
||||||
"""
|
|
||||||
A `chna` chunk table entry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
track_index: int
|
|
||||||
"Track index (indexed from 1)"
|
|
||||||
|
|
||||||
uid: str
|
|
||||||
"audioTrackUID"
|
|
||||||
|
|
||||||
track_ref: str
|
|
||||||
"audioTrackFormatID"
|
|
||||||
|
|
||||||
pack_ref: str
|
|
||||||
"audioPackFormatID"
|
|
||||||
|
|
||||||
|
|
||||||
class WavADMReader:
|
class WavADMReader:
|
||||||
"""
|
"""
|
||||||
Reads XML data from an EBU ADM (Audio Definiton Model) WAV File.
|
Reads XML data from an EBU ADM (Audio Definiton Model) WAV File.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ChannelEntry = namedtuple('ChannelEntry', "track_index uid track_ref pack_ref")
|
||||||
|
|
||||||
def __init__(self, axml_data: bytes, chna_data: bytes):
|
def __init__(self, axml_data: bytes, chna_data: bytes):
|
||||||
header_fmt = "<HH"
|
header_fmt = "<HH"
|
||||||
uid_fmt = "<H12s14s11sx"
|
uid_fmt = "<H12s14s11sx"
|
||||||
@@ -49,7 +33,54 @@ class WavADMReader:
|
|||||||
|
|
||||||
# these values are either ascii or all null
|
# these values are either ascii or all null
|
||||||
|
|
||||||
self.channel_uids.append(ChannelEntry(track_index,
|
self.channel_uids.append(WavADMReader.ChannelEntry(track_index,
|
||||||
uid.decode('ascii') , track_ref.decode('ascii'), pack_ref.decode('ascii')))
|
uid.decode('ascii') , track_ref.decode('ascii'), pack_ref.decode('ascii')))
|
||||||
|
|
||||||
offset += calcsize(uid_fmt)
|
offset += calcsize(uid_fmt)
|
||||||
|
|
||||||
|
|
||||||
|
def track_info(self, index):
|
||||||
|
"""
|
||||||
|
Information about a track in the WAV file.
|
||||||
|
|
||||||
|
:param index: index of audio track (indexed from zero)
|
||||||
|
:returns: a dictionary with content_name, object_name, pack_format_name, pack_type,
|
||||||
|
channel_format_name
|
||||||
|
"""
|
||||||
|
channel_info = next((x for x in self.channel_uids if x.track_index == index + 1), None)
|
||||||
|
|
||||||
|
if channel_info is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ret_dict = {}
|
||||||
|
|
||||||
|
nsmap = self.axml.getroot().nsmap
|
||||||
|
|
||||||
|
trackformat_elem = self.axml.find(".//audioFormatExtended/audioTrackFormat[@audioTrackFormatID='%s']" % channel_info.track_ref, namespaces=nsmap)
|
||||||
|
|
||||||
|
stream_id = trackformat_elem[0].text
|
||||||
|
|
||||||
|
channelformatref_elem = self.axml.find(".//audioFormatExtended/audioStreamFormat[@audioStreamFormatID='%s']/audioChannelFormatIDRef" % stream_id, namespaces=nsmap)
|
||||||
|
channelformat_id = channelformatref_elem.text
|
||||||
|
|
||||||
|
packformatref_elem = self.axml.find(".//audioFormatExtended/audioStreamFormat[@audioStreamFormatID='%s']/audioPackFormatIDRef" % stream_id, namespaces=nsmap)
|
||||||
|
packformat_id = packformatref_elem.text
|
||||||
|
|
||||||
|
channelformat_elem = self.axml.find(".//audioFormatExtended/audioChannelFormat[@audioChannelFormatID='%s']" % channelformat_id, namespaces=nsmap)
|
||||||
|
ret_dict['channel_format_name'] = channelformat_elem.get("audioChannelFormatName")
|
||||||
|
|
||||||
|
packformat_elem = self.axml.find(".//audioFormatExtended/audioPackFormat[@audioPackFormatID='%s']" % packformat_id, namespaces=nsmap)
|
||||||
|
ret_dict['pack_type'] = packformat_elem.get("typeDefinition")
|
||||||
|
ret_dict['pack_format_name'] = packformat_elem.get("audioPackFormatName")
|
||||||
|
|
||||||
|
object_elem = self.axml.find(".//audioFormatExtended/audioObject[audioPackFormatIDRef = '%s']" % packformat_id, namespaces=nsmap)
|
||||||
|
ret_dict['audio_object_name'] = object_elem.get("audioObjectName")
|
||||||
|
object_id = object_elem.get("audioObjectID")
|
||||||
|
|
||||||
|
content_elem = self.axml.find(".//audioFormatExtended/audioContent/[audioObjectIDRef = '%s']" % object_id, namespaces=nsmap)
|
||||||
|
ret_dict['content_name'] = content_elem.get("audioContentName")
|
||||||
|
|
||||||
|
return ret_dict
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return dict(channel_entries=list(map(lambda z: z._asdict(), self.channel_uids)))
|
||||||
Reference in New Issue
Block a user