剧本杀直播
剧本杀直播完整运营方案
第一部分:发展模式与流程规划
一、发展阶段路线图
第一阶段(1-2个月) 第二阶段(2-4个月) 第三阶段(4-6个月) ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 单人剧本杀 │ → │ 小规模互动 │ → │ 多人互动 │ │ - 主播演绎 │ │ - 弹幕投票 │ │ - 多角色 │ │ - 观众观看 │ │ - 简单分支 │ │ - 礼物系统 │ │ - 积累粉丝 │ │ - 礼物影响 │ │ - 实时互动 │ └─────────────────┘ └─────────────────┘ └─────────────────┘
二、核心玩法设计
1. 基础互动机制
| 互动方式 | 触发条件 | 效果 |
|---|---|---|
| 弹幕投票 | 发送指定关键词 | 决定剧情走向 |
| 礼物加持 | 小礼物(1-10元) | 给角色增加线索/道具 |
| 礼物惩罚 | 中礼物(10-50元) | 增加恐怖元素/难度 |
| 剧情解锁 | 大礼物(50元以上) | 解锁隐藏剧情线 |
| 弹幕猜凶 | 发送"凶手是XXX" | 参与最终推理 |
2. 创新玩法建议
道具商店系统
观众可用礼物兑换虚拟道具
手电筒(照亮黑暗区域)
护身符(跳过一次惊吓)
通灵板(获取额外线索)
阵营对抗
观众分为"求生派"和"作死派"
两派通过礼物对抗决定主角命运
增加观众参与感和竞争性
成就系统
记录观众参与次数和贡献
设置粉丝等级和专属称号
高等级粉丝享有剧情建议权
三、变现模式
收入来源构成: ├── 礼物打赏(主要)────────── 60% │ ├── 剧情互动礼物 │ ├── 道具购买礼物 │ └── 阵营对抗礼物 ├── 粉丝团收入 ────────────── 15% ├── 广告植入 ──────────────── 10% ├── 剧本售卖 ──────────────── 10% └── 周边商品 ──────────────── 5%
第二部分:恐怖剧本《镜中人》
剧本基本信息
时长:60分钟
类型:心理恐怖/悬疑推理
人数:单人演绎(后期可扩展)
难度:中等
结局数:4个
故事背景
你是一名刚搬进老旧公寓的年轻人。房东说这间房子已经空置三年,上一任租客在浴室的镜子前离奇死亡。你不信邪,却在搬进的第一晚发现——镜子里的自己,动作总是慢了半拍……
详细剧本脚本
【序章】入住(约5分钟)
场景:老旧公寓走廊 → 房间内部
画面:昏暗走廊,闪烁的灯光,门牌号"404"
AI旁白:
"2024年10月31日,晚上8点。 你拖着行李箱,站在这栋建于1980年代的公寓楼前。 房东说这里租金便宜,却没告诉你原因。 现在你知道了——404号房,三年前有人死在这里。 (停顿2秒) 但你不在乎。毕竟,死人不会说话。"
互动点1 [弹幕投票]
AI提示:"你要先做什么?" 选项A:发送"检查"- 先检查房间 选项B:发送"休息"- 直接休息 (倒计时30秒,票数多的选项触发)
分支A - 检查房间:
画面切换:房间各处特写 - 客厅:落满灰尘的沙发 - 厨房:生锈的水龙头 - 卧室:一张老旧的单人床 - 浴室:一面巨大的落地镜 AI旁白:"你注意到浴室的镜子异常干净,与周围的灰尘格格不入。镜子边缘似乎有些…刮痕?" [获得线索:镜子刮痕]
分支B - 直接休息:
画面:快速过渡到床上 AI旁白:"你太累了,倒头就睡。 半夜,一阵奇怪的声音把你吵醒—— 是水龙头滴水的声音,来自浴室。" [触发后续强制检查浴室]
【第一章】镜中异象(约12分钟)
场景:浴室
时间:凌晨3点
画面:浴室全景,镜子占据画面中心
AI旁白:
"你走进浴室,打开灯。 灯泡闪了几下才亮起,发出嗡嗡的电流声。 你看向镜子—— (停顿3秒,制造紧张感) 镜子里的你,看起来很正常。 但你总觉得哪里不对劲……"
[恐怖事件1] - 镜像延迟
画面:主角举起右手 镜子中的影像延迟0.5秒才跟着举起 (使用画中画效果制造不同步感) AI旁白(低声):"你的手放下了,但镜子里的手……还举着。"
互动点2 [弹幕选择]
AI提示:"你要怎么做?" 选项A:发送"靠近"- 凑近镜子查看 选项B:发送"离开"- 立刻离开浴室 选项C:发送"打碎"- 尝试打碎镜子 [礼物互动] - 送"玫瑰":镜中人会微笑(增加恐怖感) - 送"啤酒":主角获得勇气buff,可以多一个选项
分支A - 凑近镜子:
画面:镜子特写,逐渐拉近 AI旁白:"你凑近镜子,呼出的气息在镜面上形成一层薄雾。 当雾气散去的瞬间—— (突然惊吓音效) 镜子里的你睁大了眼睛,但你确定自己没有动。" [获得线索:镜中人有自主意识] [恐惧值+10]
分支B - 离开浴室:
画面:转身离开的背影 AI旁白:"你转身想离开,却感觉背后有目光在盯着你。 你加快脚步,几乎是逃出浴室。 关门的瞬间,你听到镜子里传来一声轻笑……" [恐惧值+5] [错过线索机会]
分支C - 打碎镜子(需要勇气buff):
画面:拳头砸向镜子的慢动作 AI旁白:"你的拳头砸向镜子—— 但镜面像水面一样荡起涟漪,却没有碎裂。 你的手穿进了镜子里…… (惊吓音效) 有什么东西抓住了你的手腕!" [获得关键线索:镜子是入口] [恐惧值+20] [触发隐藏剧情线]
【第一章续】调查(约10分钟)
场景:房间内 → 笔记本电脑前
AI旁白:
"你决定调查这间房子的历史。 打开笔记本,搜索'404号房 死亡事件'……"
画面:模拟网页界面
搜索结果展示:
新闻标题1:《女子在公寓浴室离奇死亡,死因不明》 新闻标题2:《404号房连续三任租客失踪,房东称纯属巧合》 新闻标题3:《心理学教授:镜子恐惧症或与童年创伤有关》
互动点3 [弹幕选择]
AI提示:"你要查看哪条新闻?" 发送"1"、"2"或"3"选择对应新闻 (每条新闻提供不同线索)
新闻1内容:
画面:模拟新闻页面 "2021年11月2日 李某某(女,27岁)被发现死于某公寓404号房浴室内。 警方到达现场时,死者面朝镜子倒在地上,面部表情扭曲。 法医鉴定死因为心脏骤停,但死者并无心脏病史。 值得注意的是,现场镜子上留有死者指甲的刮痕……" [获得线索:前任租客死亡细节]
新闻2内容:
画面:时间线信息图 "404号房历史: 2019年 - 租客A,住了3个月后搬走,称'总感觉有人在看自己' 2020年 - 租客B,住了2周后失踪,至今下落不明 2021年 - 租客C(李某某),入住第7天死亡 2021至今 - 空置" AI旁白:"你是第四个租客。今天是你入住的第一天……" [获得线索:七天规律]
新闻3内容:
画面:学术文章界面 "镜子在许多文化中被视为连接阴阳两界的媒介。 古人相信,镜子能困住灵魂。 如果一个人在镜子前死去,他的灵魂可能会被困在镜中……" [获得线索:镜子困魂]
【第二章】第二夜(约12分钟)
场景:卧室 → 浴室
时间:第二天凌晨
AI旁白:
"你强迫自己入睡,却在凌晨3点33分准时醒来。 (显示时钟画面) 浴室的灯亮着。 你确定睡前关掉了所有的灯……"
[恐怖事件2] - 镜中对话
画面:浴室门缝透出的光 你走向浴室,推开门—— 镜子里的你正在对你微笑。 AI旁白(用不同的声音,模拟镜中人): "终于…又有人来了……"
互动点4 [关键对话选择]
AI提示:"镜中人想和你说话,你要问什么?" 选项A:发送"你是谁" 选项B:发送"你想要什么" 选项C:发送"我该怎么逃" [礼物互动] - 送任意礼物满50元:解锁隐藏选项D"前任租客去哪了"
对话分支A - "你是谁":
镜中人AI声音: "我?我曾经是你。 或者说,我是住在这里的每一个人。 我们都困在镜子里了…… (画面闪过之前租客的面孔) 很快,你也会成为我们的一部分。" [获得线索:镜中有多个灵魂]
对话分支B - "你想要什么":
镜中人AI声音: "我只想出去…… 镜子里好黑,好冷…… (声音变得扭曲) 只要你进来,我就能出去。 公平交易,不是吗?" [获得关键线索:替换机制]
对话分支C - "我该怎么逃":
镜中人AI声音: "逃?(笑声) 你以为那些人没试过吗? (语气突然阴沉) 七天。你只有七天。 第七天的凌晨3点33分,镜子会来找你…… 不管你在哪里。" [获得关键线索:七天时限] [解锁真相线索]
对话分支D - "前任租客去哪了"(隐藏):
镜中人AI声音: "他们都在这里……" (镜子深处出现无数张脸,向外伸手) "帮帮我们……打碎镜子…… 不是普通的打碎…… 要用……她的东西……" (声音被干扰打断) [获得关键线索:破解方法提示] [解锁最佳结局路线]
【第二章续】寻找真相(约8分钟)
场景:房间内搜索 → 发现暗格
AI旁白:
"镜中人的话让你意识到,这件事的源头是第一个受害者。 你必须找到更多关于'她'的信息……"
画面:搜索房间各处的蒙太奇
互动点5 [弹幕搜索]
AI提示:"帮助主角搜索房间,发送你想检查的位置" 可搜索位置: - "床下" - 发现灰尘和一只耳环 - "衣柜" - 发现刻在木板上的日期 - "地板" - 发现一块松动的地板 - "墙壁" - 发现一处不自然的补丁 弹幕数量最多的位置会被详细搜索
[关键发现] - 地板暗格:
画面:撬开地板的特写 AI旁白: "地板下有一个铁盒子…… 里面是一本日记,和一面小型化妆镜。 日记的主人是——王美琳,这栋楼1980年代的住户。"
日记内容(画面配合泛黄纸张):
1987年3月15日 搬进新家的第一天。这面落地镜是我的嫁妆,我很喜欢。 1987年4月2日 最近总觉得镜子里的自己在看着我。一定是我太累了。 1987年4月10日 我看见了。镜子里的我在动,但我没有动。我要把它封起来! 1987年4月15日 它不让我封。每次我想遮住镜子,它就会让我看到可怕的东西…… 我受不了了。如果我进去,是不是就不会害怕了? (最后一页染着褐色的污渍)
AI旁白:
"原来,这一切的源头是一面有问题的镜子被带进了这栋楼。 王美琳是第一个受害者,她的灵魂被困在镜中,开始捕猎新的猎物…… 打破诅咒的方法,可能就与她有关。"
【第三章】最终对决(约10分钟)
场景:浴室
时间:第七天,凌晨3点
AI旁白:
"第七天到了。 你做好了准备——或者说,你以为自己准备好了。 浴室的镜子开始发出诡异的光芒……"
[恐怖事件3] - 镜子入侵
画面:镜子表面像液体一样波动 镜中人的声音: "时间到了…… 进来吧…… (无数只手从镜子里伸出) 和我们永远在一起……"
最终互动 [决定结局]
AI提示:"最后的选择,你要怎么做?" 基于之前收集的线索,可用选项不同: [选项出现条件] A. "接受命运" - 始终可选 B. "打碎镜子" - 需要获得[镜子是入口]线索 C. "使用化妆镜" - 需要找到王美琳的化妆镜 D. "用她的名字" - 需要获得全部3条关键线索 [礼物决战] 此时观众可以通过礼物影响结果: - "求生派"礼物:增加成功率 - "作死派"礼物:增加失败率 最终根据双方礼物总额决定成功与否
【结局】四种结局
结局A - 沉沦(Bad End)
条件:选择"接受命运"或礼物对决失败 画面:主角被拉入镜子的慢动作 AI旁白: "你放弃了抵抗…… 冰冷的手抓住你的脚踝,将你拖入镜子。 黑暗包围了你。 当你再次睁开眼睛,你发现自己站在镜子里, 看着下一个租客搬进来…… 【沉沦结局】 你成为了镜中人的一部分。" [结局CG:镜子里出现无数张脸,最外层是主角]
结局B - 逃离(Normal End)
条件:选择"打碎镜子"但未获得全部线索 画面:镜子碎裂,主角逃出公寓 AI旁白: "你用尽全力砸碎了镜子! 玻璃四溅,尖叫声消失了…… 你逃出了公寓,再也没有回去。 但有时候,在其他镜子里, 你会看到一个模糊的影子在对你招手…… 【逃离结局】 诅咒没有被打破,只是暂时放过了你。" [结局CG:主角站在街上,身后商店橱窗里有模糊身影]
结局C - 轮回(Hidden End)
条件:选择"使用化妆镜" 画面:化妆镜与落地镜产生共鸣 AI旁白: "你举起王美琳的化妆镜对准落地镜—— 两面镜子之间产生了奇异的通道。 王美琳的灵魂出现了…… '谢谢你……让我见到了自己最初的模样…… 我可以安息了……但其他人……他们还困在里面……' (化妆镜碎裂,王美琳消失) 【轮回结局】 你救出了王美琳,但镜中仍有无数灵魂在哭泣。" [结局CG:破碎的化妆镜,碎片中映出王美琳微笑的脸]
结局D - 救赎(True End)
条件:收集全部线索+选择正确+礼物对决胜利 画面:主角面对镜子,说出关键台词 AI旁白: "你举起化妆镜,大声喊出她的名字—— '王美琳!我以你之名,命令你释放所有灵魂!' 镜子发出刺眼的光芒—— 无数光点从镜中升起,飘向天空。 那些被困的灵魂,终于得到了解脱。 当光芒散去,镜子变成了一面普通的玻璃。 你看到的,只有自己真实的倒影。 【救赎结局】 诅咒被打破了。" [结局CG:阳光照进浴室,镜子反射出温暖的光]
【尾声】
根据结局播放不同的结尾画面
AI旁白:
"感谢各位观众的参与。 今晚的故事就到这里了。 获得结局:【显示结局名称】 收集线索:【X/5】 参与人数:【显示弹幕参与数】 最大贡献者:【显示礼物榜第一名】 下一期预告:《电梯里的第十三个人》 每周五晚8点,我们不见不散。"
第三部分:OBS配置详解
一、场景架构
OBS场景结构: ├── 【开场场景】 │ ├── 开场动画视频源 │ ├── 背景音乐 │ └── 倒计时文字 ├── 【主场景-房间】 │ ├── 背景图层(房间图片) │ ├── 特效图层(灯光闪烁) │ ├── 字幕图层 │ ├── 弹幕显示区 │ └── 投票显示框 ├── 【主场景-浴室】 │ ├── 背景图层(浴室图片) │ ├── 镜子特效层 │ ├── 惊吓画面层 │ └── 音效触发层 ├── 【主场景-对话】 │ ├── 对话框背景 │ ├── 角色立绘 │ ├── 文字区域 │ └── 选项按钮区 ├── 【结局场景】×4 │ ├── 结局CG │ ├── 结局音乐 │ └── 结语文字 └── 【过渡场景】 ├── 转场动画 └── Loading提示
二、详细配置步骤
1. 基础设置
【输出设置】 设置 → 输出 → 输出模式:高级 ├── 编码器:x264 或 NVENC(有N卡选这个) ├── 比特率:4000-6000 Kbps ├── 关键帧间隔:2秒 └── CPU预设:veryfast 【视频设置】 设置 → 视频 ├── 基础分辨率:1920×1080 ├── 输出分辨率:1920×1080 ├── 帧率:30 FPS └── 缩放过滤:Lanczos 【音频设置】 设置 → 音频 ├── 采样率:48kHz ├── 声道:立体声 └── 桌面音频:默认(播放音效用)
2. 场景源配置
背景图层设置:
添加 → 图像 ├── 名称:背景-房间 ├── 图像文件:选择对应场景图片 ├── 勾选"当源不活跃时卸载图像" └── 变换 → 拉伸到屏幕 滤镜设置(营造氛围): ├── 添加 → 色彩校正 │ ├── 亮度:-0.1 │ ├── 对比度:0.1 │ └── 饱和度:-0.3(降低色彩制造阴暗感) └── 添加 → 锐化:0.1
字幕图层设置:
添加 → 文本(GDI+) ├── 字体:思源黑体/微软雅黑 ├── 字号:48 ├── 颜色:白色 ├── 勾选"轮廓" │ ├── 颜色:黑色 │ └── 大小:4 ├── 勾选"聊天模式"(文字逐行显示) └── 对齐:居中 位置:屏幕下方1/4处 大小:宽度80%屏幕,高度自适应
弹幕显示区配置:
添加 → 浏览器 ├── URL:弹幕姬网页地址(后面详述) ├── 宽度:400 ├── 高度:600 ├── 勾选"透明背景" └── 自定义CSS(根据需要调整样式) 位置:屏幕右侧
3. 特效图层配置
灯光闪烁效果:
方法:使用多个图层叠加 图层1:正常亮度背景 图层2:暗色背景(亮度-0.5) 通过场景切换或图层可见性切换模拟闪烁 可以用Advanced Scene Switcher插件自动切换
镜子特效层(关键):
添加 → 媒体源 ├── 名称:镜子波纹 ├── 视频文件:镜子波纹动画(透明背景) ├── 勾选"循环" └── 不勾选"当隐藏时重新启动播放" 添加 → 图像 ├── 名称:镜中人 ├── 图像:处理过的恐怖人脸 └── 默认隐藏,触发时显示 组合这些层模拟镜子异象
惊吓画面层:
添加 → 媒体源 ├── 名称:Jump Scare ├── 视频文件:惊吓视频(带音效) ├── 不勾选"循环" ├── 勾选"当隐藏时重新启动播放" └── 勾选"播放结束后隐藏" 默认隐藏,通过热键触发
4. 热键配置
设置 → 热键 场景切换: ├── 切换到【开场场景】:F1 ├── 切换到【房间场景】:F2 ├── 切换到【浴室场景】:F3 ├── 切换到【对话场景】:F4 └── 切换到【结局场景】:F5-F8 特效触发: ├── 显示/隐藏 惊吓画面:F9 ├── 显示/隐藏 镜中人:F10 ├── 播放 灯光闪烁:F11 └── 播放 特定音效:F12 注意:这些热键后期会通过脚本自动触发
三、高级插件配置
1. Advanced Scene Switcher(自动场景切换)
安装:OBS → 工具 → 脚本 → 下载 Advanced Scene Switcher 配置自动切换规则: ├── 媒体触发器: │ └── 当"开场动画"播放结束 → 切换到"房间场景" ├── 时间触发器: │ └── 每隔X秒检查弹幕投票结果 └── 文件触发器: └── 监控特定文件变化触发场景切换
2. Source Dock(源控制面板)
安装:OBS官方插件库下载 用途:在控制面板快速切换图层可见性 配置:将常用源(惊吓画面、镜中人等)添加到面板
3. OBS WebSocket
安装:从GitHub下载obs-websocket 用途:允许外部程序控制OBS 配置: ├── 工具 → WebSocket服务器设置 ├── 启用WebSocket服务器 ├── 服务器端口:4455(默认) └── 设置密码(建议设置) 后续弹幕控制和AI语音系统将通过此接口控制OBS
第四部分:素材准备详解
一、视觉素材
1. 场景背景图
AI生成方式(推荐Midjourney/Stable Diffusion):
Midjourney提示词示例: 【老旧公寓走廊】 "Creepy old apartment hallway, flickering lights, door number 404, 1980s Chinese architecture, dim lighting, horror atmosphere, photorealistic, 8k, cinematic --ar 16:9 --v 6" 【浴室场景】 "Old Chinese apartment bathroom, large standing mirror, dim fluorescent light, wet tiles, horror movie scene, photorealistic, unsettling atmosphere --ar 16:9 --v 6" 【卧室场景】 "Abandoned apartment bedroom, old single bed, dusty furniture, moonlight through window, Chinese 1990s style, creepy atmosphere, photorealistic --ar 16:9 --v 6"
Stable Diffusion本地生成:
安装:从GitHub下载AUTOMATIC1111版本 模型推荐:Realistic Vision V5.1 提示词模板: 正向:场景描述 + "horror atmosphere, photorealistic, 8k, cinematic" 负向:"cartoon, anime, bright colors, cheerful" 参数设置: ├── 采样器:DPM++ 2M Karras ├── 步数:30-50 ├── CFG Scale:7-9 └── 分辨率:1920×1080
免费下载渠道:
1. Unsplash (https://unsplash.com) 搜索:abandoned room, creepy hallway, old mirror 2. Pexels (https://www.pexels.com) 搜索:horror scene, dark room, scary interior 3. Pixabay (https://pixabay.com) 搜索:haunted house, creepy bathroom 下载后用Photoshop/GIMP调整色调使其统一
2. 角色立绘/镜中人图像
AI生成方法:
Midjourney提示词: 【正常状态】 "Portrait of a young Chinese person, casual clothes, neutral expression, soft lighting, waist up shot, white background for easy cutout --ar 3:4 --v 6" 【镜中人-恐怖版】 "Same person but slightly distorted, unsettling smile, pale skin, dark circles under eyes, horror movie style, eerie lighting --ar 3:4 --v 6" 后期处理: ├── 使用Remove.bg去除背景 ├── 用Photoshop添加裂纹、失真效果 └── 调整色调使其更加苍白阴暗
3. 特效动画素材
镜子波纹效果:
免费下载: ├── Mixkit (https://mixkit.co) - 搜索"water ripple" ├── Videvo (https://www.videvo.net) - 搜索"distortion" └── Videezy (https://www.videezy.com) - 搜索"glitch" 处理方法: 1. 下载后用DaVinci Resolve打开 2. 去除背景(使用键控/抠像) 3. 调整颜色使其呈现幽绿/深蓝色调 4. 导出为WebM格式(支持透明背景)
惊吓画面:
制作方法(使用Canva/Photoshop): 1. 准备一张恐怖面孔图片 2. 添加闪烁效果(快速切换黑白) 3. 添加噪点/失真效果 4. 导出为短视频(1-2秒) 免费素材网站: ├── Freepik (https://www.freepik.com) - 搜索"horror face" └── 123RF免费区 - 搜索"scary portrait"
二、音频素材
1. 背景音乐
免费下载网站:
1. Incompetech (https://incompetech.com) 推荐:Kevin MacLeod的恐怖类音乐(CC许可) 搜索:horror, suspense, creepy 2. Freesound (https://freesound.org) 搜索:ambient horror, dark atmosphere 注意查看许可证类型 3. YouTube Audio Library 筛选:恐怖/悬疑类别 可商用,无需标注
AI生成音乐:
1. Suno AI (https://suno.ai) 提示词:"dark ambient horror music, suspenseful, piano and strings, slow tempo, 5 minutes" 2. Stable Audio (https://stableaudio.com) 提示词:"horror movie soundtrack, tension building, orchestral, minor key" 导出后用Audacity调整音量和循环点
2. 音效
必备音效列表:
├── 环境音效 │ ├── 水滴声 │ ├── 老旧灯管电流声 │ ├── 门吱呀声 │ └── 风声/呼吸声 ├── 恐怖音效 │ ├── Jump Scare音效(突然惊吓) │ ├── 心跳声 │ ├── 低沉嗡鸣 │ └── 尖叫/笑声 └── UI音效 ├── 选项出现音 ├── 投票倒计时 └── 结局揭晓音
获取方式:
免费网站: 1. Freesound.org - 最全面的免费音效库 2. Zapsplat (https://www.zapsplat.com) - 需注册 3. SoundBible (http://soundbible.com) - 部分免费 AI生成: 1. ElevenLabs Sound Effects 描述你想要的音效,AI生成 2. AudioCraft by Meta(开源) 本地部署,生成自定义音效
三、AI语音素材
1. TTS(文字转语音)系统选择
国内推荐方案:
1. 阿里云语音合成 ├── 特点:中文效果好,有多种音色 ├── 价格:有免费额度,超出按量计费 └── API文档:https://ai.aliyun.com/nls 2. 讯飞开放平台 ├── 特点:语音质量高,情感丰富 ├── 价格:有免费额度 └── 特色:支持SSML标记控制语音情感 3. 腾讯云语音合成 ├── 特点:接入简单 └── 价格:有免费额度
免费/开源方案:
1. Edge TTS(推荐) ├── 完全免费 ├── 使用微软Azure的语音 └── Python库:pip install edge-tts 使用示例: import edge_tts import asyncio async def generate_voice(text, output_file): communicate = edge_tts.Communicate(text, "zh-CN-XiaoyiNeural") await communicate.save(output_file) 2. GPT-SoVITS(开源) ├── 可以克隆任意声音 ├── 效果好但需要GPU └── GitHub上下载 3. VITS/Bert-VITS2 ├── 开源TTS模型 ├── 可以训练自己的音色 └── 需要一定技术基础
2. 语音文件准备
根据剧本预先生成所有语音文件: 文件组织结构: voice/ ├── narrator/ # 旁白语音 │ ├── prologue_01.mp3 # 序章第1段 │ ├── prologue_02.mp3 # 序章第2段 │ ├── chapter1_01.mp3 # 第一章第1段 │ └── ... ├── mirror_person/ # 镜中人语音 │ ├── dialogue_01.mp3 │ ├── dialogue_02.mp3 │ └── ... ├── system/ # 系统提示音 │ ├── vote_start.mp3 # "投票开始" │ ├── countdown_10.mp3 # "还剩10秒" │ └── ... └── endings/ # 结局语音 ├── ending_a.mp3 ├── ending_b.mp3 ├── ending_c.mp3 └── ending_d.mp3
批量生成脚本示例(Edge TTS):
pythonDownloadCopy codeimport edge_tts
import asyncio
import json# 语音内容配置文件scripts = {
"prologue_01": "2024年10月31日,晚上8点。你拖着行李箱,站在这栋建于1980年代的公寓楼前。",
"prologue_02": "房东说这里租金便宜,却没告诉你原因。", # ... 更多内容}
async def batch_generate():
for filename, text in scripts.items():
communicate = edge_tts.Communicate(
text,
"zh-CN-XiaoyiNeural", # 女声
rate="-10%", # 语速稍慢
pitch="-5Hz" # 音调稍低,更有氛围
)
await communicate.save(f"voice/narrator/{filename}.mp3")
print(f"Generated: {filename}")
asyncio.run(batch_generate())第五部分:无人直播系统搭建
一、系统架构图
┌─────────────────────────────────────────────────────────────┐ │ 无人直播系统架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 弹幕获取 │ → │ 弹幕解析 │ → │ 指令判断 │ │ │ │ (抖音/快手) │ │ (关键词匹配) │ │ (投票/礼物) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ ↓ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ OBS推流 │ ← │ OBS控制 │ ← │ 剧情引擎 │ │ │ │ (输出) │ │ (WebSocket) │ │ (核心) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ ↑ ↓ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ 音频播放 │ ← │ 语音合成 │ │ │ │ (本地播放) │ │ (Edge TTS) │ │ │ └──────────────┘ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘
二、核心模块代码
1. 弹幕获取模块
抖音弹幕获取(使用开源项目):
pythonDownloadCopy code# 推荐使用: douyin-live(GitHub开源项目)# 安装: pip install douyin-livefrom douyin_live import DouyinLive
class DouyinDanmu:
def __init__(self, room_id, callback):
self.room_id = room_id
self.callback = callback
self.live = None
async def start(self):
self.live = DouyinLive(self.room_id)
@self.live.on("chat")
def on_chat(msg): # msg包含:用户名、内容、等级等
self.callback({
"type": "chat",
"user": msg.user.nickname,
"content": msg.content
})
@self.live.on("gift")
def on_gift(msg):
self.callback({
"type": "gift",
"user": msg.user.nickname,
"gift_name": msg.gift.name,
"gift_value": msg.gift.diamond_count
})
await self.live.start()快手弹幕获取:
pythonDownloadCopy code# 快手需要使用浏览器自动化方案# 推荐: playwright + 页面弹幕捕获from playwright.async_api import async_playwright
import asyncio
class KuaishouDanmu:
def __init__(self, room_url, callback):
self.room_url = room_url
self.callback = callback
async def start(self):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
await page.goto(self.room_url)
# 监听弹幕DOM变化
await page.evaluate('''
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.classList && node.classList.contains('comment-item')) {
// 发送弹幕数据到Python
window.danmuCallback(node.textContent);
}
});
});
});
observer.observe(document.querySelector('.comment-list'),
{ childList: true });
''')
# 保持运行
while True:
await asyncio.sleep(1)2. 剧情引擎模块
pythonDownloadCopy codeimport json
import asyncio
from enum import Enum
from dataclasses import dataclass
from typing import Dict, List, Callable, Optional
class SceneType(Enum):
NARRATIVE = "narrative" # 叙事场景
CHOICE = "choice" # 选择场景
VOTE = "vote" # 投票场景
GIFT_BATTLE = "gift_battle" # 礼物对决@dataclass
class Choice:
keyword: str # 触发关键词
description: str # 选项描述
next_scene: str # 下一场景ID
required_clue: str = None # 需要的线索(可选)@dataclass
class Scene:
id: str
type: SceneType
background: str # 背景图片/OBS场景名
voice_file: str # 语音文件路径
text: str # 显示文字
duration: int # 场景持续时间(秒)
choices: List[Choice] = None
vote_time: int = 30 # 投票时间
next_scene: str = None # 下一场景(非选择场景)
effects: List[str] = None # 特效列表
clue_reward: str = None # 获得的线索class StoryEngine:
def __init__(self):
self.scenes: Dict[str, Scene] = {}
self.current_scene: str = None
self.player_clues: List[str] = []
self.fear_level: int = 0
self.vote_results: Dict[str, int] = {}
self.gift_battle: Dict[str, int] = {"survive": 0, "die": 0}
# 回调函数
self.on_scene_change: Callable = None
self.on_play_voice: Callable = None
self.on_show_choices: Callable = None
self.on_trigger_effect: Callable = None
def load_story(self, story_file: str):
"""加载剧本JSON文件"""
with open(story_file, 'r', encoding='utf-8') as f:
data = json.load(f)
for scene_data in data['scenes']:
choices = None
if 'choices' in scene_data:
choices = [Choice(**c) for c in scene_data['choices']]
scene = Scene(
id=scene_data['id'],
type=SceneType(scene_data['type']),
background=scene_data['background'],
voice_file=scene_data['voice_file'],
text=scene_data['text'],
duration=scene_data.get('duration', 10),
choices=choices,
vote_time=scene_data.get('vote_time', 30),
next_scene=scene_data.get('next_scene'),
effects=scene_data.get('effects', []),
clue_reward=scene_data.get('clue_reward')
)
self.scenes[scene.id] = scene
async def start(self, start_scene: str = "prologue"):
"""开始剧本"""
self.current_scene = start_scene
await self._play_scene(self.scenes[start_scene])
async def _play_scene(self, scene: Scene):
"""播放场景""" # 切换OBS场景/背景
if self.on_scene_change:
await self.on_scene_change(scene.background)
# 触发特效
if scene.effects:
for effect in scene.effects:
if self.on_trigger_effect:
await self.on_trigger_effect(effect)
# 播放语音
if self.on_play_voice:
await self.on_play_voice(scene.voice_file, scene.text)
# 等待语音播放完成
await asyncio.sleep(scene.duration)
# 记录线索
if scene.clue_reward:
self.player_clues.append(scene.clue_reward)
# 根据场景类型处理后续
if scene.type == SceneType.NARRATIVE: # 叙事场景:直接进入下一场景
if scene.next_scene:
await self._play_scene(self.scenes[scene.next_scene])
elif scene.type == SceneType.CHOICE: # 选择场景:等待投票
await self._handle_choice(scene)
elif scene.type == SceneType.VOTE: # 投票场景:开启投票
await self._handle_vote(scene)
elif scene.type == SceneType.GIFT_BATTLE: # 礼物对决场景
await self._handle_gift_battle(scene)
async def _handle_choice(self, scene: Scene):
"""处理选择场景""" # 显示可用选项
available_choices = []
for choice in scene.choices:
if choice.required_clue is None or choice.required_clue in self.player_clues:
available_choices.append(choice)
if self.on_show_choices:
await self.on_show_choices(available_choices)
# 重置投票
self.vote_results = {c.keyword: 0 for c in available_choices}
# 等待投票时间
await asyncio.sleep(scene.vote_time)
# 统计投票结果
winner = max(self.vote_results, key=self.vote_results.get)
# 找到对应选项
for choice in available_choices:
if choice.keyword == winner:
await self._play_scene(self.scenes[choice.next_scene])
break
async def _handle_gift_battle(self, scene: Scene):
"""处理礼物对决""" # 重置礼物统计
self.gift_battle = {"survive": 0, "die": 0}
# 等待对决时间
await asyncio.sleep(scene.vote_time)
# 判断结果
if self.gift_battle["survive"] >= self.gift_battle["die"]: # 存活
await self._play_scene(self.scenes["ending_good"])
else: # 死亡
await self._play_scene(self.scenes["ending_bad"])
def process_danmu(self, content: str, user: str):
"""处理弹幕"""
content = content.lower().strip()
# 检查是否为投票关键词
if content in self.vote_results:
self.vote_results[content] += 1
def process_gift(self, gift_name: str, gift_value: int, user: str):
"""处理礼物""" # 根据礼物类型判断阵营
survive_gifts = ["玫瑰", "小心心", "加油"]
die_gifts = ["墓碑", "幽灵", "骷髅"]
if gift_name in survive_gifts:
self.gift_battle["survive"] += gift_value
elif gift_name in die_gifts:
self.gift_battle["die"] += gift_value3. OBS控制模块
pythonDownloadCopy codeimport obsws_python as obs
import asyncio
class OBSController:
def __init__(self, host="localhost", port=4455, password=""):
self.host = host
self.port = port
self.password = password
self.client = None
def connect(self):
"""连接OBS WebSocket"""
self.client = obs.ReqClient(
host=self.host,
port=self.port,
password=self.password
)
def disconnect(self):
"""断开连接"""
if self.client:
self.client.disconnect()
def switch_scene(self, scene_name: str):
"""切换场景"""
self.client.set_current_program_scene(scene_name)
def set_source_visibility(self, scene_name: str, source_name: str, visible: bool):
"""设置源可见性"""
self.client.set_scene_item_enabled(
scene_name=scene_name,
item_id=self._get_source_id(scene_name, source_name),
enabled=visible
)
def _get_source_id(self, scene_name: str, source_name: str) -> int:
"""获取源ID"""
items = self.client.get_scene_item_list(scene_name).scene_items
for item in items:
if item['sourceName'] == source_name:
return item['sceneItemId']
return -1
def set_text(self, source_name: str, text: str):
"""设置文字内容"""
self.client.set_input_settings(
source_name,
{"text": text},
overlay=True
)
def play_media(self, source_name: str):
"""播放媒体"""
self.client.trigger_media_input_action(
source_name,
"OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART"
)
def trigger_effect(self, effect_name: str):
"""触发特效""" # 根据特效类型执行不同操作
effects = {
"jumpscare": lambda: self._play_jumpscare(),
"flicker": lambda: self._trigger_flicker(),
"mirror_ripple": lambda: self._show_mirror_effect(),
}
if effect_name in effects:
effects[effect_name]()
def _play_jumpscare(self):
"""播放惊吓画面"""
self.set_source_visibility("主场景", "惊吓画面", True) # 2秒后自动隐藏(通过OBS媒体源设置)
def _trigger_flicker(self):
"""触发闪烁效果""" # 快速切换图层可见性
import threading
def flicker():
for _ in range(6):
self.set_source_visibility("主场景", "暗色图层", True)
import time
time.sleep(0.1)
self.set_source_visibility("主场景", "暗色图层", False)
time.sleep(0.1)
threading.Thread(target=flicker).start()
def _show_mirror_effect(self):
"""显示镜子特效"""
self.set_source_visibility("浴室场景", "镜子波纹", True)4. 语音播放模块
pythonDownloadCopy codeimport edge_tts
import asyncio
import pygame
import os
class VoicePlayer:
def __init__(self, voice_folder: str = "voice"):
self.voice_folder = voice_folder
self.current_voice = "zh-CN-XiaoyiNeural" # 默认女声
pygame.mixer.init()
async def play_pregenerated(self, filename: str):
"""播放预生成的语音"""
filepath = os.path.join(self.voice_folder, filename)
if os.path.exists(filepath):
pygame.mixer.music.load(filepath)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
await asyncio.sleep(0.1)
async def speak_realtime(self, text: str, emotion: str = "normal"):
"""实时生成并播放语音""" # 根据情感调整语音参数
voice_params = {
"normal": {"rate": "-5%", "pitch": "+0Hz"},
"scary": {"rate": "-15%", "pitch": "-10Hz"},
"urgent": {"rate": "+10%", "pitch": "+5Hz"},
"whisper": {"rate": "-20%", "pitch": "-5Hz", "volume": "-20%"}
}
params = voice_params.get(emotion, voice_params["normal"])
# 生成临时文件
temp_file = "temp_voice.mp3"
communicate = edge_tts.Communicate(
text,
self.current_voice,
rate=params.get("rate", "+0%"),
pitch=params.get("pitch", "+0Hz")
)
await communicate.save(temp_file)
# 播放
pygame.mixer.music.load(temp_file)
pygame.mixer.music.play()
while pygame.mixer.music.get_busy():
await asyncio.sleep(0.1)
# 删除临时文件
os.remove(temp_file)
def set_voice(self, voice_name: str):
"""切换语音角色"""
voices = {
"narrator": "zh-CN-XiaoyiNeural", # 女性旁白
"mirror_person": "zh-CN-YunxiNeural", # 镜中人(男性,略恐怖)
"system": "zh-CN-XiaochenNeural" # 系统提示
}
self.current_voice = voices.get(voice_name, self.current_voice)
def play_sound_effect(self, effect_name: str):
"""播放音效"""
effects = {
"heartbeat": "sfx/heartbeat.mp3",
"door_creak": "sfx/door_creak.mp3",
"water_drop": "sfx/water_drop.mp3",
"jumpscare": "sfx/jumpscare.mp3",
"whisper": "sfx/whisper.mp3"
}
if effect_name in effects:
sound = pygame.mixer.Sound(effects[effect_name])
sound.play()5. 主控制程序
pythonDownloadCopy codeimport asyncio
import json
from story_engine import StoryEngine
from obs_controller import OBSController
from voice_player import VoicePlayer
from danmu_handler import DouyinDanmu # 或 KuaishouDanmuclass LiveController:
def __init__(self, config_file: str = "config.json"): # 加载配置
with open(config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f)
# 初始化各模块
self.story = StoryEngine()
self.obs = OBSController(
host=self.config['obs']['host'],
port=self.config['obs']['port'],
password=self.config['obs']['password']
)
self.voice = VoicePlayer(self.config['voice_folder'])
self.danmu = None
# 设置回调
self.story.on_scene_change = self._on_scene_change
self.story.on_play_voice = self._on_play_voice
self.story.on_show_choices = self._on_show_choices
self.story.on_trigger_effect = self._on_trigger_effect
async def start(self):
"""启动直播""" # 连接OBS
self.obs.connect()
# 加载剧本
self.story.load_story(self.config['story_file'])
# 启动弹幕监听
self.danmu = DouyinDanmu(
room_id=self.config['room_id'],
callback=self._on_danmu
)
# 并行运行
await asyncio.gather(
self.danmu.start(),
self.story.start("prologue")
)
def _on_danmu(self, data: dict):
"""处理弹幕回调"""
if data['type'] == 'chat':
self.story.process_danmu(data['content'], data['user'])
elif data['type'] == 'gift':
self.story.process_gift(
data['gift_name'],
data['gift_value'],
data['user']
)
async def _on_scene_change(self, scene_name: str):
"""场景切换回调"""
self.obs.switch_scene(scene_name)
async def _on_play_voice(self, voice_file: str, text: str):
"""播放语音回调""" # 同时显示字幕
self.obs.set_text("字幕", text)
# 播放语音
await self.voice.play_pregenerated(voice_file)
async def _on_show_choices(self, choices: list):
"""显示选项回调""" # 构建选项文字
choice_text = "请发送弹幕选择:\n"
for c in choices:
choice_text += f"【{c.keyword}】{c.description}\n"
# 显示选项
self.obs.set_text("选项文字", choice_text)
self.obs.set_source_visibility("主场景", "选项框", True)
# 播放提示音
await self.voice.speak_realtime(
f"请发送弹幕选择,投票时间30秒",
emotion="normal"
)
async def _on_trigger_effect(self, effect_name: str):
"""触发特效回调"""
self.obs.trigger_effect(effect_name)
# 某些特效需要配合音效
effect_sounds = {
"jumpscare": "jumpscare",
"mirror_ripple": "whisper",
"flicker": "electricity"
}
if effect_name in effect_sounds:
self.voice.play_sound_effect(effect_sounds[effect_name])# 启动入口if __name__ == "__main__":
controller = LiveController("config.json")
asyncio.run(controller.start())三、配置文件模板
config.json:
jsonDownloadCopy code{
"obs": {
"host": "localhost",
"port": 4455,
"password": "your_password"
},
"room_id": "your_douyin_room_id",
"platform": "douyin",
"story_file": "stories/mirror_person.json",
"voice_folder": "voice",
"settings": {
"vote_duration": 30,
"gift_battle_duration": 60,
"subtitle_speed": 50
}
}stories/mirror_person.json(剧本配置):
jsonDownloadCopy code{
"title": "镜中人",
"author": "Your Name",
"duration": 60,
"scenes": [
{
"id": "prologue",
"type": "narrative",
"background": "走廊场景",
"voice_file": "narrator/prologue_01.mp3",
"text": "2024年10月31日,晚上8点...",
"duration": 15,
"next_scene": "choice_first"
},
{
"id": "choice_first",
"type": "choice",
"background": "房间场景",
"voice_file": "narrator/choice_01.mp3",
"text": "你要先做什么?",
"duration": 5,
"vote_time": 30,
"choices": [
{
"keyword": "检查",
"description": "先检查房间",
"next_scene": "check_room"
},
{
"keyword": "休息",
"description": "直接休息",
"next_scene": "rest_first"
}
]
} // ... 更多场景
]
}第六部分:部署清单与成本估算
一、硬件需求
| 设备 | 最低配置 | 推荐配置 | 参考价格 |
|---|---|---|---|
| 电脑CPU | i5-10400 | i7-12700 | ¥1000-2500 |
| 内存 | 16GB | 32GB | ¥300-600 |
| 显卡 | GTX 1060 | RTX 3060 | ¥1500-2500 |
| 硬盘 | 256GB SSD | 512GB SSD | ¥200-400 |
| 网络 | 50Mbps上行 | 100Mbps上行 | 按月付费 |
二、软件需求
| 软件 | 用途 | 价格 |
|---|---|---|
| OBS Studio | 直播推流 | 免费 |
| Python 3.10+ | 运行控制程序 | 免费 |
| Edge TTS | 语音合成 | 免费 |
| Stable Diffusion | AI绘图 | 免费(本地部署) |
| Audacity | 音频编辑 | 免费 |
| DaVinci Resolve | 视频编辑 | 免费版够用 |
三、API服务(可选)
| 服务 | 用途 | 价格 |
|---|---|---|
| 阿里云语音 | 高质量TTS | 免费额度后按量 |
| Midjourney | AI绘图 | $10-30/月 |
| 云服务器 | 24小时运行 | ¥50-200/月 |
四、启动检查清单
启动前检查: □ OBS场景配置完成 □ 所有背景图片就位 □ 语音文件全部生成 □ 音效文件准备完成 □ 剧本JSON配置无误 □ OBS WebSocket已启用 □ 弹幕获取测试通过 □ 网络上行带宽充足 □ 备用方案准备(手动切换热键)
总结
这个方案为你提供了从0到1搭建剧本杀直播的完整路径。建议按以下步骤执行:
第一周:准备素材(背景图、音效、语音)
第二周:配置OBS,测试基础直播
第三周:开发控制程序,实现弹幕互动
第四周:整合测试,优化细节
第五周:正式上线,收集反馈