You are not logged in.
I like to listen to livestreams and from time to time I think to myself: "Argh, great song. I wish I had had streamripper running."
Then again who wants to have streamripper running all the time?
Since I'm not aware of any program that solves this problem, I wrote this simple cli-tool that does the following:
You start it with a livestream URL and the song is being played and ripped at the same time. While it plays, you can chose to either skip (default) or keep the song. When a new song starts, the old song is being deleted if it was set to "skip" or saved to a subdirectory called "keep" if it was set to "keep". That way you can listen to some livestream for hours and don't clutter your disk if there was nothing special (or have to sort out manually) - yet you can still keep any song you like.
It only works with shoutcast-like streams that send meta info with the media data, otherwise there's no way of checking when the current song has changed.
Usage:
python keeper.py URL
Hotkeys:
"s" - skip the current song
"k" - keep the current song
"m" toggle mute
"q" quit
Dependencies:
Python 2.5+
mplayer-cli
streamripper
Code:
#!/usr/bin/python
from threading import Thread
import time, subprocess as sub, os, sys, re, random, termios, fcntl, shutil, string
#player class - contains mplayer subprocess and basic interaction routines
class player:
def __init__(self,url):
self.station="none"
self.mute=False
self.p=sub.Popen(["mplayer",url], stdin=sub.PIPE,stdout=open("/dev/null"), stderr=open("/dev/null"))
print "Playing..."
def turnoff(self):
self.p.stdin.write("q")
def volup(self):
self.p.stdin.write("*")
def voldown(self):
self.p.stdin.write("/")
def muteit(self):
self.p.stdin.write("m")
if self.mute:
self.mute=False
else:
self.mute=True
#ripper class - contains streamripper subprocess and basic interaction routines
class ripper:
def __init__(self,url):
self.station="none"
stream=re.compile("stream:")
server=re.compile("server")
self.r=sub.Popen(["streamripper",url], stdin=sub.PIPE,stdout=sub.PIPE, stderr=sub.PIPE,bufsize=1)
print "getting Stream..."
#5 connection attempts
n=5
while n>0:
y=repr(self.r.stdout.read(150))
if stream.search(y)!=None:
a=stream.search(y).end()+1
if server.search(y[a:]) != None:
b=a+server.search(y[a:]).start()-2
self.station=y[a:b].strip()
break
n=n-1
time.sleep(1)
if n==0:
raise LookupError("Couldn't get stream")
def getTitle(self):
action=re.compile("ing...\s+\]")
size=re.compile("\[")
y=repr(self.r.stdout.read(150))
if action.search(y)!=None:
a=action.search(y).end()
if size.search(y[a+2:])!=None:
b=a+size.search(y[a+2:]).end()
title=y[a:b]
if "." in title:
title=title.replace(".","-")
title=title+".mp3"
return title
else:
return "none"
def turnoff(self):
os.kill(self.r.pid,9)
class outputthread(Thread):
def __init__(self,url, store):
Thread.__init__(self)
self.input=""
self.url=url
self.store=store
def run(self):
print "Initializing..."
r=ripper(self.url)
p=player(self.url)
station="none"
title="none"
title2="none"
history=""
action="skip"
#create dir to store songs:
if store in os.listdir(".") and os.path.isdir(store):
pass
else:
os.mkdir(store)
#main loop
while True:
if r.station=="Streamripper_rips": # <something's wrong, exit
os.system("clear")
print history
print "Couldn't get station."
r.turnoff()
p.turnoff()
#stream events
if r.station!=station:
if r.station.replace(".","-")==station:
pass
#print "Station: "+r.station"
else:
station=r.station
history="Station: "+station+"\n\n"
try:
title2=r.getTitle()[1:]
except TypeError:
title2="none"
if station in os.listdir("."):
pass
else:
if station.replace(".","-") in os.listdir("."):
station=station.replace(".","-")
#new song!
if title2!=title:
time.sleep(1)
if action=="keep":
if title in os.listdir(station):
#todo: overwriting existing files
sub.Popen(["cp",station+"/"+title,store])
time.sleep(0.5)
os.remove(station+"/"+title)
elif title in os.listdir(station+"/incomplete"):
sub.Popen(["cp",station+"/incomplete/"+title,store])
time.sleep(0.5)
os.remove(station+"/incomplete/"+title)
else:
if title!="none":
title="[fnf] "+title #file not found :-/
else:
if title!="none":
if title in os.listdir(station):
os.remove(station+"/"+title)
elif title in os.listdir(station+"/incomplete"):
os.remove(station+"/incomplete/"+title)
else:
if title!="none":
title="[fnf] "+title #file not found :-/
#output + history
os.system("clear")
if title!="none":
history=history+"\n"+"["+action+"] "+title
action="skip"
print history
print "["+action+"] "+title2
#copy and stuff
title=title2
#input events
if self.input=="k":
action="keep"
os.system("clear")
print history
print "["+action+"] "+title2
self.input=""
elif self.input=="s":
action="skip"
os.system("clear")
print history
print "["+action+"] "+title2
self.input=""
elif self.input=="*":
p.volup()
self.input=""
elif self.input=="/":
p.voldown()
self.input=""
elif self.input=="m":
p.muteit()
os.system("clear")
if p.mute:
print history
print "["+action+"] "+title2
print "MUTE"
else:
print history
print "["+action+"] "+title2
self.input=""
elif self.input=="q":
os.system("clear")
print history
print "Bye."
r.turnoff()
p.turnoff()
#delete dirs
try:
shutil.rmtree(station)
except OSError:
print "Couldn't remove "+station+" please delete it manually."
break
time.sleep(0.01)
def input_loop(t): #from http://pyfaq.infogami.com/how-do-i-get-a-single-keypress-at-a-time
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(1)
#print "Got char:", `c`
t.input=c
if c=="q":
break
except IOError: pass
time.sleep(0.01)
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
return c
#start
if len(sys.argv)!=2:
print "Usage: ripper.py URL where URL is that kind of URL mplayer and streamripper can handle."
else:
#default storage dir: "keep"
store="keep"
c=outputthread(sys.argv[1],store)
c.start()
input_loop(c)
Warning: If you kill the script in any unorthodox way, you will have to locate and kill the mplayer- and streamripper-processes manually.
Offline