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+):
|
Ubuntu / Debian (24.04+):
|
||||||
```sh
|
```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:
|
Fedora:
|
||||||
```sh
|
```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:
|
Then clone and install:
|
||||||
|
|||||||
@ -326,21 +326,49 @@ class LibraryView(QWidget):
|
|||||||
threading.Thread(target=_work, daemon=True).start()
|
threading.Thread(target=_work, daemon=True).start()
|
||||||
|
|
||||||
def _capture_video_thumb(self, index: int, source: str, dest: str) -> None:
|
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():
|
def _work():
|
||||||
|
extracted = False
|
||||||
try:
|
try:
|
||||||
import subprocess
|
import threading as _threading
|
||||||
result = subprocess.run(
|
import mpv as mpvlib
|
||||||
["ffmpeg", "-y", "-i", source, "-vframes", "1",
|
|
||||||
"-vf", f"scale={LIBRARY_THUMB_SIZE}:{LIBRARY_THUMB_SIZE}:force_original_aspect_ratio=decrease",
|
frame_ready = _threading.Event()
|
||||||
"-q:v", "5", dest],
|
m = mpvlib.MPV(
|
||||||
capture_output=True, timeout=10,
|
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)
|
self._signals.thumb_ready.emit(index, dest)
|
||||||
return
|
return
|
||||||
except (FileNotFoundError, Exception):
|
|
||||||
pass
|
|
||||||
# Fallback: generate a placeholder
|
# Fallback: generate a placeholder
|
||||||
from PySide6.QtGui import QPainter, QColor, QFont
|
from PySide6.QtGui import QPainter, QColor, QFont
|
||||||
from PySide6.QtGui import QPolygon
|
from PySide6.QtGui import QPolygon
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user