diff --git a/pyradio b/pyradio index f47ec99a..5448cb84 100755 --- a/pyradio +++ b/pyradio @@ -14,301 +14,311 @@ import logging def rel(path): return os.path.join(os.path.abspath(os.path.dirname(__file__)), path) -logging.basicConfig(filename=rel('log.txt'),level=logging.INFO) +logging.basicConfig(filename=rel('log.txt'), level=logging.INFO) class Log(object): - """ Log class that outputs text to a curses screen """ - - msg = None - cursesScreen = None - - def __init__(self): - pass - - def setScreen(self, cursesScreen): - self.cursesScreen = cursesScreen - self.width = cursesScreen.getmaxyx()[1] - 5 - - # Redisplay the last message - if self.msg: - self.write(self.msg) - - def write(self, msg): - self.msg = msg.strip() - - if self.cursesScreen: - self.cursesScreen.erase() - self.cursesScreen.addstr(0, 1, self.msg[0: self.width].replace("\r", "").replace("\n", "")) - self.cursesScreen.refresh() - - def readline(self): - pass + """ Log class that outputs text to a curses screen """ + + msg = None + cursesScreen = None + + def __init__(self): + pass + + def setScreen(self, cursesScreen): + self.cursesScreen = cursesScreen + self.width = cursesScreen.getmaxyx()[1] - 5 + + # Redisplay the last message + if self.msg: + self.write(self.msg) + + def write(self, msg): + self.msg = msg.strip() + + if self.cursesScreen: + self.cursesScreen.erase() + self.cursesScreen.addstr(0, 1, self.msg[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 - - def volumeUp(self): - self.sendCommand("*") - - def volumeDown(self): - self.sendCommand("/") + """ 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: + user_input = self.process.stdout.readline() + while(user_input != ''): + self.outputStream.write(user_input) + user_input = self.process.stdout.readline() + except: + pass + + def play(self, stream_url): + self.close() + if stream_url[-3:] in ['m3u', 'pls']: + opts = ["mplayer", "-quiet", "-playlist", stream_url] + else: + opts = ["mplayer", "-quiet", stream_url] + self.process = subprocess.Popen(opts, 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 + + def volumeUp(self): + self.sendCommand("*") + + def volumeDown(self): + self.sendCommand("/") class PyRadio(object): - startPos = 0 - selection = 0 - playing = -1 - - def __init__(self, stations): - self.stations = stations - - 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) - curses.init_pair(8, curses.COLOR_MAGENTA, curses.COLOR_BLACK) - curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_GREEN) - - self.log = Log() - self.player = Player(self.log) - - logging.info("Started") - - self.stdscr.nodelay(0) - self.setupAndDrawScreen() - - self.run() - - - def setupAndDrawScreen(self): - self.maxY, self.maxX = self.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.setScreen(self.footerWin) - - #self.stdscr.timeout(100) - self.bodyWin.keypad(1) - - #self.stdscr.noutrefresh() - - curses.doupdate() - - def initHead(self): - info = " PyRadio 0.2 " - 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 = self.stations[idx + self.startPos] - col = curses.color_pair(5) - - if idx + self.startPos == self.selection and self.selection == self.playing: - col = curses.color_pair(9) - self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) - elif idx + self.startPos == self.selection: - col = curses.color_pair(6) - self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) - elif idx + self.startPos == self.playing: - col = curses.color_pair(4) - self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) - self.bodyWin.addstr(idx + 1, 1, station[0], col) - - except IndexError: - break - - self.bodyWin.refresh() - - def run(self): - while True: - try: - c = self.bodyWin.getch() - logging.info(c) - ret = self.keypress(c) - if (ret == -1): return - except KeyboardInterrupt: - break - - def setStation(self, number): - """ Select the given station number """ - number = max(0, number) - number = min(number, len(self.stations) - 1) - - self.selection = number - - maxDisplayedItems = self.bodyMaxY - 2 - - if self.selection - self.startPos >= maxDisplayedItems: - self.startPos = self.selection - maxDisplayedItems + 1 - elif self.selection < self.startPos: - self.startPos = self.selection - - def playSelection(self): - self.playing = self.selection - name = self.stations[self.selection][0] - url = self.stations[self.selection][1].strip() - self.log.write('Playing ' + name) - self.player.play(url) - - def keypress(self, char): - # Number of stations to change with the page up/down keys - pageChange = 5 - # Maximum number of stations that fit on the screen at once - maxDisplayedItems = self.bodyMaxY - 2 - - if char == curses.KEY_EXIT or char == ord('q'): - self.player.close() - return -1 - elif char in (curses.KEY_ENTER, ord('\n'), ord('\r')): - self.playSelection() - self.refreshBody() - return - elif char == curses.KEY_DOWN or char== ord('j'): - self.setStation(self.selection + 1) - self.refreshBody() - return - elif char == curses.KEY_UP or char == ord('k'): - self.setStation(self.selection - 1) - self.refreshBody() - return - elif char == ord('+'): - self.player.volumeUp() - return - elif char == ord('-'): - self.player.volumeDown() - return - elif char == curses.KEY_PPAGE: - self.setStation(self.selection - pageChange) - self.refreshBody() - return - elif char == curses.KEY_NPAGE: - self.setStation(self.selection + pageChange) - self.refreshBody() - return - elif char == ord('m'): - self.player.mute() - return - elif char == ord('r'): - # Pick a random radio station - self.setStation(random.randint(0, len(self.stations))) - self.playSelection() - self.refreshBody() - elif char == ord('#') or char == curses.KEY_RESIZE: - self.setupAndDrawScreen() - #self.refreshBody() + startPos = 0 + selection = 0 + playing = -1 + + def __init__(self, stations): + self.stations = stations + + 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) + curses.init_pair(8, curses.COLOR_MAGENTA, curses.COLOR_BLACK) + curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_GREEN) + + self.log = Log() + self.player = Player(self.log) + + logging.info("Started") + + self.stdscr.nodelay(0) + self.setupAndDrawScreen() + + self.run() + + + def setupAndDrawScreen(self): + self.maxY, self.maxX = self.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.setScreen(self.footerWin) + + #self.stdscr.timeout(100) + self.bodyWin.keypad(1) + + #self.stdscr.noutrefresh() + + curses.doupdate() + + def initHead(self): + info = " PyRadio 0.2 " + 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 = self.stations[idx + self.startPos] + col = curses.color_pair(5) + + if idx + self.startPos == self.selection and self.selection == self.playing: + col = curses.color_pair(9) + self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) + elif idx + self.startPos == self.selection: + col = curses.color_pair(6) + self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) + elif idx + self.startPos == self.playing: + col = curses.color_pair(4) + self.bodyWin.hline(idx + 1, 1, ' ', self.bodyMaxX - 2, col) + self.bodyWin.addstr(idx + 1, 1, station[0], col) + + except IndexError: + break + + self.bodyWin.refresh() + + def run(self): + while True: + try: + c = self.bodyWin.getch() + logging.info(c) + ret = self.keypress(c) + if (ret == -1): + return + except KeyboardInterrupt: + break + + def setStation(self, number): + """ Select the given station number """ + number = max(0, number) + number = min(number, len(self.stations) - 1) + + self.selection = number + + maxDisplayedItems = self.bodyMaxY - 2 + + if self.selection - self.startPos >= maxDisplayedItems: + self.startPos = self.selection - maxDisplayedItems + 1 + elif self.selection < self.startPos: + self.startPos = self.selection + + def playSelection(self): + self.playing = self.selection + name = self.stations[self.selection][0] + stream_url = self.stations[self.selection][1].strip() + self.log.write('Playing ' + name) + self.player.play(stream_url) + + def keypress(self, char): + # Number of stations to change with the page up/down keys + pageChange = 5 + # Maximum number of stations that fit on the screen at once + maxDisplayedItems = self.bodyMaxY - 2 + + if char == curses.KEY_EXIT or char == ord('q'): + self.player.close() + return -1 + elif char in (curses.KEY_ENTER, ord('\n'), ord('\r')): + self.playSelection() + self.refreshBody() + return + elif char == curses.KEY_DOWN or char == ord('j'): + self.setStation(self.selection + 1) + self.refreshBody() + return + elif char == curses.KEY_UP or char == ord('k'): + self.setStation(self.selection - 1) + self.refreshBody() + return + elif char == ord('+'): + self.player.volumeUp() + return + elif char == ord('-'): + self.player.volumeDown() + return + elif char == curses.KEY_PPAGE: + self.setStation(self.selection - pageChange) + self.refreshBody() + return + elif char == curses.KEY_NPAGE: + self.setStation(self.selection + pageChange) + self.refreshBody() + return + elif char == ord('m'): + self.player.mute() + return + elif char == ord('r'): + # Pick a random radio station + self.setStation(random.randint(0, len(self.stations))) + self.playSelection() + self.refreshBody() + elif char == ord('#') or char == curses.KEY_RESIZE: + self.setupAndDrawScreen() + #self.refreshBody() if __name__ == "__main__": - # Default stations list - stations = [ - ("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"), - ("Frequence 3 (Pop)", "http://streams.frequence3.net/hd-mp3.m3u"), - ("Mostly Classical", "http://www.sky.fm/mp3/classical.pls"), - ("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"), - ("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"), - ] - csvFile = os.path.join(os.path.abspath(os.path.dirname(__file__)), "stations.csv") - if len(sys.argv) > 1 and sys.argv[1]: csvFile = sys.argv[1] - - try: - csv = open(csvFile, "r") - stations = [] - for line in csv.readlines(): - line = line.strip() - if not line: continue - try: - (name, url) = line.split(",") - stations.append((name, url)) - except: - print "Error, skipping ", line - except IOError: - print "Could not open stations file '%s'. Using default stations list" % csvFile - - pyRadio = PyRadio(stations) - curses.wrapper(pyRadio.setup) + # Default stations list + stations = [ + ("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"), + ("Frequence 3 (Pop)", "http://streams.frequence3.net/hd-mp3.m3u"), + ("Mostly Classical", "http://www.sky.fm/mp3/classical.pls"), + ("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"), + ("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"), + ] + csvFile = os.path.join(os.path.abspath(os.path.dirname(__file__)), "stations.csv") + if len(sys.argv) > 1 and sys.argv[1]: + csvFile = sys.argv[1] + + try: + csv = open(csvFile, "r") + stations = [] + for line in csv.readlines(): + line = line.strip() + if not line: + continue + try: + (name, url) = line.split(",") + stations.append((name, url)) + except: + print "Error, skipping ", line + except IOError: + print "Could not open stations file '%s'. Using default stations list" % csvFile + + pyRadio = PyRadio(stations) + curses.wrapper(pyRadio.setup)