You are not logged in.

#1 2008-08-18 18:11:52

Zoranthus
Member
From: muc
Registered: 2006-11-22
Posts: 166

Songkeeper - keep or skip livestream songs

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. big_smile

Offline

Board footer

Powered by FluxBB