added feature for playing sounds backwards
This commit is contained in:
parent
5c9de31835
commit
fad8daf05d
@ -303,8 +303,9 @@ class AudioClip():
|
|||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.Logger.info("~AudioClip()")
|
self.Logger.info("~AudioClip()")
|
||||||
|
|
||||||
def Play(self, seconds = None, rubberband = None, dec_params = None, *args):
|
def Play(self, seconds = None, rubberband = None, dec_params = None, bitrate = None, backwards = None, *args):
|
||||||
return self.AudioPlayer.PlayURI(self.URI, position = seconds, rubberband = rubberband, dec_params = dec_params, *args)
|
return self.AudioPlayer.PlayURI(self.URI, position = seconds, rubberband = rubberband, dec_params = dec_params, bitrate = bitrate,
|
||||||
|
backwards = backwards, *args)
|
||||||
|
|
||||||
def Stop(self):
|
def Stop(self):
|
||||||
return self.AudioPlayer.Stop()
|
return self.AudioPlayer.Stop()
|
||||||
|
@ -8,6 +8,33 @@ import math
|
|||||||
from .Utils import Utils, DataHolder
|
from .Utils import Utils, DataHolder
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
def get_birtate(message):
|
||||||
|
bitrate = []
|
||||||
|
try:
|
||||||
|
for msg in message[1].split(" "): #checking if
|
||||||
|
if "bitrate=" in msg:
|
||||||
|
bitrate = int(msg.split("bitrate=",1)[1])
|
||||||
|
if bitrate < 0.0:
|
||||||
|
bitrate = 0.01
|
||||||
|
if bitrate > 2000:
|
||||||
|
bitrate = 20
|
||||||
|
bitrate.append(f"bitrate=tempo={bitrate}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return bitrate
|
||||||
|
|
||||||
|
def get_backwards(message):
|
||||||
|
backwards = None
|
||||||
|
try:
|
||||||
|
for msg in message[1].split(" "): #checking if pitch= or tempo= is specified
|
||||||
|
if "backward=" in msg.lower():
|
||||||
|
backwards = True
|
||||||
|
elif "backwards=" in msg.lower():
|
||||||
|
backwards = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return backwards
|
||||||
|
|
||||||
def get_rubberBand(message):
|
def get_rubberBand(message):
|
||||||
rubberband = []
|
rubberband = []
|
||||||
try:
|
try:
|
||||||
@ -548,6 +575,8 @@ class VoiceCommands(BaseCommand):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
rubberband = get_rubberBand(message)
|
rubberband = get_rubberBand(message)
|
||||||
|
backwards = get_backwards(message)
|
||||||
|
bitrate = get_birtate(message)
|
||||||
|
|
||||||
if message[0] == "!random":
|
if message[0] == "!random":
|
||||||
Trigger = self.random.choice(list(self.VoiceTriggers.values()))
|
Trigger = self.random.choice(list(self.VoiceTriggers.values()))
|
||||||
@ -614,7 +643,7 @@ class VoiceCommands(BaseCommand):
|
|||||||
if not AudioClip:
|
if not AudioClip:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
return AudioClip.Play(rubberband = rubberband)
|
return AudioClip.Play(rubberband = rubberband, bitrate = bitrate, backwards = backwards)
|
||||||
|
|
||||||
|
|
||||||
class YouTube(BaseCommand):
|
class YouTube(BaseCommand):
|
||||||
@ -647,7 +676,9 @@ class YouTube(BaseCommand):
|
|||||||
#turning the string into a list where get_rubberband just picks the second element to search in
|
#turning the string into a list where get_rubberband just picks the second element to search in
|
||||||
dline = ['', line.split(" ", 1)[1]]
|
dline = ['', line.split(" ", 1)[1]]
|
||||||
rubberband = get_rubberBand(dline)
|
rubberband = get_rubberBand(dline)
|
||||||
return AudioClip.Play(Time, rubberband = rubberband)
|
backwards = get_backwards(dline)
|
||||||
|
bitrate = get_birtate(dline)
|
||||||
|
return AudioClip.Play(Time, rubberband = rubberband, bitrate = bitrate, backwards = backwards)
|
||||||
|
|
||||||
class YouTubeSearch(BaseCommand):
|
class YouTubeSearch(BaseCommand):
|
||||||
import json
|
import json
|
||||||
@ -693,7 +724,9 @@ class YouTubeSearch(BaseCommand):
|
|||||||
#turning the string into a list where get_rubberband just picks the second element to search in
|
#turning the string into a list where get_rubberband just picks the second element to search in
|
||||||
dline = ['', line.split(" ", 1)[1]]
|
dline = ['', line.split(" ", 1)[1]]
|
||||||
rubberband = get_rubberBand(dline)
|
rubberband = get_rubberBand(dline)
|
||||||
return AudioClip.Play(Time, rubberband = rubberband)
|
backwards = get_backwards(dline)
|
||||||
|
bitrate = get_birtate(dline)
|
||||||
|
return AudioClip.Play(Time, rubberband = rubberband, bitrate = bitrate, backwards = backwards)
|
||||||
|
|
||||||
|
|
||||||
class Say(BaseCommand):
|
class Say(BaseCommand):
|
||||||
@ -721,9 +754,17 @@ class Say(BaseCommand):
|
|||||||
try:
|
try:
|
||||||
dline = ['', message.split(" ", 1)[1]]
|
dline = ['', message.split(" ", 1)[1]]
|
||||||
rubberband = get_rubberBand(dline)
|
rubberband = get_rubberBand(dline)
|
||||||
|
backwards = get_backwards(dline)
|
||||||
except Exception:
|
except Exception:
|
||||||
rubberband = None
|
rubberband = None
|
||||||
if AudioClip.Play(rubberband = rubberband):
|
backwards = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
dline = ['', message.split(" ", 1)[1]]
|
||||||
|
bitrate = get_birtate(dline)
|
||||||
|
except Exception:
|
||||||
|
bitrate = None
|
||||||
|
if AudioClip.Play(rubberband = rubberband, bitrate = bitrate, backwards = backwards):
|
||||||
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
AudioClip.AudioPlayer.AddCallback("Stop", lambda: os.unlink(TempFile.name))
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
|
@ -12,52 +12,53 @@ import sys
|
|||||||
SAMPLEBYTES = 2
|
SAMPLEBYTES = 2
|
||||||
|
|
||||||
class FFmpegAudioPlayerFactory():
|
class FFmpegAudioPlayerFactory():
|
||||||
VALID_CALLBACKS = ["Play", "Stop", "Update"]
|
VALID_CALLBACKS = ["Play", "Stop", "Update"]
|
||||||
|
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
self.Logger = logging.getLogger(__class__.__name__)
|
self.Logger = logging.getLogger(__class__.__name__)
|
||||||
self.Master = master
|
self.Master = master
|
||||||
self.Torchlight = self.Master.Torchlight
|
self.Torchlight = self.Master.Torchlight
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.Master.Logger.info("~FFmpegAudioPlayerFactory()")
|
self.Master.Logger.info("~FFmpegAudioPlayerFactory()")
|
||||||
self.Quit()
|
self.Quit()
|
||||||
|
|
||||||
def NewPlayer(self):
|
def NewPlayer(self):
|
||||||
self.Logger.debug(sys._getframe().f_code.co_name)
|
self.Logger.debug(sys._getframe().f_code.co_name)
|
||||||
Player = FFmpegAudioPlayer(self)
|
Player = FFmpegAudioPlayer(self)
|
||||||
return Player
|
return Player
|
||||||
|
|
||||||
def Quit(self):
|
def Quit(self):
|
||||||
self.Master.Logger.info("FFmpegAudioPlayerFactory->Quit()")
|
self.Master.Logger.info("FFmpegAudioPlayerFactory->Quit()")
|
||||||
|
|
||||||
|
|
||||||
class FFmpegAudioPlayer():
|
class FFmpegAudioPlayer():
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
self.Master = master
|
self.Master = master
|
||||||
self.Torchlight = self.Master.Torchlight
|
self.Torchlight = self.Master.Torchlight
|
||||||
self.Playing = False
|
self.Playing = False
|
||||||
|
|
||||||
self.Host = (
|
self.Host = (
|
||||||
self.Torchlight().Config["VoiceServer"]["Host"],
|
self.Torchlight().Config["VoiceServer"]["Host"],
|
||||||
self.Torchlight().Config["VoiceServer"]["Port"]
|
self.Torchlight().Config["VoiceServer"]["Port"]
|
||||||
)
|
)
|
||||||
self.SampleRate = float(self.Torchlight().Config["VoiceServer"]["SampleRate"])
|
self.SampleRate = float(self.Torchlight().Config["VoiceServer"]["SampleRate"])
|
||||||
|
|
||||||
self.StartedPlaying = None
|
self.StartedPlaying = None
|
||||||
self.StoppedPlaying = None
|
self.StoppedPlaying = None
|
||||||
self.Seconds = 0.0
|
self.Seconds = 0.0
|
||||||
|
|
||||||
self.Writer = None
|
self.Writer = None
|
||||||
self.Process = None
|
self.Process = None
|
||||||
|
|
||||||
self.Callbacks = []
|
self.Callbacks = []
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.Master.Logger.debug("~FFmpegAudioPlayer()")
|
self.Master.Logger.debug("~FFmpegAudioPlayer()")
|
||||||
self.Stop()
|
self.Stop()
|
||||||
|
|
||||||
def PlayURI(self, uri, position, rubberband = None, dec_params = None, *args):
|
def PlayURI(self, uri, position, rubberband = None, dec_params = None, bitrate = None,
|
||||||
|
backwards = None, *args):
|
||||||
if position:
|
if position:
|
||||||
PosStr = str(datetime.timedelta(seconds = position))
|
PosStr = str(datetime.timedelta(seconds = position))
|
||||||
Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args]
|
Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args]
|
||||||
@ -68,122 +69,137 @@ class FFmpegAudioPlayer():
|
|||||||
if dec_params:
|
if dec_params:
|
||||||
Command += dec_params
|
Command += dec_params
|
||||||
|
|
||||||
if rubberband:
|
if rubberband and backwards:
|
||||||
Command += ["-filter:a"]
|
Command += ["-filter:a"]
|
||||||
rubberCommand = ""
|
rubberCommand = ""
|
||||||
for rubber in rubberband:
|
for rubber in rubberband:
|
||||||
rubberCommand += rubber + ", "
|
rubberCommand += rubber + ", "
|
||||||
rubberCommand = rubberCommand[:-2]
|
rubberCommand = rubberCommand[:-2]
|
||||||
Command += [rubberCommand]
|
Command += [rubberCommand + "[reversed];[reversed]areverse"] #[reversed] is intermediate stream label so reverse knows what stream label to reverse
|
||||||
|
else:
|
||||||
|
if rubberband:
|
||||||
|
Command += ["-filter:a"]
|
||||||
|
rubberCommand = ""
|
||||||
|
for rubber in rubberband:
|
||||||
|
rubberCommand += rubber + ", "
|
||||||
|
rubberCommand = rubberCommand[:-2]
|
||||||
|
Command += [rubberCommand]
|
||||||
|
if backwards:
|
||||||
|
Command += ["-af"]
|
||||||
|
Command += ["areverse"]
|
||||||
|
if bitrate:
|
||||||
|
Command += ["-ab ", str(bitrate), "k"]
|
||||||
|
self.Master.Logger.debug(f"command: {Command}")
|
||||||
Command += ["-"]
|
Command += ["-"]
|
||||||
|
#self.Master.Logger.debug(f"command: {Command}")
|
||||||
asyncio.ensure_future(self._stream_subprocess(Command))
|
asyncio.ensure_future(self._stream_subprocess(Command))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def Stop(self, force = True):
|
def Stop(self, force = True):
|
||||||
if not self.Playing:
|
if not self.Playing:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.Process:
|
if self.Process:
|
||||||
try:
|
try:
|
||||||
self.Process.terminate()
|
self.Process.terminate()
|
||||||
self.Process.kill()
|
self.Process.kill()
|
||||||
self.Process = None
|
self.Process = None
|
||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if self.Writer:
|
if self.Writer:
|
||||||
if force:
|
if force:
|
||||||
Socket = self.Writer.transport.get_extra_info("socket")
|
Socket = self.Writer.transport.get_extra_info("socket")
|
||||||
if Socket:
|
if Socket:
|
||||||
Socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
|
Socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
|
||||||
struct.pack("ii", 1, 0))
|
struct.pack("ii", 1, 0))
|
||||||
|
|
||||||
self.Writer.transport.abort()
|
self.Writer.transport.abort()
|
||||||
|
|
||||||
self.Writer.close()
|
self.Writer.close()
|
||||||
|
|
||||||
self.Playing = False
|
self.Playing = False
|
||||||
|
|
||||||
self.Callback("Stop")
|
self.Callback("Stop")
|
||||||
del self.Callbacks
|
del self.Callbacks
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def AddCallback(self, cbtype, cbfunc):
|
def AddCallback(self, cbtype, cbfunc):
|
||||||
if not cbtype in FFmpegAudioPlayerFactory.VALID_CALLBACKS:
|
if not cbtype in FFmpegAudioPlayerFactory.VALID_CALLBACKS:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.Callbacks.append((cbtype, cbfunc))
|
self.Callbacks.append((cbtype, cbfunc))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def Callback(self, cbtype, *args, **kwargs):
|
def Callback(self, cbtype, *args, **kwargs):
|
||||||
for Callback in self.Callbacks:
|
for Callback in self.Callbacks:
|
||||||
if Callback[0] == cbtype:
|
if Callback[0] == cbtype:
|
||||||
try:
|
try:
|
||||||
Callback[1](*args, **kwargs)
|
Callback[1](*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.Master.Logger.error(traceback.format_exc())
|
self.Master.Logger.error(traceback.format_exc())
|
||||||
|
|
||||||
async def _updater(self):
|
async def _updater(self):
|
||||||
LastSecondsElapsed = 0.0
|
LastSecondsElapsed = 0.0
|
||||||
|
|
||||||
while self.Playing:
|
while self.Playing:
|
||||||
SecondsElapsed = time.time() - self.StartedPlaying
|
SecondsElapsed = time.time() - self.StartedPlaying
|
||||||
|
|
||||||
if SecondsElapsed > self.Seconds:
|
if SecondsElapsed > self.Seconds:
|
||||||
SecondsElapsed = self.Seconds
|
SecondsElapsed = self.Seconds
|
||||||
|
|
||||||
self.Callback("Update", LastSecondsElapsed, SecondsElapsed)
|
self.Callback("Update", LastSecondsElapsed, SecondsElapsed)
|
||||||
|
|
||||||
if SecondsElapsed >= self.Seconds:
|
if SecondsElapsed >= self.Seconds:
|
||||||
if not self.StoppedPlaying:
|
if not self.StoppedPlaying:
|
||||||
print("BUFFER UNDERRUN!")
|
print("BUFFER UNDERRUN!")
|
||||||
self.Stop(False)
|
self.Stop(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
LastSecondsElapsed = SecondsElapsed
|
LastSecondsElapsed = SecondsElapsed
|
||||||
|
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
async def _read_stream(self, stream, writer):
|
async def _read_stream(self, stream, writer):
|
||||||
Started = False
|
Started = False
|
||||||
|
|
||||||
while stream and self.Playing:
|
while stream and self.Playing:
|
||||||
Data = await stream.read(65536)
|
Data = await stream.read(65536)
|
||||||
|
|
||||||
if Data:
|
if Data:
|
||||||
writer.write(Data)
|
writer.write(Data)
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
Bytes = len(Data)
|
Bytes = len(Data)
|
||||||
Samples = Bytes / SAMPLEBYTES
|
Samples = Bytes / SAMPLEBYTES
|
||||||
Seconds = Samples / self.SampleRate
|
Seconds = Samples / self.SampleRate
|
||||||
|
|
||||||
self.Seconds += Seconds
|
self.Seconds += Seconds
|
||||||
|
|
||||||
if not Started:
|
if not Started:
|
||||||
Started = True
|
Started = True
|
||||||
self.Callback("Play")
|
self.Callback("Play")
|
||||||
self.StartedPlaying = time.time()
|
self.StartedPlaying = time.time()
|
||||||
asyncio.ensure_future(self._updater())
|
asyncio.ensure_future(self._updater())
|
||||||
else:
|
else:
|
||||||
self.Process = None
|
self.Process = None
|
||||||
break
|
break
|
||||||
|
|
||||||
self.StoppedPlaying = time.time()
|
self.StoppedPlaying = time.time()
|
||||||
|
|
||||||
async def _stream_subprocess(self, cmd):
|
async def _stream_subprocess(self, cmd):
|
||||||
if not self.Playing:
|
if not self.Playing:
|
||||||
return
|
return
|
||||||
|
|
||||||
_, self.Writer = await asyncio.open_connection(self.Host[0], self.Host[1])
|
_, self.Writer = await asyncio.open_connection(self.Host[0], self.Host[1])
|
||||||
|
|
||||||
Process = await asyncio.create_subprocess_exec(*cmd,
|
Process = await asyncio.create_subprocess_exec(*cmd,
|
||||||
stdout = asyncio.subprocess.PIPE, stderr = asyncio.subprocess.DEVNULL)
|
stdout = asyncio.subprocess.PIPE, stderr = asyncio.subprocess.DEVNULL)
|
||||||
self.Process = Process
|
self.Process = Process
|
||||||
|
|
||||||
await self._read_stream(Process.stdout, self.Writer)
|
await self._read_stream(Process.stdout, self.Writer)
|
||||||
await Process.wait()
|
await Process.wait()
|
||||||
|
|
||||||
if self.Seconds == 0.0:
|
if self.Seconds == 0.0:
|
||||||
self.Stop()
|
self.Stop()
|
||||||
|
Loading…
Reference in New Issue
Block a user