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 : {}; 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 = { "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)}`; }