diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-13 13:07:03 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-13 13:07:03 +0100 |
| commit | d531c6b333a1310c08f5c34dfff06613cc3ab54b (patch) | |
| tree | a957adc922d049125fb948d9dc0222dfa5eb3d6c | |
| parent | 7829a18d8049087eaa963fb922a8701cc9851979 (diff) | |
| download | quickshell-d531c6b333a1310c08f5c34dfff06613cc3ab54b.tar.gz quickshell-d531c6b333a1310c08f5c34dfff06613cc3ab54b.zip | |
Haruna clears mpris:artUrl for every video on purpose, so the task
manager shows the live window, which left the island's media pill and
the volume OSD without a picture. For a local video file (by extension)
with no cover, ffmpeg grabs one frame (thumbnail filter, 480 px wide)
into $XDG_RUNTIME_DIR/qs-video-thumb.jpg; artUrl points at it once the
grab succeeds, with the video URL as fragment so Image reloads the
reused file. Component.onCompleted covers a video already playing when
the shell starts, which onVideoUrlChanged does not see.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | desktop/modules/sound/Player.qml | 37 |
1 files changed, 35 insertions, 2 deletions
diff --git a/desktop/modules/sound/Player.qml b/desktop/modules/sound/Player.qml index e32a1b9..7522d80 100644 --- a/desktop/modules/sound/Player.qml +++ b/desktop/modules/sound/Player.qml @@ -12,6 +12,7 @@ pragma Singleton import Quickshell +import Quickshell.Io import Quickshell.Services.Mpris import QtQuick @@ -59,6 +60,38 @@ Singleton { // per track, so the path can repeat while the image behind it changes. // The cache buster makes Image reload instead of showing the last cover. readonly property string artUrl: - (current?.trackArtUrl ?? "") === "" ? "" - : current.trackArtUrl + "#" + encodeURIComponent(title) + (current?.trackArtUrl ?? "") !== "" + ? current.trackArtUrl + "#" + encodeURIComponent(title) + : (videoUrl !== "" && thumbFor === videoUrl) + ? "file://" + thumbPath + "#" + encodeURIComponent(videoUrl) : "" + + // Haruna clears mpris:artUrl for every video on purpose (so the task + // manager shows the live window), so a local video with no cover gets a + // frame grabbed by ffmpeg instead. One file, overwritten per video; the + // URL fragment above makes Image reload it. + readonly property string videoUrl: { + const url = String(current?.metadata?.["xesam:url"] ?? ""); + return (current?.trackArtUrl ?? "") === "" && url.startsWith("file://") + && /\.(mp4|m4v|mkv|webm|avi|mov|wmv|flv|mpe?g|ts|ogv)$/i.test(url) ? url : ""; + } + readonly property string thumbPath: (Quickshell.env("XDG_RUNTIME_DIR") || "/tmp") + "/qs-video-thumb.jpg" + property string thumbFor: "" + + onVideoUrlChanged: grab() + Component.onCompleted: grab() + + function grab() { + if (videoUrl === "") return; + thumb.running = false; + thumb.url = videoUrl; + thumb.command = ["ffmpeg", "-y", "-v", "error", "-i", decodeURIComponent(videoUrl.slice(7)), + "-vf", "thumbnail,scale=480:-2", "-frames:v", "1", thumbPath]; + thumb.running = true; + } + + Process { + id: thumb + property string url: "" + onExited: code => { if (code === 0) root.thumbFor = url; } + } } |
