ClawSkills logoClawSkills

YT-to-Blog Content Engine

完整的内容流水线:YouTube URL → 转录 → 博客文章 → Substack 草稿 → X/Twitter 主题 → 通过 HeyGen AI 头像生成竖屏视频片段。输入一个 URL,输出全部

介绍

# YT-to-Blog Content Engine

YouTube URL → 博客文章 + Substack + 推文 + 竖屏短视频。完整的内容生产线。

## 流程概览

``` YouTube URL ↓ ① Transcript (summarize CLI) ↓ ② Blog Draft (AI-written in your voice) ↓ ③ Substack Publish (browser automation) ↓ ④ X/Twitter Post (bird CLI) ↓ ④b Facebook Group (optional reminder) ↓ ⑤ Script Splitter (extract hook moments) ↓ ⑥ HeyGen Videos (AI avatar vertical clips) ↓ ⑦ Post-Processing (ffmpeg crop/scale) ↓ 📁 Output Folder (blog.md, videos, tweet.txt, URLs) ```

**一个 URL 输入 → 五个平台输出。** 运行整个流程或单独运行任意步骤。

---

## 首次设置向导

在首次使用时引导用户完成此过程。只需约 10 分钟,之后一劳永逸。

### 步骤 1:检查依赖项

运行设置脚本以检查已安装的内容:

```bash bash skills/yt-content-engine/setup.sh ```

必需的 CLI 工具: | 工具 | 用途 | 安装方式 | |------|---------|---------| | `summarize` | YouTube 字幕提取 | `brew install steipete/tap/summarize` | | `bird` | X/Twitter 发布 | `brew install steipete/tap/bird` | | `ffmpeg` | 视频后处理 | `brew install ffmpeg` | | `curl` | 调用 HeyGen API | macOS 通常预装 | | `python3` | 辅助脚本 | macOS 通常预装 |

如果缺少任何内容,请告知用户安装什么并等待确认。

### 步骤 2:HeyGen API 密钥

1. 告知用户:“前往 https://app.heygen.com/settings — 从 API 部分获取你的 API 密钥。” 2. 如果他们没有 HeyGen 账户:“在 https://heygen.com 注册 — 免费版提供一些积分供测试。” 3. 将密钥保存到 `config.json`(参见下方的配置架构)。 4. 测试密钥:

```bash curl -s -H "X-Api-Key: API_KEY_HERE" https://api.heygen.com/v2/avatars | python3 -c "import sys,json; d=json.load(sys.stdin); print('✅ API key works!' if 'data' in d else '❌ Invalid key')" ```

### 步骤 3:HeyGen 数字人设置

告知用户:

> “对于竖屏短视频,你需要一个 HeyGen 数字人。关键点如下: > > **以竖屏(PORTRAIT)模式录制**(竖直握持手机)。这至关重要 — 如果你横屏录制,数字人将在 9:16 画面的中心显示为窄条,我们需要对其进行裁剪/缩放(虽然可行但会损失质量)。 > > 前往 https://app.heygen.com/avatars → Create Instant Avatar → 按照他们的录制指南操作。站在光线良好的地方,看着镜头,自然说话 2 分钟以上。 > > 创建完成后,从数字人详情页面获取你的 Avatar ID。”

列出他们现有的数字人以帮助选择。注意:数字人端点同时返回自定义和库存数字人 — 筛选用户的自定义数字人(它们通常排在前面且带有个人名称):

```bash curl -s -H "X-Api-Key: API_KEY" https://api.heygen.com/v2/avatars | python3 -c " import sys, json data = json.load(sys.stdin) for a in data.get('data', {}).get('avatars', []): print(f\" {a['avatar_id']} — {a.get('avatar_name', 'unnamed')}\") " ```

### 步骤 4:HeyGen 声音克隆

告知用户:

> “前往 https://app.heygen.com/voice-clone → Clone your voice。上传清晰的音频样本(你自然说话的 1-2 分钟录音)。HeyGen 将创建一个声音 ID。 > > 完成后,从声音设置中获取你的 Voice ID。”

列出他们的声音。用户克隆的声音通常排在前面;库存声音紧随其后:

```bash curl -s -H "X-Api-Key: API_KEY" https://api.heygen.com/v2/voices | python3 -c " import sys, json data = json.load(sys.stdin) for v in data.get('data', {}).get('voices', []): print(f\" {v['voice_id']} — {v.get('name', 'unnamed')} ({v.get('language', '?')})\") " ```

