Audit smart edit parity and fix caption timing and outline previews

This commit is contained in:
134 2026-09-17 20:10:59 +08:00
parent 431ca03fac
commit 68f62dae58
10 changed files with 1066 additions and 28 deletions

View File

@ -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,
}}

View File

@ -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) {

View File

@ -763,7 +763,7 @@ export function SmartEditEditor({
<X />
</Icon>
</div>
<div className="se-panel-scroll">
<div className="se-panel-scroll se-library-scroll">
<div className="se-small-label">基础转场</div>
<div className="se-transition-list">
{Object.entries(transitionNames).map(([type, name]) => (
@ -779,7 +779,7 @@ export function SmartEditEditor({
draggable={false}
/>
</span>
{name}
<span className="se-transition-caption">{name}</span>
</button>
))}
</div>
@ -1190,18 +1190,18 @@ export function SmartEditEditor({
onChange={(outlineColor) => changeStyle({ outlineColor })}
/>
<StyleNumber
label="粗细"
label="描边粗细"
value={captionStyle.outlineWidth}
min={0}
max={10}
onChange={(outlineWidth) => changeStyle({ outlineWidth })}
/>
<StyleNumber
label="不透明度"
value={Math.round(captionStyle.opacity * 100)}
label="描边不透明度"
value={Math.round((captionStyle.outlineOpacity ?? 1) * 100)}
min={0}
max={100}
onChange={(v) => 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 (
<div className="se-field">
<label htmlFor={`se-value-${suffix}`}>{label}</label>
@ -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,
),

View File

@ -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/操作记录"
}
]
}

View File

@ -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)。旧报告如有冲突,以本轮明确复核的条目为准。

View File

@ -1,5 +1,7 @@
# 智能剪辑子功能再核对
> 后续全面复核见[95项检查及像素差异](smart-edit-full-recheck-2026-09-17.md)。本文保留此前快照:描边透明度、步长和转场样式已在后续修复;本地吸附默认关闭,本文旧“默认开启”结论不准确。
范围:2026-09-17 在用户指定的 LibTV 项目逐层展开编辑器,结合此前节点输入卡片核对记录。只报告可见界面与实际操作,不推断参考站私有实现。用户随后明确要求直接使用参考网页动态预览图。所有新增代码只在本地;继续遵守“本地完整验收后再更新服务器”。
## 本轮转场差异及修复

View File

@ -1,5 +1,7 @@
# 智能剪辑界面对照与验收(2026-09-17)
> 最新状态见[全面复核与像素差异](smart-edit-full-recheck-2026-09-17.md):95个检查点,本轮修复9项,仍有25项明确差异及8项待验。下方各批验收结果是历史记录,不能解释为所有功能或全部像素已一致。
参考:LibTV 已登录画布中的智能剪辑节点及编辑器。通过可见 DOM、截图和实际点击测量,不读取参考站隐藏状态;没有提交参考站生成任务。
## 已逐项实现

View File

@ -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

View File

@ -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",
);

View File

@ -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;