mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 17:00:41 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ec42ee1d3d | ||
|
|
bba4d67641 | ||
|
|
4bc7f94198 | ||
|
|
14eb8df496 | ||
|
|
a3aee8e785 | ||
|
|
9e9b6b512b | ||
|
|
a3365c113d | ||
|
|
fbf4d72915 | ||
|
|
90f273cf99 | ||
|
|
bec3d98ee7 | ||
|
|
a87bc71755 | ||
|
|
4817e7eb49 | ||
|
|
07832f7133 | ||
|
|
f3d03296d5 | ||
|
|
9496912b15 | ||
|
|
1077b49ce0 | ||
|
|
90042d57b2 | ||
|
|
f12d7dfea0 | ||
|
|
c2327568a8 | ||
|
|
722df48f9b | ||
|
|
17c0357364 | ||
|
|
7e88c46d54 | ||
|
|
df90c67a73 | ||
|
|
cc107bf65d | ||
|
|
f9c68e0995 | ||
|
|
a98dd2668a | ||
|
|
d8ff4ed63b | ||
|
|
084c3d7ae5 | ||
|
|
e69b71e989 | ||
|
|
6fe6126f3a | ||
|
|
e57c76a722 | ||
|
|
e40a2c5471 | ||
|
|
dec6180744 |
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
|
||||
|
||||
14
.github/workflows/pythonpublish.yml
vendored
14
.github/workflows/pythonpublish.yml
vendored
@@ -2,7 +2,7 @@ name: Upload Python Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
@@ -20,7 +20,17 @@ jobs:
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_APIKEY }}
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload dist/*
|
||||
- name: Report to Mastodon
|
||||
uses: cbrgm/mastodon-github-action@v1.0.1
|
||||
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 }}
|
||||
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -89,6 +89,8 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
docs_venv/
|
||||
venv_docs/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
@@ -106,3 +108,5 @@ venv.bak/
|
||||
# vim swap
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
.vscode/
|
||||
|
||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (wavinfo)" project-jdk-type="Python SDK" />
|
||||
<component name="PyPackaging">
|
||||
<option name="earlyReleasesAsUpgrades" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/wavinfo.iml
generated
10
.idea/wavinfo.iml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (wavinfo)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
107
.idea/workspace.xml
generated
107
.idea/workspace.xml
generated
@@ -1,107 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="fb29ad56-fffe-478f-9c0e-1718aa04342e" name="Default Changelist" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/dictionaries/jamiehardt.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/dictionaries/jamiehardt.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/wavinfo.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/wavinfo.iml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FavoritesManager">
|
||||
<favorites_list name="wavinfo" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Python Script" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
<option name="UPDATE_TYPE" value="MERGE" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1VtfP9ukGK4GNIXS2m0hRLG9d7w" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
<option name="showMembers" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="TODO_SCOPE" value="Project Files" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="project-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="SvnConfiguration">
|
||||
<configuration />
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="fb29ad56-fffe-478f-9c0e-1718aa04342e" name="Default Changelist" comment="" />
|
||||
<created>1578077284420</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1578077284420</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="Vcs.Log.History.Properties">
|
||||
<option name="COLUMN_ORDER">
|
||||
<list>
|
||||
<option value="0" />
|
||||
<option value="2" />
|
||||
<option value="3" />
|
||||
<option value="1" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="oldMeFiltersMigrated" value="true" />
|
||||
</component>
|
||||
<component name="WindowStateProjectService">
|
||||
<state x="655" y="287" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog" timestamp="1598547965709">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="655" y="287" key="#com.intellij.openapi.updateSettings.impl.PluginUpdateInfoDialog/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598547965709" />
|
||||
<state x="0" y="0" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1598555737671">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="0" y="0" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598555737671" />
|
||||
<state x="476" y="183" key="SettingsEditor" timestamp="1598548365517">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="476" y="183" key="SettingsEditor/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598548365517" />
|
||||
<state x="114" y="123" width="1706" height="842" key="com.intellij.history.integration.ui.views.FileHistoryDialog" timestamp="1597689482054">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="114" y="123" width="1706" height="842" key="com.intellij.history.integration.ui.views.FileHistoryDialog/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1597689482054" />
|
||||
<state x="577" y="376" key="com.intellij.ide.util.TipDialog" timestamp="1601767353495">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="577" y="376" key="com.intellij.ide.util.TipDialog/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598550975228" />
|
||||
<state x="577" y="376" key="com.intellij.ide.util.TipDialog/1920.0.1920.1080/0.0.1920.1080@0.0.1920.1080" timestamp="1601767353495" />
|
||||
<state x="772" y="468" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2" timestamp="1598547948993">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="772" y="468" key="com.intellij.openapi.vcs.update.UpdateOrStatusOptionsDialogupdate-v2/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598547948993" />
|
||||
<state x="632" y="254" width="670" height="676" key="search.everywhere.popup" timestamp="1598554315633">
|
||||
<screen x="0" y="0" width="1920" height="1080" />
|
||||
</state>
|
||||
<state x="632" y="254" width="670" height="676" key="search.everywhere.popup/1920.0.1920.1080/0.0.1920.1080/3840.0.1920.1080@0.0.1920.1080" timestamp="1598554315633" />
|
||||
</component>
|
||||
</project>
|
||||
29
.readthedocs.yaml
Normal file
29
.readthedocs.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
# You can also specify other tool versions:
|
||||
# nodejs: "16"
|
||||
# rust: "1.55"
|
||||
# golang: "1.17"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
#If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
#Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: requirements.txt
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Jamie Hardt
|
||||
Copyright (c) 2022 Jamie Hardt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
14
README.md
14
README.md
@@ -1,7 +1,5 @@
|
||||
[](https://wavinfo.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/wavinfo/) 
|
||||

|
||||
|
||||
<!--  -->
|
||||
[](https://github.com/iluvcapra/wavinfo/actions/workflows/python-package.yml)
|
||||
|
||||
# wavinfo
|
||||
|
||||
@@ -11,19 +9,23 @@ The `wavinfo` package allows you to probe WAVE and [RF64/WAVE files][eburf64] an
|
||||
|
||||
* __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
|
||||
* ADM track metadata<sup>[3][adm]</sup>, including channel, pack formats, object and content names.
|
||||
* __iXML__ production recorder metadata<sup>[4][ixml]</sup>, 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__<sup>[5][info-tags]</sup> 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].
|
||||
* [Dolby RMU][dolby] metadata and [EBU Tech 3285 Supplement 6][ebu3285s6].
|
||||
* iXML `STEINBERG` sound library attributes.
|
||||
* __NetMix__ library attributes.
|
||||
* Pro Tools __embedded regions__.
|
||||
|
||||
[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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SOURCEDIR = .
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
25
docs/requirements.txt
Normal file
25
docs/requirements.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
alabaster==0.7.12
|
||||
Babel==2.11.0
|
||||
certifi==2022.9.24
|
||||
charset-normalizer==2.1.1
|
||||
docutils==0.17.1
|
||||
idna==3.4
|
||||
imagesize==1.4.1
|
||||
Jinja2==3.1.2
|
||||
lxml==4.9.1
|
||||
MarkupSafe==2.1.1
|
||||
packaging==21.3
|
||||
Pygments==2.13.0
|
||||
pyparsing==3.0.9
|
||||
pytz==2022.6
|
||||
requests==2.28.1
|
||||
snowballstemmer==2.2.0
|
||||
Sphinx==5.3.0
|
||||
sphinx-rtd-theme==1.1.1
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==2.0.0
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.5
|
||||
urllib3==1.26.12
|
||||
18
docs/source/classes.rst
Normal file
18
docs/source/classes.rst
Normal file
@@ -0,0 +1,18 @@
|
||||
Other wavinfo Classes
|
||||
===============
|
||||
|
||||
|
||||
.. autoclass:: wavinfo.wave_reader.WavInfoReader
|
||||
:members:
|
||||
|
||||
.. automethod:: __init__
|
||||
|
||||
|
||||
.. autoclass:: wavinfo.wave_reader.WavAudioFormat
|
||||
:members:
|
||||
|
||||
.. autoclass:: wavinfo.wave_reader.WavDataDescriptor
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
@@ -14,19 +14,21 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
sys.path.insert(0, os.path.abspath("../../.."))
|
||||
print(sys.path)
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = u'wavinfo'
|
||||
copyright = u'2019, Jamie Hardt'
|
||||
copyright = u'2022, Jamie Hardt'
|
||||
author = u'Jamie Hardt'
|
||||
|
||||
# The short X.Y version
|
||||
version = u''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = u'v1.1'
|
||||
release = u'v1.7.1'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
@@ -61,7 +63,7 @@ master_doc = 'index'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@@ -6,30 +6,16 @@
|
||||
Welcome to wavinfo's documentation!
|
||||
===================================
|
||||
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: WavInfoReader
|
||||
:members:
|
||||
|
||||
.. automethod:: __init__
|
||||
|
||||
|
||||
.. autoclass:: wavinfo.wave_reader.WavAudioFormat
|
||||
:members:
|
||||
|
||||
.. autoclass:: wavinfo.wave_reader.WavDataDescriptor
|
||||
:members:
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Notes:
|
||||
:caption: Notes
|
||||
|
||||
metadata_scopes/adm.rst
|
||||
metadata_scopes/bext.rst
|
||||
metadata_scopes/ixml.rst
|
||||
metadata_scopes/info.rst
|
||||
metadata_scopes/ixml.rst
|
||||
|
||||
classes
|
||||
|
||||
|
||||
Indices and tables
|
||||
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:
|
||||
@@ -1,11 +1,6 @@
|
||||
Broadcast WAV Extension
|
||||
=======================
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_bext_reader.WavBextReader
|
||||
:members:
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -63,3 +58,14 @@ Result:
|
||||
Originator Time: 12:40:00
|
||||
Time Reference: 2190940753
|
||||
A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch
|
||||
|
||||
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_bext_reader.WavBextReader
|
||||
:members:
|
||||
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
INFO Metadata
|
||||
=============
|
||||
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_info_reader.WavInfoChunkReader
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
@@ -28,5 +20,14 @@ music library software.
|
||||
print("INFO Comment:", bullet.info.comment)
|
||||
|
||||
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_info_reader.WavInfoChunkReader
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
iXML Production Recorder Metadata
|
||||
=================================
|
||||
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_ixml_reader.WavIXMLFormat
|
||||
:members:
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
iXML allows an XML document to be embedded in a WAV file.
|
||||
@@ -41,4 +34,12 @@ Result:
|
||||
iXML File Family UID: USSDVGR1112089007124001008206300
|
||||
|
||||
|
||||
Class Reference
|
||||
---------------
|
||||
|
||||
.. module:: wavinfo
|
||||
|
||||
.. autoclass:: wavinfo.wave_ixml_reader.WavIXMLFormat
|
||||
:members:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Wavinfo
|
||||
"""
|
||||
__version__ = '1.7.0'
|
||||
__version__ = '2.0.0'
|
||||
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
|
||||
__license__ = "MIT"
|
||||
|
||||
@@ -1,19 +1 @@
|
||||
attrs==21.4.0
|
||||
ear==2.1.0
|
||||
enum34==1.1.10
|
||||
exceptiongroup==1.0.4
|
||||
iniconfig==1.1.1
|
||||
lxml==4.9.1
|
||||
multipledispatch==0.6.0
|
||||
numpy==1.23.3
|
||||
packaging==21.3
|
||||
pluggy==1.0.0
|
||||
pyparsing==3.0.9
|
||||
pytest==7.2.0
|
||||
ruamel.yaml==0.17.21
|
||||
ruamel.yaml.clib==0.2.6
|
||||
scipy==1.9.1
|
||||
six==1.16.0
|
||||
style==1.1.0
|
||||
tomli==2.0.1
|
||||
update==0.0.1
|
||||
|
||||
5
setup.py
5
setup.py
@@ -28,9 +28,10 @@ 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', 'ear'],
|
||||
install_requires=['lxml'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'wavinfo = wavinfo.__main__:main'
|
||||
|
||||
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/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
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Union
|
||||
import binascii
|
||||
from functools import reduce
|
||||
|
||||
|
||||
@@ -13,7 +11,7 @@ class UMIDParser:
|
||||
|
||||
This implementation is based on SMPTE ST 330:2011
|
||||
"""
|
||||
def __init__(self, raw_umid: bytearray):
|
||||
def __init__(self, raw_umid: bytes):
|
||||
self.raw_umid = raw_umid
|
||||
#
|
||||
# @property
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import struct
|
||||
import binascii
|
||||
from .umid_parser import UMIDParser
|
||||
|
||||
from typing import Optional
|
||||
|
||||
class WavBextReader:
|
||||
def __init__(self, bext_data, encoding):
|
||||
@@ -16,44 +16,44 @@ class WavBextReader:
|
||||
rest_starts = struct.calcsize(packstring)
|
||||
unpacked = struct.unpack(packstring, bext_data[:rest_starts])
|
||||
|
||||
def sanitize_bytes(b):
|
||||
def sanitize_bytes(b : bytes) -> str:
|
||||
first_null = next((index for index, byte in enumerate(b) if byte == 0), None)
|
||||
trimmed = b if first_null is None else b[:first_null]
|
||||
decoded = trimmed.decode(encoding)
|
||||
return decoded
|
||||
|
||||
#: Description. A free-text field up to 256 characters long.
|
||||
self.description = sanitize_bytes(unpacked[0])
|
||||
self.description : str = sanitize_bytes(unpacked[0])
|
||||
#: Originator. Usually the name of the encoding application, sometimes
|
||||
#: a artist name.
|
||||
self.originator = sanitize_bytes(unpacked[1])
|
||||
self.originator : str = sanitize_bytes(unpacked[1])
|
||||
#: A unique identifier for the file, a serial number.
|
||||
self.originator_ref = sanitize_bytes(unpacked[2])
|
||||
self.originator_ref : str = sanitize_bytes(unpacked[2])
|
||||
#: Date of the recording, in the format YYY-MM-DD
|
||||
self.originator_date = sanitize_bytes(unpacked[3])
|
||||
self.originator_date : str = sanitize_bytes(unpacked[3])
|
||||
#: Time of the recording, in the format HH:MM:SS.
|
||||
self.originator_time = sanitize_bytes(unpacked[4])
|
||||
self.originator_time : str = sanitize_bytes(unpacked[4])
|
||||
#: The sample offset of the start of the file relative to an
|
||||
#: epoch, usually midnight the day of the recording.
|
||||
self.time_reference = unpacked[5]
|
||||
self.time_reference : int = unpacked[5]
|
||||
#: A variable-length text field containing a list of processes and
|
||||
#: and conversions performed on the file.
|
||||
self.coding_history = sanitize_bytes(bext_data[rest_starts:])
|
||||
self.coding_history : str = sanitize_bytes(bext_data[rest_starts:])
|
||||
#: BEXT version.
|
||||
self.version = unpacked[6]
|
||||
self.version : int = unpacked[6]
|
||||
#: SMPTE 330M UMID of this audio file, 64 bytes are allocated though the UMID
|
||||
#: may only be 32 bytes long.
|
||||
self.umid = None
|
||||
self.umid : Optional[bytes] = None
|
||||
#: EBU R128 Integrated loudness, in LUFS.
|
||||
self.loudness_value = None
|
||||
self.loudness_value : Optional[float] = None
|
||||
#: EBU R128 Loudness rante, in LUFS.
|
||||
self.loudness_range = None
|
||||
self.loudness_range : Optional[float] = None
|
||||
#: True peak level, in dBFS TP
|
||||
self.max_true_peak = None
|
||||
self.max_true_peak : Optional[float] = None
|
||||
#: EBU R128 Maximum momentary loudness, in LUFS
|
||||
self.max_momentary_loudness = None
|
||||
self.max_momentary_loudness : Optional[float] = None
|
||||
#: EBU R128 Maximum short-term loudness, in LUFS.
|
||||
self.max_shortterm_loudness = None
|
||||
self.max_shortterm_loudness : Optional[float] = None
|
||||
|
||||
if self.version > 0:
|
||||
self.umid = unpacked[7]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from .riff_parser import parse_chunk, ListChunkDescriptor
|
||||
|
||||
from typing import Optional
|
||||
|
||||
class WavInfoChunkReader:
|
||||
|
||||
@@ -14,40 +15,40 @@ class WavInfoChunkReader:
|
||||
self.info_chunk = next((chunk for chunk in list_chunks if chunk.signature == b'INFO'), None)
|
||||
|
||||
#: 'ICOP' Copyright
|
||||
self.copyright = self._get_field(f, b'ICOP')
|
||||
self.copyright : Optional[str] = self._get_field(f, b'ICOP')
|
||||
#: 'IPRD' Product
|
||||
self.product = self._get_field(f, b'IPRD')
|
||||
self.album = self.product
|
||||
self.product : Optional[str]= self._get_field(f, b'IPRD')
|
||||
self.album : Optional[str] = self.product
|
||||
#: 'IGNR' Genre
|
||||
self.genre = self._get_field(f, b'IGNR')
|
||||
self.genre : Optional[str] = self._get_field(f, b'IGNR')
|
||||
#: 'ISBJ' Supject
|
||||
self.subject = self._get_field(f, b'ISBJ')
|
||||
self.subject : Optional[str] = self._get_field(f, b'ISBJ')
|
||||
#: 'IART' Artist, composer, author
|
||||
self.artist = self._get_field(f, b'IART')
|
||||
self.artist : Optional[str] = self._get_field(f, b'IART')
|
||||
#: 'ICMT' Comment
|
||||
self.comment = self._get_field(f, b'ICMT')
|
||||
self.comment : Optional[str] = self._get_field(f, b'ICMT')
|
||||
#: 'ISFT' Software, encoding application
|
||||
self.software = self._get_field(f, b'ISFT')
|
||||
self.software : Optional[str] = self._get_field(f, b'ISFT')
|
||||
#: 'ICRD' Created date
|
||||
self.created_date = self._get_field(f, b'ICRD')
|
||||
self.created_date : Optional[str] = self._get_field(f, b'ICRD')
|
||||
#: 'IENG' Engineer
|
||||
self.engineer = self._get_field(f, b'IENG')
|
||||
self.engineer : Optional[str] = self._get_field(f, b'IENG')
|
||||
#: 'ITCH' Technician
|
||||
self.technician = self._get_field(f, b'ITCH')
|
||||
self.technician : Optional[str] = self._get_field(f, b'ITCH')
|
||||
#: 'IKEY' Keywords, keyword list
|
||||
self.keywords = self._get_field(f, b'IKEY')
|
||||
self.keywords : Optional[str] = self._get_field(f, b'IKEY')
|
||||
#: 'INAM' Name, title
|
||||
self.title = self._get_field(f, b'INAM')
|
||||
self.title : Optional[str] = self._get_field(f, b'INAM')
|
||||
#: 'ISRC' Source
|
||||
self.source = self._get_field(f, b'ISRC')
|
||||
self.source : Optional[str] = self._get_field(f, b'ISRC')
|
||||
#: 'TAPE' Tape
|
||||
self.tape = self._get_field(f, b'TAPE')
|
||||
self.tape : Optional[str] = self._get_field(f, b'TAPE')
|
||||
#: 'IARL' Archival Location
|
||||
self.archival_location = self._get_field(f, b'IARL')
|
||||
self.archival_location : Optional[str] = self._get_field(f, b'IARL')
|
||||
#: 'ICSM' Commissioned
|
||||
self.commissioned = self._get_field(f, b'ICMS')
|
||||
self.commissioned : Optional[str] = self._get_field(f, b'ICMS')
|
||||
|
||||
def _get_field(self, f, field_ident):
|
||||
def _get_field(self, f, field_ident) -> Optional[str]:
|
||||
search = next(((chunk.start, chunk.length) for chunk in self.info_chunk.children if chunk.ident == field_ident),
|
||||
None)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from lxml import etree as ET
|
||||
import io
|
||||
from collections import namedtuple
|
||||
|
||||
from typing import Optional
|
||||
|
||||
IXMLTrack = namedtuple('IXMLTrack', ['channel_index', 'interleave_index', 'name', 'function'])
|
||||
|
||||
@@ -20,10 +20,12 @@ class WavIXMLFormat:
|
||||
parser = ET.XMLParser(recover=True)
|
||||
self.parsed = ET.parse(xml_bytes, parser=parser)
|
||||
|
||||
def _get_text_value(self, xpath):
|
||||
def _get_text_value(self, xpath) -> Optional[str]:
|
||||
e = self.parsed.find("./" + xpath)
|
||||
if e is not None:
|
||||
return e.text
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def raw_xml(self):
|
||||
@@ -46,35 +48,35 @@ class WavIXMLFormat:
|
||||
function=track.xpath('string(FUNCTION/text())'))
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
def project(self) -> Optional[str]:
|
||||
"""
|
||||
The project/film name entered for the recording.
|
||||
"""
|
||||
return self._get_text_value("PROJECT")
|
||||
|
||||
@property
|
||||
def scene(self):
|
||||
def scene(self) -> Optional[str]:
|
||||
"""
|
||||
Scene/slate.
|
||||
"""
|
||||
return self._get_text_value("SCENE")
|
||||
|
||||
@property
|
||||
def take(self):
|
||||
def take(self) -> Optional[str]:
|
||||
"""
|
||||
Take number.
|
||||
"""
|
||||
return self._get_text_value("TAKE")
|
||||
|
||||
@property
|
||||
def tape(self):
|
||||
def tape(self) -> Optional[str]:
|
||||
"""
|
||||
Tape name.
|
||||
"""
|
||||
return self._get_text_value("TAPE")
|
||||
|
||||
@property
|
||||
def family_uid(self):
|
||||
def family_uid(self) -> Optional[str]:
|
||||
"""
|
||||
The globally-unique ID for this file family. This may be in the format
|
||||
of a GUID, or an EBU Rec 9 source identifier, or some other dumb number.
|
||||
@@ -82,8 +84,18 @@ class WavIXMLFormat:
|
||||
return self._get_text_value("FILE_SET/FAMILY_UID")
|
||||
|
||||
@property
|
||||
def family_name(self):
|
||||
def family_name(self) -> Optional[str]:
|
||||
"""
|
||||
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')
|
||||
@@ -75,6 +76,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()
|
||||
@@ -130,6 +134,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'))
|
||||
@@ -142,22 +151,29 @@ class WavInfoReader:
|
||||
metadata field, and the value.
|
||||
"""
|
||||
|
||||
scopes = ('fmt', 'data') # 'bext', 'ixml', 'info')
|
||||
scopes = ('fmt', 'data', 'ixml', 'bext', 'info')
|
||||
|
||||
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()
|
||||
if scope in ['bext']:
|
||||
bext_dict = self.bext.to_dict() if self.bext else {}
|
||||
for key in bext_dict.keys():
|
||||
yield 'bext', key, bext_dict[key]
|
||||
|
||||
if self.info is not None:
|
||||
info_dict = self.info.to_dict()
|
||||
if scope in ['info']:
|
||||
info_dict = self.info.to_dict() if self.info else {}
|
||||
for key in info_dict.keys():
|
||||
yield 'info', key, info_dict[key]
|
||||
|
||||
if scope in ['ixml']:
|
||||
ixml_dict = self.ixml.to_dict() if self.ixml else {}
|
||||
for key in ixml_dict.keys():
|
||||
yield 'ixml', key, ixml_dict[key]
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return 'WavInfoReader({}, {}, {})'.format(self.path, self.info_encoding, self.bext_encoding)
|
||||
|
||||
Reference in New Issue
Block a user