Skip to content

Instantly share code, notes, and snippets.

@RoadrunnerWMC
Last active April 1, 2020 09:32
Show Gist options
  • Save RoadrunnerWMC/2eb52d44a5b03d4f22520eeb335e47dd to your computer and use it in GitHub Desktop.
Save RoadrunnerWMC/2eb52d44a5b03d4f22520eeb335e47dd to your computer and use it in GitHub Desktop.
The script behind Skawo's April Fools 2020 video (https://youtu.be/XCb3-nU9KZc)
# RRWMC, Mar 31 2020
# The script behind Skawo's April Fools 2020 video
# (https://youtu.be/XCb3-nU9KZc)
#
# Download the source code for deeng from
# https://bisqwit.iki.fi/source/deeng.html
# Find the following in deeng.cc:
# else if(base == 'h' && res[a+1] == 'u')
# {
# base = 'f';
# }
# and replace that middle line with "base = 'h';".
# Then compile it and place the binary in the same folder as this script.
# If on Windows, you may need to search for "./deeng" in this script and
# add a ".exe" to the end. Or not; I'm not sure.
import argparse
import json
import pathlib
import string
import subprocess
# Special thanks to Bent and Meatball132 for these exception tables
COMBINATIONAL_HIRIGANA = {
'kya': 'ki ya',
'kyu': 'ki yu',
'kyo': 'ki yo',
'gya': 'gi ya',
'gyu': 'gi yu',
'gyo': 'gi yo',
'sha': 'shi ya',
'shu': 'shi yu',
'sho': 'shi yo',
'jya': 'ji ya',
'jyu': 'ji yu',
'jyo': 'ji yo',
'cha': 'chi ya',
'chu': 'chi yu',
'cho': 'chi yo',
'dya': 'di ya',
'dyu': 'di yu',
'dyo': 'di yo',
'nya': 'na ya',
'nyu': 'na yu',
'nyo': 'na yo',
'hya': 'hi ya',
'hyu': 'hi yu',
'hyo': 'hi yo',
'bya': 'bi ya',
'byu': 'bi yu',
'byo': 'bi yo',
'pya': 'pi ya',
'pyu': 'pi yu',
'pyo': 'pi yo',
'mya': 'mi ya',
'myu': 'mi yu',
'myo': 'mi yo',
'rya': 'ri ya',
'ryu': 'ri yu',
'ryo': 'ri yo',
'zya': 'zi ya',
'zyu': 'zi yu',
'zyo': 'zi yo',
'vya': 'u o ya', # really "vu ya"
'vyu': 'u o yu', # really "vu yu"
'vyo': 'u o yo', # really "vu yo"
'sya': 'shi ya',
'syu': 'shi yu',
'syo': 'shi yo',
'shya': 'shi ya',
'shyu': 'shi yu',
'shyo': 'shi yo',
'ja': 'ji ya',
'ju': 'ji yu',
'jo': 'ji yo',
'tsya': 'tsu ya',
'tsyu': 'tsu yu',
'tsyo': 'tsu yo',
'chya': 'chi ya',
'chyu': 'chi yu',
'chyo': 'chi yo',
'cha': 'chi ya',
'chu': 'chi yu',
'cho': 'chi yo',
}
KATAKANA_EXCEPTIONS = {
'fa': 'fu a',
'fi': 'fu i',
'fe': 'fu e',
'fo': 'fu o',
'she': 'shi e',
'je': 'ji e',
'che': 'chi e',
'tu': 'to u',
'ty': 'te i',
'du': 'do u',
'dy': 'de i',
'tsa': 'tsu a',
'tsi': 'tsu i',
'tse': 'tsu e',
'tso': 'tsu o',
'va': 'u a',
'bwa': 'u a',
'vi': 'u i',
'bwi': 'u i',
'bu': 'u',
've': 'u e',
'bwe': 'u e',
'vo': 'u o',
'vu': 'u o',
}
OTHER_RULES = {
'f': 'fu',
'tchi': 'tsu chi',
'tshi': 'tsu shi',
}
# http://www.kasuto.net/wind_waker.php?main=wind_waker/font.html
HYLIAN_FONT_CHARACTERS = {
'a': 'A',
'i': 'B',
'u': 'C',
'e': 'D',
'o': 'E',
'ka': 'F',
'ki': 'G',
'ku': 'H',
'ke': 'I',
'ko': 'J',
'sa': '+',
'si': 'K',
'shi': 'K',
'su': 'L',
'se': 'M',
'so': 'N',
'ta': 'O',
'ti': 'P',
'chi': 'P',
'tu': 'Q',
'tsu': 'Q',
'te': 'R',
'to': 'S',
'na': 'T',
'ni': 'U',
'nu': 'V',
'ne': 'W',
'no': 'X',
'ha': 'Y',
'hi': 'Z',
'hu': 'a',
'fu': 'a',
'he': 'b',
'ho': 'c',
'ma': 'd',
'mi': 'e',
'mu': 'f',
'me': 'g',
'mo': 'h',
'ya': 'i',
'yu': 'j',
'yo': 'k',
'ra': 'l',
'ri': 'm',
'ru': 'n',
're': 'o',
'ro': 'p',
'wa': 'q',
'wo': 'r',
'n': 's',
'ga': 't',
'gi': 'u',
'gu': 'v',
'ge': 'w',
'go': 'x',
'za': 'y',
'zi': 'z',
'ji': 'z',
'zu': '{',
'ze': '}',
'zo': ';',
'da': ':',
'di': '>',
'ji': '>',
'du': '<',
'zu': '<',
'de': '?',
'do': '/',
'ba': '!',
'bi': '@',
'bu': '#',
'be': '$',
'bo': '%',
'pa': '&',
'pi': '=',
'pu': '*',
'pe': '(',
'po': ')',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'0': '0',
}
MASTER_TABLE = dict(HYLIAN_FONT_CHARACTERS)
for combo, expanded in {**COMBINATIONAL_HIRIGANA, **KATAKANA_EXCEPTIONS, **OTHER_RULES}.items():
expanded = expanded.split(' ')
MASTER_TABLE[combo] = ''.join(MASTER_TABLE[x] for x in expanded)
MASTER_TABLE_1 = {a: b for a, b in MASTER_TABLE.items() if len(a) == 1}
MASTER_TABLE_2 = {a: b for a, b in MASTER_TABLE.items() if len(a) == 2}
MASTER_TABLE_3 = {a: b for a, b in MASTER_TABLE.items() if len(a) == 3}
MASTER_TABLE_4 = {a: b for a, b in MASTER_TABLE.items() if len(a) == 4}
MASTER_TABLE_REVERSE = {b: a for a, b in MASTER_TABLE.items()}
def deeng(s):
"""
Run the given string through deeng
"""
return subprocess.run(['./deeng', s], stdout=subprocess.PIPE).stdout.decode('utf-8').rstrip()
def deeng_separately(s):
"""
Run each word of the string through deeng separately, preserving
punctuation
"""
currentWord = []
overallOutput = []
for c in s:
if c in string.ascii_letters:
currentWord.append(c)
else:
if currentWord:
overallOutput.append(deeng(''.join(currentWord)))
currentWord.clear()
overallOutput.append(c)
if currentWord:
overallOutput.append(deeng(''.join(currentWord)))
return ''.join(overallOutput)
def getFirstSyllable(s):
"""
Get the first syllable in s, and return (syllable, fontchar)
"""
# double-consonant exception
if len(s) >= 2 and s[0] == s[1] and s[0] in (set(string.ascii_letters) - set('aeiou')):
return s[0], MASTER_TABLE['tsu']
if len(s) >= 4 and s[:4] in MASTER_TABLE_4:
return s[:4], MASTER_TABLE_4[s[:4]]
if len(s) >= 3 and s[:3] in MASTER_TABLE_3:
return s[:3], MASTER_TABLE_3[s[:3]]
if len(s) >= 2 and s[:2] in MASTER_TABLE_2:
return s[:2], MASTER_TABLE_2[s[:2]]
if len(s) >= 1 and s[:1] in MASTER_TABLE_1:
return s[:1], MASTER_TABLE_1[s[:1]]
raise ValueError('Cannot get syllable from start of ' + s)
def toHylian(s):
"""
Convert deeng output to Hylian-font-compatible text.
Output:
- string showing recognized syllables separated by spaces (for debugging)
- Hylian-font text
- list of indices of actual punctuation that should be rendered in a different font
"""
# Expand '^'s
out = []
for c in s:
if c == '^':
out.append({
'a': 'a',
'e': 'e',
'i': 'i',
'o': 'u',
'u': 'u',
}[out[-1]])
else:
out.append(c)
s = ''.join(out)
# Convert to syllables
out1 = []
out2 = []
punctuationIndices = []
i = 0
while i < len(s):
if s[i].lower() not in string.ascii_letters + '^':
if s[i] not in ' \n' + string.digits:
punctuationIndices.append(len(''.join(out2)))
out1.append(s[i])
out2.append(s[i])
i += 1
continue
syllable, char = getFirstSyllable(s[i:])
i += len(syllable)
out1.append(syllable + ' ')
out2.append(char)
return ''.join(out1), ''.join(out2), punctuationIndices
def test():
"""
Run some tests
"""
TEST_SENTENCES = [
'and then something replaces the japanization with the hylian font keystrokes',
'also reading that stuff without the smaller versions of the characters is probably a nightmare but nothing we can do ig',
'nice',
'this other font uses punctuation for actual punctuation, but then the rest of the symbols is nonsense',
]
for s in TEST_SENTENCES:
print()
print(s)
# Run sentence through deeng
deenged = deeng_separately(s)
print(deenged)
# Convert to syllables and font-compatible characters
syllables, encrypted, _ = toHylian(deenged)
print(syllables)
print(encrypted)
wordlist = []
with open('/usr/share/dict/american-english', 'r', encoding='utf-8') as f:
for line in f:
s = line.strip()
print()
print(s)
# Run sentence through deeng
deenged = deeng_separately(s)
print(deenged)
# Convert to syllables and font-compatible characters
syllables, encrypted, _ = toHylian(deenged)
print(syllables)
print(encrypted)
def modifyTscprojTextbox(box):
"""
Modify a tscproj textbox dictionary
"""
text = box['text'].replace('\r', '\n')
print()
print(text)
text = deeng_separately(text)
print(text)
text2 = text
for A, B in {'a^': 'ā', 'e^': 'ē', 'i^': 'ī', 'o^': 'ō', 'u^': 'ū'}.items():
text2 = text2.replace(A, B)
print(text2)
syllables, text, punctuationIndices = toHylian(text)
print(syllables)
print(text)
print(punctuationIndices)
def makeKeyframe(name, start, stop, value, type):
return {
'name': name,
'rangeEnd': stop,
'rangeStart': start,
'value': value,
'valueType': type,
}
fontInfo = [
makeKeyframe('underline', 0, len(text), 0, 'int'),
makeKeyframe('kerning', 0, len(text), 0, 'double'),
makeKeyframe('fontWeight', 0, len(text), 400, 'int'),
makeKeyframe('fgColor', 0, len(text), '(255,255,255,255)', 'color'),
makeKeyframe('strikethrough', 0, len(text), 0, 'int'),
makeKeyframe('fontItalic', 0, len(text), 0, 'int'),
]
HYLIAN = ('Wind Waker Hylian', 42)
PUNCTUATION = ('Ancient Hylian - English', 68)
currentFontStart = 0
currentFont = HYLIAN
for i, c in enumerate(text):
if i in punctuationIndices:
newFont = PUNCTUATION
else:
newFont = HYLIAN
if currentFont != newFont:
fontInfo.append(makeKeyframe('fontName', currentFontStart, i, currentFont[0], 'string'))
fontInfo.append(makeKeyframe('fontSize', currentFontStart, i, currentFont[1], 'double'))
currentFont = newFont
currentFontStart = i
fontInfo.append(makeKeyframe('fontName', currentFontStart, len(text), currentFont[0], 'string'))
fontInfo.append(makeKeyframe('fontSize', currentFontStart, len(text), currentFont[1], 'double'))
box['text'] = text.replace('\n', '\r')
box['textAttributes']['keyframes'] = [{'endTime': 0, 'time': 0, 'value': fontInfo, 'duration': 0}]
def main(args=None):
parser = argparse.ArgumentParser(
description='Hylian text thingy')
subparsers = parser.add_subparsers(title='commands',
description='(run a command with -h for additional help)')
def handleString(pArgs):
"""
Handle the "string" command.
"""
deenged = deeng_separately(pArgs.string)
print(deenged)
print()
syllables, encrypted, _ = toHylian(deenged)
print(syllables)
print()
print(encrypted)
parser_string = subparsers.add_parser('string', aliases=['s'],
help='convert a single string')
parser_string.add_argument('string',
help='string to convert (quoted)')
parser_string.set_defaults(func=handleString)
def handleTscproj(pArgs):
"""
Handle the "tscproj" command.
"""
outfp = pArgs.output_file
if outfp is None: outfp = pArgs.input_file.with_suffix('.out.tscproj')
with open(pArgs.input_file, 'r', encoding='utf-8') as f:
j = json.load(f)
allMedias = []
for scene in j['timeline']['sceneTrack']['scenes']:
for track in scene['csml']['tracks']:
for media in track['medias']:
if media['_type'] == 'Callout':
allMedias.append(media)
for media in sorted(allMedias, key=lambda m: m.get('start', 0)):
modifyTscprojTextbox(media['def'])
with open(str(outfp), 'w', encoding='utf-8') as f:
json.dump(j, f)
parser_tscproj = subparsers.add_parser('tscproj', aliases=['t'],
help='apply Hylian to a tscproj file')
parser_tscproj.add_argument('input_file', type=pathlib.Path,
help='input file to process')
parser_tscproj.add_argument('output_file', nargs='?', type=pathlib.Path,
help='what to save the output file as')
parser_tscproj.set_defaults(func=handleTscproj)
# Parse args and run appropriate function
pArgs = parser.parse_args(args)
if hasattr(pArgs, 'func'):
pArgs.func(pArgs)
else: # this happens if no arguments were specified at all
parser.print_usage()
# test()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment