测试视频复刻

This commit is contained in:
Azmat@qq.com
2026-08-31 10:13:56 +08:00
parent 6b6146e595
commit 284748eeff
7 changed files with 193 additions and 245 deletions
+9 -12
View File
@@ -23,7 +23,6 @@ import { OverlayPortal, useBodyScrollLock } from "../components/overlays";
import { useFileDrop } from "../components/use-file-drop";
import {
DEFAULT_BILLING_RATES,
FC_MODELS,
IMAGE_TYPES,
MAX_IMAGES,
checkRefFile,
@@ -38,11 +37,11 @@ const JOB_KEY = "airshelf:video-replace-job";
const MODE_KEY = "airshelf:video-replace-mode";
const CHARACTER_MARK = "[视频复刻·角色]";
const VIDEO_ACCEPT = "video/mp4,video/quicktime";
// 复刻(商品 / 角色)统一走 Seedance 2.5,单次能出 30 秒,参考视频也按 30 秒收口
// 复刻固定 Seedance 2.5:参考视频只用来提炼分镜稿,成片最长 30 秒。
const REPLACE_SEEDANCE_MODEL = "doubao-seedance-2-5-260628";
const PRODUCT_SOURCE_PURPOSE = "video_replace_product";
const REF_SECONDS_MAX = { product: 30, character: 30 } as const;
const REF_BYTES_MAX = { product: 200 * 1024 * 1024, character: 200 * 1024 * 1024 } as const;
// Seedance 2.5 单次出片上限。参考视频更长时不报错,成片按这个截断。
const SEEDANCE_MAX_OUTPUT_SECONDS = 30;
type ProductSource = "library" | "temporary" | "";
@@ -54,7 +53,7 @@ const REPLACE_MODE_COPY = {
targetLabel: "商品",
targetStep: "2. 选择自己的商品",
videoEmpty: "系统会先把参考视频拆成分镜稿,再照着它换成你的商品",
videoReady: "视频已就绪,将先提炼分镜稿再逐镜复刻",
videoReady: "视频已就绪,将先提炼分镜稿再出片",
libraryTitle: "从商品库选择",
libraryEmpty: "选择已创建的商品",
temporaryTitle: "临时上传素材",
@@ -62,7 +61,7 @@ const REPLACE_MODE_COPY = {
temporaryNoun: "商品图",
temporaryFallback: "临时商品素材",
generatingTitle: "正在进行商品复刻",
generatingCopy: "正在按分镜一镜一镜还原,并换上你的商品",
generatingCopy: "正在按分镜稿出片,并换上你的商品",
reviewingTitle: "正在审核参考素材",
reviewingCopy: "参考图需先通过合规审核,通过后自动开始复刻",
digestingTitle: "正在提炼参考视频",
@@ -81,7 +80,7 @@ const REPLACE_MODE_COPY = {
targetLabel: "角色",
targetStep: "2. 选择自己的角色",
videoEmpty: "系统会先把参考视频拆成分镜稿,再照着它换成你的角色",
videoReady: "视频已就绪,将先提炼分镜稿再逐镜复刻",
videoReady: "视频已就绪,将先提炼分镜稿再出片",
libraryTitle: "从人物库选择",
libraryEmpty: "选择已创建的人物",
temporaryTitle: "临时上传角色",
@@ -89,7 +88,7 @@ const REPLACE_MODE_COPY = {
temporaryNoun: "角色参考图",
temporaryFallback: "临时角色素材",
generatingTitle: "正在进行角色复刻",
generatingCopy: "正在按分镜一镜一镜还原,并换上你的角色",
generatingCopy: "正在按分镜稿出片,并换上你的角色",
reviewingTitle: "正在审核参考素材",
reviewingCopy: "参考图需先通过合规审核,通过后自动开始复刻",
digestingTitle: "正在提炼参考视频",
@@ -178,7 +177,6 @@ function formatClock(seconds: number) {
function clampDuration(seconds: number) {
const rounded = Math.round(Number(seconds) || SEEDANCE_MAX_OUTPUT_SECONDS);
// 60 秒参考视频不是错误:成片取火山能出的最长,分镜稿由模型压缩改编。
return Math.min(SEEDANCE_MAX_OUTPUT_SECONDS, Math.max(4, rounded || SEEDANCE_MAX_OUTPUT_SECONDS));
}
@@ -326,7 +324,7 @@ export function VideoReplacePage({
[modelConfigs],
);
const preferredModel = useMemo(
() => videoConfigs.find((config) => config.name === FC_MODELS[0].name) || videoConfigs[0],
() => videoConfigs.find((config) => config.name === REPLACE_SEEDANCE_MODEL) || videoConfigs[0],
[videoConfigs],
);
@@ -359,7 +357,7 @@ export function VideoReplacePage({
name: item.label || `${copy.temporaryNoun}${index + 1}`,
}));
const aspectRatio = ratioFromSize(videoMeta.width, videoMeta.height);
const outputDuration = clampDuration(videoMeta.duration || 15);
const outputDuration = clampDuration(videoMeta.duration || SEEDANCE_MAX_OUTPUT_SECONDS);
const estimated = estimateCost(preferredModel, {
ratio: aspectRatio,
resolution: "720p",
@@ -620,8 +618,7 @@ export function VideoReplacePage({
const switchReplaceMode = (next: ReplaceMode) => {
if (next === replaceMode || generating) return;
// 商品复刻能传 60 秒,角色复刻只能 15 秒。带着超长视频切过去会一路走到提交才报错,
// 这里直接清掉并说明原因。
// 两种模式都按 30 秒收口。带着超长视频切过去会一路走到提交才报错,这里直接清掉。
const tooLongForNext = (videoMeta.duration || 0) > REF_SECONDS_MAX[next] + 0.5;
if (tooLongForNext) {
setVideoFile(null);