测试视频复刻
This commit is contained in:
@@ -1231,20 +1231,6 @@
|
||||
.sb-scene-thumb .placeholder .sb-frame-rv .rv-label { display: none; }
|
||||
.sb-main-img .sb-main-rv { position: absolute; top: 10px; left: 10px; z-index: 4; }
|
||||
|
||||
/* 故事板说明层:从已确认脚本直接排版,不把文字烧进给视频模型的关键帧。 */
|
||||
.sb-director-board { margin: 0 0 14px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); overflow: hidden; }
|
||||
.sb-director-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--border-faint); background: var(--surface); }
|
||||
.sb-director-head > div { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
|
||||
.sb-director-head .mono { font-family: var(--font-mono); font-size: 10px; letter-spacing: .05em; color: var(--black-alpha-48); white-space: nowrap; }
|
||||
.sb-director-head strong { font-size: 12px; font-weight: 500; color: var(--accent-black); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.sb-director-beat { display: grid; grid-template-columns: 54px minmax(0, 1fr); border-bottom: 1px solid var(--border-faint); }
|
||||
.sb-director-beat:last-child { border-bottom: 0; }
|
||||
.sb-director-time { display: flex; align-items: flex-start; justify-content: center; padding: 11px 6px; background: var(--surface); color: var(--heat); font-family: var(--font-mono); font-size: 11px; font-variant-numeric: tabular-nums; }
|
||||
.sb-director-copy { min-width: 0; padding: 10px 12px; }
|
||||
.sb-director-direction { color: var(--accent-black); font-size: 12px; line-height: 1.55; }
|
||||
.sb-director-narration { margin-top: 5px; color: var(--black-alpha-56); font-size: 11.5px; line-height: 1.55; }
|
||||
.sb-director-empty { padding: 14px 12px; color: var(--black-alpha-48); font-size: 12px; line-height: 1.6; }
|
||||
|
||||
.sb-rerun-note { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; margin-bottom: 14px; background: rgba(180,83,9,.08); border: 1px solid rgba(180,83,9,.20); border-radius: var(--r-md); color: #7C3A05; line-height: 1.55; }
|
||||
.sb-rerun-note .warn-ic { width: 22px; height: 22px; border-radius: var(--r-sm); background: rgba(180,83,9,.12); color: #B45309; display: grid; place-items: center; flex: 0 0 22px; }
|
||||
.sb-rerun-note .warn-ic svg { width: 14px; height: 14px; }
|
||||
|
||||
@@ -34,26 +34,6 @@ const KIND_LABEL: Record<string, string> = { product: "商品", person: "角色"
|
||||
// 脚本来源 → 「来源」brief pill 文案
|
||||
// 入口收敛为「脚本辅助生成 / 上传脚本」两种。theme 键保留:历史稿的 source 仍可能是旧的「一句话主题」。
|
||||
const SOURCE_LABEL: Record<string, string> = { ai: "脚本辅助生成", theme: "脚本辅助生成", manual: "上传脚本", video: "上传视频提炼" };
|
||||
type DirectorBeat = { time: string; direction: string; narration: string };
|
||||
const DIRECTOR_BEAT_RE = /(?:^|\n)\s*(\d{1,2})\s*[-–—~到至]\s*(\d{1,2})\s*(?:s|秒)?\s*[::]*\s*([^\n]+)/g;
|
||||
function buildDirectorBeats(visual: string, narration: string, duration: number): DirectorBeat[] {
|
||||
const matches = [...(visual || "").matchAll(DIRECTOR_BEAT_RE)];
|
||||
const narrationLines = (narration || "").split(/(?<=[。!?!?])/).map((line) => line.trim()).filter(Boolean);
|
||||
if (matches.length) {
|
||||
return matches.slice(0, 5).map((match, index) => ({
|
||||
time: `${match[1]}–${match[2]}s`,
|
||||
direction: match[3].trim(),
|
||||
narration: narrationLines[index] || (index === 0 ? narration.trim() : ""),
|
||||
}));
|
||||
}
|
||||
const count = Math.min(3, Math.max(1, narrationLines.length || 1));
|
||||
const step = duration / count;
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
time: `${Math.round(index * step)}–${Math.round((index + 1) * step)}s`,
|
||||
direction: visual.trim() || "按本镜脚本完成关键动作与商品露出",
|
||||
narration: narrationLines[index] || (index === 0 ? narration.trim() : ""),
|
||||
}));
|
||||
}
|
||||
// 旁白配音音色预设(语音合成经典版试用包实测可用,与后端 VOICEOVER_VOICES 对齐)
|
||||
const VO_VOICES = [
|
||||
{ key: "BV700_streaming", label: "灿灿 · 活力女声" },
|
||||
@@ -2070,7 +2050,7 @@ export function PipelinePage(props: {
|
||||
document.body.style.userSelect = "";
|
||||
};
|
||||
}, [gutterDragging]);
|
||||
const SB_PROMPT_DEFAULT = "统一商品、人物、场景风格,生成干净、可直接指导视频的关键帧;导演分镜说明按脚本自动生成";
|
||||
const SB_PROMPT_DEFAULT = "统一商品、人物、场景风格,生成完整、可直接指导视频的导演故事板";
|
||||
// 整张风格提示词:项目级(原 StoryboardVersion.prompt 的去处),重跑时生效
|
||||
const sbSavedPrompt = (project.metadata as Record<string, unknown> | undefined)?.storyboard_prompt as string | undefined;
|
||||
const [storyboardPrompt, setStoryboardPrompt] = useState(sbSavedPrompt || SB_PROMPT_DEFAULT);
|
||||
@@ -3871,13 +3851,6 @@ export function PipelinePage(props: {
|
||||
const sbViewedIsAdopted = !sbViewedVer || sbViewedVer.is_adopted || sbViewedVer.id === sbActiveShot?.adopted_version;
|
||||
const mainUrl = sbViewedVer ? (sbViewedVer.asset_url || assetUrl(sbViewedVer.asset)) : shotImg(sbActiveShot);
|
||||
const mainAid = sbViewedVer?.asset || sbActiveShot?.adopted_asset || "";
|
||||
const activeScriptShot = sbActiveShot ? shots[sbActiveShot.sort_order] : null;
|
||||
const directorBeats = buildDirectorBeats(
|
||||
activeScriptShot?.visual_prompt || "",
|
||||
activeScriptShot?.narration || "",
|
||||
activeScriptShot?.duration_seconds || SEGMENT_DURATION_MAX,
|
||||
);
|
||||
const directorRange = sceneTimes[sbSelected] || "—";
|
||||
return (
|
||||
<section className="stage active" data-stage-pane="3">
|
||||
<div className="stage-storyboard">
|
||||
@@ -3949,22 +3922,7 @@ export function PipelinePage(props: {
|
||||
<span>{sbActiveShot.error_message}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="muted-2" style={{ fontSize: "12px", lineHeight: 1.55, marginBottom: "10px" }}>每个场生成一张干净视频关键帧;下方导演稿按本镜真实脚本展开,视频只使用关键帧,不会带入文字或边框。</div>
|
||||
<div className="sb-director-board" aria-label="本场导演故事板">
|
||||
<div className="sb-director-head">
|
||||
<div><span className="mono">DIRECTOR BOARD</span><strong>场 {sbSelected + 1} · {directorRange}</strong></div>
|
||||
<span className="pill pill-l3 neutral"><span className="dot" />9:16 · 关键帧</span>
|
||||
</div>
|
||||
{directorBeats.length ? directorBeats.map((beat, index) => (
|
||||
<div className="sb-director-beat" key={`${beat.time}-${index}`}>
|
||||
<div className="sb-director-time">{beat.time}</div>
|
||||
<div className="sb-director-copy">
|
||||
<div className="sb-director-direction">{beat.direction}</div>
|
||||
{beat.narration && <div className="sb-director-narration">口播/旁白:{beat.narration}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)) : <div className="sb-director-empty">脚本生成后,这里会自动展示每段时间、机位、动作和旁白。</div>}
|
||||
</div>
|
||||
<div className="muted-2" style={{ fontSize: "12px", lineHeight: 1.55, marginBottom: "10px" }}>每个场由 image-2 直接生成一张完整导演故事板图片;可单独「重跑本场」只重出这一张,每场各自留历史版本,互不影响。</div>
|
||||
<div className="muted mono" style={{ fontSize: "12px", fontWeight: 500, marginBottom: "6px", letterSpacing: ".04em" }}>整张风格提示词(重跑时生效,可编辑)</div>
|
||||
<PromptBox
|
||||
className="prompt-edit"
|
||||
|
||||
@@ -334,7 +334,10 @@ export function VideoReplacePage({
|
||||
resolution: "720p",
|
||||
duration: outputDuration,
|
||||
refs: [
|
||||
...(videoRef ? [{ type: "video", duration: videoRef.duration || videoMeta.duration }] : []),
|
||||
// 商品复刻的参考视频只用来提炼分镜稿,不会发给火山,算进估价会让显示积分高于实际扣费。
|
||||
...(videoRef && replaceMode === "character"
|
||||
? [{ type: "video", duration: videoRef.duration || videoMeta.duration }]
|
||||
: []),
|
||||
...((source === "library"
|
||||
? Array.from({ length: Math.max(1, libraryImageCount) }, () => ({ type: "image" as const }))
|
||||
: (tempFiles.length ? tempFiles : tempAssetRefs)
|
||||
|
||||
Reference in New Issue
Block a user