diff --git a/disco/voice/client.py b/disco/voice/client.py index 0c9ce6c..6f2eae3 100644 --- a/disco/voice/client.py +++ b/disco/voice/client.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import gevent import socket import struct @@ -6,7 +8,7 @@ import time try: import nacl.secret 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.emitter import Emitter diff --git a/disco/voice/playable.py b/disco/voice/playable.py index 2dbd6f7..98cbbc2 100644 --- a/disco/voice/playable.py +++ b/disco/voice/playable.py @@ -5,28 +5,24 @@ import gevent import struct import subprocess - +from gevent.lock import Semaphore from gevent.queue import Queue from disco.voice.opus import OpusEncoder try: - from cStringIO import cStringIO as StringIO + from cStringIO import cStringIO as BufferedIO 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(' 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): def __init__(self, sampling_rate=48000, frame_length=20, channels=2): self.sampling_rate = sampling_rate @@ -116,7 +112,7 @@ class FFmpegInput(BaseInput, AbstractOpus): # First read blocks until the subprocess finishes if not self._buffer: data, _ = self.proc.communicate() - self._buffer = StringIO(data) + self._buffer = BufferedIO(data) # Subsequent reads can just do dis thang return self._buffer.read(sz) @@ -155,22 +151,24 @@ class YoutubeDLInput(FFmpegInput): self._url = url self._ie_info = ie_info self._info = None + self._info_lock = Semaphore() @property def info(self): - if not self._info: - import youtube_dl - ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'}) + with self._info_lock: + if not self._info: + import youtube_dl + ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'}) - if self._url: - obj = ydl.extract_info(self._url, download=False, process=False) - if 'entries' in obj: - self._ie_info = obj['entries'] - else: - self._ie_info = [obj] + if self._url: + obj = ydl.extract_info(self._url, download=False, process=False) + if 'entries' in obj: + self._ie_info = obj['entries'][0] + else: + self._ie_info = obj - self._info = ydl.process_ie_result(self._ie_info, download=False) - return self._info + self._info = ydl.process_ie_result(self._ie_info, download=False) + return self._info @property def _metadata(self): @@ -195,11 +193,19 @@ class YoutubeDLInput(FFmpegInput): return self.info['url'] -class BufferedOpusEncoderPlayable(BasePlayable, AbstractOpus, OpusEncoder): +class BufferedOpusEncoderPlayable(BasePlayable, OpusEncoder, AbstractOpus): def __init__(self, source, *args, **kwargs): self.source = source 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) def _encoder_loop(self): diff --git a/examples/music.py b/examples/music.py index 63232b9..2c376c3 100644 --- a/examples/music.py +++ b/examples/music.py @@ -1,7 +1,7 @@ from disco.bot import Plugin from disco.bot.command import CommandError 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 @@ -40,7 +40,7 @@ class MusicPlugin(Plugin): @Plugin.command('play', '') 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) @Plugin.command('pause') diff --git a/requirements-optional.txt b/requirements-optional.txt index 44182d9..5402825 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1 +1,2 @@ pynacl==1.1.2 +youtube-dl==2017.4.17