⚠️ **重要提示:** 使用完整的 voice_id(例如 `69da9c9bca78499b98fdac698d2a20cd`),不要使用截断的版本。如果你使用缩短的 ID,API 将返回“Voice validation failed”(声音验证失败)。

### 步骤 5:Substack 登录

Substack 没有 API — 发布需要浏览器自动化。

1. 打开 OpenClaw 托管浏览器:使用带有 `profile="openclaw"` 的浏览器工具 2. 导航到 `https://substack.com/sign-in` 3. 帮助用户使用其凭据登录 4. 通过导航到其发布仪表板来验证访问权限 5. 将发布 URL 保存到 `config.json`

浏览器会话在重启后仍然保持。一次性设置。

### 步骤 6:保存配置

创建 `skills/yt-content-engine/config.json`(相对于你的工作区):

```json { "heygen": { "apiKey": "YOUR_API_KEY", "avatarId": "YOUR_AVATAR_ID", "voiceId": "YOUR_VOICE_ID" }, "substack": { "publication": "yourblog.substack.com" }, "twitter": { "handle": "@yourhandle" }, "author": { "voice": "Description of your writing voice and style", "name": "Your Name" }, "video": { "clipCount": 5, "maxClipSeconds": 60, "cropMode": "auto" } } ```

**提示:** 如果用户已经有来自 `yt-to-blog` 技能的声音指南,请从 `skills/yt-to-blog/references/voice-guide.md` 读取并将其用于 `author.voice` 字段。

### 步骤 7:验证一切

在配置到位的情况下运行设置脚本:

```bash bash skills/yt-content-engine/setup.sh ```

它将测试每个组件并报告状态。

---

## 如何调用

### 完整流程 ``` "Turn this into a full content suite: https://youtu.be/XXXXX" "Content engine this video: [URL]" "Run the full pipeline on [URL]" ```

### 单独步骤 ``` "Just get me the transcript from [URL]" "Write a blog post from [URL]" (steps 1-2) "Post this to Substack" (step 3, after blog exists) "Tweet about this blog post" (step 4) "Generate video clips from this blog" (steps 5-7) "Just split this into scripts" (step 5 only) ```

---

## 流程步骤

### 步骤 ①:字幕提取

为此次运行创建输出目录,然后获取 YouTube 字幕:

```bash mkdir -p /tmp/yt-content-engine/output-$(date +%Y-%m-%d)/scripts mkdir -p /tmp/yt-content-engine/output-$(date +%Y-%m-%d)/videos ```

```bash summarize "YOUTUBE_URL" --extract > /tmp/yt-content-engine/transcript.txt ```

`--extract` 标志会打印原始字幕,而不进行 LLM 摘要。读取输出。如果失败(没有可用的字幕),请尝试使用 `--youtube yt-dlp` 获取自动生成的字幕,或告知用户并建议他们提供手动字幕。

### 步骤 ②:博客草稿

将字幕转换为经过润色的长篇博客文章。

**从 `config.json` → `author.voice` 加载作者声音。** 如果在 `skills/yt-to-blog/references/voice-guide.md` 存在更详细的声音指南,请一并阅读和使用。

**分析阶段** — 在撰写之前,从字幕中提取: - 核心论点 — 最有力的单一论点或启示 - 关键数据点 — 统计数据、引言、日期、姓名 - 叙事时刻 — 轶事、示例、场景 - 来源链接 — 提及的 URL、研究、参考资料 - 缺失背景 — 视频假设读者需要了解但未提及的内容

**撰写结构:** 1. **冷开场(1-3 段):** 场景设置。在数据之前,具体、感性、情感化的钩子。 2. **论点转折(1 段):** 将场景与更大的故事联系起来。 3. **数据正文(5-15 段):** 数据与评论交替。每个数据点都有一个点睛之笔。仅在主要分隔处使用小标题。 4. **呼应(1-2 段):** 回到开篇场景/隐喻。 5. **结尾(3-6 个短段落):** 递进的片段。最后的点睛之笔。

