Browse Source

Cleanup, etc

feature/voice
Andrei 8 years ago
parent
commit
fd2a9ff594
  1. 6
      disco/voice/opus.py
  2. 100
      disco/voice/playable.py
  3. 14
      disco/voice/player.py

6
disco/voice/opus.py

@ -1,12 +1,8 @@
import sys import sys
import array import array
import struct
import gevent
import ctypes import ctypes
import ctypes.util import ctypes.util
import subprocess
from gevent.queue import Queue
from holster.enum import Enum from holster.enum import Enum
from disco.util.logging import LoggingClass from disco.util.logging import LoggingClass
@ -132,7 +128,7 @@ class OpusEncoder(BaseOpus):
return result return result
def __del__(self): def __del__(self):
if self._inst: if hasattr(self, '_inst') and self._inst:
self.opus_encoder_destroy(self._inst) self.opus_encoder_destroy(self._inst)
self._inst = None self._inst = None

100
disco/voice/playable.py

@ -1,5 +1,6 @@
import abc import abc
import six import six
import types
import gevent import gevent
import struct import struct
import subprocess import subprocess
@ -40,6 +41,7 @@ class BaseUtil(object):
def pipe(self, other, *args, **kwargs): def pipe(self, other, *args, **kwargs):
child = other(self, *args, **kwargs) child = other(self, *args, **kwargs)
setattr(child, 'metadata', self.metadata) setattr(child, 'metadata', self.metadata)
setattr(child, '_parent', self)
return child return child
@property @property
@ -99,27 +101,14 @@ class OpusFilePlayable(BasePlayable, AbstractOpus):
class FFmpegInput(BaseInput, AbstractOpus): class FFmpegInput(BaseInput, AbstractOpus):
def __init__(self, source='-', command='avconv', streaming=False, **kwargs): def __init__(self, source='-', command='avconv', streaming=False, **kwargs):
super(FFmpegInput, self).__init__(**kwargs) super(FFmpegInput, self).__init__(**kwargs)
if source:
self.source = source
self.streaming = streaming self.streaming = streaming
self.source = source
self.command = command self.command = command
self._buffer = None self._buffer = None
self._proc = None self._proc = None
@classmethod
def youtube_dl(cls, url, *args, **kwargs):
import youtube_dl
ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'})
info = ydl.extract_info(url, download=False)
if 'entries' in info:
info = info['entries'][0]
result = cls(source=info['url'], *args, **kwargs)
result.metadata = info
return result
def read(self, sz): def read(self, sz):
if self.streaming: if self.streaming:
raise TypeError('Cannot read from a streaming FFmpegInput') raise TypeError('Cannot read from a streaming FFmpegInput')
@ -141,9 +130,15 @@ class FFmpegInput(BaseInput, AbstractOpus):
@property @property
def proc(self): def proc(self):
if not self._proc: if not self._proc:
if callable(self.source):
self.source = self.source(self)
if isinstance(self.source, (tuple, list)):
self.source, self.metadata = self.source
args = [ args = [
self.command, self.command,
'-i', self.source, '-i', str(self.source),
'-f', 's16le', '-f', 's16le',
'-ar', str(self.sampling_rate), '-ar', str(self.sampling_rate),
'-ac', str(self.channels), '-ac', str(self.channels),
@ -154,6 +149,52 @@ class FFmpegInput(BaseInput, AbstractOpus):
return self._proc return self._proc
class YoutubeDLInput(FFmpegInput):
def __init__(self, url=None, ie_info=None, *args, **kwargs):
super(YoutubeDLInput, self).__init__(None, *args, **kwargs)
self._url = url
self._ie_info = ie_info
self._info = None
@property
def info(self):
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]
self._info = ydl.process_ie_result(self._ie_info, download=False)
return self._info
@property
def _metadata(self):
return self.info
@classmethod
def many(cls, url, *args, **kwargs):
import youtube_dl
ydl = youtube_dl.YoutubeDL({'format': 'webm[abr>0]/bestaudio/best'})
info = ydl.extract_info(url, download=False, process=False)
if 'entries' not in info:
yield cls(ie_info=info, *args, **kwargs)
raise StopIteration
for item in info['entries']:
yield cls(ie_info=item, *args, **kwargs)
@property
def source(self):
return self.info['url']
class BufferedOpusEncoderPlayable(BasePlayable, AbstractOpus, OpusEncoder): class BufferedOpusEncoderPlayable(BasePlayable, AbstractOpus, OpusEncoder):
def __init__(self, source, *args, **kwargs): def __init__(self, source, *args, **kwargs):
self.source = source self.source = source
@ -256,3 +297,30 @@ class FileProxyPlayable(BasePlayable, AbstractOpus):
self.output.flush() self.output.flush()
self.output.close() self.output.close()
return frame return frame
class PlaylistPlayable(BasePlayable, AbstractOpus):
def __init__(self, items, *args, **kwargs):
super(PlaylistPlayable, self).__init__(*args, **kwargs)
self.items = items
self.now_playing = None
def _get_next(self):
if isinstance(self.items, types.GeneratorType):
return next(self.items, None)
return self.items.pop()
def next_frame(self):
if not self.items:
return
if not self.now_playing:
self.now_playing = self._get_next()
if not self.now_playing:
return
frame = self.now_playing.next_frame()
if not frame:
return self.next_frame()
return frame

14
disco/voice/player.py

@ -63,6 +63,14 @@ class Player(object):
self.events.emit(self.Events.RESUME_PLAY) self.events.emit(self.Events.RESUME_PLAY)
def play(self, item): def play(self, item):
# Grab the first frame before we start anything else, sometimes playables
# can do some lengthy async tasks here to setup the playable and we
# don't want that lerp the first N frames of the playable into playing
# faster
frame = item.next_frame()
if frame is None:
return
start = time.time() start = time.time()
loops = 0 loops = 0
@ -83,13 +91,13 @@ class Player(object):
if self.client.state != VoiceState.CONNECTED: if self.client.state != VoiceState.CONNECTED:
self.client.state_emitter.wait(VoiceState.CONNECTED) self.client.state_emitter.wait(VoiceState.CONNECTED)
self.client.send_frame(frame)
self.client.timestamp += item.samples_per_frame
frame = item.next_frame() frame = item.next_frame()
if frame is None: if frame is None:
return return
self.client.send_frame(frame)
self.client.timestamp += item.samples_per_frame
next_time = start + 0.02 * loops next_time = start + 0.02 * loops
delay = max(0, 0.02 + (next_time - time.time())) delay = max(0, 0.02 + (next_time - time.time()))
gevent.sleep(delay) gevent.sleep(delay)

Loading…
Cancel
Save