|
from evdev import InputDevice,ecodes,KeyEvent |
|
import subprocess32 as subp |
|
import select |
|
import re |
|
import sys |
|
import time |
|
import logging |
|
|
|
Media = {'KEY_KP7' : ['Classical',False,['mplayer','–quiet','-playlist','http://stream2137.init7.net/listen.pls'%5D%5D, |
|
'KEY_KP8' : ['Jazz',False,['mplayer','–quiet','-playlist','http://stream2138.init7.net/listen.pls'%5D%5D, |
|
'KEY_KP9' : ['WMHT',False,['mplayer','–quiet','http://live.str3am.com:2070/wmht1'%5D%5D, |
|
'KEY_KP4' : ['Classic 1000',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/1000classicalhits.m3u'%5D%5D, |
|
'KEY_KP5' : ['DCNY 911',False,['mplayer','–quiet','-playlist','http://www.broadcastify.com/scripts/playlists/1/12305/-5857889408.m3u'%5D%5D, |
|
'KEY_KP6' : ['WAMC',False,['mplayer','–quiet','http://pubint.ic.llnwd.net/stream/pubint_wamc'%5D%5D, |
|
'KEY_KP1' : ['60s',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/all60sallthetime-keepfreemusiccom.m3u'%5D%5D, |
|
'KEY_KP2' : ['50-70s',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/golden-50-70s-hits.m3u'%5D%5D, |
|
'KEY_KP3' : ['Soft Rock',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/softrockradio.m3u'%5D%5D, |
|
'KEY_KP0' : ['Zen',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/zen-for-you.m3u'%5D%5D |
|
} |
|
|
|
CurrentKC = 'KEY_KP3' |
|
|
|
Controls = {'KEY_KPSLASH' : '//////', |
|
'KEY_KPASTERISK' : '******', |
|
'KEY_KPENTER' : ' ', |
|
'KEY_KPMINUS' : '<', |
|
'KEY_KPPLUS' : '>', |
|
'KEY_VOLUMEUP' : '*', |
|
'KEY_VOLUMEDOWN' : '/' |
|
} |
|
|
|
MuteStrings = ["TargetSpot","[Unknown]","Advert:","+++","—","SRR","Srr","ZEN FOR"] |
|
|
|
MuteDelay = 8.0 # delay before non-music track; varies with buffering |
|
UnMuteDelay = 7.5 # delay after non-music track |
|
Muted = False # keep track of muted state |
|
|
|
MixerChannel = 'PCM' # which amixer thing to use |
|
|
|
logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s',filename='/tmp/Streamer.log',level=logging.INFO) |
|
logger = logging.getLogger() |
|
|
|
# set up event inputs and polling objects |
|
# This requires some udev magic to create the symlinks |
|
|
|
k = InputDevice('/dev/input/keypad') |
|
k.grab() |
|
|
|
kp = select.poll() |
|
kp.register(k.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
|
|
v = InputDevice('/dev/input/volume') |
|
v.grab() |
|
|
|
vp = select.poll() |
|
vp.register(v.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
|
|
# set up file for output tracing |
|
|
|
lw = open('/tmp/mp.log','w') # mplayer piped output |
|
|
|
# set the mixer output low enough that the initial stream won't wake the dead |
|
|
|
subp.call(['amixer','sset',MixerChannel,'10']) |
|
|
|
# Start the player with the default stream, set up for polling |
|
|
|
print 'Starting mplayer on',Media[CurrentKC][0],' -> ',Media[CurrentKC][-1][-1] |
|
logging.info('Starting mplayer on %s -> %s',Media[CurrentKC][0],Media[CurrentKC][-1][-1]) |
|
p = subp.Popen(Media[CurrentKC][-1], |
|
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT) |
|
|
|
pp = select.poll() # this may be valid for other invocations, but is not pretty |
|
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
|
|
print ' … running' |
|
|
|
#——————– |
|
#— Play the streams |
|
|
|
while True: |
|
|
|
# pluck next line from mplayer and decode it |
|
|
|
if [] != pp.poll(10): |
|
text = p.stdout.readline() |
|
|
|
if 'ICY Info: ' in text: # these lines may contain track names |
|
lw.write(text) |
|
lw.flush() |
|
|
|
trkinfo = text.split(';') # also splits at semicolon embedded in track name |
|
# logging.info('Raw split line: %s', trkinfo) |
|
for ln in trkinfo: |
|
if 'StreamTitle' in ln: # this part contains a track name |
|
NeedMute = False # assume a listenable track |
|
if 2 == ln.count("'"): # exactly two single quotes = probably none embedded in track name |
|
trkhit = re.search(r"StreamTitle='(.*)'",ln) |
|
if trkhit: # true for valid search results |
|
TrackName = trkhit.group(1) # the string between the two quotes |
|
print 'Track name: ', TrackName |
|
logging.info('Track name: [%s]', TrackName) |
|
if Media[CurrentKC][1] and ( (len(TrackName) == 0) or any(m in TrackName for m in MuteStrings) ) : |
|
NeedMute = True |
|
else: |
|
print ' … semicolon in track name: ', ln |
|
logging.info('Semicolon in track name: [' + ln + ']') |
|
else: |
|
print ' … quotes in track name: ', ln |
|
logging.info('Quotes in track name: [' + ln + ']') |
|
if NeedMute: |
|
print ' … muting:', |
|
if Media[CurrentKC][1] and not Muted: |
|
time.sleep(MuteDelay) # brute-force assumption about buffer leadtime |
|
subp.call(['amixer','-q','sset',MixerChannel,'mute']) |
|
Muted = True |
|
print 'done' |
|
logging.info('Track muted') |
|
else: |
|
print ' … unmuting:', |
|
if Muted: |
|
if Media[CurrentKC][1]: |
|
time.sleep(UnMuteDelay) # another brute-force timing assumption |
|
Muted = False |
|
subp.call(['amixer','-q','sset',MixerChannel,'unmute']) |
|
print 'done' |
|
logging.info('Track unmuted') |
|
|
|
elif 'Exiting.' in text: # mplayer just imploded |
|
lw.write(text) |
|
lw.flush() |
|
print 'Got EOF / stream cutoff' |
|
logging.info('EOF or stream cutoff') |
|
print ' … killing dead mplayer' |
|
pp.unregister(p.stdout.fileno()) |
|
p.terminate() # p.kill() |
|
p.wait() |
|
# print ' … flushing pipes' |
|
# lw.truncate(0) |
|
print ' … discarding keys' |
|
while [] != kp.poll(0): |
|
kev = k.read |
|
print ' … restarting mplayer: ',Media[CurrentKC][0] |
|
logging.info('Restarting mplayer') |
|
p = subp.Popen(Media[CurrentKC][-1], |
|
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT) |
|
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
print ' … running' |
|
logging.info(' … running') |
|
|
|
|
|
# accept pending events from volume control knob |
|
|
|
if [] != vp.poll(10): |
|
vev = v.read() |
|
lw.write('Volume') |
|
lw.flush() |
|
for e in vev: |
|
if e.type == ecodes.EV_KEY: |
|
kc = KeyEvent(e).keycode |
|
|
|
# print 'Volume kc: ',kc |
|
|
|
if kc in Controls: |
|
print 'Vol Control: ',kc |
|
try: |
|
p.stdin.write(Controls[kc]) |
|
except Exception as e: |
|
print "Can't send control: ",e |
|
print ' … restarting player: ',Media[CurrentKC][0] |
|
logging.info('Error sending volume, restarting player') |
|
pp.unregister(p.stdout.fileno()) |
|
p = subp.Popen(Media[CurrentKC][-1], |
|
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT) |
|
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
print ' … running' |
|
logging.info(' … running') |
|
|
|
|
|
# accept pending events from keypad |
|
|
|
if [] != kp.poll(10): |
|
kev = k.read() |
|
lw.write("Keypad") |
|
lw.flush() |
|
for e in kev: |
|
if e.type == ecodes.EV_KEY: |
|
kc = KeyEvent(e).keycode |
|
|
|
if kc == 'KEY_NUMLOCK': # discard these, as we don't care |
|
continue |
|
|
|
# print 'Got: ',kc |
|
|
|
if (kc == 'KEY_BACKSPACE') and (KeyEvent(e).keystate == KeyEvent.key_hold): |
|
if True: |
|
print 'Backspace = shutdown!' |
|
p.kill() |
|
logging.shutdown() |
|
q = subp.call(['sudo','shutdown','-P','now']) |
|
q.wait() |
|
time.sleep(5) |
|
print "Oddly, we did not die…" |
|
else: |
|
print 'BS = bail from main!' |
|
logging.shutdown() |
|
sys.exit(0) |
|
break |
|
|
|
if KeyEvent(e).keystate != KeyEvent.key_down: # discard key up & other rubbish |
|
continue |
|
|
|
if kc in Controls: |
|
print 'Control:', kc |
|
try: |
|
p.stdin.write(Controls[kc]) |
|
except Exception as e: |
|
print "Can't send control: ",e |
|
print ' … restarting player: ',Media[CurrentKC][0] |
|
logging.info('Error sending controls, restarting player') |
|
pp.unregister(p.stdout.fileno()) |
|
p.terminate() # p.kill() |
|
p.wait() |
|
p = subp.Popen(Media[CurrentKC][-1], |
|
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT) |
|
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
print ' … running' |
|
logging.info(' … running') |
|
|
|
if kc in Media: |
|
print 'Switching stream to ',Media[kc][0],' -> ',Media[kc][-1][-1] |
|
logging.info('Switching stream: ' + Media[kc][0] + ' -> ' + Media[kc][-1][-1]) |
|
CurrentKC = kc |
|
print ' … halting player' |
|
try: |
|
p.communicate(input='q') |
|
except Exception as e: |
|
print 'Perhaps mplayer died?',e |
|
print ' … killing it for sure' |
|
pp.unregister(p.stdout.fileno()) |
|
p.terminate() # p.kill() |
|
p.wait() |
|
# print ' … flushing pipes' |
|
# lw.truncate(0) |
|
print ' … restarting player: ',Media[CurrentKC][0] |
|
p = subp.Popen(Media[CurrentKC][-1], |
|
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT) |
|
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR) |
|
print ' … running' |
|
logging.info(' … running') |
|
|
|
print 'Out of loop!' |
|
logging.shutdown() |