18 Commits
v1.4 ... v1.5

Author SHA1 Message Date
Jamie Hardt
6c8cc47788 Update README.md
Added note on command-line entry point
2020-01-05 10:10:37 -08:00
Jamie Hardt
a90d3f4b38 Version 1.5
Added command-line entrypoint, more UMID implementation
2020-01-05 10:05:54 -08:00
Jamie Hardt
3ede4de06a Delete pypi_upload.sh 2020-01-04 22:30:15 -08:00
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
18 changed files with 265 additions and 16 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 # vim swap
*.swp *.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="" 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

@@ -9,16 +9,17 @@ python:
script: script:
- "gunzip tests/test_files/rf64/*.gz" - "gunzip tests/test_files/rf64/*.gz"
- "python setup.py test" - "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: before_install:
- "sudo apt-get update" - "sudo apt-get update"
- "sudo add-apt-repository universe" - "sudo add-apt-repository universe"
- "sudo apt-get install -y ffmpeg" - "sudo apt-get install -y ffmpeg"
- "pip install coverage" - "pip install pytest"
- "pip install codecov" # - "pip install coverage"
- "pip install pytest-cov==2.5.0" # - "pip install codecov"
# - "pip install coverage==4.4" # - "pip install pytest-cov==2.5.0"
# - "pip install coverage==4.4"
install: install:
- "pip install setuptools" - "pip install setuptools"
after_success: # after_success:
- "codecov" # - "codecov"

View File

@@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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) [![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) [![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)
@@ -42,6 +41,12 @@ path = '../tests/test_files/A101_1.WAV'
info = WavInfoReader(path) info = WavInfoReader(path)
``` ```
The package also installs a shell command:
```sh
$ wavinfo test_files/A101_1.WAV
```
### 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.

View File

@@ -1,2 +0,0 @@
#!/bin/bash
python3 -m twine upload --repository-url https://upload.pypi.org/legacy/ dist/*

View File

@@ -1,15 +1,17 @@
from setuptools import setup from setuptools import setup
from wavinfo import __author__, __license__, __version__
with open("README.md", "r") as fh: with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
setup(name='wavinfo', setup(name='wavinfo',
version='1.4', version=__version__,
author='Jamie Hardt', author=__author__,
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,
license=__license__,
url='https://github.com/iluvcapra/wavinfo', url='https://github.com/iluvcapra/wavinfo',
project_urls={ project_urls={
'Source': 'Source':
@@ -29,5 +31,10 @@ setup(name='wavinfo',
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8"], "Programming Language :: Python :: 3.8"],
keywords='waveform metadata audio ebu smpte avi library film tv editing editorial', keywords='waveform metadata audio ebu smpte avi library film tv editing editorial',
install_requires=['lxml'] install_requires=['lxml'],
entry_points={
'console_scripts': [
'wavinfo = wavinfo.__main__:main'
]
}
) )

View File

@@ -7,6 +7,6 @@ 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 from .riff_parser import WavInfoEOFError
__version__ = '1.4' __version__ = '1.5'
__author__ = 'Jamie Hardt <jamiehardt@gmail.com>' __author__ = 'Jamie Hardt <jamiehardt@gmail.com>'
__license__ = "MIT" __license__ = "MIT"

32
wavinfo/__main__.py Normal file
View File

@@ -0,0 +1,32 @@
from optparse import OptionParser, OptionGroup
import datetime
from . import WavInfoReader
import sys
import json
def main():
parser = OptionParser()
parser.usage = 'wavinfo [FILE.wav]*'
# parser.add_option('-f', dest='output_format', help='Set the output format',
# default='json',
# metavar='FORMAT')
(options, args) = parser.parse_args(sys.argv)
for arg in args[1:]:
try:
this_file = WavInfoReader(path=arg)
ret_dict = {'file_argument': arg, 'run_date': datetime.datetime.now().isoformat() , 'scopes': {}}
for scope, name, value in this_file.walk():
if scope not in ret_dict['scopes'].keys():
ret_dict['scopes'][scope] = {}
ret_dict['scopes'][scope][name] = value
json.dump(ret_dict, fp=sys.stdout, indent=2)
except Exception as e:
print(e)
if __name__ == "__main__":
main()

129
wavinfo/umid_parser.py Normal file
View File

@@ -0,0 +1,129 @@
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
@classmethod
def binary_to_string(cls, binary_value):
result_str = ''
for n in range(len(binary_value)):
result_str = f'{binary_value[n]:x}' + result_str
return result_str
@property
def universal_label(self) -> bytearray:
return self.raw_umid[0:12]
@property
def basic_umid(self):
return self.raw_umid[0:32]
def basic_umid_to_str(self):
return "%024x-%06x-%032x" % (self.binary_to_string(self.universal_label),
self.binary_to_string(self.instance_number),
self.binary_to_string(self.material_number))
@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) -> bytearray:
return self.raw_umid[13:3]
@property
def material_number(self) -> bytearray:
return self.raw_umid[16:16]
@property
def source_pack(self) -> Union[bytearray, None]:
if self.indicated_length == 'extended':
return self.raw_umid[32:32]
else:
return None

View File

@@ -147,9 +147,13 @@ class WavInfoReader():
metadata field, and the value. metadata field, and the value.
""" """
scopes = ('fmt','data')#,'bext','ixml','info') scopes = ('fmt', 'data') #'bext', 'ixml', 'info')
for scope in scopes: for scope in scopes:
attr = self.__getattribute__(scope) attr = self.__getattribute__(scope)
for field in attr._fields: for field in attr._fields:
yield scope, field, attr.__getattribute__(field) yield scope, field, attr.__getattribute__(field)
bext_dict = self.bext.to_dict()
for key in bext_dict.keys():
yield 'bext', key, bext_dict[key]