diff --git a/README.md b/README.md index 06cecdd..2685cdb 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ # wavinfo + The `wavinfo` package allows you to probe WAVE files and extract extended metadata, with an emphasis on production metadata. @@ -23,4 +24,93 @@ This module is presently under construction and not sutiable for production at t [ebu]:https://tech.ebu.ch/docs/tech/tech3285.pdf [smpte_330m2011]:http://standards.smpte.org/content/978-1-61482-678-1/st-330-2011/SEC1.abstract -[ixml]:http://www.ixml.info +[ixml]:http://www.ixml.infoi + + + +## Demonstration + + +The entry point for wavinfo is the WavInfoReader class. + + +```python +from wavinfo import WavInfoReader + +path = '../tests/test_files/A101_1.WAV' + +info = WavInfoReader(path) +``` + +### Basic WAV Data + + +The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk. + + +```python +(info.data.frame_count, info.data.byte_count) +>>> (240239, 1441434) +(info.fmt.sample_rate, info.fmt.channel_count, info.fmt.block_align, info.fmt.bits_per_sample) +>>> (48000, 2, 6, 24) +``` + +## Broadcast WAV Extension + + + +```python +print(info.bext.description) +print("----------") +print("Originator:", info.bext.originator) +print("Originator Ref:", info.bext.originator_ref) +print("Originator Date:", info.bext.originator_date) +print("Originator Time:", info.bext.originator_time) +print("Time Reference:", info.bext.time_reference) +print(info.bext.coding_history) +``` + + sSPEED=023.976-ND + sTAKE=1 + sUBITS=$12311801 + sSWVER=2.67 + sPROJECT=BMH + sSCENE=A101 + sFILENAME=A101_1.WAV + sTAPE=18Y12M31 + sTRK1=MKH516 A + sTRK2=Boom + sNOTE= + + ---------- + Originator: Sound Dev: 702T S#GR1112089007 + Originator Ref: USSDVGR1112089007124001008206301 + Originator Date: 2018-12-31 + Originator Time: 12:40:00 + Time Reference: 2190940753 + A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch + + + +## iXML Production Recorder Metadata + + +```python +print("iXML Project:", info.ixml.project) +print("iXML Scene:", info.ixml.scene) +print("iXML Take:", info.ixml.take) +print("iXML Tape:", info.ixml.tape) +print("iXML File Family Name:", info.ixml.family_name) +print("iXML File Family UID:", info.ixml.family_uid) +``` + + iXML Project: BMH + iXML Scene: A101 + iXML Take: 1 + iXML Tape: 18Y12M31 + iXML File Family Name: None + iXML File Family UID: USSDVGR1112089007124001008206300 + A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch + + + diff --git a/demo.md b/demo.md new file mode 100644 index 0000000..cab83a8 --- /dev/null +++ b/demo.md @@ -0,0 +1,104 @@ + +# `wavinfo` Demonstration + +The entry point for wavinfo is the WavInfoReader class. + + +```python +from wavinfo import WavInfoReader + +path = '../tests/test_files/A101_1.WAV' + +info = WavInfoReader(path) +``` + +## Basic WAV Data + +The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk. + + +```python +(info.data.frame_count, info.data.byte_count) +``` + + + + + (240239, 1441434) + + + + +```python +(info.fmt.sample_rate, info.fmt.channel_count, info.fmt.block_align, info.fmt.bits_per_sample) +``` + + + + + (48000, 2, 6, 24) + + + +## Broadcast WAV Extension + + +```python +print(info.bext.description) +print("----------") +print("Originator:", info.bext.originator) +print("Originator Ref:", info.bext.originator_ref) +print("Originator Date:", info.bext.originator_date) +print("Originator Time:", info.bext.originator_time) +print("Time Reference:", info.bext.time_reference) +print(info.bext.coding_history) +``` + + sSPEED=023.976-ND + sTAKE=1 + sUBITS=$12311801 + sSWVER=2.67 + sPROJECT=BMH + sSCENE=A101 + sFILENAME=A101_1.WAV + sTAPE=18Y12M31 + sTRK1=MKH516 A + sTRK2=Boom + sNOTE= + + ---------- + Originator: Sound Dev: 702T S#GR1112089007 + Originator Ref: USSDVGR1112089007124001008206301 + Originator Date: 2018-12-31 + Originator Time: 12:40:00 + Time Reference: 2190940753 + A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch + + + +## iXML Production Recorder Metadata + + +```python +print("iXML Project:", info.ixml.project) +print("iXML Scene:", info.ixml.scene) +print("iXML Take:", info.ixml.take) +print("iXML Tape:", info.ixml.tape) +print("iXML File Family Name:", info.ixml.family_name) +print("iXML File Family UID:", info.ixml.family_uid) +``` + + iXML Project: BMH + iXML Scene: A101 + iXML Take: 1 + iXML Tape: 18Y12M31 + iXML File Family Name: None + iXML File Family UID: USSDVGR1112089007124001008206300 + A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch + + + + +```python + +``` diff --git a/examples/demo.ipynb b/examples/demo.ipynb new file mode 100644 index 0000000..1154076 --- /dev/null +++ b/examples/demo.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# `wavinfo` Demonstration\n", + "\n", + "The entry point for wavinfo is the WavInfoReader class." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from wavinfo import WavInfoReader\n", + "\n", + "path = '../tests/test_files/A101_1.WAV'\n", + "\n", + "info = WavInfoReader(path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic WAV Data\n", + "\n", + "The length of the file in frames (interleaved samples) and bytes is available, as is the contents of the format chunk." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(240239, 1441434)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(info.data.frame_count, info.data.byte_count)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(48000, 2, 6, 24)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(info.fmt.sample_rate, info.fmt.channel_count, info.fmt.block_align, info.fmt.bits_per_sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Broadcast WAV Extension" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sSPEED=023.976-ND\r\n", + "sTAKE=1\r\n", + "sUBITS=$12311801\r\n", + "sSWVER=2.67\r\n", + "sPROJECT=BMH\r\n", + "sSCENE=A101\r\n", + "sFILENAME=A101_1.WAV\r\n", + "sTAPE=18Y12M31\r\n", + "sTRK1=MKH516 A\r\n", + "sTRK2=Boom\r\n", + "sNOTE=\r\n", + "\n", + "----------\n", + "Originator: Sound Dev: 702T S#GR1112089007\n", + "Originator Ref: USSDVGR1112089007124001008206301\n", + "Originator Date: 2018-12-31\n", + "Originator Time: 12:40:00\n", + "Time Reference: 2190940753\n", + "A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\r\n", + "\n" + ] + } + ], + "source": [ + "print(info.bext.description)\n", + "print(\"----------\")\n", + "print(\"Originator:\", info.bext.originator)\n", + "print(\"Originator Ref:\", info.bext.originator_ref)\n", + "print(\"Originator Date:\", info.bext.originator_date)\n", + "print(\"Originator Time:\", info.bext.originator_time)\n", + "print(\"Time Reference:\", info.bext.time_reference)\n", + "print(info.bext.coding_history)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## iXML Production Recorder Metadata" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "iXML Project: BMH\n", + "iXML Scene: A101\n", + "iXML Take: 1\n", + "iXML Tape: 18Y12M31\n", + "iXML File Family Name: None\n", + "iXML File Family UID: USSDVGR1112089007124001008206300\n", + "A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\r\n", + "\n" + ] + } + ], + "source": [ + "print(\"iXML Project:\", info.ixml.project)\n", + "print(\"iXML Scene:\", info.ixml.scene)\n", + "print(\"iXML Take:\", info.ixml.take)\n", + "print(\"iXML Tape:\", info.ixml.tape)\n", + "print(\"iXML File Family Name:\", info.ixml.family_name)\n", + "print(\"iXML File Family UID:\", info.ixml.family_uid)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/wavinfo.ipynb b/examples/wavinfo.ipynb index 1f622e4..f7214e5 100644 --- a/examples/wavinfo.ipynb +++ b/examples/wavinfo.ipynb @@ -57,7 +57,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "WavBextFormat(description='sSPEED=023.976-ND\\r\\nsTAKE=1\\r\\nsUBITS=$12311801\\r\\nsSWVER=2.67\\r\\nsPROJECT=BMH\\r\\nsSCENE=A101\\r\\nsFILENAME=A101_1.WAV\\r\\nsTAPE=18Y12M31\\r\\nsTRK1=MKH516 A\\r\\nsTRK2=Boom\\r\\nsNOTE=\\r\\n', originator='Sound Dev: 702T S#GR1112089007', originator_ref='USSDVGR1112089007124001008206301', originator_date='2018-12-31', originator_time='12:40:00', time_reference=2190940753, version=1, umid=b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', loudness_value=0, loudness_range=0, max_true_peak=0, max_momentary_loudness=0, max_shortterm_loudness=0, coding_history='A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\\r\\n')\n" + "WavBextFormat(description='sSPEED=023.976-ND\\r\\nsTAKE=1\\r\\nsUBITS=$12311801\\r\\nsSWVER=2.67\\r\\nsPROJECT=BMH\\r\\nsSCENE=A101\\r\\nsFILENAME=A101_1.WAV\\r\\nsTAPE=18Y12M31\\r\\nsTRK1=MKH516 A\\r\\nsTRK2=Boom\\r\\nsNOTE=\\r\\n', originator='Sound Dev: 702T S#GR1112089007', originator_ref='USSDVGR1112089007124001008206301', originator_date='2018-12-31', originator_time='12:40:00', time_reference=2190940753, version=1, umid=b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', loudness_value=0.0, loudness_range=0.0, max_true_peak=0.0, max_momentary_loudness=0.0, max_shortterm_loudness=0.0, coding_history='A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\\r\\n')\n" ] } ], @@ -138,7 +138,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "WavBextFormat(description='dUBITS=12311804\\r\\ndSCENE=A101\\r\\ndTAKE=4\\r\\ndTAPE=18Y12M31\\r\\ndFRAMERATE=23.976ND\\r\\ndSPEED=023.976-NDF\\r\\ndTRK1=MKH516 A\\r\\ndTRK2=Boom\\r\\n', originator='Sound Dev: 702T S#GR1112089007', originator_ref='aa4CKtcd13Vk', originator_date='2018-12-31', originator_time='12:40:07', time_reference=2191709524, version=0, umid=b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', loudness_value=0, loudness_range=0, max_true_peak=0, max_momentary_loudness=0, max_shortterm_loudness=0, coding_history='A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\\r\\n')\n" + "WavBextFormat(description='dUBITS=12311804\\r\\ndSCENE=A101\\r\\ndTAKE=4\\r\\ndTAPE=18Y12M31\\r\\ndFRAMERATE=23.976ND\\r\\ndSPEED=023.976-NDF\\r\\ndTRK1=MKH516 A\\r\\ndTRK2=Boom\\r\\n', originator='Sound Dev: 702T S#GR1112089007', originator_ref='aa4CKtcd13Vk', originator_date='2018-12-31', originator_time='12:40:07', time_reference=2191709524, version=0, umid=b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00', loudness_value=0.0, loudness_range=0.0, max_true_peak=0.0, max_momentary_loudness=0.0, max_shortterm_loudness=0.0, coding_history='A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\\r\\n')\n" ] } ], diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..39ae5e6 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_wave_parsing + diff --git a/tests/test_files/A101_1_1.WAV b/tests/test_files/A101_1_1.WAV new file mode 100644 index 0000000..b1e45c3 Binary files /dev/null and b/tests/test_files/A101_1_1.WAV differ diff --git a/tests/test_files/A101_1_2.WAV b/tests/test_files/A101_1_2.WAV new file mode 100644 index 0000000..972be43 Binary files /dev/null and b/tests/test_files/A101_1_2.WAV differ diff --git a/tests/test_files/WaveAgentSoundReport.csv b/tests/test_files/WaveAgentSoundReport.csv new file mode 100644 index 0000000..f1b332c --- /dev/null +++ b/tests/test_files/WaveAgentSoundReport.csv @@ -0,0 +1 @@ +SOUND REPORT Project:,"",Sound Mixer:,"",Director:,"" Date:,"",Phone:,"",Producer:,"" Location:,"",Email:,"",Roll:,"" File Type:,"",Bit Depth:,"",Media:,"" Frame Rate:,"",Sample Rate:,"",Tone/dB:,"" File Name,Channels,Tape,Scene,Take,Sample Rate,Bit Depth,Length,Start TC,Wild,Circled,User,Notes "BULLET Impact Plastic LCD TV Screen Shatter Debris 2x.wav",2(POLY),"","","",96000,24,00:00:04,00:00:00:18, , ,"",," "," " "A101_1.WAV",2(POLY),"18Y12M31","A101","01",48000,24,00:00:05,12:39:59:00, , ,"",,"MKH516 A","Boom" "A101_2.WAV",2(POLY),"18Y12M31","A101","02",48000,24,00:00:10,12:40:04:00, , ,"",,"MKH516 A","Boom" "A101_3.WAV",2(POLY),"18Y12M31","A101","03",48000,24,00:00:01,12:40:14:00, , ,"",,"MKH516 A","Boom" "PT A101_4.A1.wav",2(POLY),"18Y12M31","A101","04",48000,24,00:00:03,12:40:15:00, , ,"",,"MKH516 A","Boom" "A101_4.WAV",2(POLY),"18Y12M31","A101","04",48000,24,00:00:03,12:40:15:00, , ,"",,"MKH516 A","Boom" \ No newline at end of file diff --git a/tests/test_wave_parsing.py b/tests/test_wave_parsing.py new file mode 100644 index 0000000..2a257b8 --- /dev/null +++ b/tests/test_wave_parsing.py @@ -0,0 +1,99 @@ +import os.path +import json +import subprocess + +from unittest import TestCase + +import wavinfo + +FFPROBE='/usr/local/bin/ffprobe' + + +def ffprobe(path): + + arguments = [ FFPROBE , "-of", "json" , "-show_format", "-show_streams", path ] + + process = subprocess.run(arguments, stdin=None, capture_output=True) + + if process.returncode == 0: + return json.loads(process.stdout) + else: + return None + + +class TestWaveInfo(TestCase): + + + def all_files(self): + for dirpath, dirnames, filenames in os.walk('tests/test_files'): + for filename in filenames: + name, ext = os.path.splitext(filename) + if ext == '.wav': + yield os.path.join(dirpath, filename) + + + def test_sanity(self): + for wav_file in self.all_files(): + info = wavinfo.WavInfoReader(wav_file) + self.assertTrue(info is not None) + + def test_fmt_against_ffprobe(self): + for wav_file in self.all_files(): + info = wavinfo.WavInfoReader(wav_file) + ffprobe_info = ffprobe(wav_file) + + self.assertEqual( info.fmt.channel_count , ffprobe_info['streams'][0]['channels'] ) + self.assertEqual( info.fmt.sample_rate , int(ffprobe_info['streams'][0]['sample_rate']) ) + self.assertEqual( info.fmt.bits_per_sample, int(ffprobe_info['streams'][0]['bits_per_raw_sample']) ) + + if info.fmt.audio_format == 1: + self.assertTrue(ffprobe_info['streams'][0]['codec_name'].startswith('pcm') ) + byte_rate = int(ffprobe_info['streams'][0]['sample_rate']) \ + * ffprobe_info['streams'][0]['channels'] \ + * int(ffprobe_info['streams'][0]['bits_per_raw_sample']) / 8 + self.assertEqual( info.fmt.byte_rate , byte_rate ) + + def test_data_against_ffprobe(self): + for wav_file in self.all_files(): + info = wavinfo.WavInfoReader(wav_file) + ffprobe_info = ffprobe(wav_file) + + self.assertEqual( info.data.frame_count, int(ffprobe_info['streams'][0]['duration_ts'] )) + + def test_bext_against_ffprobe(self): + for wav_file in self.all_files(): + info = wavinfo.WavInfoReader(wav_file) + ffprobe_info = ffprobe(wav_file) + + self.assertEqual( info.bext.description, ffprobe_info['format']['tags']['comment'] ) + self.assertEqual( info.bext.originator, ffprobe_info['format']['tags']['encoded_by'] ) + self.assertEqual( info.bext.originator_ref, ffprobe_info['format']['tags']['originator_reference'] ) + + # these don't always reflect the bext info + #self.assertEqual( info.bext.originator_date, ffprobe_info['format']['tags']['date'] ) + #self.assertEqual( info.bext.originator_time, ffprobe_info['format']['tags']['creation_time'] ) + self.assertEqual( info.bext.time_reference, int(ffprobe_info['format']['tags']['time_reference']) ) + self.assertEqual( info.bext.coding_history, ffprobe_info['format']['tags']['coding_history'] ) + + def test_ixml(self): + expected = {'A101_4.WAV': {'project' : 'BMH', 'scene': 'A101', 'take': '4', + 'tape': '18Y12M31', 'family_uid': 'USSDVGR1112089007124015008231000'}, + 'A101_3.WAV': {'project' : 'BMH', 'scene': 'A101', 'take': '3', + 'tape': '18Y12M31', 'family_uid': 'USSDVGR1112089007124014008228300'}, + 'A101_2.WAV': {'project' : 'BMH', 'scene': 'A101', 'take': '2', + 'tape': '18Y12M31', 'family_uid': 'USSDVGR1112089007124004008218600'}, + 'A101_1.WAV': {'project' : 'BMH', 'scene': 'A101', 'take': '1', + 'tape': '18Y12M31', 'family_uid': 'USSDVGR1112089007124001008206300'}, + } + + for wav_file in self.all_files(): + basename = os.path.basename(wav_file) + if basename in expected: + info = wavinfo.WavInfoReader(wav_file) + e = expected[basename] + + self.assertEqual( e['project'], info.ixml.project ) + self.assertEqual( e['scene'], info.ixml.scene ) + self.assertEqual( e['take'], info.ixml.take ) + self.assertEqual( e['tape'], info.ixml.tape ) + self.assertEqual( e['family_uid'], info.ixml.family_uid ) diff --git a/wavinfo/wave_reader.py b/wavinfo/wave_reader.py index 2d37016..76e78fb 100644 --- a/wavinfo/wave_reader.py +++ b/wavinfo/wave_reader.py @@ -9,8 +9,8 @@ WavDataDescriptor = namedtuple('WavDataDescriptor','byte_count frame_count') WavInfoFormat = namedtuple("WavInfoFormat",'audio_format channel_count sample_rate byte_rate block_align bits_per_sample') -WavBextFormat = namedtuple("WavBextFormat",'description originator originator_ref ' + - 'originator_date originator_time time_reference version umid ' + +WavBextFormat = namedtuple("WavBextFormat",'description originator originator_ref ' + + 'originator_date originator_time time_reference version umid ' + 'loudness_value loudness_range max_true_peak max_momentary_loudness max_shortterm_loudness ' + 'coding_history') @@ -25,10 +25,10 @@ class WavInfoReader(): def __init__(self, path): with open(path, 'rb') as f: chunks = parse_chunk(f) - + self.main_list = chunks.children f.seek(0) - + self.fmt = self._get_format(f) self.bext = self._get_bext(f) self.ixml = self._get_ixml(f) @@ -53,14 +53,14 @@ class WavInfoReader(): def _describe_data(self,f): data_chunk = next(c for c in self.main_list if c.ident == b'data') - return WavDataDescriptor(byte_count= data_chunk.length, + return WavDataDescriptor(byte_count= data_chunk.length, frame_count= int(data_chunk.length / self.fmt.block_align)) - + def _get_format(self,f): fmt_data = self._find_chunk_data(b'fmt ',f) - + # The format chunk is # audio_format U16 # channel_count U16 @@ -70,9 +70,9 @@ class WavInfoReader(): # bits_per_sampl U16 packstring = "