replace ffmpeg with mpv for library video thumbnails
behavior change: video thumbnails in the Library tab are now generated by a headless mpv instance (vo=null, pause=True, screenshot-to-file) instead of shelling out to ffmpeg. Resized to LIBRARY_THUMB_SIZE with PIL. Falls back to the same placeholder on failure. ffmpeg removed from README install commands — no longer a dependency.
This commit is contained in:
parent
0d72b0ec8a
commit
14c81484c9
@ -58,12 +58,12 @@ AUR: [/packages/booru-viewer-git](https://aur.archlinux.org/packages/booru-viewe
|
||||
|
||||
Ubuntu / Debian (24.04+):
|
||||
```sh
|
||||
sudo apt install python3 python3-pip python3-venv mpv libmpv-dev ffmpeg
|
||||
sudo apt install python3 python3-pip python3-venv mpv libmpv-dev
|
||||
```
|
||||
|
||||
Fedora:
|
||||
```sh
|
||||
sudo dnf install python3 python3-pip qt6-qtbase mpv mpv-libs-devel ffmpeg
|
||||
sudo dnf install python3 python3-pip qt6-qtbase mpv mpv-libs-devel
|
||||
```
|
||||
|
||||
Then clone and install:
|
||||
|
||||
@ -326,21 +326,49 @@ class LibraryView(QWidget):
|
||||
threading.Thread(target=_work, daemon=True).start()
|
||||
|
||||
def _capture_video_thumb(self, index: int, source: str, dest: str) -> None:
|
||||
"""Grab first frame from video. Tries ffmpeg, falls back to placeholder."""
|
||||
"""Grab first frame from video using mpv, falls back to placeholder."""
|
||||
def _work():
|
||||
extracted = False
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["ffmpeg", "-y", "-i", source, "-vframes", "1",
|
||||
"-vf", f"scale={LIBRARY_THUMB_SIZE}:{LIBRARY_THUMB_SIZE}:force_original_aspect_ratio=decrease",
|
||||
"-q:v", "5", dest],
|
||||
capture_output=True, timeout=10,
|
||||
import threading as _threading
|
||||
import mpv as mpvlib
|
||||
|
||||
frame_ready = _threading.Event()
|
||||
m = mpvlib.MPV(
|
||||
vo='null', ao='null', aid='no',
|
||||
pause=True, keep_open='yes',
|
||||
terminal=False, config=False,
|
||||
)
|
||||
if Path(dest).exists():
|
||||
try:
|
||||
@m.property_observer('video-params')
|
||||
def _on_params(_name, value):
|
||||
if isinstance(value, dict) and value.get('w'):
|
||||
frame_ready.set()
|
||||
|
||||
m.loadfile(source)
|
||||
if frame_ready.wait(timeout=10):
|
||||
m.command('screenshot-to-file', dest, 'video')
|
||||
finally:
|
||||
m.terminate()
|
||||
|
||||
if Path(dest).exists() and Path(dest).stat().st_size > 0:
|
||||
from PIL import Image
|
||||
with Image.open(dest) as img:
|
||||
img.thumbnail(
|
||||
(LIBRARY_THUMB_SIZE, LIBRARY_THUMB_SIZE),
|
||||
Image.LANCZOS,
|
||||
)
|
||||
if img.mode in ("RGBA", "P"):
|
||||
img = img.convert("RGB")
|
||||
img.save(dest, "JPEG", quality=85)
|
||||
extracted = True
|
||||
except Exception as e:
|
||||
log.debug("mpv thumb extraction failed for %s: %s", source, e)
|
||||
|
||||
if extracted and Path(dest).exists():
|
||||
self._signals.thumb_ready.emit(index, dest)
|
||||
return
|
||||
except (FileNotFoundError, Exception):
|
||||
pass
|
||||
|
||||
# Fallback: generate a placeholder
|
||||
from PySide6.QtGui import QPainter, QColor, QFont
|
||||
from PySide6.QtGui import QPolygon
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user