Browse Source

Fix various Python 3 voice issues

- Use print function for nacl library warning
- Use BufferedIO instead of StringIO on Python3, we're dealing with
bytes here
- Fix MRO issue w/ BufferedOpusEncoder
- Fix race condition on loading url information within YoutubeDLPlayable
feature/storage
Andrei 8 years ago
parent
commit
517c0037ee
  1. 4
      disco/voice/client.py
  2. 36
      disco/voice/playable.py
  3. 4
      examples/music.py
  4. 1
      requirements-optional.txt

4
disco/voice/client.py

@ -1,3 +1,5 @@
from __future__ import print_function
import gevent import gevent
import socket import socket
import struct import struct
@ -6,7 +8,7 @@ import time
try: try:
import nacl.secret import nacl.secret
except ImportError: except ImportError:
print 'WARNING: nacl is not installed, voice support is disabled' print('WARNING: nacl is not installed, voice support is disabled')
from holster.enum import Enum from holster.enum import Enum
from holster.emitter import Emitter from holster.emitter import Emitter

36
disco/voice/playable.py

@ -5,28 +5,24 @@ import gevent
import struct import struct
import subprocess import subprocess
from gevent.lock import Semaphore
from gevent.queue import Queue from gevent.queue import Queue
from disco.voice.opus import OpusEncoder from disco.voice.opus import OpusEncoder
try: try:
from cStringIO import cStringIO as StringIO from cStringIO import cStringIO as BufferedIO
except: except:
from six import StringIO if six.PY2:
from StringIO import StringIO as BufferedIO
else:
from io import BytesIO as BufferedIO
OPUS_HEADER_SIZE = struct.calcsize('<h') OPUS_HEADER_SIZE = struct.calcsize('<h')
# Play from file:
# OpusFilePlayable(open('myfile.opus', 'r'))
# PCMFileInput(open('myfile.pcm', 'r')).pipe(DCADOpusEncoder) => OpusPlayable
# FFMpegInput.youtube_dl('youtube.com/yolo').pipe(DCADOpusEncoder) => OpusPlayable
# FFMpegInput.youtube_dl('youtube.com/yolo').pipe(OpusEncoder).pipe(DuplexStream, open('cache_file.opus', 'w')) => OpusPlayable
class AbstractOpus(object): class AbstractOpus(object):
def __init__(self, sampling_rate=48000, frame_length=20, channels=2): def __init__(self, sampling_rate=48000, frame_length=20, channels=2):
self.sampling_rate = sampling_rate self.sampling_rate = sampling_rate
@ -116,7 +112,7 @@ class FFmpegInput(BaseInput, AbstractOpus):
# First read blocks until the subprocess finishes # First read blocks until the subprocess finishes
if not self._buffer: if not self._buffer:
data, _ = self.proc.communicate() data, _ = self.proc.communicate()
self._buffer = StringIO(data) self._buffer = BufferedIO(data)
# Subsequent reads can just do dis thang # Subsequent reads can just do dis thang
return self._buffer.read(sz) return self._buffer.read(sz)
@ -155,9 +151,11 @@ class YoutubeDLInput(FFmpegInput):
self._url = url self._url = url
self._ie_info = ie_info self._ie_info = ie_info
self._info = None self._info = None
self._info_lock = Semaphore()
@property @property
def info(self): def info(self):
with self._info_lock:
if not self._info: if not self._info:
import youtube_dl import youtube_dl
ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'}) ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'})
@ -165,9 +163,9 @@ class YoutubeDLInput(FFmpegInput):
if self._url: if self._url:
obj = ydl.extract_info(self._url, download=False, process=False) obj = ydl.extract_info(self._url, download=False, process=False)
if 'entries' in obj: if 'entries' in obj:
self._ie_info = obj['entries'] self._ie_info = obj['entries'][0]
else: else:
self._ie_info = [obj] self._ie_info = obj
self._info = ydl.process_ie_result(self._ie_info, download=False) self._info = ydl.process_ie_result(self._ie_info, download=False)
return self._info return self._info
@ -195,11 +193,19 @@ class YoutubeDLInput(FFmpegInput):
return self.info['url'] return self.info['url']
class BufferedOpusEncoderPlayable(BasePlayable, AbstractOpus, OpusEncoder): class BufferedOpusEncoderPlayable(BasePlayable, OpusEncoder, AbstractOpus):
def __init__(self, source, *args, **kwargs): def __init__(self, source, *args, **kwargs):
self.source = source self.source = source
self.frames = Queue(kwargs.pop('queue_size', 4096)) self.frames = Queue(kwargs.pop('queue_size', 4096))
super(BufferedOpusEncoderPlayable, self).__init__(*args, **kwargs)
# Call the AbstractOpus constructor, as we need properties it sets
AbstractOpus.__init__(self, *args, **kwargs)
# Then call the OpusEncoder constructor, which requires some properties
# that AbstractOpus sets up
OpusEncoder.__init__(self, self.sampling_rate, self.channels)
# Spawn the encoder loop
gevent.spawn(self._encoder_loop) gevent.spawn(self._encoder_loop)
def _encoder_loop(self): def _encoder_loop(self):

4
examples/music.py

@ -1,7 +1,7 @@
from disco.bot import Plugin from disco.bot import Plugin
from disco.bot.command import CommandError from disco.bot.command import CommandError
from disco.voice.player import Player from disco.voice.player import Player
from disco.voice.playable import FFmpegInput, DCADOpusEncoderPlayable from disco.voice.playable import YoutubeDLInput, BufferedOpusEncoderPlayable
from disco.voice.client import VoiceException from disco.voice.client import VoiceException
@ -40,7 +40,7 @@ class MusicPlugin(Plugin):
@Plugin.command('play', '<url:str>') @Plugin.command('play', '<url:str>')
def on_play(self, event, url): def on_play(self, event, url):
item = FFmpegInput.youtube_dl(url).pipe(DCADOpusEncoderPlayable) item = YoutubeDLInput(url).pipe(BufferedOpusEncoderPlayable)
self.get_player(event.guild.id).queue.put(item) self.get_player(event.guild.id).queue.put(item)
@Plugin.command('pause') @Plugin.command('pause')

1
requirements-optional.txt

@ -1 +1,2 @@
pynacl==1.1.2 pynacl==1.1.2
youtube-dl==2017.4.17

Loading…
Cancel
Save