Skip to content

Commit

Permalink
NF: Create a movie from the Tk postscript mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
Debilski committed Aug 13, 2019
1 parent ac451de commit 2299359
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 10 deletions.
14 changes: 9 additions & 5 deletions pelita/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@


class TkViewer:
def __init__(self, *, address, controller, geometry=None, delay=None, stop_after=None):
self.proc = self._run_external_viewer(address, controller, geometry=geometry, delay=delay, stop_after=stop_after)
def __init__(self, *, address, controller, geometry=None, delay=None, stop_after=None, snapshot_folder=None):
self.proc = self._run_external_viewer(address, controller, geometry=geometry, delay=delay, stop_after=stop_after, snapshot_folder=snapshot_folder)

def _run_external_viewer(self, subscribe_sock, controller, geometry, delay, stop_after):
def _run_external_viewer(self, subscribe_sock, controller, geometry, delay, stop_after, snapshot_folder):
# Something on OS X prevents Tk from running in a forked process.
# Therefore we cannot use multiprocessing here. subprocess works, though.
viewer_args = [ str(subscribe_sock) ]
Expand All @@ -54,6 +54,8 @@ def _run_external_viewer(self, subscribe_sock, controller, geometry, delay, stop
viewer_args += ["--delay", str(delay)]
if stop_after is not None:
viewer_args += ["--stop-after", str(stop_after)]
if snapshot_folder is not None:
viewer_args += ["--snapshot-folder", str(snapshot_folder)]

tkviewer = 'pelita.scripts.pelita_tkviewer'
external_call = [sys.executable,
Expand Down Expand Up @@ -145,12 +147,14 @@ def setup_viewers(viewers=None, options=None):
proc = TkViewer(address=zmq_publisher.socket_addr, controller=viewer_state['controller'].socket_addr,
stop_after=options.get('stop_at'),
geometry=options.get('geometry'),
delay=options.get('delay'))
delay=options.get('delay'),
snapshot_folder=options.get('snapshot_folder'))
else:
proc = TkViewer(address=zmq_publisher.socket_addr, controller=None,
stop_after=options.get('stop_at'),
geometry=options.get('geometry'),
delay=options.get('delay'))
delay=options.get('delay'),
snapshot_folder=options.get('snapshot_folder'))

else:
raise ValueError(f"Unknown viewer {viewer}.")
Expand Down
5 changes: 4 additions & 1 deletion pelita/scripts/pelita_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def long_help(s):
metavar='REPLAYFILE', dest='replayfile', const='pelita.dump', nargs='?')
parser.add_argument('--store-output', help=long_help('Write all player’s stdout/stderr to the given folder (must exist)'),
metavar='FOLDER')
parser.add_argument('--snapshot-folder', help=long_help('Store thw replay in this folder'),
metavar='FOLDER', dest='snapshot_folder')
parser.add_argument('--list-layouts', action='store_true',
help='List all available built-in layouts.')
parser.add_argument('--check-team', action="store_true",
Expand Down Expand Up @@ -205,7 +207,8 @@ def main():
viewer_options = {
"geometry": geometry,
"delay": delay,
"stop_at": stop_at
"stop_at": stop_at,
"snapshot_folder": args.snapshot_folder,
}

if args.reply_to:
Expand Down
5 changes: 4 additions & 1 deletion pelita/scripts/pelita_tkviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def geometry_string(s):
help='delay')
parser.add_argument('--stop-after', type=int, metavar="N",
help='Stop after N rounds.')
parser.add_argument('--snapshot-folder', help='Store thw replay in this folder',
metavar='FOLDER', dest='snapshot_folder')
parser._optionals = parser.add_argument_group('Options')
parser.add_argument('--version', help='show the version number and exit',
action='store_const', const=True)
Expand All @@ -55,7 +57,8 @@ def main():
'controller_address': args.controller_address,
'geometry': args.geometry,
'delay': args.delay,
'stop_after': args.stop_after
'stop_after': args.stop_after,
'snapshot_folder': args.snapshot_folder
}
v = TkViewer(**{k: v for k, v in list(tkargs.items()) if v is not None})
v.run()
Expand Down
68 changes: 67 additions & 1 deletion pelita/ui/tk_canvas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
from pathlib import Path
import time

import zmq
Expand All @@ -18,6 +19,17 @@ def _ensure_tuples(list):
""" Ensures that an iterable is a list of position tuples. """
return [tuple(item) for item in list]

NINJA_TEMPLATE = r"""
rule convert
command = convert -alpha off -density 800 -resize 25% -format png $in -append $out
rule ffmpeg
command = ffmpeg -y -framerate 10 -i snap-%04d.png -start_number 2 -s:v 1424x778 -c:v libx264 -profile:v high -crf 20 -pix_fmt yuv420p $out
build movie.mp4: ffmpeg {pngs}
{converts}
"""


def guess_size(display_string, bounding_width, bounding_height, rel_size=0):
no_lines = display_string.count("\n") + 1
Expand Down Expand Up @@ -123,7 +135,7 @@ class UI:

class TkApplication:
def __init__(self, master, controller_address=None,
geometry=None, delay=1, stop_after=None):
geometry=None, delay=1, stop_after=None, snapshot_folder=None):
self.master = master
self.master.configure(background="white")

