Skip to content

Instantly share code, notes, and snippets.

@Pendrokar
Last active May 8, 2022 11:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pendrokar/9311de9c2be9e51c608372637d4e8583 to your computer and use it in GitHub Desktop.
Save Pendrokar/9311de9c2be9e51c608372637d4e8583 to your computer and use it in GitHub Desktop.
Mary TTS for Infinity Battlescape
import sys
import winsound
import urllib
import requests
import os
import random
import time
import logging
# find latest logfile
paths = [
"%s" % (f) for t, f in
sorted([(os.path.getctime(x),x) for x in os.listdir(".")], reverse=True)
]
logfilepath = paths[0]
print('Watching logfile: '+ paths[0])
paths = []
url = 'http://localhost:59125/process?INPUT_TYPE=TEXT&OUTPUT_TYPE=AUDIO&VOICE_SELECTIONS=cmu-slt-hsmm%20en_US%20female%20hmm&AUDIO_OUT=WAVE_FILE&LOCALE=en_US&VOICE=cmu-slt-hsmm&AUDIO=WAVE_FILE'
class LogDictation(object):
# last line before parsing it
lastLine = ''
# lastVoiceLine - to compare and not say the same voiceline
lastVoiceLine = ''
# text of last missing line
lastMissingLine = ''
def __init__(self):
self._cached_stamp = 0
self.filename = logfilepath
def ook(self):
stamp = os.stat(self.filename).st_mtime
if stamp != self._cached_stamp:
self._cached_stamp = stamp
# File has changed, so do something...
print('File has changed')
hasPassedLastLine = False
# missed lines since last log parse
missedLines = []
logfile = open(logfilepath,"r")
for line in logfile:
# TODO: collect missed lines and prepare a collected statement
if self.lastLine != '' and line == self.lastLine:
print('now passing previously read line')
hasPassedLastLine = True
# now start the missing lines, so skip this one
continue
if hasPassedLastLine:
missedLines.append(line)
self.lastLine = line
logfile.close()
voiceLines = ''
for line in missedLines:
voiceLine = self.parseLine(line)
if voiceLine != '':
print(line)
# a last period helps TTS voices
voiceLines += voiceLine + '. '
# print(voiceLines)
if voiceLines.strip() != '':
self.tts(voiceLines)
def parseLine(self, line):
isTextNotification = False
if (line.find('credits from team effort pool') > 0):
print('Repetitive line')
return ''
index = line.find('Localized')
# find text within line
voiceLineStart = line.find('(\'') + 2
voiceLineEnd = line.find('\')')
if (index >= 0):
line = line[voiceLineStart:voiceLineEnd]
isTextNotification = True
index = line.find('Text notification')
voiceLineStart = line.find('\'') + 1
voiceLineEnd = len(line) - 1
if (index >= 0):
# check for empty text: Battlescape bug
# TODO: make it not be the reason for blocking speech
if (line.find(": ''") > 0):
print('Empty notification')
return ''
if (line.find("Battle will start in") > 0):
# TODO: make unrepettitive
print('Repetitive line')
return ''
if (line.find("Victory odds") > 0):
# TODO: make unrepettitive
print('Repetitive line')
return ''
# print('Shortened notification')
# line = line[line.find("Victory odds:"):voiceLineEnd]
isTextNotification = True
line = line[voiceLineStart:voiceLineEnd]
if (self.lastMissingLine == line):
# skip same voice line
print('Skip same voiceline')
return ''
lastMissingLine = line
# if (self.lastVoiceLine == voiceLine):
# # skip same voice line
# print('Skip same voiceline')
# return
voiceLine = line.replace('Hauler HAULER', 'Hauler')
# TODO: apply angry emotion (speedup)
index = voiceLine.find('Griefing !')
if (index >= 0):
voiceLine = 'Griefer!'
return voiceLine
index = voiceLine.find('Bounty on')
if (index >= 0):
voiceLine = voiceLine.replace('Bounty on', 'Bounty on,')
voiceLine = voiceLine.replace('has been claimed', ' has been claimed')
voiceLine = self.convertRankHuman(voiceLine)
return voiceLine
index = voiceLine.find('You killed')
if (index >= 0):
voiceLine = voiceLine.replace('You killed', '')
voiceLine = self.convertRankHuman(voiceLine)
# cleanup percentage
parenthesisStart = voiceLine.find('(')
if parenthesisStart >= 0:
voiceLine = voiceLine[0:parenthesisStart]
voiceLine += self.getDeathLine()
# voiceLine += ' is destroyed'
# hmm capital ships aren't pilots
# voiceLine += ' is K I Ay '
return voiceLine
index = voiceLine.find('Kill assist')
if (index >= 0):
voiceLine = voiceLine.replace('Kill assist', 'Kill assist on')
voiceLine = self.convertRankHuman(voiceLine)
# cleanup percentage
parenthesisStart = voiceLine.find('%))') - 3
if parenthesisStart >= 0:
voiceLine = voiceLine[0:parenthesisStart]
return voiceLine
index = voiceLine.find('Critical hit!')
if (index >= 0):
voiceLine = voiceLine.replace('Critical hit!', '')
return voiceLine
index = voiceLine.find('Critical hit on target')
if (index >= 0):
voiceLine = voiceLine.replace('Critical hit on target', '')
voiceLine = 'Target\'s '+ voiceLine[1:]
return voiceLine
if isTextNotification:
return voiceLine
else:
return ''
def tts(self, voiceLine):
# copy constant
urlToOpen = url
# Audio effects
# F0Scale - 0 = monotone (1.0 - 2.0)
F0Scale = 1 + random.random()
urlToOpen = urlToOpen +'&effect_F0Scale_selected=on&effect_F0Scale_parameters=f0Scale%3A'+ '{:.1f}'.format(F0Scale) +'%3B'
# F0Add - Husky voice when up to negative 150
huskyLevel = -10 + random.random() * 30
urlToOpen = urlToOpen +'&effect_F0Add_selected=on&effect_F0Add_parameters=f0Add%3A'+ '{:.1f}'.format(huskyLevel) +'%3B'
# TractScaler
traceScale = 0.9 + random.random() / 10
# urlToOpen = urlToOpen +'&effect_TractScaler_selected=on&effect_TractScaler_parameters=amount%3A'+ str(traceScale) +'%3B'
# Pitch
pitchLevel = 0.9 + random.random() / 10
# stadium effect
# urlToOpen = urlToOpen +'&effect_Stadium_selected=on&effect_Stadium_parameters=amount%3A100.0'
urlToOpen = urlToOpen +'&INPUT_TEXT='+ urllib.parse.quote_plus(voiceLine)
print('Converting voiceline... (Web Request)')
r = requests.get(urlToOpen)
print('Saying (F0Scale:'+ '{:.1f}'.format(F0Scale) +'; F0Add:'+ str(int(huskyLevel)) +'): '+ voiceLine)
self.lastVoiceLine = voiceLine
winsound.PlaySound(r.content, winsound.SND_MEMORY)
# Make rank abbrevations human readable
def convertRankHuman(self, line):
line = line.replace('RCT', 'Recruit')
line = line.replace('PVT', 'Private')
line = line.replace('PV1', 'Private First Class')
# more important ranks, extra pause
line = line.replace('GRD', 'Guardian,')
line = line.replace('PO3', 'Petty Officer 3rd Class,')
line = line.replace('PO2', 'Petty Officer 2nd Class,')
line = line.replace('PO1', 'Petty Officer 1st Class,')
line = line.replace('CPO', 'Chief Petty Officer,')
line = line.replace('SPO', 'Senior Chief Petty Officer,')
line = line.replace('MP0', 'Master Chief Petty Officer,')
line = line.replace('ENS', 'Ensign,')
line = line.replace('LTJ', 'Lieutenant Junior,')
line = line.replace('LTN', 'Lieutenant,')
line = line.replace('LTC', 'Lieutenant Commander,')
# most important ranks, exclamation
line = line.replace('CMD', 'Commander!')
line = line.replace('CPT', 'Captain!')
line = line.replace('CDR', 'Commodore!')
line = line.replace('RADM', 'Rear Admiral!')
line = line.replace('VADM', 'Vice Admiral!')
line = line.replace('ADM', 'Admiral!')
line = line.replace('FADM', 'Fleet Admiral!')
return line
# get a random deathline
def getDeathLine(self):
return random.choice([
' is destroyed',
' is annihilated',
' is eradicated',
' is vaporized',
' is obliterated',
' is gone',
' is toast',
' is disintegrated',
' is now dust',
])
logdictation = LogDictation()
if __name__ == "__main__":
# try:
while True:
time.sleep(1)
logdictation.ook()
# finally:
# # except KeyboardInterrupt:
# # break
# exit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment