feat: reduce build size — replace Pillow with cv2, refactor build scripts
All checks were successful
Build Release / create-release (push) Successful in 1s
Build Release / build-docker (push) Successful in 42s
Lint & Test / test (push) Successful in 2m50s
Build Release / build-windows (push) Successful in 3m27s
Build Release / build-linux (push) Successful in 1m59s
All checks were successful
Build Release / create-release (push) Successful in 1s
Build Release / build-docker (push) Successful in 42s
Lint & Test / test (push) Successful in 2m50s
Build Release / build-windows (push) Successful in 3m27s
Build Release / build-linux (push) Successful in 1m59s
- Create utils/image_codec.py with cv2-based image helpers - Replace PIL usage across all routes, filters, and engines with cv2 - Move Pillow from core deps to [tray] optional in pyproject.toml - Extract shared build logic into build-common.sh (detect_version, cleanup, etc.) - Strip unused NumPy/PIL/zeroconf/debug files in build scripts
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
"""Postprocessing template routes."""
|
||||
|
||||
import base64
|
||||
import io
|
||||
import time
|
||||
|
||||
import httpx
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from fastapi import APIRouter, HTTPException, Depends, Query, WebSocket, WebSocketDisconnect
|
||||
|
||||
from wled_controller.api.auth import AuthRequired
|
||||
@@ -198,6 +195,13 @@ async def test_pp_template(
|
||||
|
||||
raw_stream = chain["raw_stream"]
|
||||
|
||||
from wled_controller.utils.image_codec import (
|
||||
encode_jpeg_data_uri,
|
||||
load_image_bytes,
|
||||
load_image_file,
|
||||
thumbnail as make_thumbnail,
|
||||
)
|
||||
|
||||
if isinstance(raw_stream, StaticImagePictureSource):
|
||||
# Static image: load directly
|
||||
from pathlib import Path
|
||||
@@ -209,12 +213,12 @@ async def test_pp_template(
|
||||
async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client:
|
||||
resp = await client.get(source)
|
||||
resp.raise_for_status()
|
||||
pil_image = Image.open(io.BytesIO(resp.content)).convert("RGB")
|
||||
image = load_image_bytes(resp.content)
|
||||
else:
|
||||
path = Path(source)
|
||||
if not path.exists():
|
||||
raise HTTPException(status_code=400, detail=f"Image file not found: {source}")
|
||||
pil_image = Image.open(path).convert("RGB")
|
||||
image = load_image_file(path)
|
||||
|
||||
actual_duration = time.perf_counter() - start_time
|
||||
frame_count = 1
|
||||
@@ -268,53 +272,37 @@ async def test_pp_template(
|
||||
if last_frame is None:
|
||||
raise RuntimeError("No frames captured during test")
|
||||
|
||||
if isinstance(last_frame.image, np.ndarray):
|
||||
pil_image = Image.fromarray(last_frame.image)
|
||||
else:
|
||||
if not isinstance(last_frame.image, np.ndarray):
|
||||
raise ValueError("Unexpected image format from engine")
|
||||
image = last_frame.image
|
||||
|
||||
# Create thumbnail
|
||||
thumbnail_width = 640
|
||||
aspect_ratio = pil_image.height / pil_image.width
|
||||
thumbnail_height = int(thumbnail_width * aspect_ratio)
|
||||
thumbnail = pil_image.copy()
|
||||
thumbnail.thumbnail((thumbnail_width, thumbnail_height), Image.Resampling.LANCZOS)
|
||||
thumb = make_thumbnail(image, 640)
|
||||
|
||||
# Apply postprocessing filters (expand filter_template references)
|
||||
flat_filters = pp_store.resolve_filter_instances(pp_template.filters)
|
||||
if flat_filters:
|
||||
pool = ImagePool()
|
||||
|
||||
def apply_filters(img):
|
||||
arr = np.array(img)
|
||||
def apply_filters(arr):
|
||||
for fi in flat_filters:
|
||||
f = FilterRegistry.create_instance(fi.filter_id, fi.options)
|
||||
result = f.process_image(arr, pool)
|
||||
if result is not None:
|
||||
arr = result
|
||||
return Image.fromarray(arr)
|
||||
return arr
|
||||
|
||||
thumbnail = apply_filters(thumbnail)
|
||||
pil_image = apply_filters(pil_image)
|
||||
thumb = apply_filters(thumb)
|
||||
image = apply_filters(image)
|
||||
|
||||
# Encode thumbnail
|
||||
img_buffer = io.BytesIO()
|
||||
thumbnail.save(img_buffer, format='JPEG', quality=85)
|
||||
img_buffer.seek(0)
|
||||
thumbnail_b64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
|
||||
thumbnail_data_uri = f"data:image/jpeg;base64,{thumbnail_b64}"
|
||||
|
||||
# Encode full-resolution image
|
||||
full_buffer = io.BytesIO()
|
||||
pil_image.save(full_buffer, format='JPEG', quality=90)
|
||||
full_buffer.seek(0)
|
||||
full_b64 = base64.b64encode(full_buffer.getvalue()).decode('utf-8')
|
||||
full_data_uri = f"data:image/jpeg;base64,{full_b64}"
|
||||
# Encode as JPEG
|
||||
thumbnail_data_uri = encode_jpeg_data_uri(thumb, quality=85)
|
||||
full_data_uri = encode_jpeg_data_uri(image, quality=90)
|
||||
|
||||
actual_fps = frame_count / actual_duration if actual_duration > 0 else 0
|
||||
avg_capture_time_ms = (total_capture_time / frame_count * 1000) if frame_count > 0 else 0
|
||||
width, height = pil_image.size
|
||||
thumb_w, thumb_h = thumbnail.size
|
||||
height, width = image.shape[:2]
|
||||
thumb_h, thumb_w = thumb.shape[:2]
|
||||
|
||||
return TemplateTestResponse(
|
||||
full_capture=CaptureImage(
|
||||
|
||||
Reference in New Issue
Block a user