利用 Claude Code 生成视频:18 集 STM32 万年历教学视频
利用 Claude Code 生成视频:18 集 STM32 万年历教学视频
脚本 → 配音 → 画面 → 字幕 → 合成的完整踩坑实录
作者:knight · 项目:LED 全彩点阵屏万年历
本文记录这套 18 集零基础教学视频的完整制作过程——如何用 Claude Code 从一份文字脚本出发,自动生成配音、画面、字幕并三轨同步合成,以及一路上踩过的坑。
一、项目是什么
这套视频教的是一个完整的嵌入式小项目:用 STM32 做一块 LED 全彩点阵屏日历时钟。成品长这样(128×64 点阵屏上同时显示):

它涵盖了一个嵌入式新手要走通的完整链路:画原理图 → 画 PCB → 打样焊接 → 用 Keil 写程序 → 驱动 DS1302 实时时钟 → 驱动 HUB75 点阵屏 → 烧录成品。
1.1 硬件平台
|
部分 |
选型 |
|
主控 |
STM32F103C8(Cortex-M3,64KB Flash,20KB SRAM,72MHz,LQFP48) |
|
RTC |
DS1302 三线串行实时时钟,外接 32.768kHz 晶振 |
|
显示 |
128×64 LED 全彩点阵屏,HUB75 接口,1/32 扫描 |
|
供电 |
5V |
|
电路板 |
两层板,自行设计打样 |
HUB75 接口接线(这是真机接线,不是示意图):
|
信号 |
GPIO |
信号 |
GPIO |
|
R1 |
PB0 |
A |
PA3 |
|
G1 |
PB1 |
B |
PA4 |
|
B1 |
PA2 |
C |
PA5 |
|
R2 |
PB12 |
D |
PA6 |
|
G2 |
PB8 |
E |
PA7 |
|
B2 |
PB5 |
CLK |
PA1 |
|
LAT |
PA0 |
OE |
PB6 |
DS1302 接线:
|
信号 |
GPIO |
|
CLK |
PA8 |
|
DAT |
PA9 |
|
RST(CE) |
PA10 |
注意:工程里禁用了 JTAG(保留 SWD),避免 PB3/PB4/PA15 与 LED 信号冲突。
1.2 软件架构
IDE 为 Keil MDK uVision 5.24.2.0,编译器 ARMCC V5.06,标准外设库 STM32F10x StdPeriph V3.5.0。工程结构(经典的 Keil 四文件夹):
+-- Start/ 启动文件 + CMSIS(startup_stm32f10x_md.s 等)
+-- Library/ STM32 标准外设库(约 50 个文件)
+-- User/ 核心用户代码
| +-- main.c 主程序:帧渲染 + 扫描 + 时间管理
| +-- DS1302.c/h DS1302 RTC 驱动
| +-- led_matrix.c/h LED 矩阵底层操作
| +-- display.c/h 显示缓冲(备用方案)
| +-- Delay.c/h 精确延时(ms + us)
| +-- stm32f10x_it.c/h
+-- Objects/ 编译输出
几个值得一提的设计点:
1. 软件校准跳秒:DS1302 晶振偏快,实测每天快约 15 秒(≈174 ppm)。于是启动时从 DS1302 读一次基准时间,之后由 TIM2 的 1ms 中断做软件秒递增,每 5760 秒跳过 1 次递增——一天 86400 秒正好跳过 15 次,把误差从 +15s 压到 1s 以内。
2. 编译指纹自动对时:用 __DATE__ / __TIME__ 宏生成 16 位指纹,存进 DS1302 电池备份 RAM;新固件首次上电指纹不匹配就同步时间,同一固件再次上电就保留 DS1302 里已有的时间。
3. 星期用 Sakamoto 算法由年月日直接推算,DS1302 存的星期值仅作参考。
4. 3-bit 彩色系统:每个像素 1 字节低 3 位,8 种颜色(黑/红/绿/黄/蓝/洋红/青/白),字库分 ASCII 5×7 与中文 16×16(周一二三四五六日)。
二、为什么要做这套视频
市面上的 STM32 教程要么只讲单片机不讲成品,要么只给代码不讲"为什么"。这套视频定位是零基础实战:跟着走,能从一个元器件清单开始,一路做到一块挂在墙上的彩色日历时钟。
所以每集都遵循"先给结果、再拆原理、最后动手"的节奏,并且所有引脚、参数都严格对应真实工程,没有一处是画饼。
三、视频制作整体管线(核心)
整套视频是程序化生成的:写一个 Python 脚本,从一份文字脚本出发,自动产出配音、画面、字幕,最后用 FFmpeg 合成。每集目录结构一致:
episode_NN/
+-- script/script.json 文字脚本(每场景的旁白 + 标题)
+-- narration/ TTS 配音 mp3 + 词级时间戳 + timeline.json
+-- generated/ PIL 生成的静态画面 PNG
+-- subtitles/ .ass 字幕
+-- scenes/ 逐场景视频片段 + 静音成片
+-- output/ 最终 mp4
+-- gen_assets.py 生成静态画面
+-- tts_gen.py 生成配音 + 时间轴
+-- build_subtitles.py 生成 .ass 字幕
+-- build_scenes.py 把画面编成逐场景视频
+-- compose.py 配音 + 字幕 + 角标 + 渐隐合成
一条流水线:
script.json
|
+-- tts_gen.py --> 配音 mp3 + timeline.json(场景时长、间隔)
| |
+-- gen_assets.py -> 1920x1080 静态画面(示意图/表格)
| |
+------------------------+--------------+
v v
build_scenes.py build_subtitles.py
逐场景片段 .ass 字幕
| |
v v
compose.py(FFmpeg)
|
output/第N集.mp4
视频生成项目·目录结构:

