Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
OBS Studio Python script to load ALttP Randomizer JSON spoilers and process them into OBS text elements.
# Copyright (c) 2019, Andrew Michaud
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# load-alttp-randomizer-spoilers.py
# an OBS Studio script to load JSON ALttP Randomizer spoilers,
# and spit them out into a text file OBS can read as an element.
import json
import os
import obspython
# input and output files.
SPOILER_FILE_PATH_KEY = "spoiler_file_path"
OUTPUT_FILE_PATH_KEY = "output_file_path"
# supported meta items
# goal: pedestal|ganon|dungeons|triforce hunt
GOAL_KEY = "goal"
GOAL_ALIAS_DEFAULT = "goal"
# mode: standard|open|inverted
MODE_KEY = "mode"
MODE_ALIAS_DEFAULT = "mode"
# logic: no glitches|overworld glitches|major glitches|none
LOGIC_KEY = "rom_mode"
LOGIC_ALIAS_DEFAULT = "logic"
# weapons: randomized|uncle|swordless
WEAPONS_KEY = "weapons"
WEAPONS_ALIAS_DEFAULT = "uncle sword"
# variation: none|timed race|timed OHKO|OHKO|keysanity|retro
VARIATION_KEY = "variation"
VARIATION_ALIAS_DEFAULT = "variation"
# difficulty: easy|normal|hard|expert|insane
DIFFICULTY_KEY = "difficulty"
DIFFICULTY_ALIAS_DEFAULT = "difficulty"
KEYS = [
GOAL_KEY,
MODE_KEY,
LOGIC_KEY,
WEAPONS_KEY,
VARIATION_KEY,
DIFFICULTY_KEY,
]
DEFAULTS = [
GOAL_ALIAS_DEFAULT,
MODE_ALIAS_DEFAULT,
LOGIC_ALIAS_DEFAULT,
WEAPONS_ALIAS_DEFAULT,
VARIATION_ALIAS_DEFAULT,
DIFFICULTY_ALIAS_DEFAULT,
]
# mappings of randomizer setting VALUES to other things. incomplete, probably.
VALUE_MAPPINGS = {
LOGIC_KEY: {
"NoGlitches": "no glitches",
"OverworldGlitches": "overworld glitches",
},
WEAPONS_KEY: {
"uncle": "yes",
},
VARIATION_KEY: {
"key-sanity": "key randomizer",
},
}
def script_description():
"""returns description for script."""
return "<b>ALttP Randomizer Spoiler Loader</b>" + \
"<hr/>" + \
"Python script for loading the spoiler metadata " + \
"from an ALttP Randomizer spoiler file." + \
"<br/><br/>" + \
"Made by Andrew Michaud, copyright 2019"
def script_update(settings):
"""
called on script startup with settings (internal, not user-defined).
performs the main work of the script, updating every time settings are changed.
"""
# load spoiler file, and then metadata.
spoiler_file_path = obspython.obs_data_get_string(settings, SPOILER_FILE_PATH_KEY)
if spoiler_file_path is None or spoiler_file_path == "":
obspython.script_log(
obspython.LOG_ERROR,
"cannot read spoilers if there's no file given!",
)
return
if not os.path.isfile(spoiler_file_path):
obspython.script_log(
obspython.LOG_ERROR,
f"provided file {spoiler_file_path} is not a file!",
)
return
with open(spoiler_file_path, "r", encoding="utf-8") as f:
spoiler_data = json.loads(f.read())
obspython.script_log(obspython.LOG_INFO, "successfully loaded spoiler file.")
meta = spoiler_data.get("meta", None)
if meta is None:
obspython.script_log(obspython.LOG_ERROR, "cannot load metadata from file!")
return
if len(KEYS) != len(DEFAULTS):
obspython.script_log(
obspython.LOG_ERROR,
(
"KEYS and DEFAULTS are of different lengths in the script file, "
"and must be the same length! check the script file!"
)
)
return
# with meta, actually read values and perform user mappings.
largest_key_size = 0
largest_value_size = 0
data = {}
for index, key in enumerate(KEYS):
user_value = obspython.obs_data_get_string(settings, key)
if user_value is None or user_value == "":
user_value = DEFAULTS[index]
# track length of values to aid padding later.
if len(user_value) > largest_key_size:
largest_key_size = len(user_value)
# map meta values according to internal mapping.
bare_meta_value = meta.get(key, "")
meta_value = bare_meta_value
if key in VALUE_MAPPINGS and bare_meta_value in VALUE_MAPPINGS[key]:
meta_value = VALUE_MAPPINGS[key][bare_meta_value]
if len(meta_value) > largest_value_size:
largest_value_size = len(meta_value)
data[user_value] = meta_value
obspython.script_log(obspython.LOG_INFO, "was able to parse spoiler values.")
obspython.script_log(
obspython.LOG_DEBUG,
f"parsed values: {json.dumps(data, indent=4, sort_keys=True)}",
)
# and save to file.
output_file_path = obspython.obs_data_get_string(settings, OUTPUT_FILE_PATH_KEY)
if output_file_path is None or output_file_path == "":
obspython.script_log(
obspython.LOG_ERROR,
"cannot write if there's no file given!",
)
return
if not os.path.isfile(output_file_path):
obspython.script_log(
obspython.LOG_ERROR,
f"provided file {output_file_path} is not a file!",
)
return
# write file with unicode box art because why not.
with open(output_file_path, "w", encoding="utf-8") as f:
f.write(
"┌" +
"─" * (largest_key_size + 2) +
"┬" +
"─" * (largest_value_size + 2) +
f"┐{os.linesep}"
)
for key, value in data.items():
key_spacer = " " * (largest_key_size - len(key))
value_spacer = " " * (largest_value_size - len(value))
f.write(f"│ {key}{key_spacer}{value}{value_spacer}{os.linesep}")
f.write(
"└" +
"─" * (largest_key_size + 2) +
"┴" +
"─" * (largest_value_size + 2) +
f"┘{os.linesep}"
)
obspython.script_log(obspython.LOG_INFO, "wrote successfully to output file!")
def script_properties():
"""called to set properties of script from user."""
props = obspython.obs_properties_create()
home = os.path.expanduser("~")
downloads_path = os.path.join(
home,
"Downloads",
)
obspython.obs_properties_add_path(
props,
SPOILER_FILE_PATH_KEY,
"path to ALttP spoiler file.",
obspython.OBS_PATH_FILE,
"*.txt;;*.json;;*.*",
downloads_path,
)
obspython.obs_properties_add_path(
props,
OUTPUT_FILE_PATH_KEY,
"path to output file.",
obspython.OBS_PATH_FILE,
"*.txt;;*.*",
downloads_path,
)
# overrides for the various ALttP options.
for index, key in enumerate(KEYS):
default_value = DEFAULTS[index]
obspython.obs_properties_add_text(
props,
key,
f"how to refer to '{key}' in output.\ndefault: {default_value}",
obspython.OBS_TEXT_DEFAULT,
)
return props
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment