mirror of
https://github.com/mpv-player/mpv.git
synced 2025-12-22 10:57:08 +00:00
To make mypy happy. PyQtGraph doesn't define types, so ignore those. PyQt6 has types, but we use only one function from it, so to avoid installing it, ignore it too.
184 lines
5.2 KiB
Python
Executable File
184 lines
5.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import re
|
|
import sys
|
|
from typing import Any
|
|
|
|
import pyqtgraph as pg # type: ignore
|
|
from PyQt6 import QtWidgets # type: ignore
|
|
|
|
filename = sys.argv[1]
|
|
|
|
events = ".*"
|
|
if len(sys.argv) > 2:
|
|
events = sys.argv[2]
|
|
event_regex = re.compile(events)
|
|
|
|
"""
|
|
This script is meant to display stats written by mpv --dump-stats=filename.
|
|
In general, each line in that file is an event of the form:
|
|
|
|
<timestamp in microseconds> <text> '#' <comment>
|
|
|
|
e.g.:
|
|
|
|
10474959 start flip #cplayer
|
|
|
|
<text> is what MP_STATS(log, "...") writes. The rest is added by msg.c.
|
|
|
|
Currently, the following event types are supported:
|
|
|
|
'signal' <name> singular event
|
|
'start' <name> start of the named event
|
|
'end' <name> end of the named event
|
|
'value' <float> <name> a normal value (as opposed to event)
|
|
'event-timed' <ts> <name> singular event at the given timestamp
|
|
'value-timed' <ts> <float> <name> a value for an event at the given timestamp
|
|
'range-timed' <ts1> <ts2> <name> like start/end, but explicit times
|
|
<name> singular event (same as 'signal')
|
|
|
|
"""
|
|
|
|
class Event:
|
|
name: str
|
|
vals: list[tuple[float, float]] = []
|
|
type: str
|
|
marker = ""
|
|
|
|
class G:
|
|
events: dict[str, Event] = {}
|
|
start = 0.0
|
|
markers = ["o", "s", "t", "d"]
|
|
curveno: dict[Any, int] = {}
|
|
sevents: list[Event] = []
|
|
|
|
def find_marker():
|
|
if len(G.markers) == 0:
|
|
return "o"
|
|
m = G.markers[0]
|
|
G.markers = G.markers[1:]
|
|
return m
|
|
|
|
def get_event(event, evtype):
|
|
if event not in G.events:
|
|
e = Event()
|
|
e.name = event
|
|
e.vals = []
|
|
e.type = evtype
|
|
e.marker = "o"
|
|
if e.type == "event-signal":
|
|
e.marker = find_marker()
|
|
if not event_regex.match(e.name):
|
|
return e
|
|
G.events[event] = e
|
|
return G.events[event]
|
|
|
|
colors = [
|
|
(0.0, 0.5, 0.0),
|
|
(0.0, 0.0, 1.0),
|
|
(0.0, 0.0, 0.0),
|
|
(1.0, 0.0, 0.0),
|
|
(0.75, 0.75, 0),
|
|
(0.0, 0.75, 0.75),
|
|
(0.75, 0, 0.75),
|
|
]
|
|
|
|
def mk_color(t):
|
|
return pg.mkColor(int(t[0] * 255), int(t[1] * 255), int(t[2] * 255))
|
|
|
|
SCALE = 1e6 # microseconds to seconds
|
|
|
|
with open(filename) as file:
|
|
for line in file:
|
|
line = line.split("#")[0].strip()
|
|
if not line:
|
|
continue
|
|
ts_str, event = line.split(" ", 1)
|
|
ts = int(ts_str) / SCALE
|
|
if G.start is None:
|
|
G.start = ts
|
|
ts -= G.start
|
|
|
|
match event.split(" ", 1):
|
|
case ["start", name]:
|
|
e = get_event(name, "event")
|
|
e.vals.append((ts, 0))
|
|
e.vals.append((ts, 1))
|
|
case ["end", name]:
|
|
e = get_event(name, "event")
|
|
e.vals.append((ts, 1))
|
|
e.vals.append((ts, 0))
|
|
case ["value", rest]:
|
|
val, name = rest.split(" ", 1)
|
|
e = get_event(name, "value")
|
|
e.vals.append((ts, float(val)))
|
|
case ["event-timed", rest]:
|
|
val, name = rest.split(" ", 1)
|
|
e = get_event(name, "event-signal")
|
|
e.vals.append((int(val) / SCALE - G.start, 1))
|
|
case ["range-timed", rest]:
|
|
ts1_str, ts2_str, name = rest.split(" ", 2)
|
|
ts1 = int(ts1_str) / SCALE - G.start
|
|
ts2 = int(ts2_str) / SCALE - G.start
|
|
e = get_event(name, "event")
|
|
e.vals.append((ts1, 0))
|
|
e.vals.append((ts1, 1))
|
|
e.vals.append((ts2, 1))
|
|
e.vals.append((ts2, 0))
|
|
case ["value-timed", rest]:
|
|
tsval, val, name = rest.split(" ", 2)
|
|
e = get_event(name, "value")
|
|
e.vals.append((int(tsval) / SCALE - G.start, float(val)))
|
|
case ["signal", name]:
|
|
e = get_event(name, "event-signal")
|
|
e.vals.append((ts, 1))
|
|
case _:
|
|
e = get_event(event, "event-signal")
|
|
e.vals.append((ts, 1))
|
|
|
|
# deterministically sort them; make sure the legend is sorted too
|
|
G.sevents = list(G.events.values())
|
|
G.sevents.sort(key=lambda x: x.name)
|
|
hasval = False
|
|
|
|
for e, index in zip(G.sevents, range(len(G.sevents))):
|
|
m = len(G.sevents)
|
|
if e.type == "value":
|
|
hasval = True
|
|
else:
|
|
e.vals = [(x, y * (m - index) / m) for (x, y) in e.vals]
|
|
|
|
pg.setConfigOption("background", "w")
|
|
pg.setConfigOption("foreground", "k")
|
|
app = QtWidgets.QApplication([])
|
|
win = pg.GraphicsLayoutWidget()
|
|
win.show()
|
|
|
|
ax = [None, None]
|
|
plots = 2 if hasval else 1
|
|
ax[0] = win.addPlot()
|
|
|
|
if hasval:
|
|
win.nextRow()
|
|
ax[1] = win.addPlot()
|
|
ax[1].setXLink(ax[0]) # type: ignore
|
|
|
|
for cur in ax:
|
|
if cur is not None:
|
|
cur.addLegend(offset = (-1, 1))
|
|
|
|
for e in G.sevents:
|
|
cur = ax[1 if e.type == "value" else 0]
|
|
if cur not in G.curveno:
|
|
G.curveno[cur] = 0
|
|
args = {"name": e.name,"antialias":True}
|
|
color = mk_color(colors[G.curveno[cur] % len(colors)])
|
|
if e.type == "event-signal":
|
|
args["symbol"] = e.marker
|
|
args["symbolBrush"] = pg.mkBrush(color, width=0)
|
|
else:
|
|
args["pen"] = pg.mkPen(color, width=0)
|
|
G.curveno[cur] += 1
|
|
cur.plot(*zip(*e.vals), **args) # type: ignore
|
|
|
|
app.exec()
|