Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
Python
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184#!/usr/bin/env python3import reimport sysfrom typing import Any
import pyqtgraph as pg # type: ignorefrom 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 tooG.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 1ax[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()