87 lines
4.1 KiB
TypeScript
87 lines
4.1 KiB
TypeScript
export const VIDEO_DURATION_OPTIONS = [6, 9, 10, 15] as const;
|
||
export const VIDEO_RESOLUTION_OPTIONS = [480, 720, 1080, 1440, 2160] as const;
|
||
export const VIDEO_RESOLUTION_CAPABILITY_OPTIONS = VIDEO_RESOLUTION_OPTIONS.map((value) => `${value}p`);
|
||
export const VIDEO_DURATION_MIN = 1;
|
||
|
||
export function normalizeVideoDuration(value: string | number | undefined) {
|
||
const seconds = Math.floor(Number(value) || VIDEO_DURATION_OPTIONS[0]);
|
||
return String(Math.max(VIDEO_DURATION_MIN, seconds));
|
||
}
|
||
|
||
export function normalizeVideoResolution(value: string | number | undefined) {
|
||
const raw = String(value || "").trim();
|
||
const token = raw.toLowerCase();
|
||
if (!token) return "";
|
||
if (token === "low") return "480";
|
||
if (token === "auto" || token === "medium" || token === "high") return "720";
|
||
if (token === "2k") return "1440";
|
||
if (token === "4k") return "2160";
|
||
const resolution = Number(token.replace(/p$/i, ""));
|
||
if (Number.isFinite(resolution) && resolution > 0) return String(Math.floor(resolution));
|
||
// Channel-declared values are opaque enums, not necessarily numeric tiers.
|
||
// Preserve them verbatim so values such as `768p竖` remain selectable and
|
||
// can be sent back to the provider without being collapsed to 720p.
|
||
return raw;
|
||
}
|
||
|
||
export function normalizeVideoResolutionValue(value: string | number | undefined): string {
|
||
return normalizeVideoResolution(value) || "720";
|
||
}
|
||
|
||
export function isVideoResolutionMatch(selected: string | undefined, target: string | undefined) {
|
||
const s = String(selected || "").trim().toLowerCase().replace(/p$/i, "");
|
||
const t = String(target || "").trim().toLowerCase().replace(/p$/i, "");
|
||
if (!s && !t) return true;
|
||
if (s === t) return true;
|
||
if ((s === "2k" || s === "1440") && (t === "2k" || t === "1440")) return true;
|
||
if ((s === "4k" || s === "2160") && (t === "4k" || t === "2160")) return true;
|
||
if ((s === "768" || s === "768p") && (t === "768" || t === "768p")) return true;
|
||
if ((s === "1080" || s === "1080p") && (t === "1080" || t === "1080p")) return true;
|
||
if ((s === "720" || s === "720p") && (t === "720" || t === "720p")) return true;
|
||
if ((s === "480" || s === "480p") && (t === "480" || t === "480p")) return true;
|
||
return false;
|
||
}
|
||
|
||
export function videoResolutionComparisonKey(value: string | number | undefined) {
|
||
const raw = String(value || "").trim();
|
||
if (!raw) return "";
|
||
const normalized = normalizeVideoResolution(raw);
|
||
return /^\d+$/.test(normalized) ? `${normalized}p` : normalized.toLowerCase();
|
||
}
|
||
|
||
export function formatVideoResolutionLabel(value: string | number | undefined) {
|
||
const raw = String(value || "").trim();
|
||
if (!raw) return "";
|
||
const normalized = normalizeVideoResolution(raw);
|
||
if (normalized === "1440" || normalized.toLowerCase() === "2k") return "2K";
|
||
if (normalized === "2160" || normalized.toLowerCase() === "4k") return "4K";
|
||
if (/^\d+$/.test(normalized)) return `${normalized}P`;
|
||
return normalized.replace(/^(\d+)p/i, "$1P");
|
||
}
|
||
|
||
export function videoDimensionsForRatioAndResolution(ratio: string | undefined, resolution: string | number | undefined) {
|
||
const ratioMatch = String(ratio || "")
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace("×", "x")
|
||
.match(/^(\d+(?:\.\d+)?)(?::|x)(\d+(?:\.\d+)?)$/);
|
||
if (!ratioMatch) return undefined;
|
||
|
||
const ratioWidth = Number(ratioMatch[1]);
|
||
const ratioHeight = Number(ratioMatch[2]);
|
||
if (!Number.isFinite(ratioWidth) || !Number.isFinite(ratioHeight) || ratioWidth <= 0 || ratioHeight <= 0) return undefined;
|
||
|
||
const normalizedResolution = normalizeVideoResolution(resolution);
|
||
const resolutionMatch = normalizedResolution.match(/^(\d+)(?:p)?/i);
|
||
const shortEdge = Number(resolutionMatch?.[1]);
|
||
if (!Number.isFinite(shortEdge) || shortEdge <= 0) return undefined;
|
||
|
||
const aspect = ratioWidth / ratioHeight;
|
||
if (aspect >= 1) return { width: evenDimension(shortEdge * aspect), height: shortEdge };
|
||
return { width: shortEdge, height: evenDimension(shortEdge / aspect) };
|
||
}
|
||
|
||
function evenDimension(value: number) {
|
||
return Math.max(2, Math.round(value / 2) * 2);
|
||
}
|