-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmulti_render.py
183 lines (145 loc) · 4.88 KB
/
multi_render.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import argparse
import random
import sys
from collections import deque
from glob import glob
from os.path import join
import pygame
import pygame.gfxdraw
from recordclass import recordclass
import osr
BLACK = (0, 0, 0)
GRAY = (100, 100, 100)
WHITE = (255, 255, 255)
def pick_color():
return tuple(random.randrange(64, 256) for i in range(3))
def quit():
print("\n")
pygame.quit()
sys.exit(42)
parser = argparse.ArgumentParser(description="osu! replay visualizer")
parser.add_argument("path", help="folder containing replays and mp3")
parser.add_argument("-t", "--tail", help="tail length", type=int, default=100)
parser.add_argument("-r", "--radius", help="circle radius", type=int, default=5)
parser.add_argument(
"-w",
"--no-wipe",
help="don't wipe the screen each frame",
dest="wipe",
action="store_false",
)
parser.add_argument(
"-f", "--no-flip", help="don't flip hr plays", dest="flip", action="store_false"
)
args = parser.parse_args()
pathname = args.path
tail = args.tail
radius = args.radius
wipe = args.wipe
flip = args.flip
files = glob(join(pathname, "**/*.osr"), recursive=True)
if len(files) == 0:
sys.exit("no replays to read")
replays = []
for name in files:
replay = osr.read_file(name, flip)
replays.append(replay)
replays.sort()
n = len(replays)
for r in replays:
print("%2d. %15s - %d" % (n, r.player, r.score))
n -= 1
print("read %d replays" % len(replays))
State = recordclass("State", "replay color x y z trail")
states = []
for replay in replays:
# we don't need anything else on the replay object so remove the references
replay = replay.replay
color = WHITE if len(replays) == 1 else pick_color()
states.append(State(replay, color, 0, 0, 0, deque()))
del replays
HEIGHT = 768
WIDTH = 1366
KEYSIZE = min((WIDTH - 1024) / 5, HEIGHT / len(states))
# This centers the play on the window just like in osu! client, which helps with
# easy overlay for video. It only works for 1366x768 though
X_CHANGE = 273
Y_CHANGE = 89
SCALE = 1.551
def scale(x, y):
return (x + X_CHANGE / SCALE) * SCALE, (y + Y_CHANGE / SCALE) * SCALE
pygame.mixer.pre_init(44100)
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("radius=%d tail=%d" % (radius, tail))
pygame.mixer.music.load(*glob(join(pathname, "*.mp3")))
pygame.mixer.music.play()
pygame.mixer.music.set_volume(0.5)
clock = pygame.time.Clock()
UPDATE_FPS = pygame.USEREVENT
pygame.time.set_timer(UPDATE_FPS, 100)
last_pos = 0
screen.fill(BLACK)
while pygame.mixer.music.get_busy():
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
quit()
elif event.mod & pygame.KMOD_CTRL and event.key == pygame.K_c:
quit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # left mouse button
radius += 1
elif event.button == 3: # right mouse button
radius = max(0, radius - 1)
elif event.button == 4: # scroll up
tail += 10
elif event.button == 5: # scroll down
tail = max(0, tail - 10)
if event.button == 2: # middle mouse button
wipe = not wipe
pygame.display.set_caption("radius=%d tail=%d" % (radius, tail))
elif event.type == UPDATE_FPS:
sys.stderr.write("%5.0f fps\r" % clock.get_fps())
clock.tick()
pos = pygame.mixer.music.get_pos()
if wipe:
screen.fill(BLACK)
lines = []
circles = []
rects = []
for i, state in enumerate(states):
r = state.replay
trail = state.trail
color = state.color
while r and r[0].t <= pos:
p = r.popleft()
state.x, state.y, state.z = p.x, p.y, p.z
circles.append((scale(state.x, state.y), color))
trail.append(p)
o = (scale(state.x, state.y), color)
if o not in circles:
circles.append(o)
while trail and (pos - trail[0].t) > tail:
trail.popleft()
if len(trail) > 1:
points = [scale(p.x, p.y) for p in trail]
lines.append((points, color))
y = i * KEYSIZE
for j, o in enumerate(osr.keys(state.z)):
x = WIDTH - KEYSIZE * 5 + j * KEYSIZE
rects.append(((x, y, KEYSIZE, KEYSIZE), color if o else BLACK))
if tail:
for points, color in lines:
pygame.draw.lines(screen, color, False, points)
if radius:
for (x, y), color in circles:
x, y = int(x), int(y)
pygame.gfxdraw.filled_circle(screen, x, y, radius, color)
pygame.gfxdraw.aacircle(screen, x, y, radius, BLACK)
for rect, color in rects:
pygame.draw.rect(screen, color, rect)
pygame.display.flip()
pygame.quit()