diff --git a/components/canvas/smart-edit/EditPreview.tsx b/components/canvas/smart-edit/EditPreview.tsx index 41751f146..2ea458f6c 100644 --- a/components/canvas/smart-edit/EditPreview.tsx +++ b/components/canvas/smart-edit/EditPreview.tsx @@ -483,7 +483,7 @@ export function EditPreview({ letterSpacing: style.spacing * scale, color: style.color, opacity: style.opacity, - WebkitTextStroke: `${style.outlineWidth * scale * 2}px ${style.outlineColor}`, + WebkitTextStroke: `${style.outlineWidth * scale * 2}px ${style.outlineColor}${Math.round((style.outlineOpacity ?? 1) * 255).toString(16).padStart(2, "0")}`, paintOrder: "stroke fill", lineHeight: 1.2, }} diff --git a/components/canvas/smart-edit/SmartEditEditor.module.css b/components/canvas/smart-edit/SmartEditEditor.module.css index 1b1ce3044..b34179293 100644 --- a/components/canvas/smart-edit/SmartEditEditor.module.css +++ b/components/canvas/smart-edit/SmartEditEditor.module.css @@ -163,9 +163,9 @@ .root :global(.se-panel) { width: 300px; flex: none; - border: 1px solid #373737; + border: 1px solid #ffffff1a; border-radius: 12px; - background: #1f1f1f; + background: #202020; display: flex; flex-direction: column; overflow: hidden; @@ -182,10 +182,12 @@ justify-content: space-between; padding: 0 16px; font-size: 14px; + font-weight: 500; + line-height: 20px; } .root :global(.se-panel-head .se-icon) { - width: 28px; - height: 28px; + width: 24px; + height: 24px; } .root :global(.se-panel-head .se-icon svg) { width: 24px; @@ -418,21 +420,42 @@ grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; } +.root :global(.se-library-scroll) { + padding-top: 0; +} +.root :global(.se-library-scroll > .se-small-label) { + display: flex; + align-items: center; + height: 48px; + margin: 0; + font-size: 13px; + line-height: 20px; + color: #919191; +} .root :global(.se-transition-card) { min-width: 0; text-align: left; padding: 4px; - border-radius: 4px; + border-radius: 6px; color: #999; font-size: 12px; line-height: 18px; } -.root :global(.se-transition-card:hover), -.root :global(.se-transition-card.selected) { - background: #363636; +.root :global(.se-transition-card:hover) { + background: #ffffff0d; } .root :global(.se-transition-card.selected) { - color: #8bc7d8; + background: #ffffff1a; +} +.root :global(.se-transition-card.selected) { + color: #09caf5; +} +.root :global(.se-transition-caption) { + display: block; + padding: 0 4px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .root :global(.se-transition-demo) { display: block; @@ -452,15 +475,16 @@ object-fit: cover; } .root :global(.se-transition-card.selected .se-transition-demo) { - border: 2px solid #a1d7e8; + border-color: #09caf5; } .root :global(.se-transition-settings .se-small-label) { display: block; margin-bottom: 8px; + line-height: 16px; } .root :global(.se-transition-name) { font-size: 12px; - line-height: 18px; + line-height: 16px; margin-bottom: 16px; } .root :global(.se-transition-duration) { diff --git a/components/canvas/smart-edit/SmartEditEditor.tsx b/components/canvas/smart-edit/SmartEditEditor.tsx index f7e7bf65b..1b202dc35 100644 --- a/components/canvas/smart-edit/SmartEditEditor.tsx +++ b/components/canvas/smart-edit/SmartEditEditor.tsx @@ -763,7 +763,7 @@ export function SmartEditEditor({ -
+
基础转场
{Object.entries(transitionNames).map(([type, name]) => ( @@ -779,7 +779,7 @@ export function SmartEditEditor({ draggable={false} /> - {name} + {name} ))}
@@ -1190,18 +1190,18 @@ export function SmartEditEditor({ onChange={(outlineColor) => changeStyle({ outlineColor })} /> changeStyle({ outlineWidth })} /> changeStyle({ opacity: v / 100 })} + onChange={(v) => changeStyle({ outlineOpacity: v / 100 })} /> ) : ( @@ -1879,9 +1879,13 @@ function ValueField({ useEffect(() => setText(value.toFixed(precision)), [value, precision]); const commit = () => { const v = Number(text); - if (text.trim() && Number.isFinite(v)) onChange(clamp(v, min, max)); - else setText(value.toFixed(precision)); + if (text.trim() && Number.isFinite(v)) { + const bounded = clamp(v, min, max); + setText(bounded.toFixed(precision)); + onChange(bounded); + } else setText(value.toFixed(precision)); }; + const step = suffix.endsWith("dB") ? 0.5 : suffix.endsWith("x") ? 0.01 : 0.1; return (
@@ -1892,7 +1896,7 @@ function ValueField({ type="number" min={min} max={max} - step={10 ** -precision} + step={step} value={text} onChange={(e) => setText(e.target.value)} onBlur={commit} @@ -1916,7 +1920,7 @@ function ValueField({ onClick={() => onChange( clamp( - value + (suffix.endsWith("dB") ? 1 : suffix.endsWith("x") ? 0.05 : 0.1), + value + step, min, max, ), @@ -1931,7 +1935,7 @@ function ValueField({ onClick={() => onChange( clamp( - value - (suffix.endsWith("dB") ? 1 : suffix.endsWith("x") ? 0.05 : 0.1), + value - step, min, max, ), diff --git a/docs/smart-edit-full-recheck-2026-09-17.json b/docs/smart-edit-full-recheck-2026-09-17.json new file mode 100644 index 000000000..4cbb0d95b --- /dev/null +++ b/docs/smart-edit-full-recheck-2026-09-17.json @@ -0,0 +1,775 @@ +{ + "scope": "智能剪辑节点、编辑器及直接依赖的音频/导出链路;不是全站所有模块", + "baseline": "431ca03f", + "local_only": true, + "counts": { + "通过": 35, + "待验": 8, + "代码核对": 17, + "差异": 25, + "已修复": 9, + "既有证据": 1 + }, + "items": [ + { + "id": "SE-001", + "group": "节点与输入", + "item": "打开剪辑", + "status": "通过", + "detail": "参考和本地均有入口;本地进入真实编辑组件", + "evidence": "本轮UI" + }, + { + "id": "SE-002", + "group": "节点与输入", + "item": "五种模式名称和顺序", + "status": "通过", + "detail": "默认、讲解、批量广告、口播、素材混剪相同", + "evidence": "参考node-modes截图/SmartEditNode" + }, + { + "id": "SE-003", + "group": "节点与输入", + "item": "五种模式算法效果", + "status": "待验", + "detail": "本地有模式分支;没有重新提交参考站付费生成,不能认定效果等同", + "evidence": "execute/contract" + }, + { + "id": "SE-004", + "group": "节点与输入", + "item": "六种比例", + "status": "通过", + "detail": "21:9、16:9、9:16、1:1、4:3、3:4;实际合成覆盖", + "evidence": "参考node-settings/本轮FFmpeg" + }, + { + "id": "SE-005", + "group": "节点与输入", + "item": "三种清晰度", + "status": "通过", + "detail": "480P、720P、1080P;控件一致", + "evidence": "参考node-settings/SmartEditNode" + }, + { + "id": "SE-006", + "group": "节点与输入", + "item": "时长范围", + "status": "通过", + "detail": "0–300秒、步长1秒;本地0为自动", + "evidence": "参考可见DOM/contract" + }, + { + "id": "SE-007", + "group": "节点与输入", + "item": "提示词编辑与展开", + "status": "代码核对", + "detail": "本地受控输入、展开及提交;参考既有提示词未改动", + "evidence": "SmartEditNode" + }, + { + "id": "SE-008", + "group": "节点与输入", + "item": "引用画布素材", + "status": "代码核对", + "detail": "参考进入引用选择;生产组件onReference连接连线状态,本地独立测试页只是候选列表", + "evidence": "参考引用模式/主画布调用点" + }, + { + "id": "SE-009", + "group": "节点与输入", + "item": "素材序号、时长、移除", + "status": "代码核对", + "detail": "本地有缩略图、序号、时长及移除;未删除参考原素材", + "evidence": "SmartEditNode" + }, + { + "id": "SE-010", + "group": "节点与输入", + "item": "本地上传与数量限制", + "status": "代码核对", + "detail": "真实上传入口,上限12个素材、单个125MiB;参考上限尚未验证", + "evidence": "SmartEditNode/SmartEditDialog" + }, + { + "id": "SE-011", + "group": "节点与输入", + "item": "节点缩略时间轴", + "status": "差异", + "detail": "本地节点卡片仍固定生成12个刻度、两行8个色块,未绑定实际剪辑轨道", + "evidence": "SmartEditNodeCard" + }, + { + "id": "SE-012", + "group": "节点与输入", + "item": "批量广告多版本", + "status": "差异", + "detail": "本地高级设置支持1–3版,节点默认variants为1;候选顺序变体不等于多套广告创意", + "evidence": "contract/buildEditPlans" + }, + { + "id": "SE-013", + "group": "节点与输入", + "item": "提示词要求卡点", + "status": "差异", + "detail": "真正卡点要求音乐和beatSync;仅在提示词写“卡点”不会自动打开节拍流程", + "evidence": "execute/主画布onSubmit" + }, + { + "id": "SE-014", + "group": "布局与操作", + "item": "顶栏高度和边距", + "status": "通过", + "detail": "48px顶栏、8px页面边距;本轮图像对照", + "evidence": "参考DOM/本地CSS" + }, + { + "id": "SE-015", + "group": "布局与操作", + "item": "左右参数栏宽度", + "status": "通过", + "detail": "转场库372px、参数栏300px,均可拖动调整", + "evidence": "参考DOM/本地CSS" + }, + { + "id": "SE-016", + "group": "布局与操作", + "item": "面板背景与关闭按钮", + "status": "已修复", + "detail": "背景统一#202020,边框白色10%,关闭按钮24px,标题500/14px", + "evidence": "参考transition.json/本地CSS" + }, + { + "id": "SE-017", + "group": "布局与操作", + "item": "默认时间轴高度", + "status": "差异", + "detail": "打开时均约35vh;参考改变视口保留原像素高度,本地未手动拖动时随窗口重算", + "evidence": "本轮宽屏切换/resize处理" + }, + { + "id": "SE-018", + "group": "布局与操作", + "item": "折叠预览", + "status": "差异", + "detail": "参考保留底部时间轴并露出画布;本地把时间轴铺满编辑器且保留顶栏", + "evidence": "参考collapsed截图/SmartEditEditor" + }, + { + "id": "SE-019", + "group": "布局与操作", + "item": "全屏预览", + "status": "代码核对", + "detail": "本地调用Fullscreen API并显示拒绝错误;本轮未重测不同浏览器权限状态", + "evidence": "SmartEditEditor" + }, + { + "id": "SE-020", + "group": "布局与操作", + "item": "撤销重做", + "status": "通过", + "detail": "本轮分离音轨后撤销成功;参考宽屏有按钮、窄屏隐藏,先前未见不代表没有", + "evidence": "本轮UI/参考1512宽度" + }, + { + "id": "SE-021", + "group": "布局与操作", + "item": "播放、暂停、逐帧", + "status": "代码核对", + "detail": "Space与左右键、30fps步进、片尾停止;前批浏览器已验证,本轮保留代码核对口径", + "evidence": "SmartEditEditor/EditPreview" + }, + { + "id": "SE-022", + "group": "布局与操作", + "item": "吸附默认值", + "status": "通过", + "detail": "参考关闭,本地SmartEditEditor也是false;纠正旧文档“本地开启”误记", + "evidence": "参考AX/SmartEditEditor:state" + }, + { + "id": "SE-023", + "group": "布局与操作", + "item": "退出与Esc", + "status": "差异", + "detail": "参考Esc退出整个编辑器;本地Esc只关闭菜单/参数和取消选中", + "evidence": "前批参考UI/本轮源码" + }, + { + "id": "SE-024", + "group": "布局与操作", + "item": "窄屏可用性", + "status": "待验", + "detail": "参考1024宽度测得左右仍372/300;本地三栏与工具条更窄窗口尚无完整可用性验收", + "evidence": "reference-transition-1024" + }, + { + "id": "SE-025", + "group": "视频片段", + "item": "四角缩放、移动与重置", + "status": "通过", + "detail": "本轮真实合成像素验证;本地有选框和重置,参考四角手柄可见", + "evidence": "本轮FFmpeg/参考video" + }, + { + "id": "SE-026", + "group": "视频片段", + "item": "变速上下调步长", + "status": "已修复", + "detail": "参考1.00→1.01,本地0.05改为0.01;浏览器已复测", + "evidence": "本轮UI" + }, + { + "id": "SE-027", + "group": "视频片段", + "item": "变速支持范围", + "status": "待验", + "detail": "本地0.5–2倍;参考未穷尽上下限,不能因步长一致声称范围一致", + "evidence": "contract/参考video" + }, + { + "id": "SE-028", + "group": "视频片段", + "item": "时长输入", + "status": "代码核对", + "detail": "按原素材剩余长度和变速约束,不能超过素材", + "evidence": "SmartEditEditor/contract" + }, + { + "id": "SE-029", + "group": "视频片段", + "item": "数值越界后的显示", + "status": "已修复", + "detail": "到上限2后再输入99,显示回到2.00,不残留非法数字", + "evidence": "本轮UI" + }, + { + "id": "SE-030", + "group": "视频片段", + "item": "增益步长", + "status": "已修复", + "detail": "参考0.5dB,本地由1改0.5;浏览器0→-0.5确认", + "evidence": "本轮UI" + }, + { + "id": "SE-031", + "group": "视频片段", + "item": "正增益", + "status": "差异", + "detail": "参考能到+0.5dB;本地最大0dB,预览及合同最大gain=1", + "evidence": "参考UI/contract/EditPreview" + }, + { + "id": "SE-032", + "group": "视频片段", + "item": "边缘裁剪和排序", + "status": "代码核对", + "detail": "已有按source时间/速度换算和撤销;本轮未重新拖遍所有方向", + "evidence": "SmartEditEditor" + }, + { + "id": "SE-033", + "group": "视频片段", + "item": "分割和左右裁剪", + "status": "通过", + "detail": "边界禁用、源范围及片长合同回归通过;本轮未对拍参考破坏性编辑", + "evidence": "本轮helpers/SmartEditEditor" + }, + { + "id": "SE-034", + "group": "视频片段", + "item": "至少保留一个片段", + "status": "待验", + "detail": "本地阻止删最后一段;参考空时间轴行为未删除原素材验证", + "evidence": "SmartEditEditor" + }, + { + "id": "SE-035", + "group": "视频片段", + "item": "右键菜单", + "status": "差异", + "detail": "本地缺人声分离子菜单,增加撤销/重做/设置;“删除片段”与参考“删除”不同", + "evidence": "本轮参考及本地UI" + }, + { + "id": "SE-036", + "group": "视频片段", + "item": "转场覆盖区右键", + "status": "差异", + "detail": "本地右键落在覆盖区时显示时间轴菜单,不能直接选中底下视频;从未覆盖位置可正常分离", + "evidence": "本轮本地UI" + }, + { + "id": "SE-037", + "group": "视频片段", + "item": "静音后恢复原增益", + "status": "差异", + "detail": "本地静音后恢复统一gain=1,会丢失原先降低的增益;独立音轨也同样处理", + "evidence": "SmartEditEditor静音处理" + }, + { + "id": "SE-038", + "group": "字幕", + "item": "空列表、新建、搜索", + "status": "通过", + "detail": "参考空列表和新建已操作;本地新建成功、搜索有真实过滤", + "evidence": "本轮UI/源码" + }, + { + "id": "SE-039", + "group": "字幕", + "item": "列表逐条新增与删除", + "status": "差异", + "detail": "参考行内有“在后面新增字幕/删除字幕”;本地只有顶端新建、右键删除", + "evidence": "reference-subtitle-list/源码" + }, + { + "id": "SE-040", + "group": "字幕", + "item": "列表时间格式", + "status": "差异", + "detail": "参考00:00–00:02;本地数字秒输入,布局与交互不同", + "evidence": "参考截图/本轮本地UI" + }, + { + "id": "SE-041", + "group": "字幕", + "item": "选中文本、编辑与位置", + "status": "通过", + "detail": "本轮本地样式编辑和实际字幕位置像素测试通过", + "evidence": "本轮UI/FFmpeg" + }, + { + "id": "SE-042", + "group": "字幕", + "item": "显示和隐藏", + "status": "代码核对", + "detail": "subtitles控制预览和导出;关闭面板仅隐藏编辑轨道", + "evidence": "SmartEditEditor/compiledSubtitles" + }, + { + "id": "SE-043", + "group": "字幕", + "item": "字体搜索和长列表", + "status": "差异", + "detail": "参考可搜索Source Han、Noto、Roboto、Inter等;本地单一select且onChange为空", + "evidence": "reference-font-menu/源码" + }, + { + "id": "SE-044", + "group": "字幕", + "item": "字体分类", + "status": "差异", + "detail": "参考全部/常用/推荐/Google字体;本地无分类", + "evidence": "reference-font-categories" + }, + { + "id": "SE-045", + "group": "字幕", + "item": "预览与导出的字形", + "status": "差异", + "detail": "本地浏览器依赖系统字体回退,导出固定Noto CJK;不能声明与参考字体逐字形一致", + "evidence": "EditPreview/subtitlesToAss" + }, + { + "id": "SE-046", + "group": "字幕", + "item": "字号范围和显示单位", + "status": "差异", + "detail": "参考默认56、12–72;本地默认28、8–160且以720为基准,需统一单位再调整", + "evidence": "参考DOM/contract" + }, + { + "id": "SE-047", + "group": "字幕", + "item": "字间距范围和控件", + "status": "差异", + "detail": "参考X数值框0–100;本地滑条与数值框-5–30", + "evidence": "参考DOM/本地UI" + }, + { + "id": "SE-048", + "group": "字幕", + "item": "字体颜色和十六进制", + "status": "代码核对", + "detail": "两处均有取色和hex;本地失焦校验非法值;样式烧录已回归", + "evidence": "StyleColor/FFmpeg" + }, + { + "id": "SE-049", + "group": "字幕", + "item": "六种预设入口", + "status": "通过", + "detail": "默认、白黑、黑白、黄黑、白粉、粉黑均有", + "evidence": "本轮参考及本地UI" + }, + { + "id": "SE-050", + "group": "字幕", + "item": "预设按钮外观", + "status": "差异", + "detail": "本地六个按钮都显示T,参考默认项显示“默认”,间距和选中态仍不同", + "evidence": "参考subtitle-style/源码" + }, + { + "id": "SE-051", + "group": "字幕", + "item": "描边颜色和粗细", + "status": "差异", + "detail": "参考默认白色、粗细0–20;本地默认黑色、0–10;颜色选择可用", + "evidence": "参考DOM/contract" + }, + { + "id": "SE-052", + "group": "字幕", + "item": "描边不透明度语义", + "status": "已修复", + "detail": "独立outlineOpacity;0时保留文字,只去描边;旧整行opacity保持兼容", + "evidence": "UI截图/ASS合同/实际像素测试" + }, + { + "id": "SE-053", + "group": "字幕", + "item": "字号等数值单位装饰", + "status": "差异", + "detail": "参考px/%以及上下箭头;本地多为原生数字框,尺寸及结构未完全一致", + "evidence": "参考subtitle-style/StyleNumber" + }, + { + "id": "SE-054", + "group": "字幕", + "item": "应用全部", + "status": "代码核对", + "detail": "复制完整style含新outlineOpacity;本轮未覆盖所有字幕数量组合", + "evidence": "SmartEditEditor" + }, + { + "id": "SE-055", + "group": "字幕", + "item": "自动字幕与长转场", + "status": "已修复", + "detail": "居中转场不再把后片段字幕向后延迟整个转场时长,避免片头字幕丢失", + "evidence": "新增合同回归" + }, + { + "id": "SE-056", + "group": "字幕", + "item": "手工字幕与SRT", + "status": "通过", + "detail": "手工字幕保留位置和样式,SRT仅保留文本时间;ASS字符转义回归通过", + "evidence": "本轮helpers/FFmpeg" + }, + { + "id": "SE-057", + "group": "字幕", + "item": "语音自动转写", + "status": "差异", + "detail": "已有能力探测,但当前本地测试服务asr=false;完整转写尚无可用验收结果", + "evidence": "本地capabilities/已有部署记录" + }, + { + "id": "SE-058", + "group": "转场", + "item": "三种基础转场入口", + "status": "通过", + "detail": "淡入淡出、黑场过渡、白场过渡;这不是全站视频生成特效库", + "evidence": "本轮参考UI" + }, + { + "id": "SE-059", + "group": "转场", + "item": "原始动态素材", + "status": "通过", + "detail": "直接使用参考原始WebP,每张75帧/3秒;本地动画可见", + "evidence": "public/transitions/本轮UI" + }, + { + "id": "SE-060", + "group": "转场", + "item": "分类栏与卡片位置", + "status": "已修复", + "detail": "分类48px高、13px字体,卡片108×130,图框100×100,列距7px", + "evidence": "参考DOM/本地CSS" + }, + { + "id": "SE-061", + "group": "转场", + "item": "选中边框和颜色", + "status": "已修复", + "detail": "2px改1px,避免图片缩小;边框/文字改#09caf5,圆角6px", + "evidence": "参考DOM/本地截图" + }, + { + "id": "SE-062", + "group": "转场", + "item": "时长与名称行间距", + "status": "已修复", + "detail": "改16px行高,时长框与90px小预览纵向位置对齐", + "evidence": "参考DOM/本地CSS" + }, + { + "id": "SE-063", + "group": "转场", + "item": "时长上下限与步长", + "status": "通过", + "detail": "0.2–4秒,0.1步进,非法值失焦归一化", + "evidence": "前批UI/本轮合同" + }, + { + "id": "SE-064", + "group": "转场", + "item": "相邻素材小预览", + "status": "通过", + "detail": "本地使用相邻真实视频,静音循环并应用效果", + "evidence": "本轮UI" + }, + { + "id": "SE-065", + "group": "转场", + "item": "主画面预览时机", + "status": "待验", + "detail": "本地选中转场会循环预览;参考本轮主画面多保持原播放头,小预览循环,触发规则需进一步对拍", + "evidence": "reference-transition/TransitionPreview" + }, + { + "id": "SE-066", + "group": "转场", + "item": "居中时序和总片长", + "status": "通过", + "detail": "三种2.2/4秒真实成片均保留4秒总长", + "evidence": "本轮FFmpeg" + }, + { + "id": "SE-067", + "group": "转场", + "item": "黑场与白场中点", + "status": "通过", + "detail": "真实像素验证纯黑/纯白中点", + "evidence": "本轮FFmpeg" + }, + { + "id": "SE-068", + "group": "转场", + "item": "极短片段和连续接点", + "status": "通过", + "detail": "短片段约束和避免三片同时重叠的合同通过", + "evidence": "本轮helpers/前批压力合成" + }, + { + "id": "SE-069", + "group": "转场", + "item": "删除与硬切", + "status": "通过", + "detail": "已有删除恢复硬切;本轮逐片段与硬切组合实际合成通过", + "evidence": "FFmpeg/源码" + }, + { + "id": "SE-070", + "group": "转场", + "item": "时间轴覆盖框", + "status": "差异", + "detail": "参考top=4px、高60px;本地top=0、高64px,选中填充/图标仍需精调", + "evidence": "参考DOM/本地CSS" + }, + { + "id": "SE-071", + "group": "转场", + "item": "旧方案兼容", + "status": "通过", + "detail": "未设置alignment的旧重叠方案保留原片长语义", + "evidence": "本轮helpers/FFmpeg" + }, + { + "id": "SE-072", + "group": "音频", + "item": "有声视频分离", + "status": "通过", + "detail": "本轮真实点按生成独立音轨,撤销后消失", + "evidence": "本轮本地UI" + }, + { + "id": "SE-073", + "group": "音频", + "item": "无音轨时分离禁用", + "status": "通过", + "detail": "参考当前无音轨素材禁用;本地按hasAudio禁用", + "evidence": "本轮参考UI/源码" + }, + { + "id": "SE-074", + "group": "音频", + "item": "分离后声音不叠加", + "status": "通过", + "detail": "实际音频RMS与转场包络回归通过", + "evidence": "本轮FFmpeg" + }, + { + "id": "SE-075", + "group": "音频", + "item": "独立音轨变速、淡入淡出", + "status": "通过", + "detail": "实际2倍速、增益、延迟、淡入淡出及长音轨黑尾回归通过", + "evidence": "本轮FFmpeg" + }, + { + "id": "SE-076", + "group": "音频", + "item": "独立音轨裁剪、分割、移动", + "status": "代码核对", + "detail": "有完整source范围与时间轴处理;前批UI通过,本轮未全部重拖", + "evidence": "SmartEditEditor/contract" + }, + { + "id": "SE-077", + "group": "音频", + "item": "人声分离两个分支", + "status": "差异", + "detail": "参考“仅保留人声/仅保留背景音”子菜单;本地未接专用分离服务", + "evidence": "reference-video-menu" + }, + { + "id": "SE-078", + "group": "音频", + "item": "音频参数栏调整宽度", + "status": "差异", + "detail": "本地视频/字幕/转场栏有拖拽柄,音频栏缺同类柄;参考有声参数栏未验", + "evidence": "SmartEditEditor" + }, + { + "id": "SE-079", + "group": "音频", + "item": "真实波形", + "status": "待验", + "detail": "本地音频条是纯色色块,没有采样波形;本轮未取得参考有声轨波形证据", + "evidence": "se-wave/se-audio-clip" + }, + { + "id": "SE-080", + "group": "音频", + "item": "音乐与卡点", + "status": "通过", + "detail": "节拍与候选剪辑合同回归通过,音乐混音真实输出通过;不是参考算法效果对拍", + "evidence": "本轮helpers/FFmpeg" + }, + { + "id": "SE-081", + "group": "导出与恢复", + "item": "导出菜单位置", + "status": "通过", + "detail": "本地/画布两个目的地和参考相同", + "evidence": "参考export-menu/源码" + }, + { + "id": "SE-082", + "group": "导出与恢复", + "item": "导出菜单视觉", + "status": "代码核对", + "detail": "本地有200px菜单、40px条目;边框/图标需独立像素精调", + "evidence": "参考截图/CSS" + }, + { + "id": "SE-083", + "group": "导出与恢复", + "item": "真实本地合成", + "status": "通过", + "detail": "FFmpeg实际生成结果,不用模拟成功替代", + "evidence": "本轮ffmpeg.log" + }, + { + "id": "SE-084", + "group": "导出与恢复", + "item": "提交任务ID和幂等", + "status": "通过", + "detail": "请求ID回归与远程确定性任务身份测试通过", + "evidence": "本轮helpers" + }, + { + "id": "SE-085", + "group": "导出与恢复", + "item": "刷新恢复导出任务", + "status": "通过", + "detail": "任务检查点/reconcile合同通过;本轮未执行真实Next页面断网恢复", + "evidence": "本轮helpers" + }, + { + "id": "SE-086", + "group": "导出与恢复", + "item": "浏览器中返回画布结果", + "status": "待验", + "detail": "独立测试页任务回调仍是样例;本轮未跑主站Next API全链路", + "evidence": "ui-entry/onRender" + }, + { + "id": "SE-087", + "group": "导出与恢复", + "item": "任务错误和服务不可用", + "status": "代码核对", + "detail": "导出按钮按能力禁用,失败状态显示错误;本轮未切断实际服务故障注入", + "evidence": "SmartEditEditor/SmartEditDialog" + }, + { + "id": "SE-088", + "group": "导出与恢复", + "item": "取消远程导出", + "status": "差异", + "detail": "没有主动取消运行中合成任务的完整UI/API链路;本轮参考菜单也未验证取消", + "evidence": "源码" + }, + { + "id": "SE-089", + "group": "导出与恢复", + "item": "多版本全导出", + "status": "代码核对", + "detail": "高级设置有导出全部版本;主节点默认只一版,参考完整多版结果未生成", + "evidence": "SmartEditDialog" + }, + { + "id": "SE-090", + "group": "导出与恢复", + "item": "最高时长与比例", + "status": "通过", + "detail": "300秒约束、宽屏远程结果下载合同、6比例真实本地输出通过", + "evidence": "本轮helpers/FFmpeg" + }, + { + "id": "SE-091", + "group": "边界与性能", + "item": "最大片段数", + "status": "代码核对", + "detail": "最多12视频片段、24独立音轨、500字幕;不是参考站已确认相同限制", + "evidence": "contract" + }, + { + "id": "SE-092", + "group": "边界与性能", + "item": "后台标签恢复", + "status": "待验", + "detail": "循环预览监听visibilitychange;本轮未覆盖系统休眠/后台节流完整矩阵", + "evidence": "TransitionPreview" + }, + { + "id": "SE-093", + "group": "边界与性能", + "item": "原生媒体加载失败", + "status": "代码核对", + "detail": "play的AbortError忽略,其余报错可见;本轮未测试全部外部域名/CORS", + "evidence": "EditPreview" + }, + { + "id": "SE-094", + "group": "边界与性能", + "item": "高清与12片段性能", + "status": "既有证据", + "detail": "前批1080P4秒合成12.81秒、12片段7.604秒;本轮未重复此压力测试", + "evidence": "transition-stress-native.log" + }, + { + "id": "SE-095", + "group": "边界与性能", + "item": "服务部署状态", + "status": "通过", + "detail": "本轮所有修改仅本地,未更新48或算力服务器", + "evidence": "git/操作记录" + } + ] +} diff --git a/docs/smart-edit-full-recheck-2026-09-17.md b/docs/smart-edit-full-recheck-2026-09-17.md new file mode 100644 index 000000000..fccc142f9 --- /dev/null +++ b/docs/smart-edit-full-recheck-2026-09-17.md @@ -0,0 +1,206 @@ +# 智能剪辑全面复核与像素差异(2026-09-17) + +本轮在基线`431ca03f`之上复核 **95个检查点**,覆盖智能剪辑节点输入、编辑器布局、视频、字幕、转场、音频、导出恢复和边界。结论:**仍有遗漏,不能验收为全部功能齐全或100%像素一致**。这轮继续只在本地修复和验证,没有更新服务器。 + +该范围承接本次“转场预览图”对话,不扩大为整个视频画布、3D导演台或参考站全站重新验收;它们的旧清单不能被本轮结果覆盖。各检查点区别本轮UI、真实合成、代码核对、既有证据和待验,避免把功能存在误写为全链路成功。 + +## 结果分布 + +| 状态 | 数量 | +| --- | ---: | +| 通过 | 35 | +| 待验 | 8 | +| 代码核对 | 17 | +| 差异 | 25 | +| 已修复 | 9 | +| 既有证据 | 1 | + +“通过”仅对该行明确的测试边界成立,不能换算为全站完成率。“差异”包含缺失能力、交互不同和视觉偏差,不代表同等严重程度。 + +## 本轮修复 + +- 自动字幕时序:居中转场不能按旧重叠转场推迟后片段字幕,否则片头字幕会被丢弃。 +- 独立描边透明度:新增可选`outlineOpacity`,预览和ASS输出保持一致;旧`opacity`仍保持历史整行透明度语义,避免改坏已保存工程。 +- 变速按钮步长改0.01、音量步长改0.5dB;输入超上限后立即显示约束后的数值,包括已经到上限再输入的情况。 +- 转场分类栏、卡片内边距、圆角、选中蓝色、边框粗细、名称/时长行高及参数面板背景、关闭按钮尺寸按参考可见DOM修正。 + +## 像素测量 + +在参考站1331×681、1512×752和1024×768 CSS视口测量可见DOM。独立本地页也设置过1331×681设备视口检查三栏;最终宽屏截图来自本机原生浏览器。原生截图包含浏览器外壳且经过显示缩放,汇总图裁去顶部106px并将参考图归一到1331×662显示尺寸。为排除面板状态差异,宽屏对照临时把参考时间轴增高25px后截图,随后恢复。 + +这些图用于布局叠位和控件对照,不计算混入不同视频、不同动画帧、系统字体抗锯齿的整屏“相似度百分比”。严格逐像素验收仍需要固定同一视频帧、字体文件、DPR和全部交互状态;本轮没有宣称完成这种全矩阵黄金截图验收。 + +| 控件 | 参考测量 | 本轮本地修正后 | 结论 | +| --- | --- | --- | --- | +| 顶栏 | 48px | 48px | 一致 | +| 页面边距/栏间距 | 8px/8px | 8px/8px | 一致 | +| 转场库/参数栏 | 372px/300px | 372px/300px | 一致 | +| 参数面板 | #202020、白10%边框、12px圆角 | 相同CSS值 | 修正 | +| 面板关闭按钮 | 24×24px | 24×24px | 修正 | +| 分类栏 | 48px高、13px字 | 相同 | 修正 | +| 卡片 | 默认栏宽下108×130px,列距7px | 相同盒模型 | 修正 | +| 动态图框 | 100×100px,1px边框 | 选中也保留1px,内图不缩小 | 修正 | +| 选中边框/文字 | #09caf5 | #09caf5 | 修正 | +| 卡片圆角/文字内边距 | 6px / 左右4px | 相同 | 修正 | +| 名称/时长文字 | 12px/16px行高 | 相同 | 修正 | +| 右侧实际预览 | 90px高 | 90px | 一致 | +| 转场轨道覆盖框 | top4、高60px | top0、高64px | 仍有差异 | +| 时间轴缩放数值范围 | 0–100、默认20 | 0–40、默认20 | 范围不同;尚未对齐最大倍率与刻度密度 | +| 字幕数值控件 | px/%装饰、自绘步进、不同字号范围 | 原生数值输入、旧720基准 | 仍有差异 | + +## 尚未完成的优先事项 + +1. 字体库、搜索/分类、真实字体文件与预览/导出映射;字号、字间距、描边粗细的单位及范围。 +2. 正音量增益、静音恢复原增益、人声/背景音分离。 +3. 字幕列表逐条新增/删除、时间格式与样式控件;折叠预览露出画布。 +4. 节点缩略时间轴绑定实际计划;节点卡点需求到音乐/节拍设置的联动;批量广告版本设置。 +5. 转场覆盖框和时间轴缩放细节,长转场覆盖区域的右键交互,以及主画面转场演示触发规则对拍。 +6. 主站端到端导出、断网/后台恢复、有声参考轨更多功能、窄窗口完整验收;目前不能用本地样例任务回调代替这些结果。 + +## 逐项记录 + + +### 节点与输入 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-001 | 打开剪辑 | 通过 | 参考和本地均有入口;本地进入真实编辑组件 | 本轮UI | +| SE-002 | 五种模式名称和顺序 | 通过 | 默认、讲解、批量广告、口播、素材混剪相同 | 参考node-modes截图/SmartEditNode | +| SE-003 | 五种模式算法效果 | 待验 | 本地有模式分支;没有重新提交参考站付费生成,不能认定效果等同 | execute/contract | +| SE-004 | 六种比例 | 通过 | 21:9、16:9、9:16、1:1、4:3、3:4;实际合成覆盖 | 参考node-settings/本轮FFmpeg | +| SE-005 | 三种清晰度 | 通过 | 480P、720P、1080P;控件一致 | 参考node-settings/SmartEditNode | +| SE-006 | 时长范围 | 通过 | 0–300秒、步长1秒;本地0为自动 | 参考可见DOM/contract | +| SE-007 | 提示词编辑与展开 | 代码核对 | 本地受控输入、展开及提交;参考既有提示词未改动 | SmartEditNode | +| SE-008 | 引用画布素材 | 代码核对 | 参考进入引用选择;生产组件onReference连接连线状态,本地独立测试页只是候选列表 | 参考引用模式/主画布调用点 | +| SE-009 | 素材序号、时长、移除 | 代码核对 | 本地有缩略图、序号、时长及移除;未删除参考原素材 | SmartEditNode | +| SE-010 | 本地上传与数量限制 | 代码核对 | 真实上传入口,上限12个素材、单个125MiB;参考上限尚未验证 | SmartEditNode/SmartEditDialog | +| SE-011 | 节点缩略时间轴 | 差异 | 本地节点卡片仍固定生成12个刻度、两行8个色块,未绑定实际剪辑轨道 | SmartEditNodeCard | +| SE-012 | 批量广告多版本 | 差异 | 本地高级设置支持1–3版,节点默认variants为1;候选顺序变体不等于多套广告创意 | contract/buildEditPlans | +| SE-013 | 提示词要求卡点 | 差异 | 真正卡点要求音乐和beatSync;仅在提示词写“卡点”不会自动打开节拍流程 | execute/主画布onSubmit | + +### 布局与操作 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-014 | 顶栏高度和边距 | 通过 | 48px顶栏、8px页面边距;本轮图像对照 | 参考DOM/本地CSS | +| SE-015 | 左右参数栏宽度 | 通过 | 转场库372px、参数栏300px,均可拖动调整 | 参考DOM/本地CSS | +| SE-016 | 面板背景与关闭按钮 | 已修复 | 背景统一#202020,边框白色10%,关闭按钮24px,标题500/14px | 参考transition.json/本地CSS | +| SE-017 | 默认时间轴高度 | 差异 | 打开时均约35vh;参考改变视口保留原像素高度,本地未手动拖动时随窗口重算 | 本轮宽屏切换/resize处理 | +| SE-018 | 折叠预览 | 差异 | 参考保留底部时间轴并露出画布;本地把时间轴铺满编辑器且保留顶栏 | 参考collapsed截图/SmartEditEditor | +| SE-019 | 全屏预览 | 代码核对 | 本地调用Fullscreen API并显示拒绝错误;本轮未重测不同浏览器权限状态 | SmartEditEditor | +| SE-020 | 撤销重做 | 通过 | 本轮分离音轨后撤销成功;参考宽屏有按钮、窄屏隐藏,先前未见不代表没有 | 本轮UI/参考1512宽度 | +| SE-021 | 播放、暂停、逐帧 | 代码核对 | Space与左右键、30fps步进、片尾停止;前批浏览器已验证,本轮保留代码核对口径 | SmartEditEditor/EditPreview | +| SE-022 | 吸附默认值 | 通过 | 参考关闭,本地SmartEditEditor也是false;纠正旧文档“本地开启”误记 | 参考AX/SmartEditEditor:state | +| SE-023 | 退出与Esc | 差异 | 参考Esc退出整个编辑器;本地Esc只关闭菜单/参数和取消选中 | 前批参考UI/本轮源码 | +| SE-024 | 窄屏可用性 | 待验 | 参考1024宽度测得左右仍372/300;本地三栏与工具条更窄窗口尚无完整可用性验收 | reference-transition-1024 | + +### 视频片段 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-025 | 四角缩放、移动与重置 | 通过 | 本轮真实合成像素验证;本地有选框和重置,参考四角手柄可见 | 本轮FFmpeg/参考video | +| SE-026 | 变速上下调步长 | 已修复 | 参考1.00→1.01,本地0.05改为0.01;浏览器已复测 | 本轮UI | +| SE-027 | 变速支持范围 | 待验 | 本地0.5–2倍;参考未穷尽上下限,不能因步长一致声称范围一致 | contract/参考video | +| SE-028 | 时长输入 | 代码核对 | 按原素材剩余长度和变速约束,不能超过素材 | SmartEditEditor/contract | +| SE-029 | 数值越界后的显示 | 已修复 | 到上限2后再输入99,显示回到2.00,不残留非法数字 | 本轮UI | +| SE-030 | 增益步长 | 已修复 | 参考0.5dB,本地由1改0.5;浏览器0→-0.5确认 | 本轮UI | +| SE-031 | 正增益 | 差异 | 参考能到+0.5dB;本地最大0dB,预览及合同最大gain=1 | 参考UI/contract/EditPreview | +| SE-032 | 边缘裁剪和排序 | 代码核对 | 已有按source时间/速度换算和撤销;本轮未重新拖遍所有方向 | SmartEditEditor | +| SE-033 | 分割和左右裁剪 | 通过 | 边界禁用、源范围及片长合同回归通过;本轮未对拍参考破坏性编辑 | 本轮helpers/SmartEditEditor | +| SE-034 | 至少保留一个片段 | 待验 | 本地阻止删最后一段;参考空时间轴行为未删除原素材验证 | SmartEditEditor | +| SE-035 | 右键菜单 | 差异 | 本地缺人声分离子菜单,增加撤销/重做/设置;“删除片段”与参考“删除”不同 | 本轮参考及本地UI | +| SE-036 | 转场覆盖区右键 | 差异 | 本地右键落在覆盖区时显示时间轴菜单,不能直接选中底下视频;从未覆盖位置可正常分离 | 本轮本地UI | +| SE-037 | 静音后恢复原增益 | 差异 | 本地静音后恢复统一gain=1,会丢失原先降低的增益;独立音轨也同样处理 | SmartEditEditor静音处理 | + +### 字幕 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-038 | 空列表、新建、搜索 | 通过 | 参考空列表和新建已操作;本地新建成功、搜索有真实过滤 | 本轮UI/源码 | +| SE-039 | 列表逐条新增与删除 | 差异 | 参考行内有“在后面新增字幕/删除字幕”;本地只有顶端新建、右键删除 | reference-subtitle-list/源码 | +| SE-040 | 列表时间格式 | 差异 | 参考00:00–00:02;本地数字秒输入,布局与交互不同 | 参考截图/本轮本地UI | +| SE-041 | 选中文本、编辑与位置 | 通过 | 本轮本地样式编辑和实际字幕位置像素测试通过 | 本轮UI/FFmpeg | +| SE-042 | 显示和隐藏 | 代码核对 | subtitles控制预览和导出;关闭面板仅隐藏编辑轨道 | SmartEditEditor/compiledSubtitles | +| SE-043 | 字体搜索和长列表 | 差异 | 参考可搜索Source Han、Noto、Roboto、Inter等;本地单一select且onChange为空 | reference-font-menu/源码 | +| SE-044 | 字体分类 | 差异 | 参考全部/常用/推荐/Google字体;本地无分类 | reference-font-categories | +| SE-045 | 预览与导出的字形 | 差异 | 本地浏览器依赖系统字体回退,导出固定Noto CJK;不能声明与参考字体逐字形一致 | EditPreview/subtitlesToAss | +| SE-046 | 字号范围和显示单位 | 差异 | 参考默认56、12–72;本地默认28、8–160且以720为基准,需统一单位再调整 | 参考DOM/contract | +| SE-047 | 字间距范围和控件 | 差异 | 参考X数值框0–100;本地滑条与数值框-5–30 | 参考DOM/本地UI | +| SE-048 | 字体颜色和十六进制 | 代码核对 | 两处均有取色和hex;本地失焦校验非法值;样式烧录已回归 | StyleColor/FFmpeg | +| SE-049 | 六种预设入口 | 通过 | 默认、白黑、黑白、黄黑、白粉、粉黑均有 | 本轮参考及本地UI | +| SE-050 | 预设按钮外观 | 差异 | 本地六个按钮都显示T,参考默认项显示“默认”,间距和选中态仍不同 | 参考subtitle-style/源码 | +| SE-051 | 描边颜色和粗细 | 差异 | 参考默认白色、粗细0–20;本地默认黑色、0–10;颜色选择可用 | 参考DOM/contract | +| SE-052 | 描边不透明度语义 | 已修复 | 独立outlineOpacity;0时保留文字,只去描边;旧整行opacity保持兼容 | UI截图/ASS合同/实际像素测试 | +| SE-053 | 字号等数值单位装饰 | 差异 | 参考px/%以及上下箭头;本地多为原生数字框,尺寸及结构未完全一致 | 参考subtitle-style/StyleNumber | +| SE-054 | 应用全部 | 代码核对 | 复制完整style含新outlineOpacity;本轮未覆盖所有字幕数量组合 | SmartEditEditor | +| SE-055 | 自动字幕与长转场 | 已修复 | 居中转场不再把后片段字幕向后延迟整个转场时长,避免片头字幕丢失 | 新增合同回归 | +| SE-056 | 手工字幕与SRT | 通过 | 手工字幕保留位置和样式,SRT仅保留文本时间;ASS字符转义回归通过 | 本轮helpers/FFmpeg | +| SE-057 | 语音自动转写 | 差异 | 已有能力探测,但当前本地测试服务asr=false;完整转写尚无可用验收结果 | 本地capabilities/已有部署记录 | + +### 转场 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-058 | 三种基础转场入口 | 通过 | 淡入淡出、黑场过渡、白场过渡;这不是全站视频生成特效库 | 本轮参考UI | +| SE-059 | 原始动态素材 | 通过 | 直接使用参考原始WebP,每张75帧/3秒;本地动画可见 | public/transitions/本轮UI | +| SE-060 | 分类栏与卡片位置 | 已修复 | 分类48px高、13px字体,卡片108×130,图框100×100,列距7px | 参考DOM/本地CSS | +| SE-061 | 选中边框和颜色 | 已修复 | 2px改1px,避免图片缩小;边框/文字改#09caf5,圆角6px | 参考DOM/本地截图 | +| SE-062 | 时长与名称行间距 | 已修复 | 改16px行高,时长框与90px小预览纵向位置对齐 | 参考DOM/本地CSS | +| SE-063 | 时长上下限与步长 | 通过 | 0.2–4秒,0.1步进,非法值失焦归一化 | 前批UI/本轮合同 | +| SE-064 | 相邻素材小预览 | 通过 | 本地使用相邻真实视频,静音循环并应用效果 | 本轮UI | +| SE-065 | 主画面预览时机 | 待验 | 本地选中转场会循环预览;参考本轮主画面多保持原播放头,小预览循环,触发规则需进一步对拍 | reference-transition/TransitionPreview | +| SE-066 | 居中时序和总片长 | 通过 | 三种2.2/4秒真实成片均保留4秒总长 | 本轮FFmpeg | +| SE-067 | 黑场与白场中点 | 通过 | 真实像素验证纯黑/纯白中点 | 本轮FFmpeg | +| SE-068 | 极短片段和连续接点 | 通过 | 短片段约束和避免三片同时重叠的合同通过 | 本轮helpers/前批压力合成 | +| SE-069 | 删除与硬切 | 通过 | 已有删除恢复硬切;本轮逐片段与硬切组合实际合成通过 | FFmpeg/源码 | +| SE-070 | 时间轴覆盖框 | 差异 | 参考top=4px、高60px;本地top=0、高64px,选中填充/图标仍需精调 | 参考DOM/本地CSS | +| SE-071 | 旧方案兼容 | 通过 | 未设置alignment的旧重叠方案保留原片长语义 | 本轮helpers/FFmpeg | + +### 音频 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-072 | 有声视频分离 | 通过 | 本轮真实点按生成独立音轨,撤销后消失 | 本轮本地UI | +| SE-073 | 无音轨时分离禁用 | 通过 | 参考当前无音轨素材禁用;本地按hasAudio禁用 | 本轮参考UI/源码 | +| SE-074 | 分离后声音不叠加 | 通过 | 实际音频RMS与转场包络回归通过 | 本轮FFmpeg | +| SE-075 | 独立音轨变速、淡入淡出 | 通过 | 实际2倍速、增益、延迟、淡入淡出及长音轨黑尾回归通过 | 本轮FFmpeg | +| SE-076 | 独立音轨裁剪、分割、移动 | 代码核对 | 有完整source范围与时间轴处理;前批UI通过,本轮未全部重拖 | SmartEditEditor/contract | +| SE-077 | 人声分离两个分支 | 差异 | 参考“仅保留人声/仅保留背景音”子菜单;本地未接专用分离服务 | reference-video-menu | +| SE-078 | 音频参数栏调整宽度 | 差异 | 本地视频/字幕/转场栏有拖拽柄,音频栏缺同类柄;参考有声参数栏未验 | SmartEditEditor | +| SE-079 | 真实波形 | 待验 | 本地音频条是纯色色块,没有采样波形;本轮未取得参考有声轨波形证据 | se-wave/se-audio-clip | +| SE-080 | 音乐与卡点 | 通过 | 节拍与候选剪辑合同回归通过,音乐混音真实输出通过;不是参考算法效果对拍 | 本轮helpers/FFmpeg | + +### 导出与恢复 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-081 | 导出菜单位置 | 通过 | 本地/画布两个目的地和参考相同 | 参考export-menu/源码 | +| SE-082 | 导出菜单视觉 | 代码核对 | 本地有200px菜单、40px条目;边框/图标需独立像素精调 | 参考截图/CSS | +| SE-083 | 真实本地合成 | 通过 | FFmpeg实际生成结果,不用模拟成功替代 | 本轮ffmpeg.log | +| SE-084 | 提交任务ID和幂等 | 通过 | 请求ID回归与远程确定性任务身份测试通过 | 本轮helpers | +| SE-085 | 刷新恢复导出任务 | 通过 | 任务检查点/reconcile合同通过;本轮未执行真实Next页面断网恢复 | 本轮helpers | +| SE-086 | 浏览器中返回画布结果 | 待验 | 独立测试页任务回调仍是样例;本轮未跑主站Next API全链路 | ui-entry/onRender | +| SE-087 | 任务错误和服务不可用 | 代码核对 | 导出按钮按能力禁用,失败状态显示错误;本轮未切断实际服务故障注入 | SmartEditEditor/SmartEditDialog | +| SE-088 | 取消远程导出 | 差异 | 没有主动取消运行中合成任务的完整UI/API链路;本轮参考菜单也未验证取消 | 源码 | +| SE-089 | 多版本全导出 | 代码核对 | 高级设置有导出全部版本;主节点默认只一版,参考完整多版结果未生成 | SmartEditDialog | +| SE-090 | 最高时长与比例 | 通过 | 300秒约束、宽屏远程结果下载合同、6比例真实本地输出通过 | 本轮helpers/FFmpeg | + +### 边界与性能 + +| ID | 检查项 | 状态 | 结论/范围 | 证据 | +| --- | --- | --- | --- | --- | +| SE-091 | 最大片段数 | 代码核对 | 最多12视频片段、24独立音轨、500字幕;不是参考站已确认相同限制 | contract | +| SE-092 | 后台标签恢复 | 待验 | 循环预览监听visibilitychange;本轮未覆盖系统休眠/后台节流完整矩阵 | TransitionPreview | +| SE-093 | 原生媒体加载失败 | 代码核对 | play的AbortError忽略,其余报错可见;本轮未测试全部外部域名/CORS | EditPreview | +| SE-094 | 高清与12片段性能 | 既有证据 | 前批1080P4秒合成12.81秒、12片段7.604秒;本轮未重复此压力测试 | transition-stress-native.log | +| SE-095 | 服务部署状态 | 通过 | 本轮所有修改仅本地,未更新48或算力服务器 | git/操作记录 | + +## 验证与原始证据 + +- TypeScript通过;智能剪辑18项回归通过;完整helpers493项通过、1项跳过。 +- 本轮真实FFmpeg回归通过:旧重叠转场、三种居中2.2/4秒转场、六比例、字幕烧录、画面变换、音轨变速/延迟/包络、描边透明度。 +- 新的描边像素检查:白字像素仍存在,原黑描边像素消失,避免只测表单值。 +- 本轮未调用参考站付费生成或重新部署服务;参考原有2.2秒白场、1倍速、0dB保持;临时字幕已从列表删除,引用模式已退出。 +- 原始截图、DOM测量、测试日志位于`.qa/smart-edit/recheck/`;`transition-side-by-side.png`为并排对照,`local-outline-opacity-zero.jpg`为描边语义验证。 +- 参考站一度在字幕列表为空后仍显示旧字幕预览,重开编辑器后消失;这不是本地持久化成功或失败的证据。 +- 机器可读逐项清单:[JSON](smart-edit-full-recheck-2026-09-17.json)。旧报告如有冲突,以本轮明确复核的条目为准。 diff --git a/docs/smart-edit-subfeature-audit-2026-09-17.md b/docs/smart-edit-subfeature-audit-2026-09-17.md index dd6d4ce86..777dc7dd0 100644 --- a/docs/smart-edit-subfeature-audit-2026-09-17.md +++ b/docs/smart-edit-subfeature-audit-2026-09-17.md @@ -1,5 +1,7 @@ # 智能剪辑子功能再核对 +> 后续全面复核见[95项检查及像素差异](smart-edit-full-recheck-2026-09-17.md)。本文保留此前快照:描边透明度、步长和转场样式已在后续修复;本地吸附默认关闭,本文旧“默认开启”结论不准确。 + 范围:2026-09-17 在用户指定的 LibTV 项目逐层展开编辑器,结合此前节点输入卡片核对记录。只报告可见界面与实际操作,不推断参考站私有实现。用户随后明确要求直接使用参考网页动态预览图。所有新增代码只在本地;继续遵守“本地完整验收后再更新服务器”。 ## 本轮转场差异及修复 diff --git a/docs/smart-edit-ui-parity-2026-09-17.md b/docs/smart-edit-ui-parity-2026-09-17.md index 09fdd58ba..8acce7542 100644 --- a/docs/smart-edit-ui-parity-2026-09-17.md +++ b/docs/smart-edit-ui-parity-2026-09-17.md @@ -1,5 +1,7 @@ # 智能剪辑界面对照与验收(2026-09-17) +> 最新状态见[全面复核与像素差异](smart-edit-full-recheck-2026-09-17.md):95个检查点,本轮修复9项,仍有25项明确差异及8项待验。下方各批验收结果是历史记录,不能解释为所有功能或全部像素已一致。 + 参考:LibTV 已登录画布中的智能剪辑节点及编辑器。通过可见 DOM、截图和实际点击测量,不读取参考站隐藏状态;没有提交参考站生成任务。 ## 已逐项实现 diff --git a/lib/video-canvas/smart-edit/contract.ts b/lib/video-canvas/smart-edit/contract.ts index c42e37d73..0a08bd875 100644 --- a/lib/video-canvas/smart-edit/contract.ts +++ b/lib/video-canvas/smart-edit/contract.ts @@ -29,6 +29,8 @@ export const captionStyleSchema = z.object({ .default("#000000"), outlineWidth: z.number().min(0).max(10).default(0), opacity: z.number().min(0).max(1).default(1), + // Optional so existing edits keep their whole-caption opacity unchanged. + outlineOpacity: z.number().min(0).max(1).optional(), spacing: z.number().min(-5).max(30).default(0), x: z.number().min(0).max(1).optional(), y: z.number().min(0).max(1).optional(), @@ -250,9 +252,9 @@ export function compiledSubtitles(plan: EditPlan) { const a = Math.max(s.start, clip.sourceIn), b = Math.min(s.end, clip.sourceOut); if (b <= a) return []; - // A crossfade's incoming subtitle starts when the outgoing subtitle ends. + // Only legacy overlapping edits delay incoming captions until the outgoing clip ends. const start = Math.max( - clip.start + clip.overlap, + clip.start + (clip.transition?.alignment === "center" ? 0 : clip.overlap), clip.start + Math.round((a - clip.sourceIn) / clip.speed), ); const end = Math.min(clip.end, clip.start + Math.round((b - clip.sourceIn) / clip.speed)); @@ -286,7 +288,7 @@ export function subtitlesToAss(plan: EditPlan, width: number, height: number) { const scale = height / 720; const styles = rows.map((s, i) => { const style = s.style || defaultCaptionStyle; - return `Style: c${i},Noto Sans CJK SC,${style.fontSize * scale},${color(style.color, style.opacity)},&H00FFFFFF,${color(style.outlineColor, style.opacity)},&HFF000000,0,0,0,0,100,100,${style.spacing * scale},0,1,${style.outlineWidth * scale},0,2,20,20,${Math.round(height * 0.05)},1`; + return `Style: c${i},Noto Sans CJK SC,${style.fontSize * scale},${color(style.color, style.opacity)},&H00FFFFFF,${color(style.outlineColor, style.opacity * (style.outlineOpacity ?? 1))},&HFF000000,0,0,0,0,100,100,${style.spacing * scale},0,1,${style.outlineWidth * scale},0,2,20,20,${Math.round(height * 0.05)},1`; }); const events = rows.map((s, i) => { const text = s.text diff --git a/scripts/qa/video-canvas-smart-edit.cjs b/scripts/qa/video-canvas-smart-edit.cjs index b0c20d6c6..8fc732045 100644 --- a/scripts/qa/video-canvas-smart-edit.cjs +++ b/scripts/qa/video-canvas-smart-edit.cjs @@ -453,7 +453,8 @@ const fs = require("node:fs"), fontSize: 80, color: "#ffffff", outlineColor: "#000000", - outlineWidth: 0, + outlineWidth: 8, + outlineOpacity: 0, opacity: 1, spacing: 0, x: 0.25, @@ -481,13 +482,15 @@ const fs = require("node:fs"), ], { maxBuffer: 4 * 1024 * 1024 }, ); - let whitePixels = 0; + let whitePixels = 0, blackPixels = 0; for (let y = 170; y < 240; y++) for (let x = 90; x < 340; x++) { const offset = (y * 854 + x) * 3; if (frame[offset] > 200 && frame[offset + 1] > 200 && frame[offset + 2] > 200) whitePixels++; + if (frame[offset] < 30 && frame[offset + 1] < 30 && frame[offset + 2] < 30) blackPixels++; } assert.ok(whitePixels > 150, `caption must render at moved position: ${whitePixels}`); + assert.ok(blackPixels < 10, `zero outline opacity must remove only the black outline: ${blackPixels}`); console.log( "PASS: real transform pixels, caption position, detached audio without doubling, 2x audio, gain, fades, delay and black tail", ); diff --git a/tests/routes/video-canvas-smart-edit.test.ts b/tests/routes/video-canvas-smart-edit.test.ts index bcebbad77..42ad4774b 100644 --- a/tests/routes/video-canvas-smart-edit.test.ts +++ b/tests/routes/video-canvas-smart-edit.test.ts @@ -113,6 +113,26 @@ test("EDL computes overlap, speed and remapped subtitle timestamps from a shared ]); expect(parseSrt(subtitlesToSrt(compiledSubtitles(p)))).toEqual(compiledSubtitles(p)); }); +test("centered transitions keep incoming source captions at their original edit time", () => { + const p = plan(); + p.clips[1].transition = { type: "fade", duration: 2, alignment: "center" }; + expect(compiledSubtitles(p)).toEqual([ + { start: SECOND, end: 2 * SECOND, text: "第一句" }, + { start: 3 * SECOND, end: 3.5 * SECOND, text: "第二句" }, + ]); +}); +test("outline opacity leaves caption fill visible and preserves legacy whole-text opacity", () => { + const p = plan(); + const style = { ...defaultCaptionStyle, outlineWidth: 4, outlineOpacity: 0 }; + p.captions = [{ id: "alpha", start: 0, end: SECOND, text: "描边", style }]; + let fields = subtitlesToAss(p, 1280, 720).split("\n").find((s) => s.startsWith("Style:"))!.split(","); + expect(fields[3]).toBe("&H00ffffff"); + expect(fields[5]).toBe("&Hff000000"); + p.captions[0].style = { ...defaultCaptionStyle, opacity: 0.5 }; + fields = subtitlesToAss(p, 1280, 720).split("\n").find((s) => s.startsWith("Style:"))!.split(","); + expect(fields[3]).toBe("&H80ffffff"); + expect(fields[5]).toBe("&H80000000"); +}); test("invalid source ranges, duplicate identity and overlong compositions cannot render", () => { const p = plan(); p.clips[1].sourceOut = 8 * SECOND;