mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2026-01-02 09:50:41 +00:00
Merge branch 'feature-cues' into maint-rm-umid
This commit is contained in:
24
README.md
24
README.md
@@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
# wavinfo
|
# wavinfo
|
||||||
|
|
||||||
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.
|
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 Support
|
## Metadata Support
|
||||||
@@ -13,17 +15,18 @@ The `wavinfo` package allows you to probe WAVE and [RF64/WAVE files][eburf64] an
|
|||||||
|
|
||||||
* [Broadcast-WAVE][bext] metadata, including embedded program
|
* [Broadcast-WAVE][bext] metadata, including embedded program
|
||||||
loudness, coding history and [SMPTE UMID][smpte_330m2011].
|
loudness, coding history and [SMPTE UMID][smpte_330m2011].
|
||||||
* [ADM][adm] track metadata and schema, including channel, pack formats, object, content and programme.
|
* [Audio Definition Model (ADM)][adm] track metadata and schema, including
|
||||||
|
channel, pack formats,
|
||||||
|
object, content and programme.
|
||||||
* [Dolby Digital Plus][ebu3285s6] and Dolby Atmos `dbmd` metadata.
|
* [Dolby Digital Plus][ebu3285s6] and Dolby Atmos `dbmd` metadata.
|
||||||
* [iXML][ixml] production recorder metadata, including project, scene, and take tags, recorder notes
|
* [iXML][ixml] production recorder metadata, including project, scene, and
|
||||||
and file family information.
|
take tags, recorder notes and file family information.
|
||||||
* iXML `STEINBERG` sound library attributes.
|
* iXML `STEINBERG` sound library attributes.
|
||||||
|
* Wave embedded cue markers, cue marker labels, notes and timed ranges as used
|
||||||
|
by Zoom, iZotope RX, etc.
|
||||||
* Most of the common [RIFF INFO][info-tags] 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
|
* The __wav format__ is also parsed, so you can access the basic sample rate
|
||||||
information.
|
and channel count information.
|
||||||
|
|
||||||
In progress:
|
|
||||||
* Pro Tools __embedded regions__.
|
|
||||||
|
|
||||||
[bext]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html
|
[bext]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html
|
||||||
[smpte_330m2011]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html#wavinfo.wave_bext_reader.WavBextReader.umid
|
[smpte_330m2011]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html#wavinfo.wave_bext_reader.WavBextReader.umid
|
||||||
@@ -57,4 +60,5 @@ $ wavinfo test_files/A101_1.WAV
|
|||||||
|
|
||||||
## Other Resources
|
## Other Resources
|
||||||
|
|
||||||
* For other file formats and ID3 decoding, look at [audio-metadata](https://github.com/thebigmunch/audio-metadata).
|
* For other file formats and ID3 decoding,
|
||||||
|
look at [audio-metadata](https://github.com/thebigmunch/audio-metadata).
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ instance of :class:`WaveInfoReader`.
|
|||||||
adm_metadata = info.adm
|
adm_metadata = info.adm
|
||||||
ixml_metadata = info.ixml
|
ixml_metadata = info.ixml
|
||||||
|
|
||||||
|
WavInfoReader Class Documentation
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
.. module:: wavinfo
|
.. module:: wavinfo
|
||||||
:noindex:
|
:noindex:
|
||||||
|
|
||||||
.. autoclass:: wavinfo.wave_reader.WavInfoReader
|
.. autoclass:: wavinfo.wave_reader.WavInfoReader
|
||||||
:members:
|
:members:
|
||||||
|
:special-members: __init__
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ iXML
|
|||||||
|
|
||||||
RIFF Metadata
|
RIFF Metadata
|
||||||
-------------
|
-------------
|
||||||
* `1991. Multimedia Programming Interface and Data Specifications 1.0<https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf>`_
|
* `1991. Multimedia Programming Interface and Data Specifications 1.0 <https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf>`_
|
||||||
* `Exiftool Documentation <https://exiftool.org/TagNames/RIFF.html#Info_docs>`_
|
* `Exiftool Documentation <https://exiftool.org/TagNames/RIFF.html#Info_docs>`_
|
||||||
|
|
||||||
|
|||||||
@@ -4,32 +4,43 @@ Broadcast WAV Extension Metadata
|
|||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
A WAV file produced to Broadcast-WAV specifications will have the broadcast metadata extension,
|
A WAV file produced to Broadcast-WAV specifications will have the broadcast
|
||||||
which includes a 256-character free text descrption, creating entity identifier (usually the
|
metadata extension, which includes a 256-character free text descrption,
|
||||||
recording application or equipment), the date and time of recording and a time reference for
|
creating entity identifier (usually the recording application or equipment),
|
||||||
timecode synchronization.
|
the date and time of recording and a time reference for timecode
|
||||||
|
synchronization.
|
||||||
|
|
||||||
The :py:attr:`coding_history<wavinfo.wave_bext_reader.WavBextReader.coding_history>`
|
The :py:attr:`coding_history<wavinfo.wave_bext_reader.WavBextReader.coding_history>`
|
||||||
is designed to contain a record of every conversion performed on the audio file.
|
is designed to contain a record of every conversion performed on the audio file.
|
||||||
|
|
||||||
In this example (from a Sound Devices 702T) the bext metadata contains scene/take slating
|
In this example (from a Sound Devices 702T) the bext metadata contains
|
||||||
information in the :py:attr:`description<wavinfo.wave_bext_reader.WavBextReader.description>`.
|
scene/take slating information in the
|
||||||
Here also the :py:attr:`originator_ref<wavinfo.wave_bext_reader.WavBextReader.originator_ref>`
|
:py:attr:`description<wavinfo.wave_bext_reader.WavBextReader.description>`.
|
||||||
|
Here also the
|
||||||
|
:py:attr:`originator_ref<wavinfo.wave_bext_reader.WavBextReader.originator_ref>`
|
||||||
is a serial number conforming to EBU Rec 99.
|
is a serial number conforming to EBU Rec 99.
|
||||||
|
|
||||||
If the bext metadata conforms to `EBU 3285 v1`_, it will contain the WAV's 32 or 64 byte `SMPTE
|
If the bext metadata conforms to `EBU 3285 v1`_, it will contain the WAV's 32
|
||||||
ST 330 UMID`_. The 32-byte version of the UMID is usually just a random number, while the 64-byte
|
or 64 byte `SMPTE ST 330 UMID`_. The 32-byte version of the UMID is usually
|
||||||
UMID will also have information on the recording date and time, recording equipment and entity,
|
just a random number, while the 64-byte UMID will also have information on the
|
||||||
and geolocation data.
|
recording date and time, recording equipment and entity, and geolocation data.
|
||||||
|
|
||||||
If the bext metadata conforms to `EBU 3285 v2`_, it will hold precomputed program loudness values
|
If the bext metadata conforms to `EBU 3285 v2`_, it will hold precomputed
|
||||||
as described by `EBU Rec 128`_.
|
program loudness values as described by `EBU Rec 128`_.
|
||||||
|
|
||||||
.. _EBU 3285 v1: https://tech.ebu.ch/publications/tech3285s1
|
.. _EBU 3285 v1: https://tech.ebu.ch/publications/tech3285s1
|
||||||
.. _SMPTE ST 330 UMID: https://standards.globalspec.com/std/1396751/smpte-st-330
|
.. _SMPTE ST 330 UMID: https://standards.globalspec.com/std/1396751/smpte-st-330
|
||||||
.. _EBU 3285 v2: https://tech.ebu.ch/publications/tech3285s2
|
.. _EBU 3285 v2: https://tech.ebu.ch/publications/tech3285s2
|
||||||
.. _EBU Rec 128: https://tech.ebu.ch/publications/r128
|
.. _EBU Rec 128: https://tech.ebu.ch/publications/r128
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
All text fields in the Broadcast-WAV metadata structure are decoded by
|
||||||
|
default as flat ASCII. To override this and use a different encoding, pass
|
||||||
|
an string encoding name to the ``bext_encoding`` parameter of
|
||||||
|
:py:meth:`WavInfoReader()<wavinfo.wave_reader.WavInfoReader.__init__>`
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
-------
|
-------
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|||||||
31
docs/source/scopes/cue.rst
Normal file
31
docs/source/scopes/cue.rst
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
Cue Marker and Range Metadata
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
Notes
|
||||||
|
=====
|
||||||
|
|
||||||
|
Cue metadata stores timed markers that clients use to mark times of interest
|
||||||
|
in a wave file, and optionally give them a name and longer comment. Markers
|
||||||
|
can also have an associated length, allowing ranges of times in a file to be
|
||||||
|
marked.
|
||||||
|
|
||||||
|
String Encoding of Cue Metadata
|
||||||
|
"""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
Cue labels and notes will be decoded using the string encoding passed to
|
||||||
|
:py:meth:`WavInfoReader's<wavinfo.wave_reader.WaveInfoReader.__init__>`
|
||||||
|
``info_encoding=`` parameter, which by default is ``latin_1`` (ISO 8859-1).
|
||||||
|
|
||||||
|
Text associated with ``ltxt`` time ranges may specify their own encoding in
|
||||||
|
the form of a Windows codepage number. `wavinfo` will attempt to use the
|
||||||
|
encoding specified.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``cset`` character set/locale metadata is not supported. If it is present
|
||||||
|
in the file it will be ignored by `wavinfo`.
|
||||||
|
|
||||||
|
Class Reference
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. autoclass:: wavinfo.wave_cues_reader.WavCuesReader
|
||||||
|
:members:
|
||||||
@@ -20,16 +20,16 @@ music library software.
|
|||||||
print("INFO Comment:", bullet.info.comment)
|
print("INFO Comment:", bullet.info.comment)
|
||||||
|
|
||||||
|
|
||||||
On Encodings
|
String Encoding of INFO Metadata
|
||||||
""""""""""""
|
""""""""""""""""""""""""""""""""
|
||||||
According to Microsoft, the original developers of the RIFF file and RIFF INFO
|
|
||||||
metadata, these fields are always to be interpreted as ISO Latin 1 characters,
|
|
||||||
and this is the default encoding used by `wavinfo` for these fields. You can
|
|
||||||
select a different encoding (like Shift-JIS) by passing an encoding name (as
|
|
||||||
would be used by `string.encode()`) to `WavInfoReader.__init__()`'s
|
|
||||||
`info_encoding=` parameter.
|
|
||||||
|
|
||||||
|
Info metadata fields will be decoded using the string encoding passed to
|
||||||
|
:py:meth:`WavInfoReader's<wavinfo.wave_reader.WaveInfoReader.__init__>`
|
||||||
|
``info_encoding=`` parameter, which by default is ``latin_1`` (ISO 8859-1).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``cset`` character set/locale metadata is not supported. If it is present
|
||||||
|
in the file it will be ignored by `wavinfo`.
|
||||||
|
|
||||||
Class Reference
|
Class Reference
|
||||||
---------------
|
---------------
|
||||||
|
|||||||
@@ -6,7 +6,16 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# `wavinfo` Demonstration\n",
|
"# `wavinfo` Demonstration\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The entry point for wavinfo is the WavInfoReader class."
|
"The `wavinfo` module allows you to read most of the metadata formats that are available for WAV files."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Opening a WAV file for reading metadata\n",
|
||||||
|
"\n",
|
||||||
|
"The entry point for wavinfo is the `WavInfoReader` class:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -26,7 +35,35 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Basic WAV Data\n",
|
"Once you have a `WavInfoReader`, you can access different metadata systems or \"scopes.\"\n",
|
||||||
|
"\n",
|
||||||
|
"The scopes that are presently supported are: \n",
|
||||||
|
" * `fmt`: sample format, sample rate, bit depth, block alignment, etc.\n",
|
||||||
|
" * `data`: data chunk description, bytes length and frames length.\n",
|
||||||
|
" * `ixml`: Gallery Software's iXML metadata, used by production sound recorder equipment and DAWs.\n",
|
||||||
|
" * `bext`: Broacast-WAV metadata as used by DAWs.\n",
|
||||||
|
" * `info`: title, artist and description metadata tags, among other items.\n",
|
||||||
|
" * `adm`: EBU Audio Defintion Model metadata, as used by Dolby Atmos.\n",
|
||||||
|
" * `cues`: Cue marker metadata, including labels and notes \n",
|
||||||
|
" * `dolby`: Dolby recorder and playback metadata\n",
|
||||||
|
"\n",
|
||||||
|
"Each of these is an attribute of a `WavInfoReader` object.\n",
|
||||||
|
"\n",
|
||||||
|
"Each scope corresponds to a vendor-defined metadata system. Many scopes directly represent a specific file *chunk*, like `fmt` or `ixml`, and some may involve data read from many chunks. Examples of this would include `cues` or `adm`.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Metadata Scopes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### `data` and `fmt`: Basic WAV Data\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk."
|
"The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk."
|
||||||
]
|
]
|
||||||
@@ -51,6 +88,13 @@
|
|||||||
"(info.data.frame_count, info.data.byte_count)"
|
"(info.data.frame_count, info.data.byte_count)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"The `fmt` scope allows the client to read metadata from the WAVE format description."
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 3,
|
||||||
@@ -75,7 +119,9 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Broadcast WAV Extension"
|
"### `bext`: Broadcast WAV Extension\n",
|
||||||
|
"\n",
|
||||||
|
"The `bext` scope allows the client to access Broadcast-WAV metadata. "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,17 +133,17 @@
|
|||||||
"name": "stdout",
|
"name": "stdout",
|
||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"sSPEED=023.976-ND\r\n",
|
"sSPEED=023.976-ND\n",
|
||||||
"sTAKE=1\r\n",
|
"sTAKE=1\n",
|
||||||
"sUBITS=$12311801\r\n",
|
"sUBITS=$12311801\n",
|
||||||
"sSWVER=2.67\r\n",
|
"sSWVER=2.67\n",
|
||||||
"sPROJECT=BMH\r\n",
|
"sPROJECT=BMH\n",
|
||||||
"sSCENE=A101\r\n",
|
"sSCENE=A101\n",
|
||||||
"sFILENAME=A101_1.WAV\r\n",
|
"sFILENAME=A101_1.WAV\n",
|
||||||
"sTAPE=18Y12M31\r\n",
|
"sTAPE=18Y12M31\n",
|
||||||
"sTRK1=MKH516 A\r\n",
|
"sTRK1=MKH516 A\n",
|
||||||
"sTRK2=Boom\r\n",
|
"sTRK2=Boom\n",
|
||||||
"sNOTE=\r\n",
|
"sNOTE=\n",
|
||||||
"\n",
|
"\n",
|
||||||
"----------\n",
|
"----------\n",
|
||||||
"Originator: Sound Dev: 702T S#GR1112089007\n",
|
"Originator: Sound Dev: 702T S#GR1112089007\n",
|
||||||
@@ -105,7 +151,7 @@
|
|||||||
"Originator Date: 2018-12-31\n",
|
"Originator Date: 2018-12-31\n",
|
||||||
"Originator Time: 12:40:00\n",
|
"Originator Time: 12:40:00\n",
|
||||||
"Time Reference: 2190940753\n",
|
"Time Reference: 2190940753\n",
|
||||||
"A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\r\n",
|
"A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\n",
|
||||||
"\n"
|
"\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -125,7 +171,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## iXML Production Recorder Metadata"
|
"### `ixml`: iXML Production Recorder Metadata"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -156,11 +202,83 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "markdown",
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"source": [
|
||||||
"source": []
|
"### `cues`: Cues Metadata\n",
|
||||||
|
"\n",
|
||||||
|
"Cue time markers are accessible through the `cues` scope. The `each_cue` method returns an iterator that yields a tuple of each cue \"name\" or integer UID, and sample location. "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Cue ID: 1\n",
|
||||||
|
"Cue Offset: 29616\n",
|
||||||
|
"Cue ID: 2\n",
|
||||||
|
"Cue Offset: 74592\n",
|
||||||
|
"Cue ID: 3\n",
|
||||||
|
"Cue Offset: 121200\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"path = \"../tests/test_files/cue_chunks/STE-000.wav\"\n",
|
||||||
|
"info = WavInfoReader(path)\n",
|
||||||
|
"\n",
|
||||||
|
"for cue in info.cues.each_cue():\n",
|
||||||
|
" print(f\"Cue ID: {cue[0]}\")\n",
|
||||||
|
" print(f\"Cue Offset: {cue[1]}\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"There is also a convenience method to get the appropriate label and note for a given marker. (Note here also `WavInfoReader`'s facility for overriding default text encodings.)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Cue ID: 1\n",
|
||||||
|
" Label: Marker 1\n",
|
||||||
|
" At: 1000\n",
|
||||||
|
" Note: <NO NOTE>\n",
|
||||||
|
"Cue ID: 2\n",
|
||||||
|
" Label: Marker 2\n",
|
||||||
|
" At: 5000\n",
|
||||||
|
" Note: Marker Comment 1\n",
|
||||||
|
"Cue ID: 3\n",
|
||||||
|
" Label: Marker 3\n",
|
||||||
|
" At: 10000\n",
|
||||||
|
" Note: Лорем ипсум долор сит амет, тимеам вивендум хас ет, цу адолесценс дефинитионес еам.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"path = \"../tests/test_files/cue_chunks/izotoperx_cues_test.wav\"\n",
|
||||||
|
"info = WavInfoReader(path, info_encoding=\"utf-8\") # iZotope RX seems to encode marker text as UTF-8\n",
|
||||||
|
"\n",
|
||||||
|
"for cue in info.cues.each_cue():\n",
|
||||||
|
" print(f\"Cue ID: {cue[0]}\")\n",
|
||||||
|
" label, note = info.cues.label_and_note(cue[0])\n",
|
||||||
|
" print(f\" Label: {label}\")\n",
|
||||||
|
" print(f\" At: {cue[1]}\")\n",
|
||||||
|
" print(f\" Note: {note or '<NO NOTE>'}\")"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
@@ -172,7 +290,7 @@
|
|||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"display_name": "Python 3",
|
"display_name": "Python 3 (ipykernel)",
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"name": "python3"
|
"name": "python3"
|
||||||
},
|
},
|
||||||
@@ -186,9 +304,9 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.7.2"
|
"version": "3.11.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
"nbformat_minor": 2
|
"nbformat_minor": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 1,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import wavinfo\n",
|
|
||||||
"import pprint"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 2,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"pp = pprint.PrettyPrinter(indent=4)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 3,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"path = '../tests/test_files/protools/PT A101_4.A1.wav'\n",
|
|
||||||
"\n",
|
|
||||||
"info = wavinfo.WavInfoReader(path)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 4,
|
|
||||||
"metadata": {
|
|
||||||
"scrolled": true
|
|
||||||
},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"[ ChunkDescriptor(ident=b'bext', start=20, length=858),\n",
|
|
||||||
" ChunkDescriptor(ident=b'iXML', start=886, length=5226),\n",
|
|
||||||
" ChunkDescriptor(ident=b'fmt ', start=6120, length=16),\n",
|
|
||||||
" ChunkDescriptor(ident=b'data', start=6144, length=864840),\n",
|
|
||||||
" ChunkDescriptor(ident=b'umid', start=870992, length=24),\n",
|
|
||||||
" ChunkDescriptor(ident=b'minf', start=871024, length=16),\n",
|
|
||||||
" ChunkDescriptor(ident=b'regn', start=871048, length=92)]\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"import wavinfo.wave_parser\n",
|
|
||||||
"\n",
|
|
||||||
"with open(path,'rb') as f:\n",
|
|
||||||
" chunk_tree = wavinfo.wave_parser.parse_chunk(f)\n",
|
|
||||||
"\n",
|
|
||||||
"pp.pprint(chunk_tree.children)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 5,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00*\\xfd\\xf5\\x0c$\\xe4s\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'\n",
|
|
||||||
"000000000000002afdf50c24e47380000000000000000000\n",
|
|
||||||
"24\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"with open(path,'rb') as f:\n",
|
|
||||||
" f.seek( chunk_tree.children[4].start )\n",
|
|
||||||
" umid_bin = f.read(chunk_tree.children[4].length)\n",
|
|
||||||
" f.seek( chunk_tree.children[6].start )\n",
|
|
||||||
" regn_bin = f.read(chunk_tree.children[6].length)\n",
|
|
||||||
" \n",
|
|
||||||
"print(umid_bin)\n",
|
|
||||||
"print(umid_bin.hex())\n",
|
|
||||||
"print(len(umid_bin))"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 6,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"<wavinfo.wave_bext_reader.WavBextReader object at 0x10d5f8ac8>\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"print(info.bext)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 7,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"b'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00*\\xfd\\xf5\\x0c$\\xe4s\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c3\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00T\\xd5\\xa2\\x82\\x00\\x00\\x00\\x00\\x10PT A101_4.A1.wavGK\\xaa\\xaf\\x7f\\x00\\x00@ }\\x06\\x00`\\x00\\x00'\n",
|
|
||||||
"01000000000000000000002afdf50c24e473800000000000000000000c330200000000000000000000000000000000000000000054d5a2820000000010505420413130315f342e41312e776176474baaaf7f000040207d0600600000\n",
|
|
||||||
"92\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"\n",
|
|
||||||
"print(regn_bin)\n",
|
|
||||||
"print(regn_bin.hex())\n",
|
|
||||||
"print(len(regn_bin))"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 8,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"{ 'artist': 'Frank Bry',\n",
|
|
||||||
" 'comment': 'BULLET Impact Plastic LCD TV Screen Shatter Debris 2x',\n",
|
|
||||||
" 'copyright': '2018 Creative Sound Design, LLC (The Recordist Christmas '\n",
|
|
||||||
" '2018) www.therecordist.com',\n",
|
|
||||||
" 'created_date': '2018-11-15',\n",
|
|
||||||
" 'engineer': None,\n",
|
|
||||||
" 'genre': 'Bullets',\n",
|
|
||||||
" 'keywords': None,\n",
|
|
||||||
" 'product': 'The Recordist Christmas 2018',\n",
|
|
||||||
" 'software': 'Soundminer',\n",
|
|
||||||
" 'source': None,\n",
|
|
||||||
" 'tape': None,\n",
|
|
||||||
" 'title': None}\n",
|
|
||||||
"{ 'coding_history': '',\n",
|
|
||||||
" 'description': 'BULLET Impact Plastic LCD TV Screen Shatter Debris 2x',\n",
|
|
||||||
" 'loudness_range': None,\n",
|
|
||||||
" 'loudness_value': None,\n",
|
|
||||||
" 'max_momentary_loudness': None,\n",
|
|
||||||
" 'max_shortterm_loudness': None,\n",
|
|
||||||
" 'max_true_peak': None,\n",
|
|
||||||
" 'originator': 'TheRecordist',\n",
|
|
||||||
" 'originator_date': '2018-12-20',\n",
|
|
||||||
" 'originator_ref': 'aaiAKt3fCGTk',\n",
|
|
||||||
" 'originator_time': '12:15:37',\n",
|
|
||||||
" 'time_reference': 57882,\n",
|
|
||||||
" 'version': 0}\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"path = '../tests/test_files/BULLET Impact Plastic LCD TV Screen Shatter Debris 2x.wav'\n",
|
|
||||||
"\n",
|
|
||||||
"info = wavinfo.WavInfoReader(path)\n",
|
|
||||||
"\n",
|
|
||||||
"with open(path,'rb') as f:\n",
|
|
||||||
" chunk_tree = wavinfo.wave_parser.parse_chunk(f)\n",
|
|
||||||
" \n",
|
|
||||||
"pp.pprint(info.info.to_dict())\n",
|
|
||||||
"pp.pprint(info.bext.to_dict())"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [],
|
|
||||||
"source": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3",
|
|
||||||
"language": "python",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"codemirror_mode": {
|
|
||||||
"name": "ipython",
|
|
||||||
"version": 3
|
|
||||||
},
|
|
||||||
"file_extension": ".py",
|
|
||||||
"mimetype": "text/x-python",
|
|
||||||
"name": "python",
|
|
||||||
"nbconvert_exporter": "python",
|
|
||||||
"pygments_lexer": "ipython3",
|
|
||||||
"version": "3.7.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 2
|
|
||||||
}
|
|
||||||
74
tests/test_cue.py
Normal file
74
tests/test_cue.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
import wavinfo
|
||||||
|
|
||||||
|
class TestCue(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.test_files = glob("tests/test_files/cue_chunks/*.wav")
|
||||||
|
return super().setUp()
|
||||||
|
|
||||||
|
def test_enumerate(self):
|
||||||
|
file1 = "tests/test_files/cue_chunks/STE-000.wav"
|
||||||
|
w1 = wavinfo.WavInfoReader(file1)
|
||||||
|
self.assertIsNotNone(w1.cues)
|
||||||
|
vals = list(w1.cues.each_cue())
|
||||||
|
self.assertEqual(vals, [(1,29616),(2,74592),(3,121200)])
|
||||||
|
|
||||||
|
def test_labels_notes(self):
|
||||||
|
file = "tests/test_files/cue_chunks/izotoperx_cues_test.wav"
|
||||||
|
w1 = wavinfo.WavInfoReader(file)
|
||||||
|
self.assertIsNotNone(w1.cues)
|
||||||
|
assert w1.cues is not None
|
||||||
|
|
||||||
|
for name, _ in w1.cues.each_cue():
|
||||||
|
self.assertIn(name,[1,2,3])
|
||||||
|
label, note = w1.cues.label_and_note(name)
|
||||||
|
if name == 1:
|
||||||
|
self.assertEqual("Marker 1", label)
|
||||||
|
self.assertIsNone(note)
|
||||||
|
|
||||||
|
def test_range(self):
|
||||||
|
file = "tests/test_files/cue_chunks/izotoperx_cues_test.wav"
|
||||||
|
w1 = wavinfo.WavInfoReader(file)
|
||||||
|
self.assertIsNotNone(w1.cues)
|
||||||
|
assert w1.cues is not None
|
||||||
|
|
||||||
|
self.assertEqual(w1.cues.range(3), 10000)
|
||||||
|
|
||||||
|
def test_encoding_fallback(self):
|
||||||
|
"""
|
||||||
|
Added this after I noticed that iZotope RX seems to just encode "notes"
|
||||||
|
as utf-8 without bothering to dump this info into the ltxt or
|
||||||
|
specifying an encoding by some other means.
|
||||||
|
"""
|
||||||
|
file = "tests/test_files/cue_chunks/izotoperx_cues_test.wav"
|
||||||
|
w = wavinfo.WavInfoReader(file, info_encoding='utf-8')
|
||||||
|
expected = ("Лорем ипсум долор сит амет, тимеам вивендум хас ет, "
|
||||||
|
"цу адолесценс дефинитионес еам.")
|
||||||
|
|
||||||
|
assert w.cues is not None
|
||||||
|
note = [n for n in w.cues.notes if n.name == 3]
|
||||||
|
self.assertEqual(len(note), 1)
|
||||||
|
self.assertEqual(note[0].text, expected)
|
||||||
|
|
||||||
|
def test_label(self):
|
||||||
|
file = "tests/test_files/cue_chunks/izotoperx_cues_test.wav"
|
||||||
|
w = wavinfo.WavInfoReader(file)
|
||||||
|
|
||||||
|
self.assertIsNotNone(w.cues)
|
||||||
|
assert w.cues is not None
|
||||||
|
|
||||||
|
self.assertEqual(len(w.cues.labels), 3)
|
||||||
|
for label in w.cues.labels:
|
||||||
|
self.assertIn(label.name, [1,2,3])
|
||||||
|
if label.name == 1:
|
||||||
|
self.assertEqual(label.text, "Marker 1")
|
||||||
|
elif label.name == 2:
|
||||||
|
self.assertEqual(label.text, "Marker 2")
|
||||||
|
elif label.name == 3:
|
||||||
|
self.assertEqual(label.text, "Marker 3")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
tests/test_files/cue_chunks/izotoperx_cues_test.wav
Normal file
BIN
tests/test_files/cue_chunks/izotoperx_cues_test.wav
Normal file
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import wavinfo
|
import wavinfo
|
||||||
|
|
||||||
|
import glob
|
||||||
|
|
||||||
class TestWalk(unittest.TestCase):
|
class TestWalk(unittest.TestCase):
|
||||||
def test_walk_metadata(self):
|
def test_walk_metadata(self):
|
||||||
@@ -20,6 +21,17 @@ class TestWalk(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(tested_data and tested_format)
|
self.assertTrue(tested_data and tested_format)
|
||||||
|
|
||||||
|
def test_walk_all(self):
|
||||||
|
for file in glob.glob('tests/test_files/**/*.wav'):
|
||||||
|
info = wavinfo.WavInfoReader(file)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for _, _, _ in info.walk():
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
self.fail(f"Failed to walk metadata in file {file}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import encodings
|
|||||||
from .riff_parser import ChunkDescriptor
|
from .riff_parser import ChunkDescriptor
|
||||||
|
|
||||||
from struct import unpack, calcsize
|
from struct import unpack, calcsize
|
||||||
from typing import Optional, NamedTuple, List, Dict, Any
|
from typing import Optional, Tuple, NamedTuple, List, Dict, Any, Generator
|
||||||
|
|
||||||
#: Country Codes used in the RIFF standard to resolve locale. These codes
|
#: Country Codes used in the RIFF standard to resolve locale. These codes
|
||||||
#: appear in CSET and LTXT metadata.
|
#: appear in CSET and LTXT metadata.
|
||||||
@@ -130,7 +130,7 @@ class LabelEntry(NamedTuple):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def read(cls, data: bytes, encoding: str):
|
def read(cls, data: bytes, encoding: str):
|
||||||
return cls(name=unpack("<I", data[0:4])[0],
|
return cls(name=unpack("<I", data[0:4])[0],
|
||||||
text=data[4:].decode(encoding))
|
text=data[4:].decode(encoding).rstrip("\0"))
|
||||||
|
|
||||||
|
|
||||||
NoteEntry = LabelEntry
|
NoteEntry = LabelEntry
|
||||||
@@ -166,12 +166,14 @@ class WavCuesReader:
|
|||||||
cues: List[CueEntry]
|
cues: List[CueEntry]
|
||||||
labels: List[LabelEntry]
|
labels: List[LabelEntry]
|
||||||
ranges: List[RangeLabel]
|
ranges: List[RangeLabel]
|
||||||
|
notes: List[NoteEntry]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def merge(cls, f,
|
def read_all(cls, f,
|
||||||
cues: Optional[ChunkDescriptor],
|
cues: Optional[ChunkDescriptor],
|
||||||
labls: List[ChunkDescriptor],
|
labls: List[ChunkDescriptor],
|
||||||
ltxts: List[ChunkDescriptor],
|
ltxts: List[ChunkDescriptor],
|
||||||
|
notes: List[ChunkDescriptor],
|
||||||
fallback_encoding: str) -> 'WavCuesReader':
|
fallback_encoding: str) -> 'WavCuesReader':
|
||||||
|
|
||||||
cue_list = []
|
cue_list = []
|
||||||
@@ -200,17 +202,71 @@ class WavCuesReader:
|
|||||||
fallback_encoding=fallback_encoding)
|
fallback_encoding=fallback_encoding)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
note_list = []
|
||||||
|
for note in notes:
|
||||||
|
note_list.append(
|
||||||
|
NoteEntry.read(note.read_data(f),
|
||||||
|
encoding=fallback_encoding)
|
||||||
|
)
|
||||||
|
|
||||||
return WavCuesReader(cues=cue_list, labels=label_list,
|
return WavCuesReader(cues=cue_list, labels=label_list,
|
||||||
ranges=range_list)
|
ranges=range_list, notes=note_list)
|
||||||
|
|
||||||
|
def each_cue(self) -> Generator[Tuple[int, int], None, None]:
|
||||||
|
"""
|
||||||
|
Iterate through each cue.
|
||||||
|
|
||||||
|
:yields: the cue's ``name`` and ``sample_offset``
|
||||||
|
"""
|
||||||
|
for cue in self.cues:
|
||||||
|
yield (cue.name, cue.sample_offset)
|
||||||
|
|
||||||
|
def label_and_note(self, cue_ident: int) -> Tuple[Optional[str],
|
||||||
|
Optional[str]]:
|
||||||
|
"""
|
||||||
|
Get the label and note (extended comment) for a cue.
|
||||||
|
|
||||||
|
:param cue_ident: the cue's name, its unique identifying number
|
||||||
|
:returns: a tuple of the the cue's label (if present) and note (if
|
||||||
|
present)
|
||||||
|
"""
|
||||||
|
label = next((l.text for l in self.labels
|
||||||
|
if l.name == cue_ident), None)
|
||||||
|
note = next((n.text for n in self.notes
|
||||||
|
if n.name == cue_ident), None)
|
||||||
|
return (label, note)
|
||||||
|
|
||||||
|
def range(self, cue_ident: int) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Get the length of the time range for a cue, if it has one.
|
||||||
|
|
||||||
|
:param cue_ident: the cue's name, its unique identifying number
|
||||||
|
:returns: the length of the marker's range, or `None`
|
||||||
|
"""
|
||||||
|
return next((r.length for r in self.ranges
|
||||||
|
if r.name == cue_ident), None)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return dict(cues=[c.__dict__ for c in self.cues],
|
retval = dict()
|
||||||
labels=[l.__dict__ for l in self.labels],
|
|
||||||
ranges=[r.__dict__ for r in self.ranges])
|
for n, t in self.each_cue():
|
||||||
|
retval[n] = dict()
|
||||||
|
retval[n]['frame'] = t
|
||||||
|
label, note = self.label_and_note(n)
|
||||||
|
r = self.range(n)
|
||||||
|
|
||||||
|
if label is not None:
|
||||||
|
retval[n]['label'] = label
|
||||||
|
if note is not None:
|
||||||
|
retval[n]['note'] = note
|
||||||
|
if r is not None:
|
||||||
|
retval[n]['length'] = r
|
||||||
|
|
||||||
|
return retval
|
||||||
|
# return dict(cues=[c._asdict() for c in self.cues],
|
||||||
|
# labels=[l._asdict() for l in self.labels],
|
||||||
|
# ranges=[r._asdict() for r in self.ranges],
|
||||||
|
# notes=[n._asdict() for n in self.notes])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,8 @@ class WavInfoReader:
|
|||||||
file handle to an open file.
|
file handle to an open file.
|
||||||
|
|
||||||
:param info_encoding:
|
:param info_encoding:
|
||||||
The text encoding of the INFO, LABL and other RIFF-defined metadata
|
The text encoding of the ``INFO``, ``LABL`` and other RIFF-defined
|
||||||
fields. latin_1/ISO 8859-1/Win CP819 is the safest assumption for
|
metadata fields.
|
||||||
this; chunks that define their own encoding explicitly (like LTXT)
|
|
||||||
will override this setting.
|
|
||||||
|
|
||||||
:param bext_encoding:
|
:param bext_encoding:
|
||||||
The text encoding to use when decoding the string
|
The text encoding to use when decoding the string
|
||||||
@@ -73,7 +71,7 @@ class WavInfoReader:
|
|||||||
#: RIFF INFO metadata.
|
#: RIFF INFO metadata.
|
||||||
self.info :Optional[WavInfoChunkReader]= None
|
self.info :Optional[WavInfoChunkReader]= None
|
||||||
|
|
||||||
#: RIFF CUE, LABL and LTXT metadata.
|
#: RIFF cues markers, labels, and notes.
|
||||||
self.cues :Optional[WavCuesReader] = None
|
self.cues :Optional[WavCuesReader] = None
|
||||||
|
|
||||||
if hasattr(path, 'read'):
|
if hasattr(path, 'read'):
|
||||||
@@ -137,25 +135,12 @@ class WavInfoReader:
|
|||||||
def _get_format(self, f):
|
def _get_format(self, f):
|
||||||
fmt_data = self._find_chunk_data(b'fmt ', f)
|
fmt_data = self._find_chunk_data(b'fmt ', f)
|
||||||
assert fmt_data is not None, "Fmt data not found, not a valid wav file"
|
assert fmt_data is not None, "Fmt data not found, not a valid wav file"
|
||||||
# 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
|
|
||||||
packstring = "<HHIIHH"
|
packstring = "<HHIIHH"
|
||||||
rest_starts = struct.calcsize(packstring)
|
rest_starts = struct.calcsize(packstring)
|
||||||
|
|
||||||
unpacked = struct.unpack(packstring, fmt_data[:rest_starts])
|
unpacked = struct.unpack(packstring, fmt_data[:rest_starts])
|
||||||
|
|
||||||
# 0x0001 WAVE_FORMAT_PCM PCM
|
|
||||||
# 0x0003 WAVE_FORMAT_IEEE_FLOAT IEEE float
|
|
||||||
# 0x0006 WAVE_FORMAT_ALAW 8-bit ITU-T G.711 A-law
|
|
||||||
# 0x0007 WAVE_FORMAT_MULAW 8-bit ITU-T G.711 µ-law
|
|
||||||
# 0xFFFE WAVE_FORMAT_EXTENSIBLE Determined by SubFormat
|
|
||||||
|
|
||||||
# https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html
|
|
||||||
return WavAudioFormat(audio_format=unpacked[0],
|
return WavAudioFormat(audio_format=unpacked[0],
|
||||||
channel_count=unpacked[1],
|
channel_count=unpacked[1],
|
||||||
sample_rate=unpacked[2],
|
sample_rate=unpacked[2],
|
||||||
@@ -198,11 +183,13 @@ class WavInfoReader:
|
|||||||
adtl = self._find_list_chunk(b'adtl')
|
adtl = self._find_list_chunk(b'adtl')
|
||||||
labls = []
|
labls = []
|
||||||
ltxts = []
|
ltxts = []
|
||||||
|
notes = []
|
||||||
if adtl is not None:
|
if adtl is not None:
|
||||||
labls = [child for child in adtl.children if child.ident == b'labl']
|
labls = [c for c in adtl.children if c.ident == b'labl']
|
||||||
ltxts = [child for child in adtl.children if child.ident == b'ltxt']
|
ltxts = [c for c in adtl.children if c.ident == b'ltxt']
|
||||||
|
notes = [c for c in adtl.children if c.ident == b'note']
|
||||||
|
|
||||||
return WavCuesReader.merge(f, cue, labls, ltxts,
|
return WavCuesReader.read_all(f, cue, labls, ltxts, notes,
|
||||||
fallback_encoding=self.info_encoding)
|
fallback_encoding=self.info_encoding)
|
||||||
|
|
||||||
def walk(self) -> Generator[str,str,Any]: #FIXME: this should probably be named "iter()"
|
def walk(self) -> Generator[str,str,Any]: #FIXME: this should probably be named "iter()"
|
||||||
@@ -214,7 +201,8 @@ class WavInfoReader:
|
|||||||
"fmt", "data", "ixml", "bext", "info", "dolby", "cues" or "adm".
|
"fmt", "data", "ixml", "bext", "info", "dolby", "cues" or "adm".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
scopes = ('fmt', 'data', 'ixml', 'bext', 'info', 'adm', 'cues', 'dolby')
|
scopes = ('fmt', 'data', 'ixml', 'bext', 'info', 'adm', 'cues',
|
||||||
|
'dolby')
|
||||||
|
|
||||||
for scope in scopes:
|
for scope in scopes:
|
||||||
if scope in ['fmt', 'data']:
|
if scope in ['fmt', 'data']:
|
||||||
|
|||||||
Reference in New Issue
Block a user