186 lines
8.3 KiB
Markdown
186 lines
8.3 KiB
Markdown
## 🔧 鉴权到期治理 + 多素材时间轴稳定性修复 (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`
|