Posts: 451
Threads: 94
Joined: Jun 2015
Reputation:
6
Has anyone a working solution for text to speech (and send it to sonos)?
Before i used: translate.google.com, but now its banned by google.
Code: function sonosSay(ip, lang, speak)
local url = require("socket.url")
local speak = url.escape(speak)
uri = string.format("x-rincon-mp3radio://translate.google.com/translate_tts?tl=%s&q=%s", lang, speak)
upnpavcmd(ip, 1400, 'SetAVTransportURI', '<CurrentURI>'..uri..'</CurrentURI><CurrentURIMetaData/>')
upnpavcmd(ip, 1400, 'Play', '<Speed>1</Speed>')
end
I like to use the dutch language.
Posts: 451
Threads: 94
Joined: Jun 2015
Reputation:
6
Posts: 57
Threads: 7
Joined: Jul 2016
Reputation:
1
(07.12.2016, 19:20)gjniewenhuijse Wrote: Has anyone a working solution for text to speech (and send it to sonos)?
Before i used: translate.google.com, but now its banned by google.
Code: function sonosSay(ip, lang, speak)
local url = require("socket.url")
local speak = url.escape(speak)
uri = string.format("x-rincon-mp3radio://translate.google.com/translate_tts?tl=%s&q=%s", lang, speak)
upnpavcmd(ip, 1400, 'SetAVTransportURI', '<CurrentURI>'..uri..'</CurrentURI><CurrentURIMetaData/>')
upnpavcmd(ip, 1400, 'Play', '<Speed>1</Speed>')
end
I like to use the dutch language.
Hi,
I am looking into this also...thinking of using VoiceRSS since it will require minimal change to use.
See weblink - http://www.voicerss.org/api/documentation.aspx
I already have my code available to continue playing music/playlist after playing my doorbell sound or tts.
Mvg, B
B
Posts: 451
Threads: 94
Joined: Jun 2015
Reputation:
6
I tested also with voicerss and that doesn't work for me. I think it failed because:
- i opened a direct mp3 stream into my sonos from google/voicerss
- but voicerss failed because it doesn't use mp3 as extension from the returning url
dirty workaround:
save voicerss file into lm
rename to .mp3
play with sonos
remove mp3 file
Posts: 57
Threads: 7
Joined: Jul 2016
Reputation:
1
As requested my code using voicerss.
First the user lib I use for controlling Sonos based on what existed - added some new functions (track info)
Code: function upnpavcmd(host, port, cmd, param)
local client, soap, reqs, service, res, err
require('socket')
client = socket.tcp()
client:settimeout(3)
-- try connecting to upnp endpoint
res, err = client:connect(host, port)
if not res then
return nil, err
end
-- guess service name based on command
if cmd =='SetGroupVolume' or cmd == 'SetGroupMute' or cmd == 'SnapshotGroupVolume' then
service = 'GroupRenderingControl'
elseif cmd == 'SetVolume' or cmd == 'GetVolume' or cmd == 'SetMute' or cmd == 'GetMute' then
service = 'RenderingControl'
else
service = 'AVTransport'
end
-- soap envelope
soap = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' ..
'<s:Body>' ..
'<u:' .. cmd .. ' xmlns:u="urn:schemas-upnp-org:service:' .. service .. ':1">' ..
'<InstanceID>0</InstanceID>' ..
(param or '') ..
'</u:' .. cmd .. '>' ..
'</s:Body>' ..
'</s:Envelope>'
-- http request
reqs = 'POST /MediaRenderer/' .. service .. '/Control HTTP/1.1\r\n' ..
'CONNECTION: close\r\n' ..
'HOST: ' .. host .. ':' ..port.. '\r\n' ..
'CONTENT-LENGTH: ' .. soap:len() .. '\r\n' ..
'CONTENT-TYPE: text/xml; charset="utf-8"\r\n' ..
'SOAPACTION: "urn:schemas-upnp-org:service:' .. service .. ':1#' .. cmd .. '"\r\n' ..
'\r\n' .. soap
-- send http request
res, err = client:send(reqs)
if not res then
return nil, err
end
-- get reply and close connection
res, err = client:receive('*a')
client:close()
return res, err
end
function Sonos_Find(sonos_name)
require("socket")
require('socket.http')
local udp = socket.udp()
if udp == nil then
return
else
local result, message
local datagram = "M-SEARCH * HTTP/1.1\r\n"
.. "HOST: 239.255.255.250:1900\r\n"
.. "ST: urn:schemas-upnp-org:device:ZonePlayer:1\r\n"
.. "\r\n"
result, message = udp:sendto(datagram, "239.255.255.250", 1900)
for i = 1, 254 do
datagram, message = udp:receivefrom()
ip = datagram:match("http://(.-):1400")
if datagram == nil or ip == nil then
break
else
data = socket.http.request("http://" .. ip .. ":1400/status/zp")
zonename = data:match([[<ZoneName>(.-)</ZoneName>]])
ipaddress = data:match([[<IPAddress>(.-)</IPAddress>]])
macaddress = data:match([[<MACAddress>(.-)</MACAddress>]])
rincon = data:match([[<LocalUID>(.-)</LocalUID>]])
if zonename == sonos_name then
return {sonos_name = zonename, sonos_ipaddress = ipaddress, sonos_macaddress = macaddress, sonos_rincon = rincon}
end
end
end
end
udp:close()
end
function Sonos_Getpositioninfo(sonos_ipaddress)
-- Get Sonos Player Position Info
position_info = upnpavcmd(sonos_ipaddress,1400,'GetPositionInfo')
-- Get Track
for i in string.gmatch(position_info, '<Track.-</Track>') do
if i ~= nil then
track_nr = i:match([[<Track>(.-)</Track>]])
end
end -- Get Track
for i in string.gmatch(position_info, '<TrackDuration.-</TrackDuration>') do
if i ~= nil then
track_duration = i:match([[<TrackDuration>(.-)</TrackDuration>]])
end
end
-- Get Track Playing Time
for i in string.gmatch(position_info, '<RelTime.-</RelTime>') do
if i ~= nil then
track_playing_time = i:match([[<RelTime>(.-)</RelTime>]])
end
end
-- Get Track Title
for i in string.gmatch(position_info, 'dc:title.-/dc:title') do
if i ~= nil then
track_title = i:match([[dc:title>(.-)</dc:title]])
end
end
-- Get Track Artist
for i in string.gmatch(position_info, 'dc:creator.-/dc:creator') do
if i ~= nil then
creator = i:match([[dc:creator>(.-)</dc:creator]])
end
end
if creator == nil then
for i in string.gmatch(position_info, '<TrackMetaData.-</TrackMetaData>') do
if i ~= nil then
creator = i:match([[streamContent>(.-)</r:streamContent]])
end
end
end
-- Get Track Album
for i in string.gmatch(position_info, 'upnp:album.-/upnp:album') do
if i ~= nil then
album = i:match([[upnp:album>(.-)</upnp:album]])
end
end
-- -- Get Track Image URL
-- for i in string.gmatch(position_info, 'upnp:albumArtURI.-/upnp:albumArtURI') do
-- if i ~= nil then
-- albumart = i:match([[upnp:albumArtURI>(.-)</upnp:albumArtURI]])
-- end
-- end
-- if albumart == nil then
-- for i in string.gmatch(position_info, '<TrackURI.-</TrackURI>') do
-- if i ~= nil then
-- albumart = i:match([[<TrackURI>(.-)</TrackURI>]])
-- end
-- end
-- end
return {TrackNr = track_nr, TrackDuration = track_duration, TrackRelTime = track_playing_time, TrackTitle = track_title, TrackCreator = creator, TrackAlbum = album}
end
function Sonos_Gettransportinfo(sonos_ipaddress)
-- Get Sonos Player Transport Info
reply = upnpavcmd(sonos_ipaddress,1400,'GetTransportInfo')
for i in string.gmatch(reply, '<CurrentTransportState.-</CurrentTransportState>') do
if i ~= nil then
play_pause = i:match([[<CurrentTransportState>(.-)</CurrentTransportState>]])
end
end
-- 'TRANSITIONING' , 'PLAYING' , 'PAUSED_PLAYBACK' or 'STOPPED'
return {Transportmode = play_pause}
end
function Sonos_GetVolume(sonos_ipaddress)
-- Get Sonos Player Volume Info
reply = upnpavcmd(sonos_ipaddress,1400,'GetVolume','<Channel>Master</Channel>')
for i in string.gmatch(reply, '<CurrentVolume.-</CurrentVolume>') do
if i ~= nil then
volume = i:match([[<CurrentVolume>(.-)</CurrentVolume>]])
end
end
return {Volume = volume}
end
function Sonos_Getmediainfo(sonos_ipaddress)
-- Get Sonos Player Media Info
reply = upnpavcmd(sonos_ipaddress,1400,'GetMediaInfo')
for i in string.gmatch(reply, '<CurrentURI.-</CurrentURI>') do
if i ~= nil then
currenturi = i:match([[<CurrentURI>(.-)</CurrentURI>]])
end
end
-- 'TRANSITIONING' , 'PLAYING' , 'PAUSED_PLAYBACK' or 'STOPPED'
return {CurrentURI = currenturi}
end
Below the actual script:
It gets te mp3info and saves it on my synologyNas so the sonos can play it.
Before pausing the music (if playing), I save the tracknum and relpos so I can continue afterwards - works as expected.
My code only makes a distinction between playlist and radio station stubru - either one of them can be playing.
Since currently testing - it is not yet a function to call with a custom text - easy to make it that way.
The following weblink details the parameters for voicerss. http://www.voicerss.org/api/documentation.aspx
Have fun, Bart
Code: -- Load modules
require('json')
require('socket.http')
require('socket.ftp')
require('user.Sonos')
-- Set SONOS Variables
Sonos_Player_Name = 'Eetkamer'
action = 'none'
-- Read IP address from controller
data = io.readproc('if-json')
data = json.decode(data)
LMip = data.eth0.inetaddr
-- Set timout
socket.http.TIMEOUT = 5
-- Set volumes
TTS_Volume = 80
Default_Volume = 25
--Check if player exists
reply = Sonos_Find(Sonos_Player_Name)
current_sonosname = reply.sonos_name
current_sonosplayerip = reply.sonos_ipaddress
current_sonosmacaddress = reply.sonos_macaddress
current_sonosrincon = reply.sonos_rincon
-- Check if Sonos is playing?
reply = Sonos_Gettransportinfo(current_sonosplayerip)
previous_transportmode = reply.Transportmode
-- Get VolumeInfo
reply = Sonos_GetVolume(current_sonosplayerip)
current_volume = reply.Volume
-- Get VolumeInfo
reply = Sonos_Getmediainfo(current_sonosplayerip)
current_URI = reply.CurrentURI
local MP3_data = socket.http.request("http://api.voicerss.org/?key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx&hl=nl-nl&f=16khz_16bit_stereo&src=Mooi%20weer%20vandaag%20ik%20doe%20alles%20vandaag%20een%20beetje%20traag")
--Store on synology
res, err = socket.ftp.put({
host = '192.168.1.200',
user = 'xxx',
password = 'xxx',
path = '/music/Domotica/MP3/TTS.mp3',
source = ltn12.source.string(MP3_data)
})
-- Get trackinfo
reply=Sonos_Getpositioninfo(current_sonosplayerip)
current_mediatype = reply.TrackTitle
current_TrackRelTime = reply.TrackRelTime
current_TrackNr = reply.TrackNr
if previous_transportmode == 'PLAYING' then
upnpavcmd(current_sonosplayerip, 1400, 'Pause')
end
upnpavcmd(current_sonosplayerip, 1400, 'SetVolume', '<Channel>Master</Channel><DesiredVolume>' ..TTS_Volume.. '</DesiredVolume>')
upnpavcmd(current_sonosplayerip, 1400, 'SetAVTransportURI', '<CurrentURI>x-file-cifs://DISKSTATION/music/Domotica/MP3/TTS.mp3</CurrentURI><CurrentURIMetaData/>')
os.sleep(1)
-- Check if Sonos is playing?
upnpavcmd(current_sonosplayerip, 1400, 'Play', '<Speed>1</Speed>')
os.sleep(1)
reply = Sonos_Gettransportinfo(current_sonosplayerip)
current_transportmode = reply.Transportmode
--Wait until playing TTS-MP3 is finished
while (current_transportmode == 'PLAYING') do
os.sleep(1)
reply = Sonos_Gettransportinfo(current_sonosplayerip)
current_transportmode = reply.Transportmode
end
upnpavcmd(current_sonosplayerip, 1400, 'Pause')
if current_mediatype == 'stubru.aac' then
Sonos_Action = 'playuri'
Sonos_Listtype = 'favorites'
Sonos_Listnumber = 3
local reply = socket.http.request('http://' .. LMip .. '/apps/data/sonos/sonos.lp?action=' .. Sonos_Action .. '&name=' .. Sonos_Player_Name .. '&listtype=' .. Sonos_Listtype.. '&listnumber=' ..Sonos_Listnumber.. '')
else
upnpavcmd(current_sonosplayerip, 1400, 'SetAVTransportURI', '<CurrentURI>' ..current_URI.. '</CurrentURI><CurrentURIMetaData/>')
upnpavcmd(current_sonosplayerip, 1400, 'Seek', '<InstanceID>0</InstanceID><Unit>TRACK_NR</Unit><Target>' ..current_TrackNr.. '</Target>')
upnpavcmd(current_sonosplayerip, 1400, 'Seek', '<InstanceID>0</InstanceID><Unit>REL_TIME</Unit><Target>' ..current_TrackRelTime.. '</Target>')
end
upnpavcmd(current_sonosplayerip, 1400, 'SetVolume', '<Channel>Master</Channel><DesiredVolume>' ..current_volume.. '</DesiredVolume>')
upnpavcmd(current_sonosplayerip, 1400, 'Pause')
if previous_transportmode == 'PLAYING' then
upnpavcmd(current_sonosplayerip, 1400, 'Play', '<Speed>1</Speed>')
end
script.disable(_SCRIPTNAME)
-- loop while condition is met
|