Analog-Input
190 lines (161 with data), 6.1 kB
#!/usr/bin/env python
from __future__ import print_function
# Reference script for reading an audio input and sending to Songcast,
# with an appropriate interface to be controlled by upmpdcli.
#
# The script can also be executed from the command line for
# testing. No need for parameters, but you should set the device name
# at least (and maybe the mixer scripts), see further down.
#
# The general idea is that upmpdcli will create an Openhome Source for
# each script it finds inside a designated directory.
#
# By default, the directory is '/usr/share/upmpdcli/src_scripts', but
# it can be changed by setting the "ohsrc_scripts_dir" configuration
# variable inside /etc/upmpdcli.conf
#
# Entries inside the directory will typically be created as symbolic
# links to this file, which is installed as /usr/share/upmpdcli/Analog-Input
#
# The links must be named like SourceType-SourceName, where SourceType
# MUST BE one of 'Analog', 'Digital', or 'Hdmi', (which are all the
# same, and for display purpose only), and you can choose 'SourceName'
# as you wish, but it should contain no space characters.
#
# The Source will appear with type Analog, Digital or Hdmi and name
# SourceName in an OpenHome Source select dialog (e.g from upplay).
#
# If a file named device-SourceName exists in the same directory as
# the link, we read the device name from it (the contents should be
# a single line with the device name). Else, the device name is
# 'default', which has little chance to work
#
# If a file named prescript-SourceName exists in the same directory,
# it must be executable, and we try to execute it before
# activating. We renounce if it fails. This is meant for mixer
# commands to set up the device.
# ! With upmpdcli release 1.1.0, the prescript MUST NOT write to
# stdout. redirect its output to stderr or /dev/null. The current
# version of Analog-Input has corrected this constraint (by reading
# an discarding the output).
#
# If a file named postcript-SourceName exists in the same directory,
# it must be executable, and we try to execute it before terminating
# This is meant for mixer commands to reset the device.
#
import time
import subprocess
import os
import sys
import uuid
import getopt
import signal
import socket
############# Defaults
# Capture device. Use arecord -L to list possible values.
# Set this in device-mySourceName
device = '''default'''
# Songcast Sender program. This reads from stdin and sends to
# Songcast. It comes with the sc2mpd package (see the upmpdcli web
# site)
uxsender = "mpd2sc"
# Upmpdcli friendly-name, the actual value comes from a parameter when
# executed from upmpdcli (as normal). Used to compute a Uuid in
# conjunction with the node name and script name
upmpdcli_fname = "UpMpd"
def usage(f):
print("Usage: %s [-h] [-f friendlyname]" % sys.argv[0], file=f)
sys.exit(1)
args = sys.argv[1:]
opts, args = getopt.getopt(args, "hup:f:")
for opt, arg in opts:
if opt in ['-h']:
usage(sys.stdout)
elif opt in ['-f']:
upmpdcli_fname = arg
else:
print("unknown option %s\n"%opt, file=sys.stderr)
usage(sys.stderr)
# Script name should be something like type-name. We use the name part
# to look for data or aux scripts
scriptdir = os.path.dirname(sys.argv[0])
scriptname = os.path.basename(sys.argv[0])
lst = scriptname.split("-")
prescript = None
postscript = None
if len(lst) == 2:
srcname = lst[1]
path = os.path.join(scriptdir, 'device-' + srcname)
if os.path.exists(path):
device = open(path).read().strip()
path = os.path.join(scriptdir, 'prescript-' + srcname)
if os.path.exists(path):
prescript = path
path = os.path.join(scriptdir, 'postscript-' + srcname)
if os.path.exists(path):
postscript = path
print("device [%s] prescript [%s] postscript [%s] " %
(device, prescript, postscript), file=sys.stderr)
# UDN and name for the Sender UPnP device. We use a hash of the
# friendly name and host name
sender_udn = uuid.uuid5(uuid.NAMESPACE_DNS,
socket.gethostname() + upmpdcli_fname + sys.argv[0]).urn
sender_name = "%s UxSender" % upmpdcli_fname
recordproc = None
senderproc = None
def cleanup(xval):
# Clean up
try:
recordproc.terminate()
except:
pass
try:
senderproc.terminate()
except:
pass
# Execute post-script if it is set
if postscript:
try:
subprocess.check_call(postscript)
except:
pass
sys.exit(xval)
def sighandler(signum, frame):
cleanup(1)
signal.signal(signal.SIGINT, sighandler)
signal.signal(signal.SIGTERM, sighandler)
# Execute pre-script if it is set. We read and discard its stdout (else if
# would confuse upmpdcli which expects informations on the first output line).
if prescript:
subprocess.check_output(prescript)
# -f S16_LE -c 2 -r 44100
try:
recordproc = subprocess.Popen(('arecord', '-D', device,
'-f', 'cd', '-t', 'raw', '-'),
stdout=subprocess.PIPE)
except Exception as err:
print("Can't start arecord: %s" % (err), file=sys.stderr)
cleanup(1)
# Start the Sender
try:
senderproc = subprocess.Popen([uxsender, "-f", "stdin",
"-A", "44100:16:2:1",
"-u", sender_udn, "-n", sender_name],
stdin=recordproc.stdout,
stdout=subprocess.PIPE,
bufsize = -1)
except Exception as err:
print("Can't start %s: %s"%(uxsender, err), file=sys.stderr)
cleanup(1)
# Get the Uri and Metadata values from the sender. These get written to stdout
urimeta = senderproc.stdout.readline()
# Tell the world we're set. upmpdcli expects this format exactly
print("Ok %d %s" % (0, urimeta))
sys.stdout.flush()
# Wait process.
while True:
if recordproc.poll() is not None or senderproc.poll() is not None:
break
time.sleep(0.5)
time.sleep(1)
cleanup(0)