**撰写规则:** - 极大地改变句子长度 — 长的数据句子,然后是短促的冲击 - 使用破折号作为补充说明,而不是括号 - 使用句子片段进行强调 - 正文中不要使用项目符号列表 — 保持叙事流畅 - 内联来源链接,不要脚注 - 不要用“总之”或“综上所述” - 自然地提及视频来源:“正如 [Name] 所说……”并附上链接 - 目标字数:1,500-3,000 字

**生成 3-5 个标题选项**,采用不同的策略(对比/反讽、启示、道德框架、呼应)。每个选项都带有副标题。让用户选择。

将最终草稿保存到输出文件夹,命名为 `blog.md`。

### 步骤 ③:Substack 发布

通过浏览器自动化将博客发布到 Substack。

1. 读取 `config.json` → `substack.publication` 2. 打开托管浏览器(`profile="openclaw"`) 3. 导航到 `https://PUBLICATION.substack.com/publish/post` 4. 点击标题字段,输入标题 5. 点击副标题区域,输入副标题 6. 点击正文区域 7. 将 markdown 写入临时文件,复制到剪贴板(`pbcopy < /tmp/post.md`),粘贴到编辑器中(Meta+v) 8. Substack 会自动保存为草稿

**已知问题:** - 破折号(`—`)在剪贴板粘贴期间可能会显示为 `,Äî` → 粘贴后查找并替换 - 长文章:在粘贴和验证之间稍微暂停 - 在 `https://PUBLICATION.substack.com/publish` 验证草稿

**默认:保存为草稿。** 仅当用户明确表示“发布它”时才发布 — 务必先确认。

将 Substack URL 保存到 `output/substack-url.txt`。

### 步骤 ④:X/Twitter 发布

使用 `bird` CLI 撰写并发布。

**撰写推文/推文串:** - 如果博客有一个杀手级钩子 → 带链接的单条推文 - 如果有多个强有力的观点 → 推文串(3-5 条推文) - 包含 Substack URL - 匹配作者的声音但更有冲击力 — 推文是钩子,不是摘要 - 使用 `config.json` → `twitter.handle` 中的账号名

**使用 bird 发布:** ```bash # Single tweet bird tweet "Your tweet text here"

# Thread (post first tweet, then reply to it) bird tweet "Tweet 1 text here" # Note the returned tweet ID, then: bird reply TWEET_ID "Tweet 2 text here" # And chain: bird reply TWEET_2_ID "Tweet 3 text here" ```

**发布前务必向用户显示推文文本并获得确认。**

将推文文本保存到 `output/tweet.txt`。

### 步骤 ④b:Facebook 群组(可选)

如果 `config.json` 包含 `facebook.group` URL,提醒用户发布到其 Facebook 群组。

**注意:** Facebook 群组 API 发布受到严格限制。由于 Facebook 的反机器人措施,浏览器自动化不可靠。最佳方法:

1. 起草内容的 Facebook 帖子版本(比推文更短、更随意) 2. 保存到 `output/facebook-post.txt` 3. 提醒用户:“别忘了发布到 [群组名称] — 这是你的草稿” 4. 用户手动发布

这使得 Facebook 分发保持在工作流程中,而无需与其 API 限制抗争。

### 步骤 ⑤:脚本拆分器

从博客文章中提取 3-5 个“钩子时刻”,并将每个时刻重写为竖屏视频的口播脚本。

**寻找什么**(扫描博客中的这些模式): 1. **钩子/争议** — 最具挑衅性的主张,让人们停止滚动的内容 2. **重磅数据** — 重新构建认知的惊人统计数据或事实 3. **反直觉观点** — 与传统智慧相矛盾的内容 4. **情感时刻** - 创造联系的故事、轶事或人文元素 5. **行动号召结尾** — 振臂高呼、挑战或“你现在应该做什么”

并非每篇博客都包含所有这五点。提取现有的内容。至少 3 个片段。

**口语重写规则:** - **钩子先行** — 以最吸引人的开场白开始。不要铺垫。 - **对话式** — 为口语而写,而非阅读。使用缩略语,自然节奏。 - **每个 30-60 秒** — 大约每个片段 75-150 字 - **自包含** — 每个片段必须独立存在,不要出现“正如我之前提到的” - **以点睛之笔结束** - 在最强的一行结束,而不是 trailing thought - **无舞台指示** — 只是需要说的词,没有其他内容

**格式化每个脚本:** ``` CLIP 1: [descriptive title] --- [Script text here, 75-150 words] ```

