Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
22588ab1a6 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.
2015-09-30 10:53:27 +02:00
4ee5a52d15 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.
2015-09-28 00:46:54 +02:00

View file

@ -19,10 +19,14 @@
from __future__ import print_function from __future__ import print_function
import subprocess import subprocess # FIXME use subprocess everywhere instead of os.spawn*
import os import os
import sys
import re import re
import requests import requests
import signal
import copy
import time
from weboob.capabilities.radio import CapRadio, Radio from weboob.capabilities.radio import CapRadio, Radio
from weboob.capabilities.audio import CapAudio, BaseAudio, Playlist, Album from weboob.capabilities.audio import CapAudio, BaseAudio, Playlist, Album
@ -198,14 +202,6 @@ class Radioob(ReplApplication):
audio.url = _obj.url 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): def audio_to_file(_audio):
ext = _audio.ext ext = _audio.ext
if not ext: if not ext:
@ -220,23 +216,188 @@ class Radioob(ReplApplication):
dest = audio_to_file(audio) dest = audio_to_file(audio)
if audio.url.startswith('rtmp'): if audio.url.startswith('rtmp'):
if not check_exec('rtmpdump'): if not self.check_exec('rtmpdump'):
return 1 return 1
args = ('rtmpdump', '-e', '-r', audio.url, '-o', dest) args = ('rtmpdump', '-e', '-r', audio.url, '-o', dest)
elif audio.url.startswith('mms'): elif audio.url.startswith('mms'):
if not check_exec('mimms'): if not self.check_exec('mimms'):
return 1 return 1
args = ('mimms', '-r', audio.url, dest) args = ('mimms', '-r', audio.url, dest)
else: else:
if check_exec('wget'): if self.check_exec('wget'):
args = ('wget', '-c', audio.url, '-O', dest) args = ('wget', '-c', audio.url, '-O', dest)
elif check_exec('curl'): elif self.check_exec('curl'):
args = ('curl', '-C', '-', audio.url, '-o', dest) args = ('curl', '-C', '-', audio.url, '-o', dest)
else: else:
return 1 return 1
os.spawnlp(os.P_WAIT, args[0], *args) 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.
"""
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)
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
dest = "records"
return streams, dest
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 processs 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)
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 not self.check_exec('streamripper'):
print('streamripper not found, please install it', file=self.stderr)
return 1
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
def complete_play(self, text, line, *ignored): def complete_play(self, text, line, *ignored):
args = line.split(' ') args = line.split(' ')
if len(args) == 2: if len(args) == 2: