Raspberry Pi Streaming Radio Player: Command Line Parsing

Some experience suggested different default stations & volume settings for the streamers in various rooms, so the Python code now parses its command line to determine how to configure itself:

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()

I should definitely pick a different variable name to avoid the obvious clash.

With that in hand, the customization takes very effort:

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'

The Location = vars() idiom returns a dictionary of all the variables and their values, of which there’s only one at the moment. The rest of the line extracts the value and normalizes it to uppercase.

Now we can poke the button and get appropriate music without having to think very hard.

Life is good!

The Python source code, which remains in dire need of refactoring, as a GitHub Gist:

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()
view raw Streamer.py hosted with ❤ by GitHub