-
-
Save Pendrokar/9311de9c2be9e51c608372637d4e8583 to your computer and use it in GitHub Desktop.
Mary TTS for Infinity Battlescape
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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