SSML for TTS

This commit is contained in:
Alex 2020-02-09 01:54:07 +01:00
parent 382a3d74b4
commit 7b85b6dce4

View File

@ -50,6 +50,8 @@
# 2020-01-03: v0.15a introduce some proper "get_volume" # 2020-01-03: v0.15a introduce some proper "get_volume"
# 2020-01-08: v0.15b cleaned merge errors # 2020-01-08: v0.15b cleaned merge errors
# 2020-02-03: v0.15c SPEAKVOL of 0 leaves the volume setting untouched # 2020-02-03: v0.15c SPEAKVOL of 0 leaves the volume setting untouched
# 2020-02-09: v0.16 TTS to Multiroom groups via USE_ANNOUNCEMENT_FOR_SPEAK + SSML for TTS
# (!!! requires Announcement feature to be enabled in each device !!!)
# #
### ###
# #
@ -99,7 +101,7 @@ SET_OATHTOOL='/usr/bin/oathtool'
SET_TMP="/tmp" SET_TMP="/tmp"
# Volume for speak commands (a SPEAKVOL of 0 leaves the volume settings untouched) # Volume for speak commands (a SPEAKVOL of 0 leaves the volume settings untouched)
SET_SPEAKVOL="30" SET_SPEAKVOL="0"
# if no current playing volume can be determined, fall back to normal volume # if no current playing volume can be determined, fall back to normal volume
SET_NORMALVOL="10" SET_NORMALVOL="10"
@ -111,6 +113,10 @@ SET_DEVICEVOLNAME="EchoDot2ndGen Echo1stGen"
SET_DEVICEVOLSPEAK="100 30" SET_DEVICEVOLSPEAK="100 30"
SET_DEVICEVOLNORMAL="100 20" SET_DEVICEVOLNORMAL="100 20"
# Announcements can be made to multiple devices, while regular SPEAK cannot
# but the notification feature has to be turned on for those devices
SET_USE_ANNOUNCEMENT_FOR_SPEAK="0"
########################################### ###########################################
# nothing to configure below here # nothing to configure below here
# #
@ -134,6 +140,7 @@ VOLMAXAGE=${VOLMAXAGE:-$SET_VOLMAXAGE}
DEVICEVOLNAME=${DEVICEVOLNAME:-$SET_DEVICEVOLNAME} DEVICEVOLNAME=${DEVICEVOLNAME:-$SET_DEVICEVOLNAME}
DEVICEVOLSPEAK=${DEVICEVOLSPEAK:-$SET_DEVICEVOLSPEAK} DEVICEVOLSPEAK=${DEVICEVOLSPEAK:-$SET_DEVICEVOLSPEAK}
DEVICEVOLNORMAL=${DEVICEVOLNORMAL:-$SET_DEVICEVOLNORMAL} DEVICEVOLNORMAL=${DEVICEVOLNORMAL:-$SET_DEVICEVOLNORMAL}
USE_ANNOUNCEMENT_FOR_SPEAK=${USE_ANNOUNCEMENT_FOR_SPEAK:-$SET_USE_ANNOUNCEMENT_FOR_SPEAK}
COOKIE="${TMP}/.alexa.cookie" COOKIE="${TMP}/.alexa.cookie"
DEVLIST="${TMP}/.alexa.devicelist.json" DEVLIST="${TMP}/.alexa.devicelist.json"
@ -198,7 +205,7 @@ usage()
while [ "$#" -gt 0 ] ; do while [ "$#" -gt 0 ] ; do
case "$1" in case "$1" in
--version) --version)
echo "v0.15c" echo "v0.16"
exit 0 exit 0
;; ;;
-d) -d)
@ -393,14 +400,22 @@ case "$COMMAND" in
fi fi
;; ;;
speak:*) speak:*)
TTS=$(echo ${COMMAND##*:} | sed -r 's/["\\]/ /g') TTS=$(echo ${COMMAND##speak:} | sed -r s/\"/\'/g)
TTS=',\"textToSpeak\":\"'${TTS}'\"' if [ $USE_ANNOUNCEMENT_FOR_SPEAK -gt 0 ] ; then
SEQUENCECMD='Alexa.Speak' if echo $TTS | grep -q -v -E '^<speak>.*<\/speak>$' ; then
SEQUENCEVAL=$TTS # always sending SSML, since this skips the announcement "bing"
TTS="<speak>${TTS}</speak>"
fi
SEQUENCECMD='AlexaAnnouncement'
else
TTS=',\"textToSpeak\":\"'${TTS}'\"'
SEQUENCECMD='Alexa.Speak'
SEQUENCEVAL=$TTS
fi
;; ;;
automation:*) automation:*)
SEQUENCECMD='automation' SEQUENCECMD='automation'
UTTERANCE=$(echo ${COMMAND##*:} | sed -r 's/["\\]/ /g') UTTERANCE=$(echo ${COMMAND##automation:} | sed -r 's/["\\]/ /g')
;; ;;
weather) weather)
SEQUENCECMD='Alexa.Weather.Play' SEQUENCECMD='Alexa.Weather.Play'
@ -619,12 +634,41 @@ if [ -n "${SEQUENCECMD}" ] ; then
ALEXACMD='{"behaviorId":"'${AUTOMATION}'","sequenceJson":"'${SEQUENCE}'","status":"ENABLED"}' ALEXACMD='{"behaviorId":"'${AUTOMATION}'","sequenceJson":"'${SEQUENCE}'","status":"ENABLED"}'
else else
if echo $COMMAND | grep -q -E "weather|traffic|flashbriefing|goodmorning|singasong|tellstory|speak" ; then # SequenceCommands are generally not supported on WHA devices
if echo $COMMAND | grep -q -E "weather|traffic|flashbriefing|goodmorning|singasong|tellstory" ; then
if [ "${DEVICEFAMILY}" = "WHA" ] ; then if [ "${DEVICEFAMILY}" = "WHA" ] ; then
echo "Skipping unsupported command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}" echo "Skipping unsupported command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}"
return return
fi fi
fi fi
# some commands support an array of devices - try to find the clusterMembers if a WHA device was supplied
if echo $COMMAND | grep -q -E "speak" ; then
MEMBER='{\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"deviceTypeId\":\"'${DEVICETYPE}'\"}'
if [ "${DEVICEFAMILY}" = "WHA" ] ; then
if [ $USE_ANNOUNCEMENT_FOR_SPEAK -gt 0 ] ; then
MEMBER=""
MEMBERDEVICES=$(jq --arg device "${DEVICE}" -r '.devices[] | select(.accountName == $device) | .clusterMembers[]' ${DEVLIST})
for MEMBERSERIAL in $MEMBERDEVICES ; do
MEMBERTYPE=$(jq --arg device "${MEMBERSERIAL}" -r '.devices[] | select(.serialNumber == $device) | .deviceType' ${DEVLIST})
if [ -n "${MEMBER}" ] ; then
MEMBER='{\"deviceSerialNumber\":\"'${MEMBERSERIAL}'\",\"deviceTypeId\":\"'${MEMBERTYPE}'\"},'${MEMBER}
else
MEMBER='{\"deviceSerialNumber\":\"'${MEMBERSERIAL}'\",\"deviceTypeId\":\"'${MEMBERTYPE}'\"}'
fi
done
if [ -z "${MEMBER}" ] ; then
echo "No clusterMembers found for command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}"
return
fi
else
echo "Speak to Multiroom is only possible when USE_ANNOUNCEMENT_FOR_SPEAK is set and notifications on the devices are enabled"
echo "Skipping unsupported command: ${COMMAND} on dev:${DEVICE} type:${DEVICETYPE} serial:${DEVICESERIALNUMBER} family:${DEVICEFAMILY}"
return
fi
fi
fi
# the speak command is treated differently if $SPEAKVOL is > 0 # the speak command is treated differently if $SPEAKVOL is > 0
if [ -n "${TTS}" -a $SPEAKVOL -gt 0 ] ; then if [ -n "${TTS}" -a $SPEAKVOL -gt 0 ] ; then
SVOL=$SPEAKVOL SVOL=$SPEAKVOL
@ -668,9 +712,17 @@ if [ -n "${SEQUENCECMD}" ] ; then
fi fi
fi fi
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${SVOL}'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\"'${TTS}'}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${VOL}'\"}}]}}","status":"ENABLED"}' if [ $USE_ANNOUNCEMENT_FOR_SPEAK -gt 0 ] ; then
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${SVOL}'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"expireAfter\":\"PT5S\",\"content\":[{\"display\":{\"title\":\"AlexaRemoteControl\",\"body\":\"Announcement\"},\"speak\":{\"type\":\"ssml\",\"value\":\"'${TTS}'\"},\"locale\":\"'${TTS_LOCALE}'\"}],\"target\":{\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"devices\":['${MEMBER}']},\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${SVOL}'\"}}]}}","status":"ENABLED"}';
else
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.SerialNode\",\"nodesToExecute\":[{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${SVOL}'\"}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\"'${TTS}'}},{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"Alexa.DeviceControls.Volume\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\",\"value\":\"'${VOL}'\"}}]}}","status":"ENABLED"}'
fi
else else
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\"'${SEQUENCEVAL}'}}}","status":"ENABLED"}' if [ -n "${TTS}" -a $USE_ANNOUNCEMENT_FOR_SPEAK -gt 0 ] ; then
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"expireAfter\":\"PT5S\",\"content\":[{\"display\":{\"title\":\"AlexaRemoteControl\",\"body\":\"Announcement\"},\"speak\":{\"type\":\"ssml\",\"value\":\"'${TTS}'\"},\"locale\":\"'${TTS_LOCALE}'\"}],\"target\":{\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"devices\":['${MEMBER}']},\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\"}}}","status":"ENABLED"}';
else
ALEXACMD='{"behaviorId":"PREVIEW","sequenceJson":"{\"@type\":\"com.amazon.alexa.behaviors.model.Sequence\",\"startNode\":{\"@type\":\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\",\"type\":\"'${SEQUENCECMD}'\",\"operationPayload\":{\"deviceType\":\"'${DEVICETYPE}'\",\"deviceSerialNumber\":\"'${DEVICESERIALNUMBER}'\",\"customerId\":\"'${MEDIAOWNERCUSTOMERID}'\",\"locale\":\"'${TTS_LOCALE}'\"'${SEQUENCEVAL}'}}}","status":"ENABLED"}'
fi
fi fi
fi fi
@ -683,7 +735,6 @@ if [ -n "${SEQUENCECMD}" ] ; then
"https://${ALEXA}/api/behaviors/preview" "https://${ALEXA}/api/behaviors/preview"
rm -f "${TMP}/.alexa.cmd" rm -f "${TMP}/.alexa.cmd"
else else
${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\ -H "Content-Type: application/json; charset=UTF-8" -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\
@ -870,27 +921,31 @@ get_volume()
if [ "$PARENTID" != "null" ] ; then if [ "$PARENTID" != "null" ] ; then
PARENTDEVICE=$(jq --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST}) PARENTDEVICE=$(jq --arg serial ${PARENTID} -r '.devices[] | select(.serialNumber == $serial) | .deviceType' ${DEVLIST})
VOL=$( ${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\ VOL=$(${CURL} ${OPTS} -s -b ${COOKIE} -A "${BROWSER}" -H "DNT: 1" -H "Connection: keep-alive" -L\
-H "Content-Type: application/json; charset=UTF-8" -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\ -H "Content-Type: application/json; charset=UTF-8" -H "Referer: https://alexa.${AMAZON}/spa/index.html" -H "Origin: https://alexa.${AMAZON}"\
-H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X GET \ -H "csrf: $(awk "\$0 ~/.${AMAZON}.*csrf[ \\s\\t]+/ {print \$7}" ${COOKIE})" -X GET \
"https://${ALEXA}/api/np/player?deviceSerialNumber=${PARENTID}&deviceType=${PARENTDEVICE}" | jq -r '.playerInfo.lemurVolume.memberVolume | to_entries[] | "\(.key) \(.value.volume) \(.value.muted)"') "https://${ALEXA}/api/np/player?deviceSerialNumber=${PARENTID}&deviceType=${PARENTDEVICE}" | jq -r '.playerInfo.lemurVolume.memberVolume | to_entries[] | "\(.key) \(.value.volume) \(.value.muted)"' 2>/dev/null)
fi fi
fi fi
# write volume and mute state to file if [ -n "${VOL}" ] ; then
OIFS=$IFS # write volume and mute state to file
IFS=' OIFS=$IFS
IFS='
' '
set -o noglob set -o noglob
for LINE in $VOL ; do for LINE in $VOL ; do
SERIAL=$(echo "${LINE}" | cut -d' ' -f1) SERIAL=$(echo "${LINE}" | cut -d' ' -f1)
VOLUME=$(echo "${LINE}" | cut -d' ' -f2) VOLUME=$(echo "${LINE}" | cut -d' ' -f2)
MUTED=$(echo "${LINE}" | cut -d' ' -f3) MUTED=$(echo "${LINE}" | cut -d' ' -f3)
echo "${VOLUME} ${MUTED}" > "${TMP}/.alexa.volume.${SERIAL}" echo "${VOLUME} ${MUTED}" > "${TMP}/.alexa.volume.${SERIAL}"
done done
IFS=$OIFS IFS=$OIFS
cut -d' ' -f1 "${TMP}/.alexa.volume.${DEVICESERIALNUMBER}"
fi
else
cut -d' ' -f1 "${TMP}/.alexa.volume.${DEVICESERIALNUMBER}"
fi fi
cut -d' ' -f1 "${TMP}/.alexa.volume.${DEVICESERIALNUMBER}"
} }
# #