使用 `config.json` → `video.clipCount` 作为目标片段数(默认:5)。 使用 `config.json` → `video.maxClipSeconds` 作为最大时长(默认:60)。

将脚本保存到 `output/scripts/clip-1.txt`、`clip-2.txt` 等。

### 步骤 ⑥:HeyGen 视频生成

将每个脚本提交给 HeyGen API v2 以生成 AI 数字人视频。

**读取配置:** ```bash # Parse config.json API_KEY=$(python3 -c "import json; c=json.load(open('config.json')); print(c['heygen']['apiKey'])") AVATAR_ID=$(python3 -c "import json; c=json.load(open('config.json')); print(c['heygen']['avatarId'])") VOICE_ID=$(python3 -c "import json; c=json.load(open('config.json')); print(c['heygen']['voiceId'])") ```

**对于每个脚本,提交视频生成请求:**

```bash curl -s -X POST "https://api.heygen.com/v2/video/generate" \ -H "X-Api-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_inputs": [{ "character": { "type": "avatar", "avatar_id": "'"$AVATAR_ID"'", "avatar_style": "normal" }, "voice": { "type": "text", "input_text": "'"$(cat output/scripts/clip-1.txt)"'", "voice_id": "'"$VOICE_ID"'" } }], "dimension": { "width": 1080, "height": 1920 } }' ```

**解析响应**以获取 `video_id`: ```python import json response = json.loads(response_text) video_id = response["data"]["video_id"] ```

**在轮询之前提交所有片段。** HeyGen 并行渲染 — 先提交所有脚本,收集所有 video_ids,然后轮询它们。这会将总渲染时间从 N×3 分钟减少到约 3 分钟。

**轮询完成情况**(每 15 秒一次,10 分钟后超时):

```bash curl -s -H "X-Api-Key: $API_KEY" \ "https://api.heygen.com/v1/video_status.get?video_id=$VIDEO_ID" \ | python3 -c "import sys,json; d=json.load(sys.stdin)['data']; print(d['status'], d.get('video_url',''))" ```

状态:`pending` → `processing` → `completed`(带有 `video_url`)或 `failed`(带有 `error`)。

**下载完成的视频:** ```bash curl -L -o "output/videos/clip-1-raw.mp4" "$VIDEO_URL" ```

**配额说明:** 约每分钟视频消耗 1 点积分。通常运行 5 个片段会消耗约 3 点积分。在提交前请提醒用户注意积分使用情况。

### 步骤 ⑦:视频后处理

如果数字人是横向录制的(常见情况),9:16 的视频将在一个带有背景填充的大画幅中显示一条细小的居中头像条。使用 ffmpeg 修复此问题。

**检查 `config.json` → `video.cropMode`:** - `"auto"` — 自动检测并裁剪 - `"portrait"` — 跳过裁剪(数字人已是竖屏录制) - `"manual"` — 询问用户裁剪坐标

**自动裁剪流程:**

```bash # 1. Detect content bounds by scanning center column for non-background pixels # Extract a single frame ffmpeg -i input.mp4 -vframes 1 -y /tmp/frame.png

# 2. Use ffmpeg cropdetect to find content bounds ffmpeg -i input.mp4 -vf "cropdetect=24:16:0" -frames:v 30 -f null - 2>&1 | grep cropdetect

# Parse the crop values from output: crop=W:H:X:Y

# 3. Crop content strip, scale up, center-crop to 1080x1920 ffmpeg -i input.mp4 \ -vf "crop=DETECTED_W:DETECTED_H:DETECTED_X:DETECTED_Y,scale=1080:-1,crop=1080:1920:0:(ih-1920)/2" \ -c:a copy \ -y output.mp4 ```

**替代的手动检测**(推荐 —— 当背景为白色/浅色时,cropdetect 经常失效):

HeyGen 通常会在 9:16 画幅中,将横向数字人渲染在白色/浅色背景的中心。 扫描中间列的非白色像素以找到实际的内容条:

