Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Python source code: Streaming radio player with room customization
from evdev import InputDevice,ecodes,KeyEvent
import subprocess32 as subp
import select
import re
import sys
import time
import logging
import os.path
import argparse as args
cmdline = args.ArgumentParser(description='Streaming Radio Player',epilog='KE4ZNU - http://softsolder.com')
cmdline.add_argument('Loc',help='Location: BR1 BR2 ...',default='any',nargs='?')
args = cmdline.parse_args()
Media = {'KEY_KP7' : ['Classical',False,['mplayer','--quiet','-playlist','http://stream2137.init7.net/listen.pls']],
'KEY_KP8' : ['Jazz',False,['mplayer','--quiet','-playlist','http://stream2138.init7.net/listen.pls']],
'KEY_KP9' : ['WMHT',False,['mplayer','--quiet','http://wmht.streamguys1.com/wmht1']],
'KEY_KP4' : ['Classic 1000',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/1000classicalhits.m3u']],
'KEY_KP5' : ['DCNY 911',False,['mplayer','--quiet','-playlist','http://www.broadcastify.com/scripts/playlists/1/12305/-5857889408.m3u']],
'KEY_KP6' : ['WAMC',False,['mplayer','--quiet','http://pubint.ic.llnwd.net/stream/pubint_wamc']],
'KEY_KP1' : ['60s',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/all60sallthetime-keepfreemusiccom.m3u']],
'KEY_KP2' : ['50-70s',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/golden-50-70s-hits.m3u']],
'KEY_KP3' : ['Soft Rock',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/softrockradio.m3u']],
'KEY_KP0' : ['Zen',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/zen-for-you.m3u']],
'KEY_KPDOT' : ['Ambient',False,['mplayer','--quiet','http://185.32.125.42:7331/maschinengeist.org.mp3']]
}
Controls = {'KEY_KPSLASH' : '//////',
'KEY_KPASTERISK' : '******',
'KEY_KPENTER' : ' ',
'KEY_KPMINUS' : '<',
'KEY_KPPLUS' : '>',
'KEY_VOLUMEUP' : '*',
'KEY_VOLUMEDOWN' : '/'
}
MuteStrings = ["TargetSpot","[Unknown]","Advert:","+++","---","SRR","Srr","ZEN FOR","Intro of","Jingle - ","*bumper*"]
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 defaults based on where we are
CurrentKC = 'KEY_KP7'
MuteDelay = 8.5 # delay before non-music track; varies with buffering
UnMuteDelay = 7.5 # delay after non-music track
MixerVol = '15' # mixer gain
Location = vars(args)['Loc'].upper()
print 'Player location: ',Location
logging.info('Player setup for: ' + Location)
if Location == 'BR1':
CurrentKC = 'KEY_KPDOT'
MixerVol = '10'
elif Location == 'BR2':
MuteDelay = 6.0
UnMuteDelay = 8.0
MixerVol = '5'
# 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)
# if volume control knob exists, then set up its events
VolumeDevice = '/dev/input/volume'
vp = select.poll()
if os.path.exists(VolumeDevice):
v = InputDevice(VolumeDevice)
v.grab()
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,MixerVol])
# 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 probably contains the track name
NeedMute = False # assume a listenable track
trkhit = re.search(r"StreamTitle='(.*)'",ln) # extract title if possible
if trkhit: # regex returned valid result?
TrackName = trkhit.group(1) # get string between two quotes
else:
print ' ... regex failed for line: ', ln
logging.info('Regex failed for line: [' + ln + ']')
TrackName = "Invalid StreamTitle format"
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
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: []',text,']'
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
time.sleep(10)
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: ' + str(e))
try:
pp.unregister(p.stdout.fileno())
except Exception as e:
print 'Trouble unregistering stdout: ',e
logging.info('Cannot unregister stdout: ' + str(e))
time.sleep(2)
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
logging.info('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: ' + str(e))
try:
pp.unregister(p.stdout.fileno())
except Exception as e:
print 'Trouble unregistering stdout: ',e
logging.info('Cannot unregister stdout: ' + str(e))
p.terminate() # p.kill()
p.wait()
time.sleep(2)
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:
pp.unregister(p.stdout.fileno())
except Exception as e:
print 'Trouble unregistering stdout: ',e
logging.info('Cannot unregister stdout: ' + str(e))
try:
p.communicate(input='q')
except Exception as e:
print 'Perhaps mplayer already died? ',e
logging.info('Already died? ' + str(e))
try:
p.terminate() # p.kill()
p.wait()
except Exception as e:
print 'Trouble with terminate or wait: ',e
logging.info('Trouble with terminate or wait: ' + str(e))
time.sleep(2)
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()
@ednisley

This comment has been minimized.

Copy link
Owner Author

ednisley commented May 6, 2017

More details on my blog at http://wp.me/poZKh-6qi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.