mirror of https://gitlab.com/bashrc2/epicyon
SSML inbox endpoint
parent
5af36bf817
commit
7c20406d3f
46
daemon.py
46
daemon.py
|
@ -269,6 +269,7 @@ from filters import isFiltered
|
|||
from filters import addGlobalFilter
|
||||
from filters import removeGlobalFilter
|
||||
from context import hasValidContext
|
||||
from speaker import getSSMLbox
|
||||
import os
|
||||
|
||||
|
||||
|
@ -487,24 +488,28 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
"""
|
||||
if not self.headers.get('Accept'):
|
||||
return False
|
||||
acceptStr = self.headers['Accept']
|
||||
if self.server.debug:
|
||||
print('ACCEPT: ' + self.headers['Accept'])
|
||||
if 'image/' in self.headers['Accept']:
|
||||
if 'text/html' not in self.headers['Accept']:
|
||||
print('ACCEPT: ' + acceptStr)
|
||||
if 'application/ssml' in acceptStr:
|
||||
if 'text/html' not in acceptStr:
|
||||
return False
|
||||
if 'video/' in self.headers['Accept']:
|
||||
if 'text/html' not in self.headers['Accept']:
|
||||
if 'image/' in acceptStr:
|
||||
if 'text/html' not in acceptStr:
|
||||
return False
|
||||
if 'audio/' in self.headers['Accept']:
|
||||
if 'text/html' not in self.headers['Accept']:
|
||||
if 'video/' in acceptStr:
|
||||
if 'text/html' not in acceptStr:
|
||||
return False
|
||||
if self.headers['Accept'].startswith('*'):
|
||||
if 'audio/' in acceptStr:
|
||||
if 'text/html' not in acceptStr:
|
||||
return False
|
||||
if acceptStr.startswith('*'):
|
||||
if self.headers.get('User-Agent'):
|
||||
if 'ELinks' in self.headers['User-Agent'] or \
|
||||
'Lynx' in self.headers['User-Agent']:
|
||||
return True
|
||||
return False
|
||||
if 'json' in self.headers['Accept']:
|
||||
if 'json' in acceptStr:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -10480,10 +10485,25 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
# arriving in your inbox
|
||||
if authorized and usersInPath and \
|
||||
self.path.endswith('/speaker'):
|
||||
self._getSpeaker(callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.domain,
|
||||
self.server.debug)
|
||||
if 'application/ssml' not in self.headers['Accept']:
|
||||
# json endpoint
|
||||
self._getSpeaker(callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.domain,
|
||||
self.server.debug)
|
||||
else:
|
||||
xmlStr = \
|
||||
getSSMLbox(self.server.baseDir,
|
||||
self.path, self.server.domain,
|
||||
self.server.systemLanguage,
|
||||
self.server.instanceTitle,
|
||||
'inbox')
|
||||
if xmlStr:
|
||||
msg = xmlStr.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/xrd+xml', msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
return
|
||||
|
||||
# redirect to the welcome screen
|
||||
|
|
11
inbox.py
11
inbox.py
|
@ -84,6 +84,7 @@ from context import hasValidContext
|
|||
from content import htmlReplaceQuoteMarks
|
||||
from speaker import speakerReplaceLinks
|
||||
from speaker import speakerPronounce
|
||||
from speaker import speakerEndpointJson
|
||||
|
||||
|
||||
def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
||||
|
@ -2201,13 +2202,9 @@ def _updateSpeaker(baseDir: str, nickname: str, domain: str,
|
|||
announcedHandle = announcedNickname + '@' + announcedDomain
|
||||
content = \
|
||||
translate['announces'] + ' ' + announcedHandle + '. ' + content
|
||||
speakerJson = {
|
||||
"name": speakerName,
|
||||
"summary": summary,
|
||||
"say": content,
|
||||
"imageDescription": imageDescription,
|
||||
"detectedLinks": detectedLinks
|
||||
}
|
||||
speakerJson = speakerEndpointJson(speakerName, summary,
|
||||
content, imageDescription,
|
||||
detectedLinks)
|
||||
saveJson(speakerJson, speakerFilename)
|
||||
|
||||
|
||||
|
|
120
speaker.py
120
speaker.py
|
@ -10,8 +10,11 @@ import os
|
|||
import random
|
||||
from auth import createBasicAuthHeader
|
||||
from session import getJson
|
||||
from utils import loadJson
|
||||
from utils import getFullDomain
|
||||
|
||||
speakerRemoveChars = ('.\n', '. ', ',', ';', '?', '!')
|
||||
|
||||
|
||||
def getSpeakerPitch(displayName: str, screenreader: str) -> int:
|
||||
"""Returns the speech synthesis pitch for the given name
|
||||
|
@ -96,9 +99,8 @@ def speakerReplaceLinks(sayText: str, translate: {},
|
|||
"""Replaces any links in the given text with "link to [domain]".
|
||||
Instead of reading out potentially very long and meaningless links
|
||||
"""
|
||||
removeChars = ('.\n', '. ', ',', ';', '?', '!')
|
||||
text = sayText
|
||||
for ch in removeChars:
|
||||
for ch in speakerRemoveChars:
|
||||
text = text.replace(ch, ' ')
|
||||
replacements = {}
|
||||
wordsList = text.split(' ')
|
||||
|
@ -136,6 +138,28 @@ def speakerReplaceLinks(sayText: str, translate: {},
|
|||
return sayText.replace('..', '.')
|
||||
|
||||
|
||||
def _addSSMLemphasis(sayText: str) -> str:
|
||||
"""Adds emphasis to *emphasised* text
|
||||
"""
|
||||
if '*' not in sayText:
|
||||
return sayText
|
||||
text = sayText
|
||||
for ch in speakerRemoveChars:
|
||||
text = text.replace(ch, ' ')
|
||||
wordsList = text.split(' ')
|
||||
replacements = {}
|
||||
for word in wordsList:
|
||||
if word.startswith('*'):
|
||||
if word.endswith('*'):
|
||||
replacements[word] = \
|
||||
'<emphasis level="strong">' + \
|
||||
word.replace('*', '') + \
|
||||
'</emphasis>'
|
||||
for replaceStr, newStr in replacements.items():
|
||||
sayText = sayText.replace(replaceStr, newStr)
|
||||
return sayText
|
||||
|
||||
|
||||
def getSpeakerFromServer(baseDir: str, session,
|
||||
nickname: str, password: str,
|
||||
domain: str, port: int,
|
||||
|
@ -166,3 +190,95 @@ def getSpeakerFromServer(baseDir: str, session,
|
|||
getJson(session, url, headers, None,
|
||||
__version__, httpPrefix, domain)
|
||||
return speakerJson
|
||||
|
||||
|
||||
def speakerEndpointJson(displayName: str, summary: str,
|
||||
content: str, imageDescription: str,
|
||||
links: []) -> {}:
|
||||
"""Returns a json endpoint for the TTS speaker
|
||||
"""
|
||||
return {
|
||||
"name": displayName,
|
||||
"summary": summary,
|
||||
"say": content,
|
||||
"imageDescription": imageDescription,
|
||||
"detectedLinks": links
|
||||
}
|
||||
|
||||
|
||||
def _speakerEndpointSSML(displayName: str, summary: str,
|
||||
content: str, imageDescription: str,
|
||||
links: [], language: str,
|
||||
instanceTitle: str,
|
||||
gender: str) -> str:
|
||||
"""Returns an SSML endpoint for the TTS speaker
|
||||
https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language
|
||||
https://www.w3.org/TR/speech-synthesis/
|
||||
"""
|
||||
langShort = 'en'
|
||||
if language:
|
||||
langShort = language[:2]
|
||||
if not gender:
|
||||
gender = 'neutral'
|
||||
else:
|
||||
if langShort == 'en':
|
||||
gender = gender.lower()
|
||||
if 'him' in gender or 'male' in gender:
|
||||
gender = 'male'
|
||||
elif 'her' in gender or 'she' in gender or \
|
||||
'fem' in gender or 'woman' in gender:
|
||||
gender = 'female'
|
||||
elif 'man' in gender:
|
||||
gender = 'male'
|
||||
else:
|
||||
gender = 'neutral'
|
||||
|
||||
content = _addSSMLemphasis(content)
|
||||
voiceParams = 'name="' + displayName + '" gender="' + gender + '"'
|
||||
return '<?xml version="1.0"?>\n' + \
|
||||
'<speak xmlns="http://www.w3.org/2001/10/synthesis"\n' + \
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' + \
|
||||
' xsi:schemaLocation="http://www.w3.org/2001/10/synthesis\n' + \
|
||||
' http://www.w3.org/TR/speech-synthesis11/synthesis.xsd"\n' + \
|
||||
' version="1.1">\n' + \
|
||||
' <metadata>\n' + \
|
||||
' <dc:title xml:lang="' + langShort + '">' + \
|
||||
instanceTitle + ' inbox</dc:title>\n' + \
|
||||
' </metadata>\n' + \
|
||||
' <p>\n' + \
|
||||
' <s xml:lang="' + language + '">\n' + \
|
||||
' <voice ' + voiceParams + '>\n' + \
|
||||
' ' + content + '\n' + \
|
||||
' </voice>\n' + \
|
||||
' </s>\n' + \
|
||||
' </p>\n' + \
|
||||
'</speak>\n'
|
||||
|
||||
|
||||
def getSSMLbox(baseDir: str, path: str,
|
||||
domain: str,
|
||||
systemLanguage: str,
|
||||
instanceTitle: str,
|
||||
boxName: str) -> str:
|
||||
"""Returns SSML for the given timeline
|
||||
"""
|
||||
nickname = path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
nickname = nickname.split('/')[0]
|
||||
speakerFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '/speaker.json'
|
||||
if not os.path.isfile(speakerFilename):
|
||||
return None
|
||||
speakerJson = loadJson(speakerFilename)
|
||||
if not speakerJson:
|
||||
return None
|
||||
gender = None
|
||||
if speakerJson.get('gender'):
|
||||
gender = speakerJson['gender']
|
||||
return _speakerEndpointSSML(speakerJson['name'],
|
||||
speakerJson['summary'],
|
||||
speakerJson['say'],
|
||||
speakerJson['imageDescription'],
|
||||
speakerJson['detectedLinks'],
|
||||
systemLanguage,
|
||||
instanceTitle, gender)
|
||||
|
|
Loading…
Reference in New Issue