Client-side optimization pipeline with apply-to-J-ID

Backend:
- POST /api/files/J-x/optimize/ applies a browser-processed file: replaces
  every copy (renaming when the extension changes), recomputes MD5, size
  and perceptual hashes, seeds guest visibility; 400 when identical,
  409 when the result matches another item, owner/staff only.

Frontend (no server-side processing by design):
- optimize.worker.ts + pipelines: Mediabunny/WebCodecs for video with a
  prefer-hardware hint and per-browser codec detection; MozJPEG/OxiPNG/
  libwebp (jSquash) for images; gifuct-js+gifenc and UPNG for GIF/APNG.
- OptimizeModal: per-file-type options, original vs processed previews
  with sizes/savings, progress bar with ETA, then Apply (overwrite).
- Optimize button on the library detail for the uploader/staff.
This commit is contained in:
2026-09-17 20:03:22 -05:00
parent 90e61bb333
commit bab9904fc8
14 changed files with 1721 additions and 8 deletions
+32
View File
@@ -0,0 +1,32 @@
import { canEncodeVideo, getEncodableVideoCodecs, type VideoCodec } from "mediabunny";
export interface VideoCodecSupport {
codec: VideoCodec;
supported: boolean;
hardware: boolean;
}
const VIDEO_CODEC_CANDIDATES: VideoCodec[] = ["avc", "hevc", "vp9", "av1"];
/** Which codecs this browser can encode, and whether hardware is preferred. */
export async function detectVideoCodecs(): Promise<VideoCodecSupport[]> {
const supported = new Set(await getEncodableVideoCodecs());
const results: VideoCodecSupport[] = [];
for (const codec of VIDEO_CODEC_CANDIDATES) {
const encodable = supported.has(codec);
let hardware = false;
if (encodable) {
try {
hardware = await canEncodeVideo(codec, {
width: 1920,
height: 1080,
hardwareAcceleration: "prefer-hardware",
});
} catch {
hardware = false;
}
}
results.push({ codec, supported: encodable, hardware });
}
return results;
}