189 lines
5.9 KiB
Python
189 lines
5.9 KiB
Python
# Shoots the menu's pages for screenshots/menu: the game list and every service tool, through
|
|
# both of the menu's renderers -- the RmlUi document the GPU draws, and the plain overlay a machine
|
|
# without one falls back to (SDL_GPU_DRIVER=nothing). A driver script runs the bundled menu
|
|
# headless, waits for the intro, and presses the switches a person would: SERVICE for the tools,
|
|
# Button 1 into each one, Button 2 out, Down to the next. Each page is saved by name so a rerun
|
|
# replaces the picture, and the pictures of each renderer are tiled into one contact sheet.
|
|
#
|
|
# Usage: python3 util/shootMenu.py (both renderers)
|
|
# python3 util/shootMenu.py --renderer overlay
|
|
#
|
|
# The engine used is the gcc preset's, as util/renderMenuVideo.py uses; build it first. The
|
|
# menu shown is the one that binary carries, so a change to the menu's files needs a build before
|
|
# a shoot -- the script checks Menu.singe against assets and says so.
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
WIDTH = 1024 # The biggest the offscreen video driver gives.
|
|
HEIGHT = 768
|
|
RENDERERS = ["document", "overlay"]
|
|
SHEET_TILE = "420x315" # A quarter of the window, four to a row.
|
|
|
|
DRIVER = '''-- Runs the bundled menu and shoots its pages by name. Written by util/shootMenu.py.
|
|
dofile("Singe/Menu.singe")
|
|
|
|
local menuUpdate = onOverlayUpdate
|
|
local steps = {}
|
|
local at = 0
|
|
local settled = 0
|
|
local frames = 0
|
|
|
|
|
|
local function press(switch)
|
|
steps[#steps + 1] = { press = switch }
|
|
end
|
|
|
|
|
|
local function wait(count)
|
|
steps[#steps + 1] = { wait = count }
|
|
end
|
|
|
|
|
|
local function shoot(name)
|
|
steps[#steps + 1] = { shoot = name }
|
|
wait(3)
|
|
end
|
|
|
|
|
|
local function slug(name)
|
|
return name:lower():gsub("[^a-z0-9]+", "-")
|
|
end
|
|
|
|
|
|
-- The game list, then the tools: the list of them, and each in turn.
|
|
wait(30)
|
|
shoot("game-list")
|
|
press(SWITCH_DOWN)
|
|
wait(10)
|
|
shoot("game-list-second")
|
|
press(SWITCH_UP)
|
|
wait(10)
|
|
press(SWITCH_SERVICE)
|
|
wait(5)
|
|
shoot("00-service-tools")
|
|
for index, tool in ipairs(TOOLS) do
|
|
press(SWITCH_BUTTON1)
|
|
wait(45)
|
|
shoot(string.format("%02d-%s", index, slug(tool.name)))
|
|
press(SWITCH_BUTTON2)
|
|
wait(3)
|
|
press(SWITCH_DOWN)
|
|
wait(3)
|
|
end
|
|
press(SWITCH_SERVICE)
|
|
wait(5)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
local result = menuUpdate()
|
|
|
|
frames = frames + 1
|
|
if MENU_RENDER.backdropReady() then
|
|
if at < #steps then
|
|
local step = steps[at + 1]
|
|
|
|
if step.wait then
|
|
settled = settled + 1
|
|
if settled >= step.wait then
|
|
settled = 0
|
|
at = at + 1
|
|
end
|
|
else
|
|
if step.press then
|
|
onInputPressed(step.press)
|
|
elseif step.shoot then
|
|
singeScreenshot(step.shoot)
|
|
end
|
|
at = at + 1
|
|
end
|
|
else
|
|
debugPrint("SHOOT done")
|
|
singeQuit()
|
|
end
|
|
end
|
|
if frames > 3000 then
|
|
debugPrint("SHOOT gave up waiting for the menu")
|
|
singeQuit()
|
|
end
|
|
return result
|
|
end
|
|
'''
|
|
|
|
|
|
def findBinary():
|
|
folder = os.path.join(REPO, ".builddir", "linux-gcc", "x86_64", "singe")
|
|
for name in sorted(os.listdir(folder)):
|
|
path = os.path.join(folder, name)
|
|
if name.startswith("Singe") and os.path.isfile(path) and os.access(path, os.X_OK):
|
|
return path
|
|
raise SystemExit("no Singe binary in %s; build first" % folder)
|
|
|
|
|
|
def sheet(renderer, folder):
|
|
names = sorted(f for f in os.listdir(folder) if f.endswith(".png"))
|
|
out = os.path.join(REPO, "screenshots", "menu", "sheet-%s.png" % renderer)
|
|
command = ["montage", "-label", "%t", "-tile", "4x", "-geometry", SHEET_TILE + "+8+8", "-background", "#202020", "-fill", "#e0e0e0"]
|
|
command += [os.path.join(folder, f) for f in names]
|
|
command.append(out)
|
|
subprocess.run(command, check=True)
|
|
return out
|
|
|
|
|
|
def shoot(binary, renderer):
|
|
work = os.path.join(REPO, ".builddir", "menuShots", renderer)
|
|
shots = os.path.join(work, "shots")
|
|
shutil.rmtree(shots, ignore_errors=True)
|
|
os.makedirs(shots)
|
|
# The menu finds its games in the folders beside it: the test scripts' games.dat is enough.
|
|
link = os.path.join(work, "testScripts")
|
|
if not os.path.islink(link):
|
|
os.symlink(os.path.join(REPO, "testScripts"), link)
|
|
with open(os.path.join(work, "shoot.singe"), "w") as out:
|
|
out.write(DRIVER)
|
|
|
|
env = dict(os.environ)
|
|
env["SDL_VIDEODRIVER"] = "offscreen"
|
|
env["SDL_AUDIO_DRIVER"] = "dummy"
|
|
env["SDL_AUDIODRIVER"] = "dummy"
|
|
if renderer == "overlay":
|
|
env["SDL_GPU_DRIVER"] = "nothing"
|
|
command = [binary, "-k", "-w", "-x", str(WIDTH), "-y", str(HEIGHT), "-d", shots + os.sep, "-v", "Singe/menuBackground.mkv", "shoot.singe"]
|
|
result = subprocess.run(command, cwd=work, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
text = result.stdout.decode("utf-8", "replace")
|
|
with open(os.path.join(work, "shoot.log"), "w") as out:
|
|
out.write(text)
|
|
if (result.returncode != 0) or ("SHOOT done" not in text):
|
|
sys.stdout.write(text)
|
|
raise SystemExit("the %s shoot did not finish; see %s" % (renderer, os.path.join(work, "shoot.log")))
|
|
mine = open(os.path.join(REPO, "assets", "Menu.singe"), "rb").read()
|
|
theirs = open(os.path.join(work, "Singe", "Menu.singe"), "rb").read()
|
|
if mine != theirs:
|
|
print("WARNING: the binary's Menu.singe is not the one in assets; rebuild and shoot again")
|
|
|
|
folder = os.path.join(REPO, "screenshots", "menu", renderer)
|
|
shutil.rmtree(folder, ignore_errors=True)
|
|
os.makedirs(folder)
|
|
names = sorted(f for f in os.listdir(shots) if f.endswith(".png"))
|
|
for name in names:
|
|
shutil.copyfile(os.path.join(shots, name), os.path.join(folder, name))
|
|
print("%s: %d pages in %s, sheet %s" % (renderer, len(names), folder, sheet(renderer, folder)))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="shoot the menu's pages through both renderers")
|
|
parser.add_argument("--renderer", choices=RENDERERS, help="just this one")
|
|
parser.add_argument("--binary", help="the engine to shoot with, in place of the built one")
|
|
args = parser.parse_args()
|
|
|
|
binary = args.binary or findBinary()
|
|
for renderer in ([args.renderer] if args.renderer else RENDERERS):
|
|
shoot(binary, renderer)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|