9 Commits
v1.2 ... v1.4

Author SHA1 Message Date
Jamie Hardt
841b86f3f4 Python 3.8 added to tests
Bumped version and added 3.8 to Travis
2020-01-02 11:55:51 -08:00
Jamie Hardt
5c90d5ff47 Rewrite of chink code
More work on #4
2020-01-02 11:44:42 -08:00
Jamie Hardt
60e329fdb4 Improved exceptions in certain EOF cases
Pursuant to #4
2020-01-02 10:50:36 -08:00
Jamie Hardt
15db4c9ffa Some setup metadata tweaks.. 2019-08-20 16:54:19 -07:00
Jamie Hardt
49ac961b94 Some setup metadata tweaks.. 2019-08-20 16:21:24 -07:00
Jamie Hardt
7fc530b2cd Some documentation tweaks. 2019-08-20 16:11:50 -07:00
Jamie Hardt
4dfc1ab33c Added iXML track list parsing 2019-08-19 11:39:13 -07:00
Jamie Hardt
4770c781b2 Update README.md 2019-06-29 21:55:19 -07:00
Jamie Hardt
45c2aae640 Update wave_ixml_reader.py 2019-06-29 21:50:20 -07:00
7 changed files with 94 additions and 48 deletions

View File

@@ -5,6 +5,7 @@ python:
- "3.6"
- "3.5"
- "3.7"
- "3.8"
script:
- "gunzip tests/test_files/rf64/*.gz"
- "python setup.py test"

View File

@@ -53,6 +53,10 @@ The length of the file in frames (interleaved samples) and bytes is available, a
>>> (48000, 2, 6, 24)
```
## Other Resources
* For other file formats and ID3 decoding, look at [audio-metadata](https://github.com/thebigmunch/audio-metadata).

View File

@@ -4,20 +4,30 @@ with open("README.md", "r") as fh:
long_description = fh.read()
setup(name='wavinfo',
version='1.2',
version='1.4',
author='Jamie Hardt',
author_email='jamiehardt@me.com',
description='Probe WAVE Files for iXML, Broadcast-WAVE and other metadata.',
long_description_content_type="text/markdown",
long_description=long_description,
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',
'License :: OSI Approved :: MIT License',
'Topic :: Multimedia',
'Topic :: Multimedia :: Sound/Audio',
'Topic :: Multimedia :: Sound/Audio',
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7"],
packages=['wavinfo'],
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8"],
keywords='waveform metadata audio ebu smpte avi library film tv editing editorial',
install_requires=['lxml']
)

View File

@@ -87,3 +87,10 @@ class TestWaveInfo(TestCase):
self.assertEqual( e['take'], info.ixml.take )
self.assertEqual( e['tape'], info.ixml.tape )
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')

View File

@@ -1,14 +1,12 @@
# -*- coding: utf-8 -*-
# :module:`wavinfo` provides methods to probe a WAV file for
# various kinds of production metadata.
#
#
#
#
"""
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 .riff_parser import WavInfoEOFError
__version__ = '1.1'
__author__ = 'Jamie Hardt'
__version__ = '1.4'
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
__license__ = "MIT"

View File

@@ -1,70 +1,72 @@
import struct
import pdb
from collections import namedtuple
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:
for chunk in self.children:
if type(chunk) is ListChunkDescriptor and \
chunk.signature is chunk_path[0]:
return chunk.find(chunk_path[1:])
return chunk.find(chunk_path[1:])
else:
for chunk in self.children:
if type(chunk) is ChunkDescriptor and \
chunk.ident is chunk_path[0]:
return chunk
return chunk
class ChunkDescriptor(namedtuple('ChunkDescriptor', 'ident start length rf64_context') ):
class ChunkDescriptor(namedtuple('ChunkDescriptor', 'ident start length rf64_context')):
def read_data(self, from_stream):
from_stream.seek(self.start)
return from_stream.read(self.length)
def parse_list_chunk(stream, length, rf64_context =None):
start = stream.tell()
def parse_list_chunk(stream, length, rf64_context=None):
start = stream.tell()
signature = stream.read(4)
#print("Parsing list chunk with siganture: ", signature)
children = []
while (stream.tell() - start) < length:
child_chunk = parse_chunk(stream, rf64_context= rf64_context)
if child_chunk:
children.append(child_chunk)
else:
break
while (stream.tell() - start + 8) < length:
child_chunk = parse_chunk(stream, rf64_context=rf64_context)
children.append(child_chunk)
stream.seek(start + length)
return ListChunkDescriptor(signature=signature, children=children)
def parse_chunk(stream, rf64_context=None):
ident = stream.read(4)
if len(ident) != 4:
return
sizeb = stream.read(4)
size = struct.unpack('<I',sizeb)[0]
if size == 0xFFFFFFFF:
def parse_chunk(stream, rf64_context=None):
header_start = stream.tell()
ident = stream.read(4)
size_bytes = stream.read(4)
if len(ident) != 4 or len(size_bytes) != 4:
raise WavInfoEOFError(identifier=ident, chunk_start=header_start)
data_size = struct.unpack('<I', size_bytes)[0]
if data_size == 0xFFFFFFFF:
if rf64_context is None and ident == b'RF64':
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:
displacement = displacement + 1
if ident in [b'RIFF',b'LIST', b'RF64']:
#print("Parsing list chunk with ident: ", ident)
return parse_list_chunk(stream=stream, length=size, rf64_context=rf64_context)
if ident in [b'RIFF', b'LIST', b'RF64']:
return parse_list_chunk(stream=stream, length=data_size, rf64_context=rf64_context)
else:
start = stream.tell()
stream.seek(displacement,1)
#print("Parsing chunk with start=%i, ident=%s" % (start, ident))
return ChunkDescriptor(ident=ident, start=start, length=size, rf64_context=rf64_context)
data_start = stream.tell()
stream.seek(displacement, 1)
return ChunkDescriptor(ident=ident, start=data_start, length=data_size, rf64_context=rf64_context)

View File

@@ -1,6 +1,10 @@
#import xml.etree.ElementTree as ET
from lxml import etree as ET
import io
from collections import namedtuple
IXMLTrack = namedtuple('IXMLTrack', ['channel_index', 'interleave_index', 'name', 'function'])
class WavIXMLFormat:
"""
@@ -27,6 +31,26 @@ class WavIXMLFormat:
if e is not None:
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
def project(self):
"""