Files
2025-09-29 00:52:08 +02:00

199 lines
9.7 KiB
Python
Executable File

# The below python script is an example of how to automatically modify the
# analysis text before sending it into FaceFX Studio. You can insert text tags,
# transliterate proper nouns, or search for phrases to key gestures.
import re
from FxStudio import *
negativeContractions = "aren't|can't|couldn't|didn't|doesn't|don't|hadn't|hasn't|haven't|isn't|mightn't|mustn't|shouldn't|wasn't|weren't|won't|wouldn't"
negativeWords = "no|not|none|neither|nor|never|nothing|nobody" + "|" + negativeContractions
# These are the emoticon and puncutation characters FaceFX recognizes. Technically these are distinct lists, but combining
# them here for the purpose of finding emoticons and phrase punctuation to reinsert into the modified text will work.
# False matches will simply get placed back into the text stream unmolested like normal emoticons.
EMOTICON_CHARACTERS = ".!?,~`@#$%^&*()-_=+;:'\\"
'''
youWords = "you|your|you're|you've|you'd|thee"
meWords = "i|me|my|mine|we|our|us|i've|we've|we'd|i'd"
highWords = "peak|pinnacle|above|climax|highest|high|max|maximum|zenith|crest|mountain|hill|more|greater|exceeding|exceeded|increase|increasing|increased|maximize|boost|boosted|boosting|amplify|amplifying|amplified|enlarge|enlarging|enlarged"
lowWords = "low|down|under|below|short|small|little|squat|stumpy|beneath|underneath|reduce|reducing|reduced|cut|diminish|diminishing|diminished|less|lessen|lessened|lessening|decrease|decreasing|decreased|shrink|shrinking|shrunk|dwindle|dwindling|dwindled"
coordinating_conjunctions = " and | but | for | nor | or | so | yet "
correlative_conjunctions = " both | not only | either | neither | nor | whether | or "
subordinating_conjunctions_cause = " because | since | now that | as | in order that | so "
subordinating_conjunctions_opposition = " although | though | even though | whereas | while "
subordinating_conjunctions_condition = " if | unless | only if | whether or not | even if | in case "
phrase_delininators = coordinating_conjunctions + "|" + correlative_conjunctions + "|" + subordinating_conjunctions_cause + "|" + subordinating_conjunctions_opposition + "|" + subordinating_conjunctions_condition
relative_pronouns = " that | which | who | whom | whose | when | where | why "
prepositions = " aboard | about | above | across | after | against | along | amid | among | anti | around | as | at | before | behind | below | beneath | beside | besides | between | beyond | but | by | concerning | considering | despite | down | during | except | excepting | excluding | following | for | from | in | inside | into | like | minus | near | of | off | on | onto | opposite | outside | over | past | per | plus | regarding | round | save | since | than | through | to | toward | towards | under | underneath | unlike | until | up | upon | versus | via | with | within | without "
subphrase_delininators = relative_pronouns + "|" + prepositions
'''
class textTag():
def __init__(self, tag):
self.tag = tag
self.wordindex = 0
def GetWordMeaningTags(analysisText):
textTags = []
tempAnalysisText = analysisText.lower()
words = tempAnalysisText.split()
flags = " blendin=.5 blendout=.5 duration=1.5 probability=.5 minstart=-.4 maxstart=-.1"
wordindex = 0
for word in words:
match = re.search("\A(?:" + negativeWords + ")$", word)
if match != None:
tag = textTag("{\"_TextEvents|Negative\"" + flags + "}")
tag.wordindex = wordindex
textTags.append(tag)
wordindex = wordindex + 1
return textTags
def GetPreExistingTextTagsAndEmoticons(analysisText):
textTags = []
tagindex = 0;
tempAnalysisText = analysisText
# find normal tags.
textTagStrings = re.findall("{.+?}|\[.+?\]|<.+?>", tempAnalysisText)
for textTagString in textTagStrings:
pos = tempAnalysisText.find(textTagString)
assert pos >= 0
textTags.append(textTag(textTagString))
tempAnalysisText = tempAnalysisText.replace(textTagString, " __text_tag_marker__ ", 1)
# remove phrase punctuation and emoticons from the text so they aren't counted as words.
# We do this after replacing normal tags with markers.
tempAnalysisText = re.sub('([{emote_chars}]{{2,}})(\d{{2}})?'.format(emote_chars=re.escape(EMOTICON_CHARACTERS)), '', analysisText)
if len(textTagStrings) > 0:
# Find the corresponding word index the tag is at
words = tempAnalysisText.split()
wordindex = 0
for word in words:
if word == "__text_tag_marker__":
textTags[tagindex].wordindex = wordindex
tagindex = tagindex + 1
# Make sure this is a real word, not some stranded punctuation or something that will get removed
elif re.search("[a-zA-Z0-9]", word) != None:
wordindex = wordindex + 1
# Now remove normal tags and find the emoticons.
tempAnalysisText = re.sub("{.+?}|\[.+?\]|<.+?>", "", analysisText)
textTagStrings = re.findall('([{emote_chars}]{{2,}})(\d{{2}})?'.format(emote_chars=re.escape(EMOTICON_CHARACTERS)), tempAnalysisText)
for textTagString in textTagStrings:
fullString = textTagString[0]+textTagString[1]
pos = tempAnalysisText.find(fullString)
assert pos >= 0
textTags.append(textTag(fullString))
tempAnalysisText = tempAnalysisText.replace(fullString, " __text_tag_marker__ ", 1)
# Find the corresponding word index the tag is at
words = tempAnalysisText.split()
wordindex = 0
for word in words:
if word == "__text_tag_marker__":
textTags[tagindex].wordindex = wordindex
tagindex = tagindex + 1
# Make sure this is a real word, not some stranded punctuation or something that will get removed
elif re.search("[a-zA-Z0-9]", word) != None:
wordindex = wordindex + 1
return textTags
def ReInsertTextTags(analysisText, textTags):
# reinsert pre-existing text tags
words = analysisText.split()
analysisText = ""
wordindex = 0
for word in words:
tagText = ""
for tag in textTags:
if wordindex == tag.wordindex:
tagText = tagText + " " + tag.tag
wordindex = wordindex + 1
analysisText = analysisText + " " + tagText + " " + word + " "
return analysisText
#-----------------------------------------------
# replace some common abbreviations with the full text. Abbreviations should not be used in the text
# anyway and they will get in the way of figuring out when sentences begin and end.
#-----------------------------------------------
def ReplaceAbbreviations(analysisText):
analysisText = re.sub("\sdr.\W", " doctor ", analysisText)
analysisText = re.sub("\smr.\W", " mister ", analysisText)
analysisText = re.sub("\smrs.\W", " misses ", analysisText)
analysisText = re.sub("\sms.\W", " miss ", analysisText)
analysisText = re.sub("\sjr.\W", " junior ", analysisText)
analysisText = re.sub("\ssgt.\W", " sergeant ", analysisText)
analysisText = re.sub("\ssr.\W", " senior ", analysisText)
analysisText = re.sub("\svs.\W", " versus ", analysisText)
return analysisText
def myAnalysisTextPreProcessor(analysisText, language):
combinedTextTags = []
# replace full stop unicode character with a period.
analysisText = re.sub(u'\u3002', '.', analysisText)
# cache the text tags before we convert to lower case.
combinedTextTags = GetPreExistingTextTagsAndEmoticons(analysisText)
# remove the tags.
analysisText = re.sub("{.+?}|\[.+?\]|<.+?>", "", analysisText)
analysisText = re.sub('([{emote_chars}]{{2,}})(\d{{2}})?'.format(emote_chars=re.escape(EMOTICON_CHARACTERS)), '', analysisText)
# Convert any remainging quotes in the string to a double-single-quote emoticon. Quotes aren't in our standard emoticon set
# due to conflicts, but this will let us use the valuable information they contain.
analysisText = re.sub('\"', ' \'\' ', analysisText)
if language == "USEnglish" or languge == "UKEnglish":
# Get rid of abbreviations.
analysisText = ReplaceAbbreviations(analysisText)
# replace pause punctuation with commas so the FaceFX-generated
# phrase events take them into account.
analysisText = re.sub('[;\:]|\s+-\s+', " , ", analysisText);
# put extra spaces around punctuation (but not ' or - because these can be inside a word.)
# also be careful not to put spaces around consecutive puncutation that will be an emoticon.
analysisText = re.sub('([{0}][{1}][{0}])'.format(re.escape("^" + EMOTICON_CHARACTERS), re.escape(EMOTICON_CHARACTERS)), ' \\1 ', analysisText)
if language == "USEnglish" or languge == "UKEnglish":
combinedTextTags.extend(GetWordMeaningTags(analysisText))
analysisText = ReInsertTextTags(analysisText, combinedTextTags)
return analysisText
def connectSignals():
# Connect to the analysistextpreprocessor signal so that this function gets called before every analysis.
connectSignal('analysistextpreprocessor', myAnalysisTextPreProcessor)
def disconnectSignals():
# If we never connected, attempting to disconnect with raise an exception so catch it
# and do nothing. This function is called from OnPostLoadActor.py to disconnect when
# loading a new actor, so this prevents the output window from popping up and displaying
# the exception.
try:
disconnectSignal('analysistextpreprocessor', myAnalysisTextPreProcessor)
except RuntimeError:
pass
connectSignals()