```bash # Extract a frame, then scan center column for content bounds ffmpeg -y -ss 5 -i input.mp4 -frames:v 1 /tmp/frame.png 2>/dev/null

ffmpeg -y -i /tmp/frame.png -vf "crop=1:ih:iw/2:0,format=gray" -f rawvideo -pix_fmt gray - 2>/dev/null | \ python3 -c " import sys data = sys.stdin.buffer.read() first = last = None for i, b in enumerate(data): if b < 240: # Non-white pixel = actual content if first is None: first = i last = i if first is not None: print(f'CONTENT_Y={first}') print(f'CONTENT_HEIGHT={last - first}') print(f'CENTER={( first + last) // 2}') else: print('No content bounds detected — avatar may already fill the frame') " ```

然后裁剪内容条,按比例缩放以填满宽度,并居中裁剪至 9:16: ```bash ffmpeg -y -i input.mp4 \ -vf "crop=iw:CONTENT_HEIGHT:0:CONTENT_Y,scale=-1:1920,crop=1080:1920:(ow-1080)/2:0" \ -c:v libx264 -crf 23 -preset fast -c:a aac \ output.mp4 ```

**常见 HeyGen 横向数字人的经验裁剪值**(1080x1920 画布): - 内容条通常位于 y≈656,高度≈607px - 示例:`crop=1080:607:0:656,scale=3413:1920,crop=1080:1920:1166:0` - 始终针对每个视频进行检测 —— 数字人位置可能会偏移

**保存处理后的视频**至 `output/videos/clip-1.mp4`、`clip-2.mp4` 等。

如果裁剪模式为 `portrait`,只需复制原始文件: ```bash cp output/videos/clip-1-raw.mp4 output/videos/clip-1.mp4 ```

### 步骤 ⑧:输出

将所有内容整理到一个带日期的输出文件夹中:

``` output-YYYY-MM-DD/ ├── blog.md # Final blog post ├── tweet.txt # Tweet text (posted or ready to post) ├── substack-url.txt # URL of Substack draft/post ├── scripts/ │ ├── clip-1.txt # Spoken word scripts │ ├── clip-2.txt │ └── ... ├── videos/ │ ├── clip-1.mp4 # Final processed vertical videos │ ├── clip-2.mp4 │ └── ... └── manifest.json # Run metadata ```

**manifest.json:** ```json { "source": "https://youtu.be/XXXXX", "date": "2026-02-03", "blog": "blog.md", "substackUrl": "https://...", "tweetUrl": "https://...", "clips": ["clip-1.mp4", "clip-2.mp4", "..."], "heygenCreditsUsed": 3 } ```

向用户报告摘要: - ✅ 博客文章:X 字 - ✅ Substack:[URL](草稿/已发布) - ✅ 推文:已发布 / 待发布 - ✅ 已生成并处理 X 个视频片段 - 💰 已使用 HeyGen 积分:约 X

---

## 配置参考

配置文件:`skills/yt-content-engine/config.json`(相对于工作区根目录)

| 键 | 描述 | 默认值 | |-----|-------------|---------| | `heygen.apiKey` | HeyGen API 密钥 | 必填 | | `heygen.avatarId` | 您的 HeyGen 数字人 ID | 必填 | | `heygen.voiceId` | 您的克隆声音 ID | 必填 | | `substack.publication` | Substack 子域名 | 必填 | | `twitter.handle` | X/Twitter 用户名 | 必填 | | `author.voice` | 写作风格描述 | 推荐 | | `author.name` | 用于署名的作者名称 | 推荐 | | `video.clipCount` | 要生成的片段数量 | `5` | | `video.maxClipSeconds` | 每个片段的最长秒数 | `60` | | `video.cropMode` | `auto`、`portrait` 或 `manual` | `auto` |

---

## 提示与故障排除

- **HeyGen 渲染每个片段需要 2-3 分钟。** 预先告知用户 —— 运行 5 个片段的渲染需要 10-15 分钟。 - **竖屏数字人可以节省时间。** 无需裁剪。如果您经常使用此工具,值得重新录制。 - **Substack 会话过期?** 重新运行浏览器登录步骤(设置中的第 5 步)。 - **bird CLI 无法发布?** 运行 `bird auth` 重新认证。 - **裁剪检测效果不佳?** 将 `cropMode` 切换为 `manual`,并从导出的帧中目视检查内容边界。 - **HeyGen 配额错误?** 在 https://app.heygen.com/settings 检查积分 —— 升级套餐或减少片段数量。 - **无法获取字幕?** 部分视频没有字幕。尝试使用 `summarize "URL" --extract --youtube yt-dlp` 获取自动生成的字幕,或向用户索要手动字幕。

更多产品