[mirror of https://git.0x0.st/mia/0x0] No-bullshit file hosting and URL shortening service https://0x0.st
1import time
2
3import fcntl
4import struct
5import termios
6
7from sys import stdout
8
9from textual import events, log
10from textual.widgets import Static
11
12from fhost import app as fhost_app
13
14
15class MpvWidget(Static):
16 def __init__(self, **kwargs):
17 super().__init__(**kwargs)
18
19 self.mpv = None
20 self.vo = fhost_app.config.get("MOD_PREVIEW_PROTO")
21
22 if self.vo not in ["sixel", "kitty"]:
23 self.update("⚠ Previews not enabled. \n\nSet MOD_PREVIEW_PROTO "
24 "to 'sixel' or 'kitty' in config.py,\nwhichever is "
25 "supported by your terminal.")
26 else:
27 try:
28 import mpv
29 self.mpv = mpv.MPV()
30 self.mpv.profile = "sw-fast"
31 self.mpv["vo"] = self.vo
32 self.mpv[f"vo-{self.vo}-config-clear"] = False
33 self.mpv[f"vo-{self.vo}-alt-screen"] = False
34 self.mpv[f"vo-sixel-buffered"] = True
35 self.mpv["audio"] = False
36 self.mpv["loop-file"] = "inf"
37 self.mpv["image-display-duration"] = 0.5 \
38 if self.vo == "sixel" else "inf"
39 except Exception as e:
40 self.mpv = None
41 self.update("⚠ Previews require python-mpv with libmpv "
42 "0.36.0 or later \n\nError was:\n"
43 f"{type(e).__name__}: {e}")
44
45 def start_mpv(self, f: str | None = None,
46 pos: float | str | None = None) -> None:
47 self.display = True
48 self.screen._refresh_layout()
49
50 if self.mpv:
51 if self.content_region.x:
52 winsz = fcntl.ioctl(0, termios.TIOCGWINSZ, '12345678')
53 r, c, w, h = struct.unpack('hhhh', winsz)
54 width = int((w / c) * self.content_region.width)
55 height = int((h / r) * (self.content_region.height +
56 (1 if self.vo == "sixel" else 0)))
57 self.mpv[f"vo-{self.vo}-left"] = self.content_region.x + 1
58 self.mpv[f"vo-{self.vo}-top"] = self.content_region.y + 1
59 self.mpv[f"vo-{self.vo}-rows"] = self.content_region.height + \
60 (1 if self.vo == "sixel" else 0)
61 self.mpv[f"vo-{self.vo}-cols"] = self.content_region.width
62 self.mpv[f"vo-{self.vo}-width"] = width
63 self.mpv[f"vo-{self.vo}-height"] = height
64
65 if pos is not None:
66 self.mpv["start"] = pos
67
68 if f:
69 self.mpv.loadfile(f)
70 else:
71 self.mpv.playlist_play_index(0)
72
73 def stop_mpv(self, wait: bool = False) -> None:
74 if self.mpv:
75 if not self.mpv.idle_active:
76 self.mpv.stop(True)
77 if wait:
78 time.sleep(0.1)
79 self.clear_mpv()
80 self.display = False
81
82 def on_resize(self, size) -> None:
83 if self.mpv:
84 if not self.mpv.idle_active:
85 t = self.mpv.time_pos
86 self.stop_mpv()
87 if t:
88 self.mpv["start"] = t
89 self.start_mpv()
90
91 def clear_mpv(self) -> None:
92 if self.vo == "kitty":
93 stdout.write("\033_Ga=d;\033\\")
94 stdout.flush()
95
96 def shutdown(self) -> None:
97 if self.mpv:
98 self.mpv.stop()
99 del self.mpv
100 if self.vo == "kitty":
101 stdout.write("\033_Ga=d;\033\\\033[?25l")
102 stdout.flush()