Skip to content

Commit 5fb27c1

Browse files
add 'run_and_record()'
1 parent 34b8788 commit 5fb27c1

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

src/asyncpygame/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
__all__ = (
2-
'run', 'quit', 'Clock', 'SDLEvent', 'PriorityExecutor',
2+
'run', 'quit', 'run_and_record', 'Clock', 'SDLEvent', 'PriorityExecutor',
33
'CommonParams', 'capture_current_frame', 'block_input_events',
44
)
55

66
from asyncgui import *
77
from asyncgui_ext.clock import Clock
8-
from ._runner import run, quit
8+
from ._runner import run, quit, run_and_record
99
from ._sdlevent import SDLEvent
1010
from ._priority_executor import PriorityExecutor
1111
from ._utils import CommonParams, capture_current_frame, block_input_events

src/asyncpygame/_runner.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ("run", "quit", )
1+
__all__ = ("run", "quit", "run_and_record", )
22

33
import pygame
44
import asyncpygame as ap
@@ -43,3 +43,72 @@ def run(main_func, *, fps=30, auto_quit=True):
4343
raise ap.ExceptionGroup(group.message, unignorable_excs)
4444
finally:
4545
main_task.cancel()
46+
47+
48+
def run_and_record(main_func, *, fps=30, auto_quit=True, output_file="./output.mkv", overwrite=False, codec='libx265',
49+
quality=0):
50+
'''
51+
Runs the program while recording the screen to a video file using ffmpeg.
52+
Requires numpy.
53+
'''
54+
import subprocess
55+
from pygame.surfarray import pixels3d
56+
57+
clock = ap.Clock()
58+
sdlevent = ap.SDLEvent()
59+
executor = ap.PriorityExecutor()
60+
main_task = ap.start(main_func(clock=clock, sdlevent=sdlevent, executor=executor))
61+
screen = pygame.display.get_surface()
62+
63+
if auto_quit:
64+
sdlevent.subscribe((pygame.QUIT, ), quit, priority=0)
65+
sdlevent.subscribe((pygame.KEYDOWN, ), lambda e, K=pygame.K_ESCAPE: e.key == K and quit(), priority=0)
66+
67+
ffmpeg_cmd = (
68+
'ffmpeg',
69+
'-y' if overwrite else '-n',
70+
'-f', 'rawvideo',
71+
'-codec:v', 'rawvideo',
72+
'-pixel_format', 'rgb24',
73+
'-video_size', f'{screen.width}x{screen.height}',
74+
'-framerate', str(fps),
75+
'-i', '-', # stdin as the input source
76+
'-an', # no audio
77+
'-codec:v', codec,
78+
'-qscale:v', str(quality),
79+
output_file,
80+
)
81+
process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE, bufsize=0)
82+
83+
# LOAD_FAST
84+
pygame_event_get = pygame.event.get
85+
clock_tick = clock.tick
86+
sdlevent_dispatch = sdlevent.dispatch
87+
process_stdin_write = process.stdin.write
88+
screen_lock = screen.lock
89+
screen_unlock = screen.unlock
90+
91+
try:
92+
dt = 1000.0 / fps
93+
while True:
94+
for event in pygame_event_get():
95+
sdlevent_dispatch(event)
96+
clock_tick(dt)
97+
executor()
98+
99+
screen_lock()
100+
frame = pixels3d(screen)
101+
frame = frame.transpose((1, 0, 2)) # 幅 高さ 色 の順にする
102+
process_stdin_write(frame.tobytes())
103+
del frame
104+
screen_unlock()
105+
except AppQuit:
106+
pass
107+
except ap.ExceptionGroup as group:
108+
unignorable_excs = tuple(e for e in group.exceptions if not isinstance(e, AppQuit))
109+
if unignorable_excs:
110+
raise ap.ExceptionGroup(group.message, unignorable_excs)
111+
finally:
112+
main_task.cancel()
113+
process.stdin.close()
114+
process.wait()

0 commit comments

Comments
 (0)