Logic Machine Forum
text to speech? - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: text to speech? (/showthread.php?tid=497)



text to speech? - gjniewenhuijse - 07.12.2016

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.


RE: text to speech? - gjniewenhuijse - 09.01.2017

UP...


RE: text to speech? - bmodeco - 21.03.2017

(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


RE: text to speech? - gjniewenhuijse - 21.03.2017

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


RE: text to speech? - bmodeco - 26.04.2017

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&gt;(.-)&lt;/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&gt;(.-)&lt;/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&gt;(.-)&lt;/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&gt;(.-)&lt;/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&gt;(.-)&lt;/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