102 lines
4.5 KiB
TypeScript
102 lines
4.5 KiB
TypeScript
export const VIDEO_WATERMARK_RULES_STORAGE_KEY = "video-canvas-watermark-rules-v1";
|
||
|
||
export type VideoCanvasVideoDownloadConfig = {
|
||
acknowledgementText: string;
|
||
applicationText: string;
|
||
responsibilityText: string;
|
||
rulesLinkLabel: string;
|
||
rulesTitle: string;
|
||
rulesUrl: string;
|
||
welcomeText: string;
|
||
};
|
||
|
||
export const defaultVideoCanvasVideoDownloadConfig: VideoCanvasVideoDownloadConfig = {
|
||
acknowledgementText:
|
||
'开启本功能开关并点击 "保存设置" 按钮,即视为您已充分知悉并同意本规则全部内容。后续您可通过 "头像 - AI水印设置" 路径调整本功能配置。',
|
||
applicationText:
|
||
'您申请我们向您提供没有添加显式标识(以下或称"水印")的AI生成合成内容之前,亦即您使用"去水印"功能之前,请您务必认真阅读并充分理解本规则。您知悉、同意并遵守以下内容的,方可使用本功能。',
|
||
responsibilityText:
|
||
"经您提出申请,{{siteName}}平台可向您提供未添加前述显式标识的AI生成合成内容。请注意:您在使用网络信息内容传播服务发布该等AI生成合成内容时,需主动作出声明,并通过传播{{siteName}}平台提供的标识功能完成标识操作。您确认已知晓并承诺:若您未依据法律法规要求在AI生成合成内容中添加显式标识,进而引发公众混淆或误认,由此产生的全部后果及法律责任均由您自行承担。更详细的说明见",
|
||
rulesLinkLabel: "{{siteName}}水印规范",
|
||
rulesTitle: "AI 生成内容水印管理规则",
|
||
rulesUrl: "https://resonate.feishu.cn/wiki/",
|
||
welcomeText: "欢迎您使用{{siteName}}的产品和服务!",
|
||
};
|
||
|
||
function normalizeDownloadCopy(value: unknown, fallback: string, maxLength: number) {
|
||
const normalized = typeof value === "string" ? value.trim().slice(0, maxLength) : "";
|
||
return normalized || fallback;
|
||
}
|
||
|
||
function normalizeRulesUrl(value: unknown, fallback: string) {
|
||
const candidate = typeof value === "string" ? value.trim().slice(0, 2048) : "";
|
||
if (candidate.startsWith("/") && !candidate.startsWith("//")) return candidate;
|
||
try {
|
||
const url = new URL(candidate);
|
||
return url.protocol === "http:" || url.protocol === "https:" ? candidate : fallback;
|
||
} catch {
|
||
return fallback;
|
||
}
|
||
}
|
||
|
||
export function normalizeVideoCanvasVideoDownloadConfig(
|
||
value: unknown,
|
||
): VideoCanvasVideoDownloadConfig {
|
||
const incoming = value && typeof value === "object" && !Array.isArray(value)
|
||
? value as Record<string, unknown>
|
||
: {};
|
||
const fallback = defaultVideoCanvasVideoDownloadConfig;
|
||
return {
|
||
acknowledgementText: normalizeDownloadCopy(
|
||
incoming.acknowledgementText,
|
||
fallback.acknowledgementText,
|
||
2_000,
|
||
),
|
||
applicationText: normalizeDownloadCopy(incoming.applicationText, fallback.applicationText, 4_000),
|
||
responsibilityText: normalizeDownloadCopy(
|
||
incoming.responsibilityText,
|
||
fallback.responsibilityText,
|
||
6_000,
|
||
),
|
||
rulesLinkLabel: normalizeDownloadCopy(incoming.rulesLinkLabel, fallback.rulesLinkLabel, 120),
|
||
rulesTitle: normalizeDownloadCopy(incoming.rulesTitle, fallback.rulesTitle, 120),
|
||
rulesUrl: normalizeRulesUrl(incoming.rulesUrl, fallback.rulesUrl),
|
||
welcomeText: normalizeDownloadCopy(incoming.welcomeText, fallback.welcomeText, 500),
|
||
};
|
||
}
|
||
|
||
export function resolveVideoDownloadRuleCopy(value: string, siteName: string) {
|
||
return value.replaceAll("{{siteName}}", siteName.trim() || "AI平台");
|
||
}
|
||
|
||
const VIDEO_EXTENSION_BY_CONTENT_TYPE: Record<string, string> = {
|
||
"video/mp4": "mp4",
|
||
"video/quicktime": "mov",
|
||
"video/webm": "webm",
|
||
};
|
||
|
||
export function sanitizeVideoDownloadTitle(value: string) {
|
||
return (value.trim() || "视频").replace(/[\\/:*?"<>|]+/g, "-").slice(0, 80);
|
||
}
|
||
|
||
export function getVideoDownloadExtension(source: string, contentType = "") {
|
||
const normalizedContentType = contentType.split(";", 1)[0]?.trim().toLowerCase();
|
||
if (normalizedContentType && VIDEO_EXTENSION_BY_CONTENT_TYPE[normalizedContentType]) {
|
||
return VIDEO_EXTENSION_BY_CONTENT_TYPE[normalizedContentType];
|
||
}
|
||
|
||
try {
|
||
const pathname = new URL(source, "https://canvas.local").pathname;
|
||
const extension = pathname.match(/\.([a-z0-9]{2,5})$/i)?.[1]?.toLowerCase();
|
||
if (extension && ["mp4", "mov", "m4v", "webm"].includes(extension)) return extension;
|
||
} catch {
|
||
// Fall through to the interoperable video default.
|
||
}
|
||
|
||
return "mp4";
|
||
}
|
||
|
||
export function getVideoDownloadFilename(title: string, source: string, contentType = "") {
|
||
return `${sanitizeVideoDownloadTitle(title)}.${getVideoDownloadExtension(source, contentType)}`;
|
||
}
|