mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 17:00:41 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d4bb13ad6 | ||
|
|
e132c5846c | ||
|
|
b87b43aab4 | ||
|
|
6b42a6bb09 | ||
|
|
7a315be242 | ||
|
|
eb3e2adc27 | ||
|
|
3e7faefedb | ||
|
|
54ce5abe77 | ||
|
|
2f124b0d56 | ||
|
|
cd11b0924b | ||
|
|
ff862aafe9 | ||
|
|
e0432458cc | ||
|
|
b4613ed6f4 | ||
|
|
8d44d411d7 | ||
|
|
6005f79e60 | ||
|
|
841b86f3f4 | ||
|
|
5c90d5ff47 | ||
|
|
60e329fdb4 | ||
|
|
15db4c9ffa | ||
|
|
49ac961b94 | ||
|
|
7fc530b2cd |
26
.github/workflows/pythonpublish.yml
vendored
Normal file
26
.github/workflows/pythonpublish.yml
vendored
Normal 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
1
.gitignore
vendored
@@ -105,3 +105,4 @@ venv.bak/
|
||||
|
||||
# vim swap
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
8
.idea/dictionaries/jamiehardt.xml
generated
Normal file
8
.idea/dictionaries/jamiehardt.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="jamiehardt">
|
||||
<words>
|
||||
<w>ident</w>
|
||||
<w>umid</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
4
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
11
.idea/wavinfo.iml
generated
Normal 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>
|
||||
16
.travis.yml
16
.travis.yml
@@ -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"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[](https://travis-ci.com/iluvcapra/wavinfo)
|
||||
[](https://codecov.io/gh/iluvcapra/wavinfo)
|
||||
[](https://wavinfo.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/wavinfo/) 
|
||||
|
||||
|
||||
|
||||
18
setup.py
18
setup.py
@@ -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']
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
@@ -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
119
wavinfo/umid_parser.py
Normal 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user