mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2026-01-01 01:10:40 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed8b5f167e | ||
|
|
97c25ab61f | ||
|
|
910b3854c7 | ||
|
|
f12bb0eea4 | ||
|
|
c88599f4fd | ||
|
|
68ccb09f53 | ||
|
|
b7d9f6758d | ||
|
|
e3adde7498 | ||
|
|
96885dfb4e | ||
|
|
126f1ea7c3 | ||
|
|
0bb664357e | ||
|
|
9e2c60caf8 | ||
|
|
82d73b0316 | ||
|
|
0612e62a7b | ||
|
|
8fd509482c | ||
|
|
a9dbfdf5ec | ||
|
|
413826b18f | ||
|
|
71201adee4 | ||
|
|
d88117878c | ||
|
|
b20c5dd1bd | ||
|
|
6c9fb38482 | ||
|
|
f5c0700e47 | ||
|
|
3c101badac | ||
|
|
50962aefcc | ||
|
|
f52b2a195a | ||
|
|
4586d19a5f | ||
|
|
3e897d030c | ||
|
|
9f943aeb61 | ||
|
|
a9c3600ad2 | ||
|
|
27d9a2f005 | ||
|
|
0c5c0a2088 |
@@ -1,11 +1,13 @@
|
|||||||
|
dist: xenial
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.4"
|
|
||||||
script:
|
script:
|
||||||
- "python3 setup.py test"
|
- "python3 setup.py test"
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get install -y ffmpeg
|
- "sudo apt-get update"
|
||||||
|
- "sudo add-apt-repository universe"
|
||||||
|
- "sudo apt-get install -y ffmpeg"
|
||||||
install:
|
install:
|
||||||
- "pip3 install setuptools"
|
- "pip3 install setuptools"
|
||||||
|
|||||||
56
README.md
56
README.md
@@ -1,3 +1,4 @@
|
|||||||
|
[](https://travis-ci.com/iluvcapra/wavinfo)
|
||||||
[](https://wavinfo.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/wavinfo/) 
|
[](https://wavinfo.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/wavinfo/) 
|
||||||
|
|
||||||
|
|
||||||
@@ -22,8 +23,6 @@ In progress:
|
|||||||
* iXML `STEINBERG` sound library attributes.
|
* iXML `STEINBERG` sound library attributes.
|
||||||
* Pro Tools __embedded regions__.
|
* Pro Tools __embedded regions__.
|
||||||
|
|
||||||
This module is presently under construction and not sutiable for production at this time.
|
|
||||||
|
|
||||||
[ebu]:https://tech.ebu.ch/docs/tech/tech3285.pdf
|
[ebu]:https://tech.ebu.ch/docs/tech/tech3285.pdf
|
||||||
[smpte_330m2011]:http://standards.smpte.org/content/978-1-61482-678-1/st-330-2011/SEC1.abstract
|
[smpte_330m2011]:http://standards.smpte.org/content/978-1-61482-678-1/st-330-2011/SEC1.abstract
|
||||||
[ixml]:http://www.ixml.info
|
[ixml]:http://www.ixml.info
|
||||||
@@ -32,10 +31,8 @@ This module is presently under construction and not sutiable for production at t
|
|||||||
|
|
||||||
## Demonstration
|
## Demonstration
|
||||||
|
|
||||||
|
|
||||||
The entry point for wavinfo is the WavInfoReader class.
|
The entry point for wavinfo is the WavInfoReader class.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from wavinfo import WavInfoReader
|
from wavinfo import WavInfoReader
|
||||||
|
|
||||||
@@ -46,10 +43,8 @@ info = WavInfoReader(path)
|
|||||||
|
|
||||||
### Basic WAV Data
|
### Basic WAV Data
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
(info.data.frame_count, info.data.byte_count)
|
(info.data.frame_count, info.data.byte_count)
|
||||||
>>> (240239, 1441434)
|
>>> (240239, 1441434)
|
||||||
@@ -57,9 +52,27 @@ The length of the file in frames (interleaved samples) and bytes is available, a
|
|||||||
>>> (48000, 2, 6, 24)
|
>>> (48000, 2, 6, 24)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Broadcast WAV Extension
|
### Broadcast WAV Extension
|
||||||
|
|
||||||
|
A WAV file produced to Broadcast-WAV specifications will have the broadcast metadata extension,
|
||||||
|
which includes a 256-character free text descrption, creating entity identifier (usually the
|
||||||
|
recording application or equipment), the date and time of recording and a time reference for
|
||||||
|
timecode synchronization.
|
||||||
|
|
||||||
|
The `coding_history` 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
|
||||||
|
information in the `description`. Here also the `originator_ref` 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
|
||||||
|
330M UMID. The 32-byte version of the UMID is usually just a random number, while the 64-byte
|
||||||
|
UMID will also have information on the 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
|
||||||
|
as described by EBU Rec 128.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
print(info.bext.description)
|
print(info.bext.description)
|
||||||
@@ -94,8 +107,17 @@ print(info.bext.coding_history)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## iXML Production Recorder Metadata
|
### iXML Production Recorder Metadata
|
||||||
|
|
||||||
|
iXML allows an XML document to be embedded in a WAV file.
|
||||||
|
|
||||||
|
The iXML website recommends a schema for recorder information but
|
||||||
|
there is no official DTD and vendors mostly do their own thing, apart from
|
||||||
|
hitting a few key xpaths. iXML is used by most location/production recorders
|
||||||
|
to save slating information, timecode and sync points in a reliable way.
|
||||||
|
|
||||||
|
iXML is also used to link "families" of WAV files together, so WAV files
|
||||||
|
recorded simultaneously or contiguously can be related by a receiving client.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
print("iXML Project:", info.ixml.project)
|
print("iXML Project:", info.ixml.project)
|
||||||
@@ -112,6 +134,22 @@ print("iXML File Family UID:", info.ixml.family_uid)
|
|||||||
iXML Tape: 18Y12M31
|
iXML Tape: 18Y12M31
|
||||||
iXML File Family Name: None
|
iXML File Family Name: None
|
||||||
iXML File Family UID: USSDVGR1112089007124001008206300
|
iXML File Family UID: USSDVGR1112089007124001008206300
|
||||||
|
|
||||||
|
|
||||||
|
### INFO Metadata
|
||||||
|
|
||||||
|
INFO Metadata is a standard method for saving tagged text data in a WAV or AVI
|
||||||
|
file. INFO fields are often read by the file explorer and host OS, and used in
|
||||||
|
music library software.
|
||||||
|
|
||||||
|
```python
|
||||||
|
bullet_path = '../tests/test_files/BULLET Impact Plastic LCD TV Screen Shatter Debris 2x.wav'
|
||||||
|
|
||||||
|
bullet = WavInfoReader(bullet_path)
|
||||||
|
```
|
||||||
|
|
||||||
|
print("INFO Artist:", bullet.info.artist)
|
||||||
|
print("INFO Copyright:", bullet.info.copyright)
|
||||||
|
print("INFO Comment:", bullet.info.comment)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 1,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from wavinfo import WavInfoReader\n",
|
"from wavinfo import WavInfoReader\n",
|
||||||
"\n",
|
"\n",
|
||||||
"path = '../tests/test_files/A101_1.WAV'\n",
|
"path = '../tests/test_files/sounddevices/A101_1.WAV'\n",
|
||||||
"\n",
|
"\n",
|
||||||
"info = WavInfoReader(path)"
|
"info = WavInfoReader(path)"
|
||||||
]
|
]
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 4,
|
"execution_count": 2,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"(240239, 1441434)"
|
"(240239, 1441434)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 4,
|
"execution_count": 2,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 7,
|
"execution_count": 3,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"(48000, 2, 6, 24)"
|
"(48000, 2, 6, 24)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 7,
|
"execution_count": 3,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"output_type": "execute_result"
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 13,
|
"execution_count": 4,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 14,
|
"execution_count": 5,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
@@ -142,9 +142,7 @@
|
|||||||
"iXML Take: 1\n",
|
"iXML Take: 1\n",
|
||||||
"iXML Tape: 18Y12M31\n",
|
"iXML Tape: 18Y12M31\n",
|
||||||
"iXML File Family Name: None\n",
|
"iXML File Family Name: None\n",
|
||||||
"iXML File Family UID: USSDVGR1112089007124001008206300\n",
|
"iXML File Family UID: USSDVGR1112089007124001008206300\n"
|
||||||
"A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\r\n",
|
|
||||||
"\n"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -157,6 +155,13 @@
|
|||||||
"print(\"iXML File Family UID:\", info.ixml.family_uid)"
|
"print(\"iXML File Family UID:\", info.ixml.family_uid)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -4,18 +4,17 @@ with open("README.md", "r") as fh:
|
|||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
setup(name='wavinfo',
|
setup(name='wavinfo',
|
||||||
version='0.3',
|
version='1.0',
|
||||||
author='Jamie Hardt',
|
author='Jamie Hardt',
|
||||||
author_email='jamiehardt@me.com',
|
author_email='jamiehardt@me.com',
|
||||||
description='WAVE sound file metadata parser.',
|
description='WAVE sound file metadata parser.',
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
url='https://github.com/iluvcapra/wavinfo',
|
url='https://github.com/iluvcapra/wavinfo',
|
||||||
classifiers=['Development Status :: 4 - Beta',
|
classifiers=['Development Status :: 5 - Production/Stable',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Topic :: Multimedia',
|
'Topic :: Multimedia',
|
||||||
'Topic :: Multimedia :: Sound/Audio',
|
'Topic :: Multimedia :: Sound/Audio',
|
||||||
"Programming Language :: Python :: 3.4",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
"Programming Language :: Python :: 3.5",
|
||||||
"Programming Language :: Python :: 3.6"],
|
"Programming Language :: Python :: 3.6"],
|
||||||
packages=['wavinfo'])
|
packages=['wavinfo'])
|
||||||
|
|||||||
Binary file not shown.
@@ -7,7 +7,7 @@ from unittest import TestCase
|
|||||||
|
|
||||||
import wavinfo
|
import wavinfo
|
||||||
|
|
||||||
FFPROBE='/usr/local/bin/ffprobe'
|
FFPROBE='ffprobe'
|
||||||
|
|
||||||
|
|
||||||
def ffprobe(path):
|
def ffprobe(path):
|
||||||
@@ -17,7 +17,8 @@ def ffprobe(path):
|
|||||||
process = subprocess.run(arguments, stdin=None, stdout=PIPE, stderr=PIPE)
|
process = subprocess.run(arguments, stdin=None, stdout=PIPE, stderr=PIPE)
|
||||||
|
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
return json.loads(process.stdout)
|
output_str = process.stdout.decode('utf-8')
|
||||||
|
return json.loads(output_str)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .wave_reader import WavInfoReader
|
from .wave_reader import WavInfoReader
|
||||||
|
|
||||||
__version__ = 0.3
|
__version__ = 1.0
|
||||||
__author__ = 'Jamie Hardt'
|
__author__ = 'Jamie Hardt'
|
||||||
|
|||||||
@@ -11,14 +11,17 @@ class WavBextReader:
|
|||||||
# lowtimeref U32
|
# lowtimeref U32
|
||||||
# hightimeref U32
|
# hightimeref U32
|
||||||
# version U16
|
# version U16
|
||||||
|
#
|
||||||
|
# V1 field
|
||||||
# umid[64]
|
# umid[64]
|
||||||
#
|
#
|
||||||
# EBU 3285 fields
|
# V2 fields
|
||||||
# loudnessvalue S16 (in LUFS*100)
|
# loudnessvalue S16 (in LUFS*100)
|
||||||
# loudnessrange S16 (in LUFS*100)
|
# loudnessrange S16 (in LUFS*100)
|
||||||
# maxtruepeak S16 (in dbTB*100)
|
# maxtruepeak S16 (in dbTB*100)
|
||||||
# maxmomentaryloudness S16 (LUFS*100)
|
# maxmomentaryloudness S16 (LUFS*100)
|
||||||
# maxshorttermloudness S16 (LUFS*100)
|
# maxshorttermloudness S16 (LUFS*100)
|
||||||
|
#
|
||||||
# reserved[180]
|
# reserved[180]
|
||||||
# codinghistory []
|
# codinghistory []
|
||||||
if bext_data is None:
|
if bext_data is None:
|
||||||
@@ -39,33 +42,30 @@ class WavBextReader:
|
|||||||
decoded = trimmed.decode(encoding)
|
decoded = trimmed.decode(encoding)
|
||||||
return decoded
|
return decoded
|
||||||
|
|
||||||
bext_version = unpacked[6]
|
self.description = sanatize_bytes(unpacked[0])
|
||||||
if bext_version > 0:
|
self.originator = sanatize_bytes(unpacked[1])
|
||||||
self.umid = unpacked[6]
|
self.originator_ref = sanatize_bytes(unpacked[2])
|
||||||
else:
|
self.originator_date = sanatize_bytes(unpacked[3])
|
||||||
self.umid = None
|
self.originator_time = sanatize_bytes(unpacked[4])
|
||||||
|
self.time_reference = unpacked[5]
|
||||||
|
self.version = unpacked[6]
|
||||||
|
self.umid = None
|
||||||
|
self.loudness_value = None
|
||||||
|
self.loudness_range = None
|
||||||
|
self.max_true_peak = None
|
||||||
|
self.max_momentary_loudness = None
|
||||||
|
self.max_shortterm_loudness = None
|
||||||
|
self.coding_history = sanatize_bytes(bext_data[rest_starts:])
|
||||||
|
|
||||||
if bext_version > 1:
|
if self.version > 0:
|
||||||
self.loudness_value = unpacked[8] / 100.0,
|
self.umid = unpacked[7]
|
||||||
|
|
||||||
|
if self.version > 1:
|
||||||
|
self.loudness_value = unpacked[8] / 100.0
|
||||||
self.loudness_range = unpacked[9] / 100.0
|
self.loudness_range = unpacked[9] / 100.0
|
||||||
self.max_true_peak = unpacked[10] / 100.0
|
self.max_true_peak = unpacked[10] / 100.0
|
||||||
self.max_momentary_loudness = unpacked[11] / 100.0
|
self.max_momentary_loudness = unpacked[11] / 100.0
|
||||||
self.max_shortterm_loudness = unpacked[12] / 100.0
|
self.max_shortterm_loudness = unpacked[12] / 100.0
|
||||||
else:
|
|
||||||
self.loudness_value = None
|
|
||||||
self.loudness_range = None
|
|
||||||
self.max_true_peak = None
|
|
||||||
self.max_momentary_loudness = None
|
|
||||||
self.max_shortterm_loudness = None
|
|
||||||
|
|
||||||
self.description = sanatize_bytes(unpacked[0])
|
|
||||||
self.originator = sanatize_bytes(unpacked[1])
|
|
||||||
self.originator_ref = sanatize_bytes(unpacked[2])
|
|
||||||
self.originator_date = sanatize_bytes(unpacked[3])
|
|
||||||
self.originator_time = sanatize_bytes(unpacked[4])
|
|
||||||
self.time_reference = unpacked[5]
|
|
||||||
self.version = unpacked[6]
|
|
||||||
self.coding_history = sanatize_bytes(bext_data[rest_starts:])
|
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
from .wave_parser import parse_chunk, ListChunkDescriptor
|
from .riff_parser import parse_chunk, ListChunkDescriptor
|
||||||
|
|
||||||
class WavInfoChunkReader:
|
class WavInfoChunkReader:
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
import io
|
||||||
|
|
||||||
class WavIXMLFormat:
|
class WavIXMLFormat:
|
||||||
"""
|
"""
|
||||||
@@ -6,17 +7,18 @@ class WavIXMLFormat:
|
|||||||
"""
|
"""
|
||||||
def __init__(self, xml):
|
def __init__(self, xml):
|
||||||
self.source = xml
|
self.source = xml
|
||||||
self.parsed = ET.fromstring(xml)
|
xmlBytes = io.BytesIO(xml)
|
||||||
|
self.parsed = ET.parse(xmlBytes)
|
||||||
|
|
||||||
def _get_text_value(self, xpath):
|
def _get_text_value(self, xpath):
|
||||||
e = self.parsed.find("./" + xpath)
|
e = self.parsed.find("./" + xpath)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
return e.text
|
return e.text
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project(self):
|
def project(self):
|
||||||
return self._get_text_value("PROJECT")
|
return self._get_text_value("PROJECT")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scene(self):
|
def scene(self):
|
||||||
return self._get_text_value("SCENE")
|
return self._get_text_value("SCENE")
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import struct
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from .wave_parser import parse_chunk, ChunkDescriptor, ListChunkDescriptor
|
from .riff_parser import parse_chunk, ChunkDescriptor, ListChunkDescriptor
|
||||||
from .wave_ixml_reader import WavIXMLFormat
|
from .wave_ixml_reader import WavIXMLFormat
|
||||||
from .wave_bext_reader import WavBextReader
|
from .wave_bext_reader import WavBextReader
|
||||||
from .wave_info_reader import WavInfoChunkReader
|
from .wave_info_reader import WavInfoChunkReader
|
||||||
@@ -118,7 +118,7 @@ class WavInfoReader():
|
|||||||
if ixml_data is None:
|
if ixml_data is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ixml_string = ixml_data.decode('utf-8')
|
ixml_string = ixml_data
|
||||||
return WavIXMLFormat(ixml_string)
|
return WavIXMLFormat(ixml_string)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user