Preview: simplify pyvips timing to single processing ms + req total.

This commit is contained in:
Leo Vasanko
2026-04-25 00:06:35 +00:00
parent 48112dec26
commit bbb73be07e
2 changed files with 112 additions and 16 deletions
+10 -16
View File
@@ -354,10 +354,14 @@ async def preview(req, path):
except PreviewError:
return empty(422)
if preview_resp and preview_resp.backend:
load_ms = int(round(preview_resp.load_ms or 0.0))
process_ms = int(round(preview_resp.process_ms or 0.0))
save_ms = int(round(preview_resp.save_ms or 0.0))
req.ctx._log_extra = f"{preview_resp.backend} {load_ms}/{process_ms}/{save_ms} ="
if preview_resp.load_ms is not None:
load_ms = int(round(preview_resp.load_ms))
process_ms = int(round(preview_resp.process_ms or 0.0))
save_ms = int(round(preview_resp.save_ms or 0.0))
timing_detail = f"{load_ms}/{process_ms}/{save_ms}"
else:
timing_detail = str(int(round(preview_resp.total_ms or 0.0)))
req.ctx._log_extra = f"{preview_resp.backend} {timing_detail}"
if not img:
# Preview generation failed, redirect to the file itself
return redirect(f"/files/{path}", status=303)
@@ -415,16 +419,12 @@ def process_image_with_timing(path, *, maxsize, quality):
def process_image_pyvips(path, *, maxsize, quality):
import pyvips
t_load = perf_counter()
t_start = perf_counter()
img = pyvips.Image.new_from_file(str(path), access="sequential")
t_proc = perf_counter()
img = img.autorot()
scale = min(maxsize / img.width, maxsize / img.height, 1.0)
if scale < 1.0:
img = img.resize(scale)
t_save = perf_counter()
ret = img.write_to_buffer(
".avif",
Q=quality,
@@ -433,17 +433,11 @@ def process_image_pyvips(path, *, maxsize, quality):
)
t_end = perf_counter()
load_ms = (t_proc - t_load) * 1000
proc_ms = (t_save - t_proc) * 1000
save_ms = (t_end - t_save) * 1000
return ret, PreviewResponse(
ok=True,
mime="image/avif",
backend="pyvips",
load_ms=round(load_ms, 1),
process_ms=round(proc_ms, 1),
save_ms=round(save_ms, 1),
total_ms=round((t_end - t_load) * 1000, 1),
total_ms=round((t_end - t_start) * 1000, 1),
)
+102
View File
@@ -0,0 +1,102 @@
import argparse
import mimetypes
from pathlib import Path
from cista.preview import process_image_with_timing
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate image previews for all files in a folder, one at a time.",
)
parser.add_argument("folder", type=Path, help="Folder to scan recursively")
parser.add_argument(
"--px",
type=int,
default=1024,
help="Maximum preview dimension in pixels (default: 1024)",
)
parser.add_argument(
"--quality",
type=int,
default=60,
help="AVIF quality passed to preview generation (default: 60)",
)
return parser.parse_args()
def is_image_file(path: Path) -> bool:
mime_type, _ = mimetypes.guess_type(path.name)
return bool(mime_type and mime_type.startswith("image/"))
def main() -> int:
args = parse_args()
folder = args.folder.resolve()
if not folder.is_dir():
raise SystemExit(f"Not a directory: {folder}")
files = sorted(path for path in folder.rglob("*") if path.is_file() and is_image_file(path))
if not files:
print(f"No image files found under {folder}")
return 0
total_files = 0
total_bytes = 0
total_load_ms: float = 0.0
total_process_ms: float = 0.0
total_save_ms: float = 0.0
total_preview_ms: float = 0.0
failures = 0
print(f"Scanning {folder}")
print(f"Generating previews for {len(files)} image files")
for path in files:
total_files += 1
rel = path.relative_to(folder)
try:
preview, timing = process_image_with_timing(
path,
maxsize=args.px,
quality=args.quality,
)
except Exception as exc:
failures += 1
print(f"FAIL {rel} error={exc}")
continue
total_bytes += len(preview)
total_load_ms += timing.load_ms or 0.0
total_process_ms += timing.process_ms or 0.0
total_save_ms += timing.save_ms or 0.0
total_preview_ms += timing.total_ms or 0.0
if timing.load_ms is not None:
detail = (
f"load={timing.load_ms:.1f}ms process={timing.process_ms:.1f}ms "
f"save={timing.save_ms:.1f}ms total={timing.total_ms:.1f}ms"
)
else:
detail = f"total={timing.total_ms:.1f}ms"
print(f"OK {rel} backend={timing.backend} bytes={len(preview)} {detail}")
completed = total_files - failures
print()
print("Summary")
print(f" files={total_files}")
print(f" completed={completed}")
print(f" failed={failures}")
print(f" preview_bytes={total_bytes}")
if completed:
if total_load_ms or total_process_ms or total_save_ms:
print(f" load_total_ms={total_load_ms:.1f}")
print(f" process_total_ms={total_process_ms:.1f}")
print(f" save_total_ms={total_save_ms:.1f}")
print(f" preview_total_ms={total_preview_ms:.1f}")
print(f" preview_avg_ms={total_preview_ms / completed:.1f}")
return 0 if failures == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())