Skip to content
This repository has been archived by the owner on Dec 21, 2021. It is now read-only.

Commit

Permalink
sound conversion now via oggdec (much faster and smaller as ffmpeg)
Browse files Browse the repository at this point in the history
  • Loading branch information
irmen committed Sep 4, 2017
1 parent 811fc36 commit dc53bdf
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 45 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
A Boulder Dash (tm) clone in pure python.
Requires Python 3.5 + and the ``pillow`` library.
If you want to hear sound, you need the ``sounddevice`` or ``pyaudio`` library as well.
To read compressed audio files, the ``ffmpeg`` tool has to be installed on your system,
and the executable must be on your PATH.
To read compressed audio files, the ``oggdec`` tool has to be installed on your system,
and the executable must be on your PATH. For Windows, a version is provided, but for
other systems (Mac OS, Linux) you should install the ``vorbis-tools`` package on your system.

Graphics and sounds are used from the MIT-licensed GDash https://bitbucket.org/czirkoszoltan/gdash

Expand Down
54 changes: 13 additions & 41 deletions bouldercaves/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import threading
import queue
import os
import shutil
import time
import wave
import pkgutil
Expand Down Expand Up @@ -53,14 +52,7 @@ def best_api(dummy_enabled=False):


class Sample:
"""
A sample of raw PCM audio data.
If the input file is not already a .wav, and/or you want to resample it,
ffmpeg/ffprobe are used to convert it in the background.
For HQ resampling, ffmpeg has to be built with libsoxr support.
"""
ffmpeg_executable = "ffmpeg"

"""A sample of raw PCM audio data. Uncompresses .ogg to PCM if needed."""
def __init__(self, name, filename=None, data=None):
self.duration = 0
self.name = name
Expand All @@ -73,24 +65,25 @@ def __init__(self, name, filename=None, data=None):
with self.convertformat(inputfile) as inputfile:
with wave.open(inputfile, "r") as wavesample:
assert wavesample.getframerate() == norm_samplerate
assert wavesample.getnchannels() == norm_channels
assert wavesample.getsampwidth() == norm_samplewidth
numchannels = wavesample.getnchannels()
assert numchannels in (1, 2)
self.sampledata = wavesample.readframes(wavesample.getnframes())
self.duration = wavesample.getnframes() / norm_samplerate
if numchannels == 1 and norm_channels == 2:
# on the fly conversion to stereo if it is a mono sample
self.sampledata = audioop.tostereo(self.sampledata, norm_samplewidth, 1, 1)
except FileNotFoundError as x:
print(x)
raise SystemExit("'ffmpeg' must be installed on your system to hear sounds in this game. Or you can start it with the --nosound option.")
raise SystemExit("'oggdec' (vorbis-tools) must be installed on your system to hear sounds in this game. "
"Or you can start it with the --nosound option.")

def convertformat(self, stream):
conversion_required = True
try:
# maybe the existing data is already a WAV in the correct format
with wave.open(stream, "r") as wavesample:
if wavesample.getnchannels() == 1 and norm_channels == 2:
stream.seek(0, 0)
stream = self.mono2stereo(stream)
conversion_required = False
if wavesample.getframerate() == norm_samplerate and wavesample.getnchannels() == norm_channels \
if wavesample.getframerate() == norm_samplerate and wavesample.getnchannels() in (1, 2) \
and wavesample.getsampwidth() == norm_samplewidth:
conversion_required = False
except (wave.Error, IOError):
Expand All @@ -99,35 +92,14 @@ def convertformat(self, stream):
stream.seek(0, 0)
if not conversion_required:
return stream

# use ffmpeg to convert the audio file on the fly to a WAV
# use oggdec to convert the audio file on the fly to a WAV
uncompress_command = ["oggdec", "--quiet", "--output", "-", "-"]
with tempfile.NamedTemporaryFile() as tmpfile:
ffmpeg_command = [self.ffmpeg_executable, "-v", "fatal", "-hide_banner", "-i", tmpfile.name]
buildconf = subprocess.check_output([self.ffmpeg_executable, "-v", "error", "-buildconf"]).decode()
if "--enable-libsoxr" in buildconf:
ffmpeg_command.extend(["-af", "aresample=resampler=soxr", "-ar", str(norm_samplerate)])
else:
ffmpeg_command.extend(["-ar", str(norm_samplerate)])
ffmpeg_command.extend(["-ac", str(norm_channels), "-acodec", "pcm_s16le", "-y", "-f", "wav", "-"])
shutil.copyfileobj(stream, tmpfile)
tmpfile.write(stream.read())
tmpfile.seek(0, 0)
converter = subprocess.Popen(ffmpeg_command, stdin=None, stdout=subprocess.PIPE)
converter = subprocess.Popen(uncompress_command, stdin=tmpfile, stdout=subprocess.PIPE)
return io.BytesIO(converter.stdout.read())

def mono2stereo(self, stream):
with wave.open(stream, "r") as wav:
rate = wav.getframerate()
width = wav.getsampwidth()
frames = wav.readframes(wav.getnframes())
frames = audioop.tostereo(frames, width, 1, 1)
new_wav = io.BytesIO()
with wave.open(new_wav, "w") as wav:
wav.setframerate(rate)
wav.setsampwidth(width)
wav.setnchannels(2)
wav.writeframes(frames)
return io.BytesIO(new_wav.getbuffer())


class DummySample(Sample):
def __init__(self, name, filename=None, duration=0):
Expand Down
Binary file modified bouldercaves/sounds/oggdec.exe
Binary file not shown.
7 changes: 5 additions & 2 deletions bouldercaves/sounds/readme.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
sound files should go here.
you can create them with the 'convert_gdash_sounds.sh' shell script.
Sound files should go here.
You can create them with the 'convert_gdash_sounds.sh' shell script.

For Windows a version of the Vorbis tools' oggdec is provided.
For other platforms, it is assumed you install the 'vorbis-tools' package yourself.

0 comments on commit dc53bdf

Please sign in to comment.