21 Commits
v1.3 ... v1.4.1

Author SHA1 Message Date
Jamie Hardt
5d4bb13ad6 v1.4.1
to test github action
2020-01-04 22:28:02 -08:00
Jamie Hardt
e132c5846c Create pythonpublish.yml
experimenting with upload workflow
2020-01-04 22:18:15 -08:00
Jamie Hardt
b87b43aab4 Update .gitignore
Ignore .DS_Store
2020-01-03 10:46:26 -08:00
Jamie Hardt
6b42a6bb09 idea files 2020-01-03 10:09:24 -08:00
Jamie Hardt
7a315be242 Update LICENSE
Bump year
2020-01-03 10:06:59 -08:00
Jamie Hardt
eb3e2adc27 Update README.md 2020-01-03 10:04:20 -08:00
Jamie Hardt
3e7faefedb Update .travis.yml 2020-01-03 10:03:56 -08:00
Jamie Hardt
54ce5abe77 Update .travis.yml 2020-01-03 09:40:01 -08:00
Jamie Hardt
2f124b0d56 Update .travis.yml 2020-01-03 09:39:50 -08:00
Jamie Hardt
cd11b0924b Update .travis.yml 2020-01-03 09:31:41 -08:00
Jamie Hardt
ff862aafe9 Update .travis.yml 2020-01-03 09:24:37 -08:00
Jamie Hardt
e0432458cc Update .travis.yml
Attempt to turn codecov back on
2020-01-03 09:03:18 -08:00
Jamie Hardt
b4613ed6f4 Update umid_parser.py
Removed type annotation which seems to die in Python 3.5
2020-01-02 17:38:32 -08:00
Jamie Hardt
8d44d411d7 Update umid_parser.py 2020-01-02 17:26:23 -08:00
Jamie Hardt
6005f79e60 Added basic UMID parser 2020-01-02 17:24:08 -08:00
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
16 changed files with 260 additions and 57 deletions

26
.github/workflows/pythonpublish.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

1
.gitignore vendored
View File

@@ -105,3 +105,4 @@ venv.bak/
# vim swap
*.swp
.DS_Store

3
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/workspace.xml

8
.idea/dictionaries/jamiehardt.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="jamiehardt">
<words>
<w>ident</w>
<w>umid</w>
</words>
</dictionary>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wavinfo.iml" filepath="$PROJECT_DIR$/.idea/wavinfo.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
.idea/wavinfo.iml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

View File

@@ -5,19 +5,21 @@ python:
- "3.6"
- "3.5"
- "3.7"
- "3.8"
script:
- "gunzip tests/test_files/rf64/*.gz"
- "python setup.py test"
# - "py.test tests/ -v --cov wavinfo --cov-report term-missing"
# - "python -m pytest tests/ -v --cov wavinfo --cov-report term-missing"
before_install:
- "sudo apt-get update"
- "sudo add-apt-repository universe"
- "sudo apt-get install -y ffmpeg"
- "pip install coverage"
- "pip install codecov"
- "pip install pytest-cov==2.5.0"
# - "pip install coverage==4.4"
- "pip install pytest"
# - "pip install coverage"
# - "pip install codecov"
# - "pip install pytest-cov==2.5.0"
# - "pip install coverage==4.4"
install:
- "pip install setuptools"
after_success:
- "codecov"
# after_success:
# - "codecov"

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Jamie Hardt
Copyright (c) 2020 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

View File

