Parent: [1d7bea] (diff)

Download this file

Analog-Input    194 lines (164 with data), 6.2 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"

externalvolume = False

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:e")
for opt, arg in opts:
    if opt in ['-h']:
        usage(sys.stdout)
    elif opt in ['-f']:
        upmpdcli_fname = arg
    elif opt in ['-e']:
        externalvolume = True    
    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)