feat: optimize mockup images with WebP and auto-regeneration

Convert mockup source images from JPG to WebP format for 76% size
reduction (20MB → 4.7MB). Variants are now auto-generated on startup
via Oban, keeping the same DRY approach as database images.

Changes:
- Add OptimizeWorker.enqueue_mockup/1 for filesystem images
- Extend VariantCache to check mockup sources on startup
- Update MockupGenerator to save source as optimized WebP
- Update .gitignore to ignore generated variants
- Convert 55 source mockups from JPG to WebP

The mockup pipeline now uses the same code paths as database images:
- Optimizer.to_optimized_webp/1 for source conversion
- Optimizer.process_file/3 for variant generation
- OptimizeWorker for Oban background processing
- VariantCache for startup cache validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-25 00:30:42 +00:00
parent 2b5b749a69
commit 252ca2268a
114 changed files with 185 additions and 7 deletions

View File

@@ -328,20 +328,35 @@ defmodule SimpleshopTheme.Printify.MockupGenerator do
end
@doc """
Download mockup images to the output directory.
Download mockup images, save as WebP source, and generate variants.
Sources are saved for regeneration on startup via VariantCache.
"""
def download_mockups(product_slug, mockup_urls) do
alias SimpleshopTheme.Images.Optimizer
File.mkdir_p!(@output_dir)
mockup_urls
|> Enum.with_index(1)
|> Enum.map(fn {url, index} ->
output_path = Path.join(@output_dir, "#{product_slug}-#{index}.jpg")
IO.puts(" Downloading mockup #{index} to #{output_path}...")
basename = "#{product_slug}-#{index}"
source_path = Path.join(@output_dir, "#{basename}.webp")
IO.puts(" Processing mockup #{index}...")
case Client.download_file(url, output_path) do
{:ok, path} -> {:ok, path}
{:error, reason} -> {:error, {url, reason}}
temp_path = Path.join(System.tmp_dir!(), "#{basename}-temp.jpg")
with {:ok, _} <- Client.download_file(url, temp_path),
{:ok, image_data} <- File.read(temp_path),
{:ok, webp_data, source_width, _} <- Optimizer.to_optimized_webp(image_data),
:ok <- File.write(source_path, webp_data),
{:ok, _} <- Optimizer.process_file(webp_data, basename, @output_dir) do
File.rm(temp_path)
IO.puts(" Saved source + variants for #{basename} (#{source_width}px)")
{:ok, basename, source_width}
else
{:error, reason} ->
File.rm(temp_path)
{:error, {url, reason}}
end
end)
end