rendered paste body#!/usr/bin/env python
# PyRadio: Curses based Internet Radio Player
# http://www.coderholic.com/pyradio
# Ben Dowling - 2008
import os
import sys
import curses
import thread
import subprocess
stations = [
("S K Y . F M - Roots Reggae", "http://scfire-ntc-aa01.stream.aol.com:80/stream/1017"),
("Radio Caraibes", "http://91.121.142.31:8000"),
("Tim's Radio - flamencoradio.net", "http://flamencoradio.net:8100"),
("Afterhours.FM (Trance/Progressive)", "http://at2.ah.fm:80"),
("BBC Radio 1", "mms://wmlive-acl.bbc.co.uk/wms/radio1/radio1_nb_e1s1"),
("Digitally Imported: Chillout", "http://di.fm/mp3/chillout.pls"),
("Digitally Imported: Trance", "http://di.fm/mp3/trance.pls"),
("Digitally Imported: Classic Techno", "http://di.fm/mp3/classictechno.pls"),
("Electronic Culture", "http://www.shouted.fm/tunein/electro-dsl.m3u"),
("Frequence 3 (Pop)", "http://streams.frequence3.net/hd-mp3.m3u"),
("Mostly Classical", "http://www.sky.fm/mp3/classical.pls"),
("Proton Radio (House/Dance)", "http://www.protonradio.com:8000"),
("Ragga Kings", "http://www.raggakings.net/listen.m3u"),
("Secret Agent (Downtempo)", "http://somafm.com/secretagent.pls"),
("Slay Radio (C64 Remix)", "http://sc.slayradio.org:8000/listen.pls"),
("Smooth FM (Jazz)", "http://66.55.143.195:9028"),
("SomaFM: Groove Salad", "http://somafm.com/startstream=groovesalad.pls"),
("SomaFM: Beat Blender", "http://somafm.com/startstream=beatblender.pls"),
("SomaFM: Cliq Hop", "http://somafm.com/startstream=cliqhop.pls"),
("SomaFM: Sonic Universe", "http://somafm.com/startstream=sonicuniverse.pls"),
("SomaFM: Tags Trance Trip", "http://somafm.com/tagstrance.pls"),
("Virgin Radio (Pop)", "http://www.smgradio.com/core/audio/mp3/live.pls?service=vrbb"),
]
class Log(object):
""" Log class that outputs text to a curses screen """
def __init__(self, cursesScreen):
self.cursesScreen = cursesScreen
self.width = cursesScreen.getmaxyx()[1] - 5
def write(self, str):
self.cursesScreen.erase()
self.cursesScreen.addstr(0, 1, str[0: self.width].replace("\r", "").replace("\n", ""))
self.cursesScreen.refresh()
def readline(self):
pass
class Player(object):
""" Media player class. Playing is handled by mplayer """
process = None
def __init__(self, outputStream):
self.outputStream = outputStream
def __del__(self):
self.close()
def updateStatus(self):
try:
input = self.process.stdout.readline()
while(input != ''):
self.outputStream.write(input)
input = self.process.stdout.readline()
except: pass
def play(self, url):
self.close()
self.process = subprocess.Popen(["mplayer", "-quiet", url], shell=False, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
thread.start_new_thread(self.updateStatus, ())
def sendCommand(self, command):
if(self.process != None):
try:
self.process.stdin.write(command)
except: pass
def mute(self):
self.sendCommand("m")
def pause(self):
self.sendCommand("p")
def close(self):
self.sendCommand("q")
if self.process != None:
os.kill(self.process.pid, 15)
self.process.wait()
self.process = None
class PyRadio(object):
startPos = 0
selection = 0
def setup(self, stdscr):
self.stdscr = stdscr
try:
curses.curs_set(0)
except:
pass
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_BLACK)
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_MAGENTA)
curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_GREEN)
self.maxY, self.maxX = stdscr.getmaxyx()
self.headWin = curses.newwin(1, self.maxX, 0, 0)
self.bodyWin = curses.newwin(self.maxY-2, self.maxX, 1, 0)
self.footerWin = curses.newwin(1, self.maxX, self.maxY-1, 0)
self.initHead()
self.initBody()
self.initFooter()
self.log = Log(self.footerWin)
self.player = Player(self.log)
curses.doupdate()
self.run()
def initHead(self):
info = " PyRadio 0.1 "
self.headWin.addstr(0, 0, info, curses.color_pair(4))
rightStr = "www.coderholic.com/pyradio"
self.headWin.addstr(0, self.maxX - len(rightStr) - 1, rightStr, curses.color_pair(2))
self.headWin.bkgd(' ', curses.color_pair(7))
self.headWin.noutrefresh()
def initBody(self):
""" Initializes the body/story window """
self.bodyWin.timeout(100)
self.bodyWin.keypad(1)
self.bodyMaxY, self.bodyMaxX = self.bodyWin.getmaxyx()
self.bodyWin.noutrefresh()
self.refreshBody()
def initFooter(self):
""" Initializes the body/story window """
self.footerWin.bkgd(' ', curses.color_pair(7))
self.footerWin.noutrefresh()
def refreshBody(self):
self.bodyWin.erase()
self.bodyWin.box()
self.bodyWin.move(1, 1)
maxDisplay = self.bodyMaxY - 1
for idx in range(maxDisplay - 1):
if(idx > maxDisplay): break
try:
station = stations[idx + self.startPos]
col = curses.color_pair(5)
if idx + self.startPos == self.selection:
col = curses.color_pair(6)
self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col)
self.bodyWin.addstr(idx + 1, 1, station[0], col)
except IndexError:
break
def run(self):
while True:
try:
c = self.bodyWin.getch()
ret = self.keypress(c)
if (ret == -1): return
except KeyboardInterrupt:
break
def keypress(self, char):
if char == curses.KEY_EXIT or char == ord('q'):
self.player.close()
return -1
elif char in (curses.KEY_ENTER, ord('\n'), ord('\r')):
url = stations[self.selection][1]
self.player.play(url)
return
elif char == curses.KEY_DOWN or char== ord('j'):
if self.selection < len(stations) - 1:
if self.selection - self.startPos > (self.bodyMaxY - 4):
self.startPos += 1
self.selection += 1
self.refreshBody()
return
elif char == curses.KEY_UP or char == ord('k'):
# Scroll stories up by one
if self.selection > 0:
self.selection -= 1
if self.selection < self.startPos:
self.startPos -= 1
self.refreshBody()
return
elif char == ord('m'):
self.player.mute()
return
if __name__ == "__main__":
pyRadio = PyRadio()
curses.wrapper(pyRadio.setup)