关键思路是"先配音,后画面":timeline.json 由 TTS 的时长驱动,同一份时间轴既决定每个场景的画面时长,也决定字幕出现的时间,从而保证视频 / 中文配音 / 中文字幕三轨严格同步。
· 配音:edge-tts,中文音色 zh-CN-YunxiNeural。
· 画面:Pillow(PIL)画的 1920×1080 示意图(浅色科技风)。
· 字幕:ASS 格式,白字 + 黑描边。
· 合成:FFmpeg,mux 配音、叠加角标、烧录字幕。
四、18 集分集结构
全套 18 集,分四个阶段:
|
集 |
标题 |
阶段 |
|
1 |
项目介绍与成品演示 |
认识 |
|
2 |
硬件清单与元器件认识 |
认识 |
|
3 |
元器件采购与选购 |
认识 |
|
4 |
STM32 入门 |
认识 |
|
5 |
DS1302 与点阵屏 |
认识 |
|
6 |
读懂原理图 |
制作 |
|
7 |
PCB 设计打样 |
制作 |
|
8 |
Gerber 导出与下单打样 |
制作 |
|
9 |
焊接与首次上电 |
制作 |
|
10 |
搭建 Keil 开发环境 |
软件 |
|
11 |
新建 STM32 工程 |
软件 |
|
12 |
主程序与点屏 |
软件 |
|
13 |
DS1302 时钟芯片 |
软件 |
|
14 |
显示与字库 |
软件 |
|
15 |
源码逐讲(一)main.c |
深入 |
|
16 |
源码逐讲(二)DS1302 |
深入 |
|
17 |
常见故障排查 |
深入 |
|
18 |
烧录与成品总结 |
深入 |

