From 4ee5a52d152f709135cab54e8d94ab239efbe654 Mon Sep 17 00:00:00 2001 From: nojhan Date: Mon, 28 Sep 2015 00:46:54 +0200 Subject: [PATCH 1/2] Add the "record" command in radioob The record command use streamripper as a way to save each track of the stream in a separated file. Extract check_exec as a member. --- weboob/applications/radioob/radioob.py | 91 ++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/weboob/applications/radioob/radioob.py b/weboob/applications/radioob/radioob.py index 9efd5218..0d7cb870 100644 --- a/weboob/applications/radioob/radioob.py +++ b/weboob/applications/radioob/radioob.py @@ -198,14 +198,6 @@ class Radioob(ReplApplication): audio.url = _obj.url - def check_exec(executable): - with open(os.devnull, 'w') as devnull: - process = subprocess.Popen(['which', executable], stdout=devnull) - if process.wait() != 0: - print('Please install "%s"' % executable, file=self.stderr) - return False - return True - def audio_to_file(_audio): ext = _audio.ext if not ext: @@ -220,23 +212,98 @@ class Radioob(ReplApplication): dest = audio_to_file(audio) if audio.url.startswith('rtmp'): - if not check_exec('rtmpdump'): + if not self.check_exec('rtmpdump'): return 1 args = ('rtmpdump', '-e', '-r', audio.url, '-o', dest) elif audio.url.startswith('mms'): - if not check_exec('mimms'): + if not self.check_exec('mimms'): return 1 args = ('mimms', '-r', audio.url, dest) else: - if check_exec('wget'): + if self.check_exec('wget'): args = ('wget', '-c', audio.url, '-O', dest) - elif check_exec('curl'): + elif self.check_exec('curl'): args = ('curl', '-C', '-', audio.url, '-o', dest) else: return 1 os.spawnlp(os.P_WAIT, args[0], *args) + + def check_exec(self, executable): + with open(os.devnull, 'w') as devnull: + process = subprocess.Popen(['which', executable], stdout=devnull) + if process.wait() != 0: + print('Please install "%s"' % executable, file=self.stderr) + return False + return True + + + def do_record(self, line): + """ + record ID [STREAM] + + Record each track of an audio stream in a separate file in a directory. + """ + _id, stream_id = self.parse_command_args(line, 2, 1) + dest = stream_id + + try: + stream_id = int(stream_id) + except (ValueError, TypeError): + stream_id = 0 + + obj = self.retrieve_obj(_id) + + if obj is None: + print('No object matches with this id:', _id, file=self.stderr) + return 3 + + if isinstance(obj, Radio): + try: + streams = [obj.streams[stream_id]] + except IndexError: + print('Stream %d not found' % stream_id, file=self.stderr) + return 1 + elif isinstance(obj, BaseAudio): + streams = [obj] + + else: + streams = obj.tracks_list + + + if len(streams) == 0: + print('Radio or Audio file not found:', _id, file=self.stderr) + return 3 + + for stream in streams: + self.record_stream(stream, dest) + + + def record_stream(self, stream, dest = None): + + if dest is None: + title = stream.title if stream.title else stream.id + dest = '%s' % re.sub('[?:/]', '-', title) + + if not os.path.exists(dest): + os.makedirs(dest) + + if not os.path.isdir(dest): + print('The destination "%s" is not a directory' % dest, file=self.stderr) + return 5 + + if self.check_exec('streamripper'): + args = ('streamripper', stream.url, '-d', dest) + else: + print('streamripper not found, please install it', file=self.stderr) + return 1 + + self.logger.debug(u'Record stream: "%s" in directory "%s"' % (stream.url, dest)) + os.spawnlp(os.P_WAIT, args[0], *args) + + + def complete_play(self, text, line, *ignored): args = line.split(' ') if len(args) == 2: From 22588ab1a6a7208e55b4753b683c3d384e2001d5 Mon Sep 17 00:00:00 2001 From: nojhan Date: Wed, 30 Sep 2015 10:53:27 +0200 Subject: [PATCH 2/2] Add the recordplay command to radioob Record a stream and serve it, then run a media player on the local stream (thus no bandwidth is wasted). The recorder is started in the background but still print on stdout. Ctr-C ends both the recorder and the player. --- weboob/applications/radioob/radioob.py | 114 ++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 10 deletions(-) diff --git a/weboob/applications/radioob/radioob.py b/weboob/applications/radioob/radioob.py index 0d7cb870..4c15a43c 100644 --- a/weboob/applications/radioob/radioob.py +++ b/weboob/applications/radioob/radioob.py @@ -19,10 +19,14 @@ from __future__ import print_function -import subprocess +import subprocess # FIXME use subprocess everywhere instead of os.spawn* import os +import sys import re import requests +import signal +import copy +import time from weboob.capabilities.radio import CapRadio, Radio from weboob.capabilities.audio import CapAudio, BaseAudio, Playlist, Album @@ -245,8 +249,72 @@ class Radioob(ReplApplication): Record each track of an audio stream in a separate file in a directory. """ + + streams, dest = self.record_parse(line) + if isinstance(streams, int): + return streams + + for stream in streams: + self.record_stream(stream, dest, background = False) + + + def do_recordplay(self, line): + """ + recordplay ID [STREAM] + + Record each track of an audio stream in a separate file AND play it at the same time. + This takes care of not wasting bandwidth: only one connection to the stream is used. + """ + + streams,dest = self.record_parse(line) + if isinstance(streams, int): + return streams + + for stream in streams: + # Start the recorder in the background + # recpid = self.record_stream(stream, dest, background=True) + recpid = self.record_stream(stream, dest, background=True) + time.sleep(1) + self.logger.debug(u'streamripper recorder in background with PID: %i' % recpid) + + # Play the local relay server, not the original stream. + relay_port = self.config.get('relay_port') + if not relay_port: + relay_port = 8000 + self.logger.debug(u'No relay_port in config file, will use default: %i' % relay_port) + # Build a local stream to avoid changing the stream URL. + # localstream = copy.deepcopy(stream) # FIXME reference kept, why? + # localstream.url = u'http://localhost:%i' % relay_port + class Media: + def __init__(self, url, title, _id): + self.url = url + self.title = title + self.id = _id + localstream = Media(u'http://localhost:%i' % relay_port, stream.title, stream.id) + + player_name = self.config.get('media_player') + media_player_args = self.config.get('media_player_args') + if not player_name: + self.logger.debug(u'No media_player in config file, will try to guess.') + try: + self.player.play(localstream, player_name=player_name, player_args=media_player_args) + except KeyboardInterrupt: + self.logger.debug(u'Player closed') + pass + except: + e = sys.exc_info()[0] + self.logger.debug(u'Encountered an error: %s' % e) + raise + finally: + # Once player ends, terminate the recorder too. + self.logger.debug(u'Terminate the recorder') + # os.kill(recpid, signal.SIGTERM) + os.kill(recpid, signal.SIGKILL) + + + def record_parse(self, line): _id, stream_id = self.parse_command_args(line, 2, 1) - dest = stream_id + try: stream_id = int(stream_id) @@ -276,12 +344,18 @@ class Radioob(ReplApplication): print('Radio or Audio file not found:', _id, file=self.stderr) return 3 - for stream in streams: - self.record_stream(stream, dest) + dest = "records" + return streams, dest - def record_stream(self, stream, dest = None): + def record_stream(self, stream, dest, background = False): + """ + Run streamripper on the given stream and save files in the given directory. + If background is False, this function returns the process id of the new process; + if background is True, returns the process’s exit code if it exits normally, + or -signal, where signal is the signal that killed the process. + """ if dest is None: title = stream.title if stream.title else stream.id dest = '%s' % re.sub('[?:/]', '-', title) @@ -293,14 +367,34 @@ class Radioob(ReplApplication): print('The destination "%s" is not a directory' % dest, file=self.stderr) return 5 - if self.check_exec('streamripper'): - args = ('streamripper', stream.url, '-d', dest) - else: + if not self.check_exec('streamripper'): print('streamripper not found, please install it', file=self.stderr) return 1 - self.logger.debug(u'Record stream: "%s" in directory "%s"' % (stream.url, dest)) - os.spawnlp(os.P_WAIT, args[0], *args) + if background: + # Start a relay server, but do not try to find a free port. + relay_port = self.config.get('relay_port') + if not relay_port: + relay_port = 8000 + args = ['streamripper', stream.url, '-d', dest, '-z', '-r', str(relay_port)] + spawn = subprocess.Popen + else: + args = ['streamripper', stream.url, '-d', dest] + spawn = subprocess.call + + try: + self.logger.debug(u'Invoking: %s' % (u' '.join([str(a) for a in args]))) + proc = spawn(args) + except OSError: + print(u'Failed to start the recorder') + return 7 + + if proc.returncode: + print('The recorder ended unexpectedly', file=self.stderr) + self.logger.debug(u'streamripper error, pid=%i, code=%i' % (proc.pid, proc.returncode)) + return 8 + + return proc.pid