@@ -1,5 +1,4 @@
[![Build Status](https://travis-ci.com/iluvcapra/wavinfo.svg?branch=master)](https://travis-ci.com/iluvcapra/wavinfo)
[![codecov](https://codecov.io/gh/iluvcapra/wavinfo/branch/master/graph/badge.svg)](https://codecov.io/gh/iluvcapra/wavinfo)
[![Documentation Status](https://readthedocs.org/projects/wavinfo/badge/?version=latest)](https://wavinfo.readthedocs.io/en/latest/?badge=latest) ![](https://img.shields.io/github/license/iluvcapra/wavinfo.svg) ![](https://img.shields.io/pypi/pyversions/wavinfo.svg) [![](https://img.shields.io/pypi/v/wavinfo.svg)](https://pypi.org/project/wavinfo/) ![](https://img.shields.io/pypi/wheel/wavinfo.svg)

View File

@@ -4,20 +4,30 @@ with open("README.md", "r") as fh:
long_description = fh.read()
setup(name='wavinfo',
version='1.3',
version='1.4.1',
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

@@ -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.3'
__author__ = 'Jamie Hardt'
__version__ = '1.4.1'
__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)

119
wavinfo/umid_parser.py Normal file
View File

@@ -0,0 +1,119 @@
import struct
from typing import Union
class UMIDParser:
"""
Parse a raw binary SMPTE 330M Universal Materials Identifier
This implementation is based on SMPTE ST 330:2011
"""
def __init__(self, raw_umid: bytearray):
self.raw_umid = raw_umid
@property
def universal_label(self) -> bytearray:
return self.raw_umid[0:12]
@property
def universal_label_is_valid(self) -> bool:
valid_preamble = b'\x06\x0a\x2b\x34\x01\x01\x01\x05\x01\x01'
return self.universal_label[0:len(valid_preamble)] == valid_preamble
@property
def material_type(self) -> str:
material_byte = self.raw_umid[10]
if material_byte == 0x1:
return 'picture'
elif material_byte == 0x2:
return 'audio'
elif material_byte == 0x3:
return 'data'
elif material_byte == 0x4:
return 'other'
elif material_byte == 0x5:
return 'picture_single_component'
elif material_byte == 0x6:
return 'picture_multiple_component'
elif material_byte == 0x7:
return 'audio_single_component'
elif material_byte == 0x9:
return 'audio_multiple_component'
elif material_byte == 0xb:
return 'auxiliary_single_component'
elif material_byte == 0xc:
return 'auxiliary_multiple_component'
elif material_byte == 0xd:
return 'mixed_components'
elif material_byte == 0xf:
return 'not_identified'
else:
return 'not_recognized'
@property
def material_number_creation_method(self) -> str:
method_byte = self.raw_umid[11]
method_byte = (method_byte << 4) & 0xf
if method_byte == 0x0:
return 'undefined'
elif method_byte == 0x1:
return 'smpte'
elif method_byte == 0x2:
return 'uuid'
elif method_byte == 0x3:
return 'masked'
elif method_byte == 0x4:
return 'ieee1394'
elif 0x5 <= method_byte <= 0x7:
return 'reserved_undefined'
else:
return 'unrecognized'
@property
def instance_number_creation_method(self) -> str:
method_byte = self.raw_umid[11]
method_byte = method_byte & 0xf
if method_byte == 0x0:
return 'undefined'
elif method_byte == 0x01:
return 'local_registration'
elif method_byte == 0x02:
return '24_bit_prs'
elif method_byte == 0x03:
return 'copy_number_and_16_bit_prs'
elif 0x04 <= method_byte <= 0x0e:
return 'reserved_undefined'
elif method_byte == 0x0f:
return 'live_stream'
else:
return 'unrecognized'
@property
def indicated_length(self) -> str:
if self.raw_umid[12] == 0x13:
return 'basic'
elif self.raw_umid[12] == 0x33:
return 'extended'
@property
def instance_number(self) -> int:
return struct.unpack('<I', self.raw_umid[13:4])[0] & 0x00ffffff
@property
def material_number(self) -> bytearray:
return self.raw_umid[14:16]
@property
def material_number_hex(self) -> str:
result_str = ''
for n in range(16):
result_str = '{:x}'.format(self.material_number[n]) + result_str
return result_str
@property
def source_pack(self) -> Union[bytearray, None]:
if self.indicated_length == 'extended':
return self.raw_umid[32:32]
else:
return None