Expand Down Expand Up @@ -170,6 +182,21 @@ def __init__(self, master, controller_address=None,
self.ui.status_canvas = tkinter.Frame(master, height=25)
self.ui.status_canvas.config(background="white")

if snapshot_folder:
self.snapshot_mode = True
self.snapshot_folder = Path(snapshot_folder)
try:
self.snapshot_folder.mkdir()
except FileExistsError:
pass
#self.quit()
#raise RuntimeError("Folder ‘{}’ already exists. Exiting.".format(snapshot_folder)) from None
self.snapshot_count = 0
else:
self.snapshot_mode = False
self.snapshot_folder = None
self.snapshot_count = 0

self.ui.game_canvas = tkinter.Canvas(master)
self.ui.game_canvas.config(background="white", bd=0, highlightthickness=0, relief='ridge')
self.ui.game_canvas.bind('<Configure>', lambda e: master.after_idle(self.update))
Expand Down Expand Up @@ -328,6 +355,8 @@ def __init__(self, master, controller_address=None,
if self.controller_socket:
self.master.after_idle(self.request_initial)

if self.snapshot_mode:
self.ui.status_canvas.pack_forget()

def init_mesh(self, game_state):
width = max(game_state['walls'])[0] + 1
Expand Down Expand Up @@ -385,6 +414,15 @@ def update(self, game_state=None):
self.mesh_graph.screen_width = self.ui.game_canvas.winfo_width()
self.mesh_graph.screen_height = self.ui.game_canvas.winfo_height()

if self.snapshot_mode:
# Ideal scaling should be 24
self.mesh_graph.num_x = max(game_state['walls'])[0] + 1
self.mesh_graph.num_y = max(game_state['walls'])[1] + 1

width = self.mesh_graph.num_x * 24
height = self.mesh_graph.num_y * 24 + self.ui.header_canvas.winfo_height()
self.master.geometry('{width}x{height}'.format(width=width, height=height))

if self.mesh_graph.screen_width < 600:
if self._default_font.cget('size') != 8:
self._default_font.configure(size=8)
Expand Down Expand Up @@ -428,6 +466,23 @@ def draw_universe(self, game_state):

self.draw_status_info(game_state)

if self.snapshot_mode:
# we need to ensure that the geometry is correct before taking a snapshot
if (not self.mesh_graph.num_x * 24 == self.ui.game_canvas.winfo_width() and
not self.mesh_graph.num_x * 24 == self.ui.game_canvas.winfo_height()):
self.master.after_idle(self.update)
return

header = self.snapshot_folder / 'snap-{:04}.header.ps'.format(self.snapshot_count)
canvas = self.snapshot_folder / 'snap-{:04}.canvas.ps'.format(self.snapshot_count)
state = self.snapshot_folder / 'snap-{:04}.state.json'.format(self.snapshot_count)
ninja = self.snapshot_folder / 'build.ninja'
canvas.write_text(self.ui.game_canvas.postscript(colormode='color'))
header.write_text(self.ui.header_canvas.postscript(colormode='color'))
state.write_text(json.dumps(game_state))
self.snapshot_count += 1


def draw_grid(self):
""" Draws a light grid on the background.
"""
Expand Down Expand Up @@ -784,6 +839,17 @@ def on_quit(self):
""" override for things which must be done when we exit.
"""
self.running = False

if self.snapshot_mode:
snap_ids = ['{:04}'.format(snap_id) for snap_id in range(self.snapshot_count)]
headers = map('snap-{}.header.ps'.format, snap_ids)
canvas = map('snap-{}.canvas.ps'.format, snap_ids)
pngs = list(map('snap-{}.png'.format, snap_ids))
ninja = self.snapshot_folder / 'build.ninja'
converts = "\n".join(map(lambda phc: "build {}: convert {} {}".format(*phc), zip(pngs, headers, canvas)))
ninja_str = NINJA_TEMPLATE.format(pngs=" ".join(pngs), converts=converts)
ninja.write_text(ninja_str)

if self.controller_socket:
_logger.debug('---> exit')
self.controller_socket.send_json({"__action__": "exit"})
Expand Down
6 changes: 4 additions & 2 deletions pelita/ui/tk_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ class TkViewer:
app : The TkApplication class
"""
def __init__(self, address, controller_address=None, geometry=None, delay=1, stop_after=None):
def __init__(self, address, controller_address=None, geometry=None, delay=1, stop_after=None, snapshot_folder=None):
self.address = address
self.controller_address = controller_address
self.delay = delay
self.geometry = geometry if geometry else (900, 580)
self.stop_after = stop_after
self.snapshot_folder = snapshot_folder

self.context = zmq.Context()
self.socket = self.context.socket(zmq.SUB)
Expand Down Expand Up @@ -111,7 +112,8 @@ def run(self):
controller_address=self.controller_address,
geometry=self.geometry,
delay=self.delay,
stop_after=self.stop_after)
stop_after=self.stop_after,
snapshot_folder=self.snapshot_folder)
# schedule next read
self.root.after_idle(self.read_queue)
try:
Expand Down

0 comments on commit 2299359

Please sign in to comment.