From 0e41fabbe63c130fbf1f14b9c889376fed0b1220 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Tue, 20 Feb 2018 11:47:33 -0600 Subject: [PATCH] Added youtube support. Closes #94 --- README.md | 2 + doc/changelog.md | 2 + src/audio_services/services.py | 5 +- src/controller/buffersController.py | 11 +--- src/controller/mainController.py | 4 +- src/sound.py | 94 ++++++++--------------------- 6 files changed, 38 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index df6d0c39..9f8bf00b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ setuptools installs a script, called easy_install. You can find it in the python * PySocks * win_inet_pton * yandex.translate +* youtube-dl +* python-vlc easy_install will automatically get the additional libraries that these packages need to work properly. Run the following command to quickly install and upgrade all packages and their dependencies: diff --git a/doc/changelog.md b/doc/changelog.md index e5ae99ab..c3695fa9 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -2,6 +2,8 @@ ## changes in this version +* Added support for playing youtube Links directly from the client. ([#94](https://github.com/manuelcortez/TWBlue/issues/94)) +* Replaced Bass with libVLC for playing URL streams. * the checkbox for indicating wether TWBlue will include everyone in a reply or not, will be unchecked by default. * You can request TWBlue to save the state for two checkboxes: Long tweet and mention all, from the global settings dialogue. * For windows 10 users, some keystrokes in the invisible user interface have been changed or merged: diff --git a/src/audio_services/services.py b/src/audio_services/services.py index b0725238..d57086b7 100644 --- a/src/audio_services/services.py +++ b/src/audio_services/services.py @@ -2,6 +2,7 @@ from audio_services import matches_url import json import re import urllib +import youtube_utils @matches_url('https://audioboom.com') def convert_audioboom(url): @@ -29,8 +30,8 @@ def convert_soundcloud (url): raise TypeError('%r is not streamable' % url) @matches_url ('https://www.youtube.com/watch') -def convert_long_youtube(url): - return "youtube-url" +def convert_youtube_long (url): + return youtube_utils.get_video_url(url) def convert_generic_audio(url): return url diff --git a/src/controller/buffersController.py b/src/controller/buffersController.py index 98446782..c563411b 100644 --- a/src/controller/buffersController.py +++ b/src/controller/buffersController.py @@ -71,8 +71,7 @@ class bufferController(object): self.session.settings["sound"]["volume"] = 0.0 else: self.session.settings["sound"]["volume"] -=0.05 - if hasattr(sound.URLPlayer, "stream"): - sound.URLPlayer.stream.volume = self.session.settings["sound"]["volume"] + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0)) self.session.sound.play("volume_changed.ogg") self.session.settings.write() @@ -82,8 +81,7 @@ class bufferController(object): self.session.settings["sound"]["volume"] = 1.0 else: self.session.settings["sound"]["volume"] +=0.05 - if hasattr(sound.URLPlayer, "stream"): - sound.URLPlayer.stream.volume = self.session.settings["sound"]["volume"] + sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100)) self.session.sound.play("volume_changed.ogg") self.session.settings.write() @@ -620,11 +618,8 @@ class baseBufferController(bufferController): if self.session.settings['sound']['indicate_img'] and utils.is_media(tweet): self.session.sound.play("image.ogg") -# @_tweets_exist def audio(self, url='', *args, **kwargs): - if hasattr(sound.URLPlayer,'stream') and sound.URLPlayer.stream.is_playing == True: - return sound.URLPlayer.stop_audio(delete=True) - elif sound.URLPlayer.mode == "youtube": + if sound.URLPlayer.player.is_playing(): return sound.URLPlayer.stop_audio() tweet = self.get_tweet() if tweet == None: return diff --git a/src/controller/mainController.py b/src/controller/mainController.py index f3a350ba..eff4ff64 100644 --- a/src/controller/mainController.py +++ b/src/controller/mainController.py @@ -504,13 +504,13 @@ class Controller(object): def seekLeft(self, *args, **kwargs): try: - sound.URLPlayer.seek(-5) + sound.URLPlayer.seek(-5000) except: output.speak("Unable to seek.",True) def seekRight(self, *args, **kwargs): try: - sound.URLPlayer.seek(5) + sound.URLPlayer.seek(5000) except: output.speak("Unable to seek.",True) diff --git a/src/sound.py b/src/sound.py index 26507b89..fe48d747 100644 --- a/src/sound.py +++ b/src/sound.py @@ -11,6 +11,7 @@ import subprocess import platform import output import youtube_utils +import vlc system = platform.system() from mysc.repeating_timer import RepeatingTimer from mysc.thread_utils import call_threaded @@ -96,99 +97,56 @@ class soundSystem(object): sound_object.play() class URLStream(object): - def __init__(self, url=None): - self.url = url + """ URL Stream Player implementation.""" + + def __init__(self): + # URL status. Should be True after URL expansion and transformation. self.prepared = False log.debug("URL Player initialized") - self.mode = "generic" + # LibVLC controls. + self.instance = vlc.Instance() + self.player = self.instance.media_player_new() def prepare(self, url): + """ Takes an URL and prepares it to be streamed. This function will try to unshorten the passed URL and, if needed, to transform it into a valid URL.""" log.debug("Preparing URL: %s" % (url,)) self.prepared = False self.url = url_shortener.unshorten(url) if self.url == None: self.url = url - log.debug("Expanded URL: %s" % (self.url,)) + log.debug("Expanded URL: %s" % (self.url,)) if self.url != None: transformer = audio_services.find_url_transformer(self.url) - transformed_url = transformer(self.url) - if transformed_url == "youtube-url": - self.url = youtube_utils.get_video_url(url) - self.mode = "youtube" - else: + transformed_url = transformer(self.url) self.url = transformed_url - self.mode = "generic" log.debug("Transformed URL: %s. Prepared" % (self.url,)) self.prepared = True - def seek(self,step): - if self.mode == "youtube": - return - pos=self.stream.get_position() - pos=self.stream.bytes_to_seconds(pos) + def seek(self, step): + pos=self.player.get_time() pos+=step - pos=self.stream.seconds_to_bytes(pos) - if pos<0: - pos=0 - self.stream.set_position(pos) + pos=self.player.set_time(pos) def playpause(self): - if self.mode == "youtube": - if youtube_utils.player != None: - youtube_utils.player.kill() - self.mode = "generic" - youtube_utils.player = None - if self.stream.is_playing==True: - self.stream.pause() + if self.player.is_playing() == True: + self.player.pause() else: - self.stream.play() + self.player.play() - def play(self, url=None, volume=1.0, stream=None,announce=True): + def play(self, url=None, volume=1.0, announce=True): if announce: output.speak(_(u"Playing...")) log.debug("Attempting to play an URL...") if url != None: self.prepare(url) - elif stream != None: - self.stream=stream if self.prepared == True: - if self.mode == "youtube": - youtube_utils.play_video(self.url) - else: - self.stream = sound_lib.stream.URLStream(url=self.url) - if hasattr(self,'stream'): - self.stream.volume = float(volume) - self.stream.play() - log.debug("played") + media = self.instance.media_new(self.url) + self.player.set_media(media) + self.player.audio_set_volume(int(volume*100)) + self.player.play() + log.debug("played") self.prepared=False - def stop_audio(self,delete=False): - if self.mode == "youtube": - youtube_utils.stop() - self.prepared = False - output.speak(_(u"Stopped."), True) - return True - else: - if hasattr(self, "stream"): - output.speak(_(u"Stopped."), True) - try: - self.stream.stop() - log.debug("Stopped audio stream.") - except: - log.exception("Exception while stopping stream.") - if delete: - del self.stream - log.debug("Deleted audio stream.") - self.prepared=False - return True - else: - return False - - @staticmethod - def delete_old_tempfiles(): - for f in glob(os.path.join(tempfile.gettempdir(), 'tmp*.wav')): - try: - os.remove(f) - except: - pass - + def stop_audio(self): + output.speak(_(u"Stopped."), True) + self.player.stop()