diff --git a/Docs/BACKEND_DEV.md b/Docs/BACKEND_DEV.md index d50a188..51f92ed 100644 --- a/Docs/BACKEND_DEV.md +++ b/Docs/BACKEND_DEV.md @@ -82,6 +82,9 @@ backend/ - 标题显示模式参数: - `title_display_mode`: `short` / `persistent`(默认 `short`) - `title_duration`: 默认 `4.0`(秒),仅 `short` 模式生效 +- 片头副标题参数: + - `secondary_title`: 副标题文字(可选,限 20 字),仅在视频画面中显示,不参与发布标题 + - `secondary_title_style_id` / `secondary_title_font_size` / `secondary_title_top_margin`: 副标题样式配置 - workflow/remotion 侧需保持字段透传一致,避免前后端语义漂移。 --- @@ -167,7 +170,6 @@ backend/user_data/{user_uuid}/cookies/ - `DOUYIN_LOCALE` / `DOUYIN_TIMEZONE_ID` - `DOUYIN_FORCE_SWIFTSHADER` - `DOUYIN_DEBUG_ARTIFACTS` / `DOUYIN_RECORD_VIDEO` / `DOUYIN_KEEP_SUCCESS_VIDEO` -- `DOUYIN_COOKIE` (抖音视频下载 Cookie) ### 支付宝 - `ALIPAY_APP_ID` / `ALIPAY_PRIVATE_KEY_PATH` / `ALIPAY_PUBLIC_KEY_PATH` diff --git a/Docs/BACKEND_README.md b/Docs/BACKEND_README.md index 1c779cb..56efce1 100644 --- a/Docs/BACKEND_README.md +++ b/Docs/BACKEND_README.md @@ -146,6 +146,10 @@ backend/ - `subtitle_font_size`: 字幕字号(覆盖样式默认值) - `title_font_size`: 标题字号(覆盖样式默认值) - `title_top_margin`: 标题距顶部像素 +- `secondary_title`: 片头副标题文字(可选,限 20 字,仅视频画面显示) +- `secondary_title_style_id`: 副标题样式 ID +- `secondary_title_font_size`: 副标题字号 +- `secondary_title_top_margin`: 副标题距主标题间距 - `subtitle_bottom_margin`: 字幕距底部像素 - `enable_subtitles`: 是否启用字幕 - `bgm_id`: 背景音乐 ID diff --git a/Docs/DevLogs/Day25.md b/Docs/DevLogs/Day25.md new file mode 100644 index 0000000..98086b2 --- /dev/null +++ b/Docs/DevLogs/Day25.md @@ -0,0 +1,254 @@ +## 🔧 文案提取助手修复 — 抖音链接无法提取文案 (Day 25) + +### 概述 + +文案提取助手粘贴抖音链接后无法提取文案,yt-dlp 报错 `Fresh cookies are needed`,手动回退方案也因抖音页面结构变化失效。本日完成了完整修复,并清理了不再需要的 `DOUYIN_COOKIE` 配置。 + +--- + +## 🐛 问题诊断 + +### 错误链路 + +1. **yt-dlp 失败**:`ERROR: [Douyin] Fresh cookies (not necessarily logged in) are needed` + - yt-dlp 版本 `2025.12.08` 过旧 + - 抖音 API `aweme/v1/web/aweme/detail/` 需要签名 cookie(`s_v_web_id` 等),即使升级 yt-dlp 到最新版 + 传入 cookie 仍无法解决,属 yt-dlp 已知问题 +2. **手动回退失败**:`Could not find RENDER_DATA in page` + - 旧方案通过桌面端用户主页 + `modal_id` 访问,抖音 SSR 已不再返回 `videoDetail` 数据 +3. **`.env` 中 `DOUYIN_COOKIE`**:时间戳 2024 年 12 月,早已过期 + +--- + +## ✅ 修复方案:移动端分享页 + 自动获取 ttwid + +### 核心思路 + +放弃依赖 yt-dlp 下载抖音视频和手动维护 cookie,改为: + +1. 自动从 ByteDance 公共 API 获取新鲜 `ttwid`(匿名令牌,不绑定账号) +2. 用 `ttwid` 访问移动端分享页 `m.douyin.com/share/video/{id}` +3. 从页面内嵌 JSON 中提取 `play_addr` 播放地址并下载 + +### 关键代码(`_download_douyin_manual` 重写) + +```python +# 1. 获取新鲜 ttwid +ttwid_resp = await client.post( + "https://ttwid.bytedance.com/ttwid/union/register/", + json={"region": "cn", "aid": 6383, "service": "www.douyin.com", ...} +) +ttwid = ttwid_resp.cookies.get("ttwid", "") + +# 2. 访问移动端分享页 +page_resp = await client.get( + f"https://m.douyin.com/share/video/{video_id}", + headers={"cookie": f"ttwid={ttwid}", ...} +) + +# 3. 提取 play_addr +addr_match = re.search(r'"play_addr":\{"uri":"([^"]+)","url_list":\["([^"]+)"', page_text) +video_url = addr_match.group(2).replace(r"\u002F", "/") +``` + +### 优势 + +- 不再依赖手动维护的 `DOUYIN_COOKIE`,ttwid 每次请求自动获取 +- 不受 yt-dlp 对抖音支持状况影响 +- 所有用户通用,不绑定特定账号 + +--- + +## 🧹 清理 DOUYIN_COOKIE 配置 + +`DOUYIN_COOKIE` 仅用于文案提取,新方案不再需要,已从以下位置删除: + +| 文件 | 变更 | +|------|------| +| `backend/.env` | 删除 `DOUYIN_COOKIE` 配置项及注释 | +| `backend/app/core/config.py` | 删除 `DOUYIN_COOKIE: str = ""` 字段定义 | +| `backend/app/modules/tools/service.py` | 删除 yt-dlp 传 cookie 逻辑和 `_write_netscape_cookies` 辅助函数 | + +--- + +## 🔤 前端文案修正 + +将文案提取界面中的"AI 洗稿结果"改为"AI 改写结果"。 + +| 文件 | 变更 | +|------|------| +| `frontend/src/features/home/ui/ScriptExtractionModal.tsx` | `AI 洗稿结果` → `AI 改写结果` | +| `backend/app/modules/tools/service.py` | 注释中"洗稿"→"改写" | +| `backend/app/services/glm_service.py` | docstring 中"洗稿"→"改写文案" | + +--- + +## 📦 其他变更 + +- **yt-dlp 升级**:`2025.12.08` → `2026.2.21` +- **yt-dlp 初始化修正**:改为 `YoutubeDL(ydl_opts)` 直接传参初始化(原先空初始化后 update params 不生效) +- **User-Agent 更新**:yt-dlp 中 `Chrome/91` → `Chrome/131` + +--- + +## 涉及文件汇总 + +### 后端修改 + +| 文件 | 变更 | +|------|------| +| `backend/app/modules/tools/service.py` | 重写 `_download_douyin_manual`(移动端分享页方案);修正 yt-dlp 初始化;清理 cookie 相关代码;注释改写 | +| `backend/app/services/glm_service.py` | docstring "洗稿" → "改写文案" | +| `backend/app/core/config.py` | 删除 `DOUYIN_COOKIE` 字段 | +| `backend/.env` | 删除 `DOUYIN_COOKIE` 配置 | +| `backend/requirements.txt` | yt-dlp 版本升级 | + +### 前端修改 + +| 文件 | 变更 | +|------|------| +| `frontend/src/features/home/ui/ScriptExtractionModal.tsx` | "AI 洗稿结果" → "AI 改写结果" | + +--- + +## ✏️ AI 智能改写 — 自定义提示词功能 + +### 概述 + +文案提取助手的"AI 智能改写"原先使用硬编码 prompt,用户无法定制改写风格。本次在 checkbox 右侧新增"自定义提示词"折叠区域,用户可编辑自定义 prompt,持久化到 localStorage,后端按需替换默认 prompt。 + +### 后端修改 + +**路由层** (`router.py`):`extract_script_tool` 新增可选 Form 参数 `custom_prompt: Optional[str] = Form(None)`,透传给 service。 + +**服务层** (`service.py`):`extract_script()` 签名新增 `custom_prompt`,透传给 `glm_service.rewrite_script(script, custom_prompt)`。 + +**AI 层** (`glm_service.py`):`rewrite_script(self, text, custom_prompt=None)`,若 `custom_prompt` 有值则用自定义 prompt + 原文拼接,否则保持原有默认 prompt。 + +```python +if custom_prompt and custom_prompt.strip(): + prompt = f"""{custom_prompt.strip()} + +原始文案: +{text}""" +else: + prompt = f"""请将以下视频文案进行改写。...(原有默认)""" +``` + +### 前端修改 + +**Hook** (`useScriptExtraction.ts`): +- 新增 `customPrompt` / `showCustomPrompt` 状态 +- 初始值从 `localStorage.getItem("vigent_rewriteCustomPrompt")` 恢复 +- `customPrompt` 变化时防抖 300ms 保存到 localStorage +- `handleExtract()` 中若 `doRewrite && customPrompt.trim()` 有值,追加 `formData.append("custom_prompt", ...)` +- modal 重置时不清空 customPrompt(持久化偏好) + +**UI** (`ScriptExtractionModal.tsx`): +- checkbox 同行右侧新增"自定义提示词 ▼"按钮(仅 `doRewrite` 时显示) +- 点击展开 textarea 编辑区域,底部提示"留空则使用默认提示词" +- 取消勾选 AI 智能改写时,自定义提示词区域自动隐藏 + +### 涉及文件 + +| 文件 | 变更 | +|------|------| +| `backend/app/modules/tools/router.py` | 新增 `custom_prompt` Form 参数 | +| `backend/app/modules/tools/service.py` | `extract_script()` 透传 `custom_prompt` | +| `backend/app/services/glm_service.py` | `rewrite_script()` 支持自定义 prompt | +| `frontend/.../useScriptExtraction.ts` | 新增状态、localStorage 持久化、FormData 传参 | +| `frontend/.../ScriptExtractionModal.tsx` | UI 按钮 + 展开 textarea | + +### 验证 + +- 后端 `python -m py_compile` 三个文件通过 +- 前端 `npx tsc --noEmit` 通过 + +--- + +## 🐛 SSR 构建修复 — localStorage is not defined + +### 问题 + +`npm run build` 报错 `ReferenceError: localStorage is not defined`,因为 `useScriptExtraction.ts` 中 `useState` 的初始化函数在 SSR(Node.js)环境下也会执行,而服务端没有 `localStorage`。 + +### 修复 + +`useState` 初始化加 `typeof window !== "undefined"` 守卫: + +```typescript +const [customPrompt, setCustomPrompt] = useState( + () => typeof window !== "undefined" ? localStorage.getItem(CUSTOM_PROMPT_KEY) || "" : "" +); +``` + +| 文件 | 变更 | +|------|------| +| `frontend/.../useScriptExtraction.ts` | `useState` 初始化增加 SSR 安全守卫 | + +--- + +## 🎬 片头副标题功能 + +### 概述 + +新增片头副标题(secondary_title),显示在主标题下方,用于补充说明或悬念引导。副标题有独立的样式配置(字体、字号、颜色等),可由 AI 同时生成,20 字限制,仅在视频画面中显示,不参与发布标题。 + +命名约定:后端 `secondary_title`(snake_case),前端 `videoSecondaryTitle`(camelCase),用户界面"片头副标题"。 + +--- + +### 后端修改 + +| 文件 | 变更 | +|------|------| +| `backend/app/modules/videos/schemas.py` | `GenerateRequest` 新增 4 个可选字段:`secondary_title`、`secondary_title_style_id`、`secondary_title_font_size`、`secondary_title_top_margin` | +| `backend/app/services/glm_service.py` | AI prompt 增加副标题生成要求(不超过20字),JSON 格式新增 `secondary_title` 字段 | +| `backend/app/modules/ai/router.py` | `GenerateMetaResponse` 增加 `secondary_title: str = ""`,endpoint 返回时取 `result.get("secondary_title", "")` | +| `backend/app/modules/videos/workflow.py` | `use_remotion` 条件增加 `or req.secondary_title`;副标题样式解析复用 `get_style("title", ...)`;字号/间距覆盖;`prepare_style_for_remotion` 处理副标题字体;`remotion_service.render()` 传入 `secondary_title` + `secondary_title_style` | +| `backend/app/services/remotion_service.py` | `render()` 新增 `secondary_title` 和 `secondary_title_style` 参数,构建 CLI 参数 `--secondaryTitle` 和 `--secondaryTitleStyle` | + +### Remotion 修改 + +| 文件 | 变更 | +|------|------| +| `remotion/render.ts` | `RenderOptions` 新增 `secondaryTitle?` + `secondaryTitleStyle?`;`parseArgs()` 新增 switch case;`inputProps` 新增两个字段 | +| `remotion/src/components/Title.tsx` | `TitleProps` 新增 `secondaryTitle?` 和 `secondaryTitleStyle?`;`AbsoluteFill` 改为 `flexDirection: 'column'` 垂直堆叠;主标题 `