mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2026-01-01 09:20:40 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
841b86f3f4 | ||
|
|
5c90d5ff47 | ||
|
|
60e329fdb4 | ||
|
|
15db4c9ffa | ||
|
|
49ac961b94 | ||
|
|
7fc530b2cd | ||
|
|
4dfc1ab33c | ||
|
|
4770c781b2 | ||
|
|
45c2aae640 |
@@ -5,6 +5,7 @@ python:
|
|||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
script:
|
script:
|
||||||
- "gunzip tests/test_files/rf64/*.gz"
|
- "gunzip tests/test_files/rf64/*.gz"
|
||||||
- "python setup.py test"
|
- "python setup.py test"
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ The length of the file in frames (interleaved samples) and bytes is available, a
|
|||||||
>>> (48000, 2, 6, 24)
|
>>> (48000, 2, 6, 24)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Other Resources
|
||||||
|
|
||||||
|
* For other file formats and ID3 decoding, look at [audio-metadata](https://github.com/thebigmunch/audio-metadata).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
setup.py
16
setup.py
@@ -4,20 +4,30 @@ with open("README.md", "r") as fh:
|
|||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
setup(name='wavinfo',
|
setup(name='wavinfo',
|
||||||
version='1.2',
|
version='1.4',
|
||||||
author='Jamie Hardt',
|
author='Jamie Hardt',
|
||||||
author_email='jamiehardt@me.com',
|
author_email='jamiehardt@me.com',
|
||||||
description='Probe WAVE Files for iXML, Broadcast-WAVE and other metadata.',
|
description='Probe WAVE Files for iXML, Broadcast-WAVE and other metadata.',
|
||||||
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',
|
||||||
|
project_urls={
|
||||||
|
'Source':
|
||||||
|
'https://github.com/iluvcapra/wavinfo',
|
||||||
|
'Documentation':
|
||||||
|
'https://wavinfo.readthedocs.io/',
|
||||||
|
'Issues':
|
||||||
|
'https://github.com/iluvcapra/wavinfo/issues',
|
||||||
|
},
|
||||||
|
packages=['wavinfo'],
|
||||||
classifiers=['Development Status :: 5 - Production/Stable',
|
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.5",
|
"Programming Language :: Python :: 3.5",
|
||||||
"Programming Language :: Python :: 3.6",
|
"Programming Language :: Python :: 3.6",
|
||||||
"Programming Language :: Python :: 3.7"],
|
"Programming Language :: Python :: 3.7",
|
||||||
packages=['wavinfo'],
|
"Programming Language :: Python :: 3.8"],
|
||||||
|
keywords='waveform metadata audio ebu smpte avi library film tv editing editorial',
|
||||||
install_requires=['lxml']
|
install_requires=['lxml']
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,3 +87,10 @@ class TestWaveInfo(TestCase):
|
|||||||
self.assertEqual( e['take'], info.ixml.take )
|
self.assertEqual( e['take'], info.ixml.take )
|
||||||
self.assertEqual( e['tape'], info.ixml.tape )
|
self.assertEqual( e['tape'], info.ixml.tape )
|
||||||
self.assertEqual( e['family_uid'], info.ixml.family_uid )
|
self.assertEqual( e['family_uid'], info.ixml.family_uid )
|
||||||
|
|
||||||
|
for track in info.ixml.track_list:
|
||||||
|
self.assertIsNotNone(track.channel_index)
|
||||||
|
if basename == 'A101_4.WAV' and track.channel_index == '1':
|
||||||
|
self.assertTrue(track.name == 'MKH516 A')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
"""
|
||||||
|
methods to probe a WAV file for various kinds of production metadata.
|
||||||
# :module:`wavinfo` provides methods to probe a WAV file for
|
|
||||||
# various kinds of production metadata.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
Go to the documentation for wavinfo.WavInfoReader for more information.
|
||||||
|
"""
|
||||||
|
|
||||||
from .wave_reader import WavInfoReader
|
from .wave_reader import WavInfoReader
|
||||||
|
from .riff_parser import WavInfoEOFError
|
||||||
|
|
||||||
__version__ = '1.1'
|
__version__ = '1.4'
|
||||||
__author__ = 'Jamie Hardt'
|
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
|
||||||
|
__license__ = "MIT"
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
|
|
||||||
import struct
|
import struct
|
||||||
import pdb
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from .rf64_parser import parse_rf64
|
from .rf64_parser import parse_rf64
|
||||||
|
|
||||||
class ListChunkDescriptor(namedtuple('ListChunkDescriptor' , 'signature children')):
|
|
||||||
|
|
||||||
def find(chunk_path):
|
class WavInfoEOFError(EOFError):
|
||||||
|
def __init__(self, identifier, chunk_start):
|
||||||
|
self.identifier = identifier
|
||||||
|
self.chunk_start = chunk_start
|
||||||
|
|
||||||
|
|
||||||
|
class ListChunkDescriptor(namedtuple('ListChunkDescriptor', 'signature children')):
|
||||||
|
def find(self, chunk_path):
|
||||||
if len(chunk_path) > 1:
|
if len(chunk_path) > 1:
|
||||||
for chunk in self.children:
|
for chunk in self.children:
|
||||||
if type(chunk) is ListChunkDescriptor and \
|
if type(chunk) is ListChunkDescriptor and \
|
||||||
@@ -24,47 +29,44 @@ class ChunkDescriptor(namedtuple('ChunkDescriptor', 'ident start length rf64_con
|
|||||||
from_stream.seek(self.start)
|
from_stream.seek(self.start)
|
||||||
return from_stream.read(self.length)
|
return from_stream.read(self.length)
|
||||||
|
|
||||||
|
|
||||||
def parse_list_chunk(stream, length, rf64_context=None):
|
def parse_list_chunk(stream, length, rf64_context=None):
|
||||||
start = stream.tell()
|
start = stream.tell()
|
||||||
|
|
||||||
signature = stream.read(4)
|
signature = stream.read(4)
|
||||||
|
|
||||||
#print("Parsing list chunk with siganture: ", signature)
|
|
||||||
children = []
|
children = []
|
||||||
while (stream.tell() - start) < length:
|
while (stream.tell() - start + 8) < length:
|
||||||
child_chunk = parse_chunk(stream, rf64_context=rf64_context)
|
child_chunk = parse_chunk(stream, rf64_context=rf64_context)
|
||||||
if child_chunk:
|
|
||||||
children.append(child_chunk)
|
children.append(child_chunk)
|
||||||
else:
|
|
||||||
break
|
stream.seek(start + length)
|
||||||
|
|
||||||
return ListChunkDescriptor(signature=signature, children=children)
|
return ListChunkDescriptor(signature=signature, children=children)
|
||||||
|
|
||||||
|
|
||||||
def parse_chunk(stream, rf64_context=None):
|
def parse_chunk(stream, rf64_context=None):
|
||||||
|
header_start = stream.tell()
|
||||||
ident = stream.read(4)
|
ident = stream.read(4)
|
||||||
if len(ident) != 4:
|
size_bytes = stream.read(4)
|
||||||
return
|
|
||||||
|
|
||||||
sizeb = stream.read(4)
|
if len(ident) != 4 or len(size_bytes) != 4:
|
||||||
size = struct.unpack('<I',sizeb)[0]
|
raise WavInfoEOFError(identifier=ident, chunk_start=header_start)
|
||||||
|
|
||||||
if size == 0xFFFFFFFF:
|
data_size = struct.unpack('<I', size_bytes)[0]
|
||||||
|
|
||||||
|
if data_size == 0xFFFFFFFF:
|
||||||
if rf64_context is None and ident == b'RF64':
|
if rf64_context is None and ident == b'RF64':
|
||||||
rf64_context = parse_rf64(stream=stream)
|
rf64_context = parse_rf64(stream=stream)
|
||||||
|
|
||||||
size = rf64_context.bigchunk_table[ident]
|
data_size = rf64_context.bigchunk_table[ident]
|
||||||
|
|
||||||
displacement = size
|
displacement = data_size
|
||||||
if displacement % 2 is not 0:
|
if displacement % 2 is not 0:
|
||||||
displacement = displacement + 1
|
displacement = displacement + 1
|
||||||
|
|
||||||
if ident in [b'RIFF', b'LIST', b'RF64']:
|
if ident in [b'RIFF', b'LIST', b'RF64']:
|
||||||
#print("Parsing list chunk with ident: ", ident)
|
return parse_list_chunk(stream=stream, length=data_size, rf64_context=rf64_context)
|
||||||
return parse_list_chunk(stream=stream, length=size, rf64_context=rf64_context)
|
|
||||||
else:
|
else:
|
||||||
start = stream.tell()
|
data_start = stream.tell()
|
||||||
stream.seek(displacement, 1)
|
stream.seek(displacement, 1)
|
||||||
#print("Parsing chunk with start=%i, ident=%s" % (start, ident))
|
return ChunkDescriptor(ident=ident, start=data_start, length=data_size, rf64_context=rf64_context)
|
||||||
return ChunkDescriptor(ident=ident, start=start, length=size, rf64_context=rf64_context)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
#import xml.etree.ElementTree as ET
|
#import xml.etree.ElementTree as ET
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
import io
|
import io
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
IXMLTrack = namedtuple('IXMLTrack', ['channel_index', 'interleave_index', 'name', 'function'])
|
||||||
|
|
||||||
class WavIXMLFormat:
|
class WavIXMLFormat:
|
||||||
"""
|
"""
|
||||||
@@ -27,6 +31,26 @@ class WavIXMLFormat:
|
|||||||
if e is not None:
|
if e is not None:
|
||||||
return e.text
|
return e.text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def raw_xml(self):
|
||||||
|
"""
|
||||||
|
The root entity of the iXML document.
|
||||||
|
"""
|
||||||
|
return self.parsed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def track_list(self):
|
||||||
|
"""
|
||||||
|
A description of each track.
|
||||||
|
:return: An Iterator
|
||||||
|
"""
|
||||||
|
for track in self.parsed.find("./TRACK_LIST").iter():
|
||||||
|
if track.tag == 'TRACK':
|
||||||
|
yield IXMLTrack(channel_index=track.xpath('string(CHANNEL_INDEX/text())'),
|
||||||
|
interleave_index=track.xpath('string(INTERLEAVE_INDEX/text())'),
|
||||||
|
name=track.xpath('string(NAME/text())'),
|
||||||
|
function=track.xpath('string(FUNCTION/text())'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project(self):
|
def project(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user