Backend only (window follows). Switched on in Settings > Modules. H3 repairs proven on a tiny H3 on CPU, UNTESTED on real weights. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpnF7GwNGMbngU11wjym7P
36 lines
1.4 KiB
Python
36 lines
1.4 KiB
Python
"""One picture out of an image file or a clip, for the tagger. Blocking; call it from a worker thread."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import shutil
|
|
import subprocess
|
|
|
|
from .store import kind_of
|
|
|
|
|
|
def picture(path, at: float = 0.5):
|
|
"""A PIL RGB image: the file itself, or the frame `at` (0 to 1) of the way through a clip."""
|
|
from PIL import Image, ImageOps
|
|
if kind_of(str(path)) == "image":
|
|
with Image.open(path) as img:
|
|
return ImageOps.exif_transpose(img).convert("RGB")
|
|
try:
|
|
import av
|
|
except ImportError:
|
|
av = None
|
|
if av is not None:
|
|
with av.open(str(path)) as box:
|
|
stream = box.streams.video[0]
|
|
if stream.duration and stream.time_base:
|
|
box.seek(int(stream.duration * min(max(at, 0.0), 1.0)), stream=stream)
|
|
for frame in box.decode(stream):
|
|
return frame.to_image().convert("RGB")
|
|
raise ValueError("the clip has no picture in it")
|
|
ff = shutil.which("ffmpeg")
|
|
if not ff:
|
|
raise ValueError("reading a clip needs PyAV or ffmpeg, and neither is here")
|
|
out = subprocess.run([ff, "-v", "error", "-i", str(path), "-vf", "thumbnail", "-frames:v", "1", "-f", "image2pipe", "-vcodec", "png", "-"],
|
|
capture_output=True, check=True).stdout
|
|
return Image.open(io.BytesIO(out)).convert("RGB")
|