2021-04-16 23:21:06 +08:00
|
|
|
"""Decoder for RIFF Wave files, using the standard library wave module.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import wave
|
|
|
|
|
2022-04-08 23:07:41 +08:00
|
|
|
from pyglet.util import DecodeException
|
2021-04-16 23:21:06 +08:00
|
|
|
from .base import StreamingSource, AudioData, AudioFormat, StaticSource
|
|
|
|
from . import MediaEncoder, MediaDecoder
|
|
|
|
|
|
|
|
|
2022-04-08 23:07:41 +08:00
|
|
|
class WAVEDecodeException(DecodeException):
|
2021-04-16 23:21:06 +08:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class WaveSource(StreamingSource):
|
|
|
|
def __init__(self, filename, file=None):
|
|
|
|
if file is None:
|
|
|
|
file = open(filename, 'rb')
|
2022-04-08 23:07:41 +08:00
|
|
|
self._file = file
|
2021-04-16 23:21:06 +08:00
|
|
|
|
|
|
|
try:
|
|
|
|
self._wave = wave.open(file)
|
|
|
|
except wave.Error as e:
|
|
|
|
raise WAVEDecodeException(e)
|
|
|
|
|
|
|
|
nchannels, sampwidth, framerate, nframes, comptype, compname = self._wave.getparams()
|
|
|
|
|
|
|
|
self.audio_format = AudioFormat(channels=nchannels, sample_size=sampwidth * 8, sample_rate=framerate)
|
|
|
|
|
|
|
|
self._bytes_per_frame = nchannels * sampwidth
|
|
|
|
self._duration = nframes / framerate
|
|
|
|
self._duration_per_frame = self._duration / nframes
|
|
|
|
self._num_frames = nframes
|
|
|
|
|
|
|
|
self._wave.rewind()
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
if hasattr(self, '_file'):
|
|
|
|
self._file.close()
|
|
|
|
|
|
|
|
def get_audio_data(self, num_bytes, compensation_time=0.0):
|
|
|
|
num_frames = max(1, num_bytes // self._bytes_per_frame)
|
|
|
|
|
|
|
|
data = self._wave.readframes(num_frames)
|
|
|
|
if not data:
|
|
|
|
return None
|
|
|
|
|
|
|
|
timestamp = self._wave.tell() / self.audio_format.sample_rate
|
|
|
|
duration = num_frames / self.audio_format.sample_rate
|
|
|
|
return AudioData(data, len(data), timestamp, duration, [])
|
|
|
|
|
|
|
|
def seek(self, timestamp):
|
|
|
|
timestamp = max(0.0, min(timestamp, self._duration))
|
|
|
|
position = int(timestamp / self._duration_per_frame)
|
|
|
|
self._wave.setpos(position)
|
|
|
|
|
|
|
|
|
|
|
|
#########################################
|
|
|
|
# Decoder class:
|
|
|
|
#########################################
|
|
|
|
|
|
|
|
class WaveDecoder(MediaDecoder):
|
|
|
|
|
|
|
|
def get_file_extensions(self):
|
|
|
|
return '.wav', '.wave', '.riff'
|
|
|
|
|
2022-04-08 23:07:41 +08:00
|
|
|
def decode(self, filename, file, streaming=True):
|
2021-04-16 23:21:06 +08:00
|
|
|
if streaming:
|
|
|
|
return WaveSource(filename, file)
|
|
|
|
else:
|
|
|
|
return StaticSource(WaveSource(filename, file))
|
|
|
|
|
|
|
|
|
|
|
|
class WaveEncoder(MediaEncoder):
|
|
|
|
|
|
|
|
def get_file_extensions(self):
|
|
|
|
return '.wav', '.wave', '.riff'
|
|
|
|
|
2022-04-08 23:07:41 +08:00
|
|
|
def encode(self, source, filename, file):
|
2021-04-16 23:21:06 +08:00
|
|
|
"""Save the Source to disk as a standard RIFF Wave.
|
|
|
|
|
|
|
|
A standard RIFF wave header will be added to the raw PCM
|
|
|
|
audio data when it is saved to disk.
|
|
|
|
|
|
|
|
:Parameters:
|
|
|
|
`filename` : str
|
|
|
|
The file name to save as.
|
2022-04-13 19:00:36 +08:00
|
|
|
`file` : file-like object
|
|
|
|
A file-like object, opened with mode 'wb'.
|
2021-04-16 23:21:06 +08:00
|
|
|
|
|
|
|
"""
|
2022-04-08 23:07:41 +08:00
|
|
|
opened_file = None
|
|
|
|
if file is None:
|
|
|
|
file = open(filename, 'wb')
|
|
|
|
opened_file = True
|
|
|
|
|
2021-04-16 23:21:06 +08:00
|
|
|
source.seek(0)
|
|
|
|
wave_writer = wave.open(file, mode='wb')
|
|
|
|
wave_writer.setnchannels(source.audio_format.channels)
|
2021-09-23 06:34:23 +08:00
|
|
|
wave_writer.setsampwidth(source.audio_format.bytes_per_sample)
|
2021-04-16 23:21:06 +08:00
|
|
|
wave_writer.setframerate(source.audio_format.sample_rate)
|
|
|
|
# Save the data in 1-second chunks:
|
|
|
|
chunksize = source.audio_format.bytes_per_second
|
|
|
|
audiodata = source.get_audio_data(chunksize)
|
|
|
|
while audiodata:
|
|
|
|
wave_writer.writeframes(audiodata.data)
|
|
|
|
audiodata = source.get_audio_data(chunksize)
|
2022-04-08 23:07:41 +08:00
|
|
|
else:
|
2022-04-13 19:00:36 +08:00
|
|
|
wave_writer.close()
|
2022-04-08 23:07:41 +08:00
|
|
|
if opened_file:
|
|
|
|
file.close()
|
2021-04-16 23:21:06 +08:00
|
|
|
|
|
|
|
|
|
|
|
def get_decoders():
|
|
|
|
return [WaveDecoder()]
|
|
|
|
|
|
|
|
|
|
|
|
def get_encoders():
|
|
|
|
return [WaveEncoder()]
|