mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 17:00:41 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fcc9787f6 | ||
|
|
52ea6fdb60 | ||
|
|
c26942db04 | ||
|
|
12eff79e5f | ||
|
|
d9e3e8deee | ||
|
|
c17fb242e3 | ||
|
|
64f3a640e3 | ||
|
|
5d4f97f6cc | ||
|
|
f9e5f28f7d | ||
|
|
3e6c485eb9 | ||
|
|
436bbe1686 | ||
|
|
ddb4d5cdca | ||
|
|
cec8165919 | ||
|
|
73a5034e02 | ||
|
|
9a46db4ae5 | ||
|
|
ccca30e234 | ||
|
|
c367acc185 | ||
|
|
2266cc5032 | ||
|
|
ec5b796181 | ||
|
|
97bdb23441 | ||
|
|
8f2fd69b00 | ||
|
|
a063fffb41 | ||
|
|
5b9d326e94 | ||
|
|
85775055a9 | ||
|
|
59509e4399 | ||
|
|
3a63ce9c8c | ||
|
|
5bfe0bd95b | ||
|
|
992de72cc9 | ||
|
|
ee305cebf4 | ||
|
|
ea4f484488 | ||
|
|
d00e07be36 | ||
|
|
68931348a6 | ||
|
|
68c75fc43f | ||
|
|
1eca249ba4 | ||
|
|
2052fa385a | ||
|
|
3096f02971 | ||
|
|
be47786439 | ||
|
|
ecde5359f1 | ||
|
|
8ae73213bc | ||
|
|
53217ce293 | ||
|
|
f9969d32cc | ||
|
|
04c402680b | ||
|
|
f10a546fe9 |
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/anaconda:1": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version":"3.11"
|
||||
},
|
||||
"ghcr.io/meaningful-ooo/devcontainer-features/fish:1": {}
|
||||
},
|
||||
"postCreateCommand": "pip3 install -r requirements.txt && pip3 install -r docs/requirements.txt && pip3 install pytest && conda install -y ffmpeg",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"ms-python.isort",
|
||||
"ms-toolsai.jupyter",
|
||||
"ms-toolsai.vscode-jupyter-cell-tags",
|
||||
"ms-toolsai.jupyter-keymap",
|
||||
"ms-toolsai.jupyter-renderers",
|
||||
"ms-toolsai.vscode-jupyter-slideshow",
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"george-alisson.html-preview-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
2
.github/workflows/python-package.yml
vendored
2
.github/workflows/python-package.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.5.0
|
||||
|
||||
2
.github/workflows/pythonpublish.yml
vendored
2
.github/workflows/pythonpublish.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
message: |
|
||||
I just released a new version of wavinfo, my library for reading WAVE file metadata!
|
||||
|
||||
#sounddesign #filmmaking #audio #python
|
||||
${{ github.server_url }}/${{ github.repository }}
|
||||
env:
|
||||
MASTODON_URL: ${{ secrets.MASTODON_URL }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -90,6 +90,7 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
docs_venv/
|
||||
venv_docs/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
@@ -107,3 +108,5 @@ docs_venv/
|
||||
# vim swap
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
.vscode/
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"esbonio.sphinx.confDir": ""
|
||||
}
|
||||
35
README.md
35
README.md
@@ -5,30 +5,35 @@
|
||||
|
||||
The `wavinfo` package allows you to probe WAVE and [RF64/WAVE files][eburf64] and extract extended metadata, with an emphasis on film, video and professional music production metadata.
|
||||
|
||||
|
||||
## Metadata Support
|
||||
|
||||
`wavinfo` reads:
|
||||
|
||||
* __Broadcast-WAVE__ metadata<sup>[1][ebu]</sup>, including embedded program
|
||||
loudness and coding history, if extant. This also includes the SMPTE UMID<sup>[2][smpte_330m2011]</sup>.
|
||||
* __iXML__ production recorder metadata<sup>[3][ixml]</sup>, including project, scene, and take tags, recorder notes
|
||||
* [__Broadcast-WAVE__][ebu] metadata, including embedded program
|
||||
loudness and coding history and [__SMPTE UMID__][smpte_330m2011].
|
||||
* [__ADM__][adm] track metadata, including channel, pack formats, object and content names.
|
||||
* [__iXML__][ixml] production recorder metadata, including project, scene, and take tags, recorder notes
|
||||
and file family information.
|
||||
* Most of the common __RIFF INFO__<sup>[4][info-tags]</sup> metadata fields.
|
||||
* Most of the common [__RIFF INFO__][info-tags] metadata fields.
|
||||
* The __wav format__ is also parsed, so you can access the basic sample rate and channel count
|
||||
information.
|
||||
|
||||
In progress:
|
||||
* ADM metadata consilient with the output of the __Dolby RMU__, perhaps later fully complaint with [ITU BS.2076-2][adm].
|
||||
* iXML `STEINBERG` sound library attributes.
|
||||
* __NetMix__ library attributes.
|
||||
* [__Dolby RMU__][dolby] metadata and [EBU Tech 3285 Supplement 6][ebu3285s6].
|
||||
* Pro Tools __embedded regions__.
|
||||
* iXML `STEINBERG` sound library attributes.
|
||||
|
||||
[dolby]:https://developer.dolby.com/globalassets/documentation/technology/dolby_atmos_master_adm_profile_v1.0.pdf
|
||||
[ebu]:https://tech.ebu.ch/docs/tech/tech3285.pdf
|
||||
[ebu3285s6]:https://tech.ebu.ch/docs/tech/tech3285s6.pdf
|
||||
[adm]:https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2076-2-201910-I!!PDF-E.pdf
|
||||
[smpte_330m2011]:http://standards.smpte.org/content/978-1-61482-678-1/st-330-2011/SEC1.abstract
|
||||
[ixml]:http://www.ixml.info
|
||||
[eburf64]:https://tech.ebu.ch/docs/tech/tech3306v1_1.pdf
|
||||
[info-tags]:https://exiftool.org/TagNames/RIFF.html#Info
|
||||
|
||||
## Demonstration
|
||||
## How To Use
|
||||
|
||||
The entry point for wavinfo is the WavInfoReader class.
|
||||
|
||||
@@ -38,6 +43,9 @@ from wavinfo import WavInfoReader
|
||||
path = '../tests/test_files/A101_1.WAV'
|
||||
|
||||
info = WavInfoReader(path)
|
||||
|
||||
adm_metadata = info.adm
|
||||
ixml_metadata = info.ixml
|
||||
```
|
||||
|
||||
The package also installs a shell command:
|
||||
@@ -46,17 +54,6 @@ The package also installs a shell command:
|
||||
$ wavinfo test_files/A101_1.WAV
|
||||
```
|
||||
|
||||
### Basic WAV Data
|
||||
|
||||
The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk.
|
||||
|
||||
```python
|
||||
(info.data.frame_count, info.data.byte_count)
|
||||
>>> (240239, 1441434)
|
||||
(info.fmt.sample_rate, info.fmt.channel_count, info.fmt.block_align, info.fmt.bits_per_sample)
|
||||
>>> (48000, 2, 6, 24)
|
||||
```
|
||||
|
||||
## Other Resources
|
||||
|
||||
* For other file formats and ID3 decoding, look at [audio-metadata](https://github.com/thebigmunch/audio-metadata).
|
||||
|
||||
@@ -18,6 +18,7 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
sys.path.insert(0, os.path.abspath("../../.."))
|
||||
print(sys.path)
|
||||
|
||||
import wavinfo
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -26,9 +27,9 @@ copyright = u'2022, Jamie Hardt'
|
||||
author = u'Jamie Hardt'
|
||||
|
||||
# The short X.Y version
|
||||
version = u''
|
||||
version = wavinfo.__version__
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = u'v1.7.1'
|
||||
release = wavinfo.__version__
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -9,10 +9,12 @@ Welcome to wavinfo's documentation!
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Notes
|
||||
|
||||
|
||||
quickstart
|
||||
metadata_scopes/adm.rst
|
||||
metadata_scopes/bext.rst
|
||||
metadata_scopes/ixml.rst
|
||||
metadata_scopes/info.rst
|
||||
metadata_scopes/ixml.rst
|
||||
|
||||
classes
|
||||
|
||||
|
||||
15
docs/source/metadata_scopes/adm.rst
Normal file
15
docs/source/metadata_scopes/adm.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
ADM (Audio Definition Model) Metadata
|
||||
=====================================
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
|
||||
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_adm_reader.WavADMReader
|
||||
:members:
|
||||
@@ -63,8 +63,6 @@ Result:
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_bext_reader.WavBextReader
|
||||
:members:
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ music library software.
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_info_reader.WavInfoChunkReader
|
||||
:members:
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ Result:
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_ixml_reader.WavIXMLFormat
|
||||
:members:
|
||||
|
||||
|
||||
12
docs/source/quickstart.rst
Normal file
12
docs/source/quickstart.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
ptulsconv Quickstart
|
||||
====================
|
||||
|
||||
.. code-block:: python
|
||||
:caption: Using wavinfo
|
||||
|
||||
import wavinfo
|
||||
|
||||
path = 'path/to/your/wave/audio.wav'
|
||||
|
||||
info = wavinfo.WavInfoReader(path)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
"""
|
||||
Wavinfo
|
||||
"""
|
||||
__version__ = '1.7.2'
|
||||
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
|
||||
__license__ = "MIT"
|
||||
5
setup.py
5
setup.py
@@ -1,5 +1,5 @@
|
||||
from setuptools import setup
|
||||
from metadata import __author__, __license__, __version__
|
||||
from wavinfo import __author__, __license__, __version__
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
@@ -28,7 +28,8 @@ setup(name='wavinfo',
|
||||
'Topic :: Multimedia :: Sound/Audio',
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10"],
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11"],
|
||||
keywords='waveform metadata audio ebu smpte avi library film tv editing editorial',
|
||||
install_requires=['lxml'],
|
||||
entry_points={
|
||||
|
||||
40
tests/test_adm.py
Normal file
40
tests/test_adm.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from unittest import TestCase
|
||||
|
||||
import wavinfo
|
||||
|
||||
class TestADMWave(TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.protools_adm_wav = "tests/test_files/protools/Test_ADM_ProTools.wav"
|
||||
return super().setUp()
|
||||
|
||||
def test_chna(self):
|
||||
info = wavinfo.WavInfoReader(self.protools_adm_wav)
|
||||
self.assertIsNotNone(info)
|
||||
|
||||
adm = info.adm
|
||||
self.assertIsNotNone(adm)
|
||||
|
||||
self.assertEqual(len(adm.channel_uids), 14)
|
||||
|
||||
def test_to_dict(self):
|
||||
info = wavinfo.WavInfoReader(self.protools_adm_wav)
|
||||
adm = info.adm
|
||||
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"])
|
||||
|
||||
BIN
tests/test_files/problems/DinerAmbience VAL085101-glued.wav
Normal file
BIN
tests/test_files/problems/DinerAmbience VAL085101-glued.wav
Normal file
Binary file not shown.
BIN
tests/test_files/protools/Test_ADM_ProTools.wav
Normal file
BIN
tests/test_files/protools/Test_ADM_ProTools.wav
Normal file
Binary file not shown.
@@ -1,5 +1,4 @@
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
@@ -22,12 +21,12 @@ class TestWaveInfo(TestCase):
|
||||
|
||||
self.assertEqual(info.fmt.channel_count, ffprobe_info['streams'][0]['channels'])
|
||||
self.assertEqual(info.fmt.sample_rate, int(ffprobe_info['streams'][0]['sample_rate']))
|
||||
self.assertEqual(info.fmt.bits_per_sample, int(ffprobe_info['streams'][0]['bits_per_raw_sample']))
|
||||
self.assertEqual(info.fmt.bits_per_sample, int(ffprobe_info['streams'][0]['bits_per_sample']))
|
||||
|
||||
if info.fmt.audio_format == 1:
|
||||
self.assertTrue(ffprobe_info['streams'][0]['codec_name'].startswith('pcm'))
|
||||
streams = ffprobe_info['streams'][0]
|
||||
byte_rate = int(streams['sample_rate']) * streams['channels'] * int(streams['bits_per_raw_sample']) / 8
|
||||
byte_rate = int(streams['sample_rate']) * streams['channels'] * int(streams['bits_per_sample']) / 8
|
||||
self.assertEqual(info.fmt.byte_rate, byte_rate)
|
||||
|
||||
def test_data_against_ffprobe(self):
|
||||
|
||||
@@ -7,3 +7,6 @@ Go to the documentation for wavinfo.WavInfoReader for more information.
|
||||
from .wave_reader import WavInfoReader
|
||||
from .riff_parser import WavInfoEOFError
|
||||
|
||||
__version__ = '2.0.1'
|
||||
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
|
||||
__license__ = "MIT"
|
||||
|
||||
89
wavinfo/wave_adm_reader.py
Normal file
89
wavinfo/wave_adm_reader.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
ADM Reader
|
||||
"""
|
||||
|
||||
from struct import unpack, unpack_from, calcsize
|
||||
from io import BytesIO
|
||||
from collections import namedtuple
|
||||
from typing import Iterable, Tuple
|
||||
|
||||
from lxml import etree as ET
|
||||
|
||||
ChannelEntry = namedtuple('ChannelEntry', "track_index uid track_ref pack_ref")
|
||||
|
||||
class WavADMReader:
|
||||
"""
|
||||
Reads XML data from an EBU ADM (Audio Definiton Model) WAV File.
|
||||
"""
|
||||
|
||||
def __init__(self, axml_data: bytes, chna_data: bytes):
|
||||
header_fmt = "<HH"
|
||||
uid_fmt = "<H12s14s11sx"
|
||||
|
||||
#: An :mod:`lxml.etree` of the ADM XML document
|
||||
self.axml = ET.parse(BytesIO(axml_data))
|
||||
|
||||
_, uid_count = unpack(header_fmt, chna_data[0:4])
|
||||
|
||||
#: A list of :class:`ChannelEntry` objects parsed from the
|
||||
#: `chna` metadata chunk.
|
||||
self.channel_uids = []
|
||||
|
||||
offset = calcsize(header_fmt)
|
||||
for _ in range(uid_count):
|
||||
|
||||
track_index, uid, track_ref, pack_ref = unpack_from(uid_fmt, chna_data, offset)
|
||||
|
||||
# these values are either ascii or all null
|
||||
|
||||
self.channel_uids.append(ChannelEntry(track_index,
|
||||
uid.decode('ascii') , track_ref.decode('ascii'), pack_ref.decode('ascii')))
|
||||
|
||||
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)))
|
||||
@@ -1,9 +0,0 @@
|
||||
from lxml import etree as ET
|
||||
from collections import namedtuple
|
||||
|
||||
from ear.fileio.bw64 import Bw64Reader
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -89,3 +89,13 @@ class WavIXMLFormat:
|
||||
The name of this file's file family.
|
||||
"""
|
||||
return self._get_text_value("FILE_SET/FAMILY_NAME")
|
||||
|
||||
def to_dict(self):
|
||||
return dict(track_list=list(map(lambda x: x._asdict(), self.track_list)),
|
||||
project=self.project,
|
||||
scene=self.scene,
|
||||
take=self.take,
|
||||
tape=self.tape,
|
||||
family_uid=self.family_uid,
|
||||
family_name=self.family_name
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ from .riff_parser import parse_chunk, ChunkDescriptor, ListChunkDescriptor
|
||||
from .wave_ixml_reader import WavIXMLFormat
|
||||
from .wave_bext_reader import WavBextReader
|
||||
from .wave_info_reader import WavInfoChunkReader
|
||||
from .wave_adm_reader import WavADMReader
|
||||
|
||||
#: Calculated statistics about the audio data.
|
||||
WavDataDescriptor = namedtuple('WavDataDescriptor', 'byte_count frame_count')
|
||||
@@ -39,6 +40,8 @@ class WavInfoReader:
|
||||
The text encoding to use when decoding the string
|
||||
fields of the Broadcast-WAV extension. Per EBU 3285 this is ASCII
|
||||
but this parameter is available to you if you encounter a weirdo.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
self.info_encoding = info_encoding
|
||||
@@ -75,6 +78,9 @@ class WavInfoReader:
|
||||
#: :class:`wavinfo.wave_ixml_reader.WavIXMLFormat` with iXML metadata
|
||||
self.ixml = self._get_ixml(wavfile)
|
||||
|
||||
#: :class:`wavinfo.wave_axml_reader.WavAxmlReader` with ADM metadata
|
||||
self.adm = self._get_adm(wavfile)
|
||||
|
||||
#: :class:`wavinfo.wave_info_reader.WavInfoChunkReader` with RIFF INFO metadata
|
||||
self.info = self._get_info(wavfile, encoding=self.info_encoding)
|
||||
self.data = self._describe_data()
|
||||
@@ -85,7 +91,7 @@ class WavInfoReader:
|
||||
return chunk_descriptor.read_data(from_stream) if chunk_descriptor else None
|
||||
|
||||
def _describe_data(self):
|
||||
data_chunk = next(c for c in self.main_list if c.ident == b'data')
|
||||
data_chunk = next(c for c in self.main_list if type(c) is ChunkDescriptor and c.ident == b'data')
|
||||
|
||||
return WavDataDescriptor(byte_count=data_chunk.length,
|
||||
frame_count=int(data_chunk.length / self.fmt.block_align))
|
||||
@@ -130,6 +136,11 @@ class WavInfoReader:
|
||||
bext_data = self._find_chunk_data(b'bext', f, default_none=True)
|
||||
return WavBextReader(bext_data, encoding) if bext_data else None
|
||||
|
||||
def _get_adm(self, f):
|
||||
axml = self._find_chunk_data(b'axml', f, default_none=True)
|
||||
chna = self._find_chunk_data(b'chna', f, default_none=True)
|
||||
return WavADMReader(axml_data=axml, chna_data=chna) if axml and chna else None
|
||||
|
||||
def _get_ixml(self, f):
|
||||
ixml_data = self._find_chunk_data(b'iXML', f, default_none=True)
|
||||
return None if ixml_data is None else WavIXMLFormat(ixml_data.rstrip(b'\0'))
|
||||
@@ -140,24 +151,23 @@ class WavInfoReader:
|
||||
|
||||
:yields: a string, the :scope: of the metadatum, the string :name: of the
|
||||
metadata field, and the value.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
scopes = ('fmt', 'data') # 'bext', 'ixml', 'info')
|
||||
scopes = ('fmt', 'data', 'ixml', 'bext', 'info', 'adm')
|
||||
|
||||
for scope in scopes:
|
||||
attr = self.__getattribute__(scope)
|
||||
for field in attr._fields:
|
||||
yield scope, field, attr.__getattribute__(field)
|
||||
if scope in ['fmt', 'data']:
|
||||
attr = self.__getattribute__(scope)
|
||||
for field in attr._fields:
|
||||
yield scope, field, attr.__getattribute__(field)
|
||||
|
||||
if self.bext is not None:
|
||||
bext_dict = (self.bext or {}).to_dict()
|
||||
for key in bext_dict.keys():
|
||||
yield 'bext', key, bext_dict[key]
|
||||
|
||||
if self.info is not None:
|
||||
info_dict = self.info.to_dict()
|
||||
for key in info_dict.keys():
|
||||
yield 'info', key, info_dict[key]
|
||||
else:
|
||||
dict = self.__getattribute__(scope).to_dict() if self.__getattribute__(scope) else {}
|
||||
for key in dict.keys():
|
||||
yield scope, key, dict[key]
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return 'WavInfoReader({}, {}, {})'.format(self.path, self.info_encoding, self.bext_encoding)
|
||||
|
||||
Reference in New Issue
Block a user