fix: align filmstrip frames with waveform

This commit is contained in:
2026-05-19 17:50:00 +08:00
parent cb991e7a17
commit 883e1d4de6
3 changed files with 40 additions and 13 deletions

View File

@@ -15,7 +15,7 @@
## 部署事实
- 平台VPS `76.13.31.179`Ubuntu 24.04 / Docker Compose / Coolify Traefik
- 发布状态已部署并验证2026-05-19逐句时间轴窄版面板 + 波形下方临时画面胶片选帧 + 胶片原位放大 + 隐藏源视频工作区音频解析摘要卡 + 隐藏工作区顶部状态提示条 + 三字段候选生成工作流 + 折叠紧凑候选区);`https://marketing.skg.com` 已启用应用内登录页,未登录 API 返回 401认证后首页 200容器内 `/health` 返回 `ok:true`
- 发布状态已部署并验证2026-05-19逐句时间轴窄版面板 + 波形同框时间对齐画面胶片 + 胶片原位放大 + 隐藏源视频工作区音频解析摘要卡 + 隐藏工作区顶部状态提示条 + 三字段候选生成工作流 + 折叠紧凑候选区);`https://marketing.skg.com` 已启用应用内登录页,未登录 API 返回 401认证后首页 200容器内 `/health` 返回 `ok:true`
- 主站 / 前端:`https://marketing.skg.com`
- API / 后端:`https://marketing.skg.com/api`
- 代码仓库 / Gitea`https://git.kang-kang.com/kangwan/20260512-skg-tk`

File diff suppressed because one or more lines are too long

View File

@@ -2628,7 +2628,9 @@ function AudioIntakePanel({
frames={filmstripPreviews}
status={filmstripStatus}
density={filmstripDensity}
duration={timelineDuration}
currentTime={currentTime}
hoverTime={waveHoverTime}
selectedTimes={frames.map((frame) => frame.timestamp)}
busyTime={filmstripBusyTime}
onDensityChange={setFilmstripDensity}
@@ -2712,7 +2714,9 @@ function TimelineFilmstrip({
frames,
status,
density,
duration,
currentTime,
hoverTime,
selectedTimes,
busyTime,
onDensityChange,
@@ -2723,7 +2727,9 @@ function TimelineFilmstrip({
frames: FilmstripPreviewFrame[]
status: FilmstripStatus
density: FilmstripDensitySeconds
duration: number
currentTime: number
hoverTime: number | null
selectedTimes: number[]
busyTime: number | null
onDensityChange: (density: FilmstripDensitySeconds) => void
@@ -2731,12 +2737,15 @@ function TimelineFilmstrip({
onDragStart: (time: number) => void
onDragEnd: () => void
}) {
const pointerPct = clampNumber((currentTime / Math.max(duration, 1)) * 100, 0, 100)
const hoverPct = hoverTime === null ? null : clampNumber((hoverTime / Math.max(duration, 1)) * 100, 0, 100)
return (
<div className="mt-2 rounded-md border border-white/10 bg-black/30 p-2">
<div className="mb-2 flex items-center justify-between gap-3">
<div className="relative z-20 mt-2 overflow-visible pt-1">
<div className="mb-1.5 flex items-center justify-between gap-3">
<div className="min-w-0">
<div className="text-[11px] font-semibold text-white/68"></div>
<div className="mt-0.5 text-[10px] text-white/34"></div>
<div className="mt-0.5 text-[10px] text-white/34"></div>
</div>
<div className="flex shrink-0 items-center gap-1">
{FILMSTRIP_DENSITIES.map((item) => (
@@ -2758,23 +2767,35 @@ function TimelineFilmstrip({
</div>
</div>
<div className="min-h-[202px] overflow-x-auto overflow-y-hidden px-14 pb-16 pt-14">
<div className="relative h-[202px] overflow-visible border-t border-white/8">
{status === "loading" ? (
<div className="flex h-[72px] items-center justify-center gap-2 rounded-md border border-dashed border-white/12 text-[11px] text-white/40">
<div className="absolute inset-x-0 top-12 flex h-[72px] items-center justify-center gap-2 rounded-md border border-dashed border-white/12 text-[11px] text-white/40">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
</div>
) : status === "failed" ? (
<div className="flex h-[72px] items-center justify-center rounded-md border border-dashed border-rose-200/20 text-[11px] text-rose-100/68">
<div className="absolute inset-x-0 top-12 flex h-[72px] items-center justify-center rounded-md border border-dashed border-rose-200/20 text-[11px] text-rose-100/68">
</div>
) : frames.length ? (
<div className="flex min-w-max items-end pl-1">
<div className="absolute bottom-7 left-0 right-0 top-4 overflow-visible">
<div className="absolute inset-x-0 bottom-[20px] h-px bg-white/14" />
{hoverPct !== null && (
<div
className="pointer-events-none absolute bottom-0 top-0 z-10 w-px bg-cyan-100/55"
style={{ left: `${hoverPct}%` }}
/>
)}
<div
className="pointer-events-none absolute bottom-0 top-0 z-10 w-[2px] bg-emerald-200 shadow-[0_0_16px_rgba(110,231,183,0.8)]"
style={{ left: `${pointerPct}%` }}
/>
{frames.map((frame, index) => {
const selected = selectedTimes.some((time) => Math.abs(time - frame.time) < 0.45)
const active = Math.abs(currentTime - frame.time) <= Math.max(density * 0.45, 0.45)
const busy = busyTime !== null && Math.abs(busyTime - frame.time) < 0.45
const tiltClass = FILMSTRIP_TILT_CLASSES[index % FILMSTRIP_TILT_CLASSES.length]
const framePct = clampNumber((frame.time / Math.max(duration, 1)) * 100, 0, 100)
return (
<div
key={`${frame.time}-${index}`}
@@ -2785,9 +2806,11 @@ function TimelineFilmstrip({
onDragStart(frame.time)
}}
onDragEnd={onDragEnd}
className={`relative shrink-0 ${index ? "-ml-2.5" : ""} ${tiltClass} cursor-grab transition-transform duration-150 hover:z-30 hover:-translate-y-3 hover:rotate-0 hover:scale-[2.45] active:cursor-grabbing`}
className={`absolute bottom-[20px] z-20 -translate-x-1/2 ${tiltClass} origin-bottom cursor-grab transition-transform duration-150 hover:z-50 hover:-translate-y-3 hover:rotate-0 hover:scale-[2.45] active:cursor-grabbing`}
style={{ left: `${framePct}%` }}
title={`${frame.time.toFixed(1)}s · 拖到关键帧库才选取`}
>
<div className="absolute left-1/2 top-full h-4 w-px -translate-x-1/2 bg-white/18" />
<MediaAssetTile
src={frame.src}
alt={`胶片 ${frame.time.toFixed(1)}s`}
@@ -2810,10 +2833,14 @@ function TimelineFilmstrip({
})}
</div>
) : (
<div className="flex h-[72px] items-center justify-center rounded-md border border-dashed border-white/12 text-[11px] text-white/34">
<div className="absolute inset-x-0 top-12 flex h-[72px] items-center justify-center rounded-md border border-dashed border-white/12 text-[11px] text-white/34">
</div>
)}
<div className="pointer-events-none absolute bottom-2 left-0 right-0 flex items-center justify-between font-mono text-[9.5px] text-white/30">
<span>0s</span>
<span>{formatSeconds(duration)}</span>
</div>
</div>
</div>
)