五、每一步怎么做
5.1 文字脚本(script.json)
每集先写一个 script.json,把整集拆成若干"场景",每个场景有一段旁白文字。这是后面所有东西的源头。
5.2 配音 + 时间轴(tts_gen.py)
用 edge-tts 把每段旁白合成成 mp3,同时拿到句级时间戳,汇总成 timeline.json(含每场景时长、场景间隔 lead_in/lead_out)。
# 核心:edge-tts 流式合成 + 拿时间戳(示意)
import edge_tts, asyncio
async def synth(text, out_mp3, out_words):
communicate = edge_tts.Communicate(text, "zh-CN-YunxiNeural")
async for chunk in communicate.stream():
if chunk["type"] in ("WordBoundary", "SentenceBoundary"):
# offset / duration 单位是 100ns
t = chunk["offset"] / 10_000_000
dur = chunk["duration"] / 10_000_000
out_words.append((chunk["text"], t, dur))
5.3 静态画面(gen_assets.py)
用 Pillow 画 1920×1080 的示意图。浅色科技风:白→浅灰渐变底、圆角卡片、天蓝色点缀(品牌色 (2,132,199))。
from PIL import Image, ImageDraw, ImageFont
W, H = 1920, 1080
def new_bg():
img = Image.new("RGB", (W, H), (255, 255, 255))
d = ImageDraw.Draw(img)
for y in range(H):
t = y / H
d.line([(0, y), (W, y)],
fill=tuple(int(255 + (238 - 255) * t) for _ in range(3)))
return img
5.4 字幕(build_subtitles.py)
按句级时间戳生成 ASS 字幕,样式白字 + 黑描边:
Style: Default,msyh,40,&H00FFFFFF,&H00FFFFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2.6,1.4,2,80,80,58,1
5.5 逐场景视频 + 合成(build_scenes.py / compose.py)
静态画面用 -loop 1 编成视频片段,再 concat 成静音成片,最后合成:
# 单张图编成 N 帧片段
ffmpeg -y -loop 1 -i scene.png -vf fps=30 -frames:v N -c:v libx264 -pix_fmt yuv420p scene.mp4
合成时:mux 配音 → 左上角叠加品牌角标(天蓝 "Dream Chaser" + "制作人:宁鹏")→ 底部加黑色渐隐(保证浅色背景下白字幕可读)→ 烧录 ASS 字幕。
六、画面设计规范(统一视觉)
1. 左上角固定角标:天蓝色 "Dream Chaser" + 黑色 "制作人:宁鹏"。
2. 底部渐隐:y=800→1080 黑→透明(alpha 0→70),字幕落在 y≈940-1060,所以正文/徽章底边不得低于 y≈706,否则会被字幕/渐隐遮挡。
3. 统一片头:18 集片头全部是同一版式——点阵网格背景 + 中央蓝色柔光 + 顶部 8 色 LED 光点带 + 主标题「LED 全彩点阵屏万年历」+ 副标题 + 蓝色集数徽章「第 N 集 · 标题」+ 底部技术栈标签(STM32/DS1302/HUB75/PCB),只有集数徽章文字不同。
七、踩坑记录(重点,全是血泪)
这部分可能是对你最有用的。视频程序化生成听上去美好,实际每个环节都有坑。
7.1 TTS 相关
1. edge-tts 的 WordBoundary 模式坏了(返回 0 个词 / 报 NoAudioReceived)。必须用默认的 SentenceBoundary 模式,只取 chunk["type"] in ("WordBoundary","SentenceBoundary") 的事件;offset/duration 单位是 100ns,要 /10_000_000 换成秒。
2. edge-tts 偶发 NoAudioReceived(限流):要加指数退避重试,成功后校验"词非空且 mp3 大小 > 0"。
3. edge-tts 会网络挂起(不抛异常,直接卡死):必须用 asyncio.wait_for(..., timeout=60) 包住流式读取。
4. 连续多请求会限流:退避要长(sleep(3.0 * attempt) + retries=6)。tts_gen.py 要做成断点续跑:每个场景成功后写 narration/{sid}.words.json,重跑时跳过已有,只补失败的场景。
7.2 FFmpeg 相关
5. 图片必须 -loop 1 -i img + -frames:v n。用 -stream_loop -1 处理图片会让它无限循环,叠加 zoompan 后内存暴涨到 930MB 直接卡死;-stream_loop -1 + -t 只能用于视频。
6. Windows 盘符冒号会破坏 -filter_complex 参数解析(C:/... 的 : 被当成参数分隔符,报 "Unable to parse ... as image size")。解决:subprocess.run(cwd=BASE) + 过滤串里用相对路径(不带盘符)。
7. -filter_complex 里直接写 overlay=文件.png 会报错("Cannot find an unused video input stream")。必须用 movie=文件.png[pad] 当源滤镜,再 [main][pad]overlay。
7.3 编码 / 环境相关
8. Windows GBK 控制台会乱码:subprocess 捕获用 capture_output=True(bytes)+ decode(errors="replace"),别用 text=True(会 UnicodeDecodeError)。
9. print 输出中文在 GBK 下会 UnicodeEncodeError:在脚本开头加 sys.stdout.reconfigure(encoding="utf-8")。
10. 字幕里的品牌名 "Dream Chaser" 要用不换行空格 \u00a0 防拆行,否则可能被拆成两行。
7.4 画图相关
11. 横向排 N 张卡片一定要用 x0 + i*(cw+gap) 算 x,别手写每张的中心点;否则第一张卡 cx - half_w 会算出负值,卡片被画到画布外面。写死中心点的话要校验 cx - half_w >= 0。
12. 放真实截图时要避开左上角角标(角标非透明区 bbox 约 (30,30,351,146)),否则截图会跟角标撞在一起,观感很差。
八、开源与交付
· 成品视频:18 集,命名 第N集 标题.mp4。
· 配套工程:一个 Keil 工程,Project.uvprojx 里全部是相对路径(.\Start .\Library .\User .\Objects),没有绝对路径,所以拷到任何电脑都能编译。
给初学者的上手步骤:
1. 装 Keil MDK uVision5(5.24.2.0 或更新),并在 Pack Installer 里装 STM32F1xx_DFP(否则芯片型号 STM32F103C8 认不出来);
2. 把工程文件夹放到纯英文路径下(例如 D:\STM32\Wannianli),别放桌面、别带中文——Keil 对非 ASCII 路径兼容差,启动文件汇编/链接可能报错;
3. 双击 Project.uvprojx,点 Build 编译,无报错即成功。
交付时只需给 Project.uvprojx + Project.uvoptx + Start/ + Library/ + User/ 五项;Objects/、Listings/、DebugConfig/、Project.uvguix.* 都是编译产物/个人设置,Keil 会自动重建,不用发。
九、结语
这套视频最有价值的不是"会点灯",而是把硬件、软件、工艺、工程化流水线串成了一条完整链路——从元器件到成品、从脚本到成片。把视频制作本身也做成一条可复用的自动化流水线,是我做这个项目时最满意的地方。
如果你也在做嵌入式教学内容,或者想给自己的项目配一套成体系的视频,希望这篇实录能帮你少踩一些坑。
(全文完)
更多推荐



所有评论(0)