Audit smart edit parity and fix caption timing and outline previews
This commit is contained in:
parent
431ca03fac
commit
68f62dae58
@ -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,
|
||||
}}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
),
|
||||
|
||||
775
docs/smart-edit-full-recheck-2026-09-17.json
Normal file
775
docs/smart-edit-full-recheck-2026-09-17.json
Normal 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/操作记录"
|
||||
}
|
||||
]
|
||||
}
|
||||
206
docs/smart-edit-full-recheck-2026-09-17.md
Normal file
206
docs/smart-edit-full-recheck-2026-09-17.md
Normal 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)。旧报告如有冲突,以本轮明确复核的条目为准。
|
||||
@ -1,5 +1,7 @@
|
||||
# 智能剪辑子功能再核对
|
||||
|
||||
> 后续全面复核见[95项检查及像素差异](smart-edit-full-recheck-2026-09-17.md)。本文保留此前快照:描边透明度、步长和转场样式已在后续修复;本地吸附默认关闭,本文旧“默认开启”结论不准确。
|
||||
|
||||
范围:2026-09-17 在用户指定的 LibTV 项目逐层展开编辑器,结合此前节点输入卡片核对记录。只报告可见界面与实际操作,不推断参考站私有实现。用户随后明确要求直接使用参考网页动态预览图。所有新增代码只在本地;继续遵守“本地完整验收后再更新服务器”。
|
||||
|
||||
## 本轮转场差异及修复
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# 智能剪辑界面对照与验收(2026-09-17)
|
||||
|
||||
> 最新状态见[全面复核与像素差异](smart-edit-full-recheck-2026-09-17.md):95个检查点,本轮修复9项,仍有25项明确差异及8项待验。下方各批验收结果是历史记录,不能解释为所有功能或全部像素已一致。
|
||||
|
||||
参考:LibTV 已登录画布中的智能剪辑节点及编辑器。通过可见 DOM、截图和实际点击测量,不读取参考站隐藏状态;没有提交参考站生成任务。
|
||||
|
||||
## 已逐项实现
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user