Faster video preview processing, added profiling debug logging.
This commit is contained in:
parent
65c6ed6a17
commit
35d20dedb1
@ -4,6 +4,7 @@ import io
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from pathlib import PurePosixPath
|
from pathlib import PurePosixPath
|
||||||
|
from time import perf_counter
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
from wsgiref.handlers import format_date_time
|
from wsgiref.handlers import format_date_time
|
||||||
|
|
||||||
@ -68,10 +69,16 @@ def dispatch(path, quality, maxsize, maxzoom):
|
|||||||
|
|
||||||
|
|
||||||
def process_image(path, *, maxsize, quality):
|
def process_image(path, *, maxsize, quality):
|
||||||
|
t_load_start = perf_counter()
|
||||||
img = Image.open(path)
|
img = Image.open(path)
|
||||||
w, h = img.size
|
# Force decode to include I/O in load timing
|
||||||
img.thumbnail((min(w, maxsize), min(h, maxsize)))
|
img.load()
|
||||||
# Fix rotation based on EXIF data
|
t_load_end = perf_counter()
|
||||||
|
|
||||||
|
# Resize and orientation fix (processing)
|
||||||
|
orig_w, orig_h = img.size
|
||||||
|
t_proc_start = perf_counter()
|
||||||
|
img.thumbnail((min(orig_w, maxsize), min(orig_h, maxsize)))
|
||||||
try:
|
try:
|
||||||
rotate_values = {3: 180, 6: 270, 8: 90}
|
rotate_values = {3: 180, 6: 270, 8: 90}
|
||||||
orientation = img.getexif().get(274)
|
orientation = img.getexif().get(274)
|
||||||
@ -80,27 +87,72 @@ def process_image(path, *, maxsize, quality):
|
|||||||
img = img.rotate(rotate_values[orientation], expand=True)
|
img = img.rotate(rotate_values[orientation], expand=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error rotating preview image: {e}")
|
logger.error(f"Error rotating preview image: {e}")
|
||||||
# Save as webp
|
t_proc_end = perf_counter()
|
||||||
|
|
||||||
|
# Save as AVIF
|
||||||
imgdata = io.BytesIO()
|
imgdata = io.BytesIO()
|
||||||
print("Image quality", quality)
|
t_save_start = perf_counter()
|
||||||
img.save(imgdata, format="avif", quality=quality)
|
img.save(imgdata, format="avif", quality=quality)
|
||||||
return imgdata.getvalue()
|
t_save_end = perf_counter()
|
||||||
|
|
||||||
|
ret = imgdata.getvalue()
|
||||||
|
|
||||||
|
load_ms = (t_load_end - t_load_start) * 1000
|
||||||
|
proc_ms = (t_proc_end - t_proc_start) * 1000
|
||||||
|
save_ms = (t_save_end - t_save_start) * 1000
|
||||||
|
logger.debug(
|
||||||
|
"Preview image %s: load=%.1fms process=%.1fms save=%.1fms out=%.1fKB %dx%d -> %dx%d q=%d",
|
||||||
|
path.name,
|
||||||
|
load_ms,
|
||||||
|
proc_ms,
|
||||||
|
save_ms,
|
||||||
|
len(ret) / 1024,
|
||||||
|
orig_w,
|
||||||
|
orig_h,
|
||||||
|
getattr(img, "width", 0),
|
||||||
|
getattr(img, "height", 0),
|
||||||
|
quality,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def process_pdf(path, *, maxsize, maxzoom, quality, page_number=0):
|
def process_pdf(path, *, maxsize, maxzoom, quality, page_number=0):
|
||||||
|
t_load_start = perf_counter()
|
||||||
pdf = fitz.open(path)
|
pdf = fitz.open(path)
|
||||||
page = pdf.load_page(page_number)
|
page = pdf.load_page(page_number)
|
||||||
w, h = page.rect[2:4]
|
w, h = page.rect[2:4]
|
||||||
zoom = min(maxsize / w, maxsize / h, maxzoom)
|
zoom = min(maxsize / w, maxsize / h, maxzoom)
|
||||||
mat = fitz.Matrix(zoom, zoom)
|
mat = fitz.Matrix(zoom, zoom)
|
||||||
pix = page.get_pixmap(matrix=mat)
|
pix = page.get_pixmap(matrix=mat) # type: ignore[attr-defined]
|
||||||
return pix.pil_tobytes(format="avif", quality=quality, method=4)
|
t_load_end = perf_counter()
|
||||||
|
|
||||||
|
t_save_start = perf_counter()
|
||||||
|
ret = pix.pil_tobytes(format="avif", quality=quality, method=4)
|
||||||
|
t_save_end = perf_counter()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Preview pdf %s: load+render=%.1fms save=%.1fms out=%.1fKB page=%d zoom=%.2f",
|
||||||
|
path.name,
|
||||||
|
(t_load_end - t_load_start) * 1000,
|
||||||
|
(t_save_end - t_save_start) * 1000,
|
||||||
|
len(ret) / 1024,
|
||||||
|
page_number,
|
||||||
|
zoom,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def process_video(path, *, maxsize, quality):
|
def process_video(path, *, maxsize, quality):
|
||||||
frame = None
|
frame = None
|
||||||
imgdata = io.BytesIO()
|
imgdata = io.BytesIO()
|
||||||
istream = ostream = icc = occ = frame = None
|
istream = ostream = icc = occ = frame = None
|
||||||
|
t_load_start = perf_counter()
|
||||||
|
# Initialize to avoid "possibly unbound" in static analysis when exceptions occur
|
||||||
|
t_load_end = t_load_start
|
||||||
|
t_save_start = t_load_start
|
||||||
|
t_save_end = t_load_start
|
||||||
with (
|
with (
|
||||||
av.open(str(path)) as icontainer,
|
av.open(str(path)) as icontainer,
|
||||||
av.open(imgdata, "w", format="avif") as ocontainer,
|
av.open(imgdata, "w", format="avif") as ocontainer,
|
||||||
@ -150,9 +202,13 @@ def process_video(path, *, maxsize, quality):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.exception(f"Error rotating video frame: {e}")
|
logger.exception(f"Error rotating video frame: {e}")
|
||||||
|
t_load_end = perf_counter()
|
||||||
|
|
||||||
|
t_save_start = perf_counter()
|
||||||
crf = str(int(63 * (1 - quality / 100) ** 2)) # Closely matching PIL quality-%
|
crf = str(int(63 * (1 - quality / 100) ** 2)) # Closely matching PIL quality-%
|
||||||
ostream = ocontainer.add_stream("av1", options={"crf": crf})
|
ostream = ocontainer.add_stream(
|
||||||
|
"av1", options={"crf": crf, "usage": "realtime"}
|
||||||
|
)
|
||||||
assert isinstance(ostream, av.VideoStream)
|
assert isinstance(ostream, av.VideoStream)
|
||||||
ostream.width = frame.width
|
ostream.width = frame.width
|
||||||
ostream.height = frame.height
|
ostream.height = frame.height
|
||||||
@ -167,8 +223,22 @@ def process_video(path, *, maxsize, quality):
|
|||||||
|
|
||||||
ocontainer.mux(ostream.encode(frame))
|
ocontainer.mux(ostream.encode(frame))
|
||||||
ocontainer.mux(ostream.encode(None)) # Flush the stream
|
ocontainer.mux(ostream.encode(None)) # Flush the stream
|
||||||
|
t_save_end = perf_counter()
|
||||||
|
|
||||||
|
# Capture frame dimensions before cleanup
|
||||||
|
fw = getattr(frame, "width", 0) if frame else 0
|
||||||
|
fh = getattr(frame, "height", 0) if frame else 0
|
||||||
ret = imgdata.getvalue()
|
ret = imgdata.getvalue()
|
||||||
|
logger.debug(
|
||||||
|
"Preview video %s: load+decode=%.1fms save=%.1fms out=%.1fKB dims=%dx%d q=%d",
|
||||||
|
path.name,
|
||||||
|
(t_load_end - t_load_start) * 1000,
|
||||||
|
(t_save_end - t_save_start) * 1000,
|
||||||
|
len(ret) / 1024,
|
||||||
|
fw,
|
||||||
|
fh,
|
||||||
|
quality,
|
||||||
|
)
|
||||||
del imgdata, istream, ostream, icc, occ, frame
|
del imgdata, istream, ostream, icc, occ, frame
|
||||||
gc.collect()
|
gc.collect()
|
||||||
return ret
|
return ret
|
||||||
|
Loading…
x
Reference in New Issue
Block a user