Add lower-corner layouts, custom blur backgrounds, and classic blur fill.
Ship composition families, optional background images for lower-corner templates, and an optional blurred cover fill for classic letterbox.
This commit is contained in:
@@ -8,7 +8,7 @@ import { audioTagsToMetadata } from "@/lib/audio-tags";
|
||||
import { resolveYouTubeTitle } from "@/lib/titles";
|
||||
import { SONG_TITLE_MAX } from "@/lib/layout";
|
||||
import type { ItemMetadata } from "@/lib/types";
|
||||
import { DEFAULT_LAYOUT, type LayoutSettings } from "@/lib/layout";
|
||||
import { DEFAULT_LAYOUT, isLowerCornerTemplate, type LayoutSettings } from "@/lib/layout";
|
||||
import { DEFAULT_WATERMARK, type WatermarkSettings } from "@/lib/watermark";
|
||||
import { CategorySelect } from "./CategorySelect";
|
||||
import { LayoutStudio } from "./LayoutStudio";
|
||||
@@ -65,6 +65,8 @@ export function UploadForm() {
|
||||
const [jobWatermark, setJobWatermark] = useState<WatermarkSettings>({ ...DEFAULT_WATERMARK });
|
||||
const [jobLayout, setJobLayout] = useState<LayoutSettings>({ ...DEFAULT_LAYOUT });
|
||||
const [logoPreviewUrl, setLogoPreviewUrl] = useState<string | null>(null);
|
||||
const [backgroundImagePath, setBackgroundImagePath] = useState<string | null>(null);
|
||||
const [backgroundPreviewUrl, setBackgroundPreviewUrl] = useState<string | null>(null);
|
||||
const [quota, setQuota] = useState<{
|
||||
maxBatchSize: number;
|
||||
used: number;
|
||||
@@ -89,6 +91,7 @@ export function UploadForm() {
|
||||
return () => {
|
||||
if (imagePreviewUrl) URL.revokeObjectURL(imagePreviewUrl);
|
||||
if (logoPreviewUrl) URL.revokeObjectURL(logoPreviewUrl);
|
||||
if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
|
||||
audioItems.forEach((a) => {
|
||||
if (a.itemImagePreview) URL.revokeObjectURL(a.itemImagePreview);
|
||||
});
|
||||
@@ -258,6 +261,21 @@ export function UploadForm() {
|
||||
return uploaded.path;
|
||||
}
|
||||
|
||||
async function handleBackgroundUpload(file: File) {
|
||||
const preview = URL.createObjectURL(file);
|
||||
if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
|
||||
setBackgroundPreviewUrl(preview);
|
||||
const uploaded = await uploadFile(file, "image");
|
||||
setBackgroundImagePath(uploaded.path);
|
||||
return uploaded.path;
|
||||
}
|
||||
|
||||
function clearBackgroundImage() {
|
||||
if (backgroundPreviewUrl) URL.revokeObjectURL(backgroundPreviewUrl);
|
||||
setBackgroundPreviewUrl(null);
|
||||
setBackgroundImagePath(null);
|
||||
}
|
||||
|
||||
function applyWatermarkToAll(next: WatermarkSettings) {
|
||||
const normalized = { ...DEFAULT_WATERMARK, ...next };
|
||||
setJobWatermark(normalized);
|
||||
@@ -276,6 +294,9 @@ export function UploadForm() {
|
||||
function applyLayoutToAll(next: LayoutSettings) {
|
||||
const normalized = { ...DEFAULT_LAYOUT, ...next };
|
||||
setJobLayout(normalized);
|
||||
if (!isLowerCornerTemplate(normalized.template)) {
|
||||
clearBackgroundImage();
|
||||
}
|
||||
setAudioItems((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
@@ -371,6 +392,10 @@ export function UploadForm() {
|
||||
includeWatermark: effectiveWatermark.mode !== "none",
|
||||
watermark: effectiveWatermark,
|
||||
layout: jobLayout,
|
||||
backgroundImagePath:
|
||||
isLowerCornerTemplate(jobLayout.template) && backgroundImagePath
|
||||
? backgroundImagePath
|
||||
: null,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
@@ -620,6 +645,7 @@ export function UploadForm() {
|
||||
previewImageUrl={
|
||||
audioItems.find((a) => a.itemImagePreview)?.itemImagePreview || imagePreviewUrl
|
||||
}
|
||||
previewBackgroundUrl={backgroundPreviewUrl}
|
||||
songTitle={
|
||||
audioItems[0]?.metadata.songTitle ||
|
||||
audioItems[0]?.metadata.title ||
|
||||
@@ -633,6 +659,9 @@ export function UploadForm() {
|
||||
onWatermarkChange={applyWatermarkToAll}
|
||||
onUploadLogo={handleLogoUpload}
|
||||
onUploadFont={handleFontUpload}
|
||||
onUploadBackground={handleBackgroundUpload}
|
||||
onClearBackground={clearBackgroundImage}
|
||||
hasCustomBackground={Boolean(backgroundImagePath)}
|
||||
logoPreviewUrl={logoPreviewUrl}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user