## 🔧 鉴权到期治理 + 多素材时间轴稳定性修复 (Day 24) ### 概述 本日主要完成两条主线: 1. **账号与鉴权治理**:会员到期改为请求时自动失效(登录/鉴权接口触发),并统一返回续费提示。 2. **视频生成稳定性**:围绕多素材时间轴、截取语义、拼接边界冻结、画面比例与字幕标题适配进行一轮端到端修复。 --- ## 🔐 会员到期请求时失效 — 第一阶段 (Day 24) ### 目标 避免依赖定时任务,用户在触发登录或访问受保护接口时即可完成到期判定与账号停用。 ### 行为调整 - 到期判断基于 `users.expires_at`。 - 判定到期后: - 将 `is_active` 自动置为 `false` - 删除该用户全部 session - 返回 `403`,提示:`会员已到期,请续费` ### 实现点 - `users.py` 新增 `deactivate_user_if_expired()`,并补充 `_parse_expires_at()` 统一时区解析。 - `deps.py` 在 `get_current_user` / `get_current_user_optional` 中统一接入到期检查。 - `auth/router.py` 在登录路径增加到期停用逻辑;`/api/auth/me` 统一走 `Depends(get_current_user)`。 --- ## 🖼️ 画面比例控制 + 字幕标题适配 — 第二阶段 (Day 24) ### 2.1 输出画面比例可配置 - 时间轴顶部新增“画面比例”下拉:`9:16` / `16:9`。 - 默认值 `9:16`,并持久化到 localStorage。 - 生成请求携带 `output_aspect_ratio`,后端在单素材与多素材流程中统一按目标分辨率处理。 ### 2.2 标题/字幕在窄屏画布防溢出 为减少“预览正常、成片溢出”的差异,统一了预览与渲染策略: - 根据 composition 宽度进行响应式缩放。 - 开启可换行:`white-space: normal` + `word-break` + `overflow-wrap`。 - 描边、字距、上下边距同步按比例缩放。 ### 2.3 片头标题显示模式(短暂/常驻) - 在“标题与字幕”面板的“片头标题”行尾新增下拉,支持:`短暂显示` / `常驻显示`。 - 默认模式为 `短暂显示`,短暂模式默认时长为 4 秒。 - 用户选择会持久化到 localStorage,刷新后保持上次配置。 - 生成请求新增 `title_display_mode`,短暂模式透传 `title_duration=4.0`。 - Remotion 端到端支持该参数: - `short`:标题在设定时长后淡出并结束渲染; - `persistent`:标题全程常驻(保留淡入动画,不执行淡出)。 --- ## 🎥 方向归一化 + 多素材拼接稳定性 — 第三阶段 (Day 24) ### 3.1 MOV 旋转元数据导致横竖识别错误 问题场景:编码分辨率是横屏,但依赖 rotation side-data 才能正确显示为竖屏(常见于手机 MOV)。 修复方案: - `get_video_metadata()` 扩展返回 `rotation/effective_width/effective_height`。 - 新增 `normalize_orientation()`,在流程前对带旋转元数据素材做物理方向归一化。 - 单素材和多素材下载后统一执行方向归一化,再做分辨率决策。 ### 3.2 多素材“只看到第一段”与边界冻结 针对拼接可靠性补了两类保护: - **分配保护**:`custom_assignments` 与素材数量不一致时,后端回退自动分配,避免异常输入导致仅首段生效。 - **编码一致性**: - 片段准备阶段统一重编码; - concat 阶段不再走拷贝; - 进一步统一为 `25fps + CFR`,并在 concat 增加 `+genpts`,降低段边界时间基不连续导致的“画面冻结口型还动”风险。 --- ## ⏱️ 时间轴截取语义对齐修复 — 第四阶段 (Day 24) ### 背景 时间轴设计语义是: - 每段可以设置 `sourceStart/sourceEnd`; - 总时长超出音频时,仅保留可见段,末段截齐音频; - 总时长不足时,由最后可见段循环补齐。 本日将前后端对齐到这一语义。 ### 4.1 `source_end` 全链路打通 此前仅传 `source_start`,导致后端无法准确知道“截到哪里”。 本次改动: - 前端 `toCustomAssignments()` 增加可选 `source_end`。 - 后端 `CustomAssignment` schema 增加 `source_end`。 - workflow 将 `source_end` 透传到 `prepare_segment()`(单素材/多素材均支持)。 - `prepare_segment()` 增加 `source_end` 参数,按 `[source_start, source_end)` 计算可用片段,并在需要循环时先裁剪再循环,避免循环范围错位。 ### 4.2 时间轴有效时长计算修复 修复 `sourceStart > 0 且 sourceEnd = 0` 时的有效时长错误: - 旧逻辑会按整段素材时长计算; - 新逻辑改为 `materialDuration - sourceStart`。 该修复同时用于: - `recalcPositions()` 的段时长计算; - TimelineEditor 中“循环补足”可视化比例计算。 ### 4.3 可见段分配优先级修复 修复“可见段数 < 已选素材数时,custom_assignments 被丢弃回退自动分配”的问题: - 生成请求优先以时间轴可见段的 `assignments` 为准; - 超出时间轴的素材不参与本次生成。 ### 4.4 单素材截取触发条件补齐 单素材模式下,若只改了终点(`sourceEnd > 0`)也会发送 `custom_assignments`,确保截取生效。 --- ## 🧭 页面交互与体验细节 — 第五阶段 (Day 24) - 页面刷新后自动回到顶部,避免从历史滚动位置进入页面。 - 素材列表与历史视频列表滚动增加“跳过首次自动滚动”保护,减少恢复状态时页面跳动。 - 时间轴比例区移除多余文案,保持信息简洁。 --- ## 涉及文件汇总 ### 后端修改 | 文件 | 变更 | |------|------| | `backend/app/repositories/users.py` | 新增 `deactivate_user_if_expired()` 与 `_parse_expires_at()` | | `backend/app/core/deps.py` | `get_current_user` / `get_current_user_optional` 接入到期失效检查 | | `backend/app/modules/auth/router.py` | 登录时到期停用 + `/api/auth/me` 统一鉴权依赖 | | `backend/app/modules/videos/schemas.py` | `CustomAssignment` 新增 `source_end`;保留 `output_aspect_ratio` | | `backend/app/modules/videos/workflow.py` | 多素材/单素材透传 `source_end`;多素材 prepare/concat 统一 25fps;标题显示模式参数透传 Remotion | | `backend/app/services/video_service.py` | 旋转元数据解析与方向归一化;`prepare_segment` 支持 `source_end/target_fps`;concat 强制 CFR + `+genpts` | | `backend/app/services/remotion_service.py` | render 支持 `title_display_mode/title_duration` 并传递到 render.ts | ### 前端修改 | 文件 | 变更 | |------|------| | `frontend/src/features/home/model/useTimelineEditor.ts` | `CustomAssignment` 新增 `source_end`;修复 sourceStart 开放终点时长计算 | | `frontend/src/features/home/model/useHomeController.ts` | 多素材以可见 assignments 为准发送;单素材截取触发条件补齐 | | `frontend/src/features/home/ui/TimelineEditor.tsx` | 画面比例下拉;循环比例按截取后有效时长计算 | | `frontend/src/features/home/model/useHomePersistence.ts` | `outputAspectRatio` 与 `titleDisplayMode` 持久化 | | `frontend/src/features/home/ui/HomePage.tsx` | 页面进入滚动到顶部;ClipTrimmer/Timeline 交互保持一致 | | `frontend/src/features/home/ui/FloatingStylePreview.tsx` | 标题/字幕样式预览与成片渲染策略对齐 | | `frontend/src/features/home/ui/TitleSubtitlePanel.tsx` | 标题行新增“短暂显示/常驻显示”下拉 | ### Remotion 修改 | 文件 | 变更 | |------|------| | `remotion/src/components/Title.tsx` | 标题响应式缩放与自动换行;新增短暂/常驻显示模式控制 | | `remotion/src/components/Subtitles.tsx` | 字幕响应式缩放与自动换行,减少预览/成片差异 | | `remotion/src/Video.tsx` | 新增 `titleDisplayMode` 透传到标题组件 | | `remotion/src/Root.tsx` | 默认 props 增加 `titleDisplayMode='short'` 与 `titleDuration=4` | | `remotion/render.ts` | CLI 参数新增 `--titleDisplayMode`,inputProps 增加 `titleDisplayMode` | --- ## 验证记录 - 后端语法检查:`python -m py_compile backend/app/modules/videos/schemas.py backend/app/modules/videos/workflow.py backend/app/services/video_service.py backend/app/services/remotion_service.py` - 前端类型检查:`npx tsc --noEmit` - 前端 ESLint:`npx eslint src/features/home/model/useHomeController.ts src/features/home/model/useHomePersistence.ts src/features/home/ui/HomePage.tsx src/features/home/ui/TitleSubtitlePanel.tsx` - Remotion 渲染脚本构建:`npm run build:render`