muvtuber

元世界总算不再铺天盖地,ChatGPT 又来遮天蔽日。看不下去了,那当然是挑选——加入这踏实的表演,诶嘿~(大误)

主意

蹭热度的主意是这样:元世界 => 皮套人,ChatGPT => 中之人,放到一同:AI 主播!

咳,实践上没那么假大空啦。实在的故事是这样:

既然我可以让 AI 看懂你的心境,并引荐应景的音乐,以一种简略的完结,那么,我还想再进一步!让 AI 站到唱机背面,倾听你的故事,为你播出此时此刻环绕心中、却难以言表的那段旋律。曲终意犹未尽其时 ,一段 AI 细语中听更入心 —— 机器不是严寒的玩具,她比你更懂你,至少她更懂那个实在的你:她不会说谎,而你每天都在骗自己。

这个主意指向的产品是一个 AI 电台,我把它叫做 muradio。在细化规划的进程中,我不得不考虑电台的传播问题。我当然可以在本地完结悉数,让 AI 电台只为你一人播,这样完结起来还简略;可是,电台果然仍是要我们一同共享吧。所以,我考虑用一种现代的办法——视频直播。

那么,在完结 muradio 之前,先让 AI 学会直播吧!

规划

我把这个 AI 主播叫做 muvtuber 1

人是怎么做的

要规划一个直播的机器,其实很简略。首要考察人是怎么完结直播的:

  • 首要需求一个人作为主播:这个人是会动会说话的,心境是会写在脸上的;(仅仅以普遍理性而论,非必要条件,更非轻视残障人士。)
  • 在开始前有一个预定的主题,也或许没有;
  • 主线:推进主题:打游戏、一同看、点歌 …;
  • 支线:主线被弹幕打断,转而与观众互动:谈天;
  • 终究,需求一个直播推流东西,比如 OBS。

互动是直播的魂灵。直播没有主题仍是直播,而没有了互动就成了电视。简略起见,咱们先疏忽主题问题,只考虑注入魂灵的互动完结。互动的进程如下:

  • 调查:看到弹幕
  • 过滤:挑选性疏忽一些没有含义或太有含义的话
  • 考虑:对过滤后留下来的话进行考虑 => 回应的话
  • 过滤:想到的部分话或许是不适合说出的,也要过滤掉
  • 发声:把回应说出来
  • 动作与表情:随同调查、考虑、表达这个流程的动作操控、表情管理。

用机器模仿人

清楚了直播的处理进程,就简略用机器模仿:

  • 主播:咱们可以用 Live2D 模型来作为主播本人。
  • 看弹幕:运用弹幕姬类程序,露出接口即可:func danmaku(room) chan text
  • 考虑:机器文本生成:func chat(question) answer
  • 发声:机器语音组成:func tts(text) audio
  • 过滤:func filter(text) bool
  • 动作表情:Live2D 模型可以具有动作、表情,可以经过某种办法驱动。

Live2D 和弹幕姬是现成的处理方案,而从看弹幕、到考虑、到发声的整个流程也是十分模块化、函数式的,直观明晰。难点是怎么让 Live2D 模型动起来?

模型何时该摆什么样的表情,何时该做什么样的动作?真人主播处理这个问题靠得是本能,虚拟主播处理这个问题靠得是动捕——归根到底仍是人。看来现实不能直接学习,那么只好做个抽象了。

我以为,表情是心境的反映,动作是表达的延伸。淦我写不清关于心境 => 表达 => 动作,所以 动作 = f(心境) 的观点

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现
(怎么会有人问 ChatGPT “我要怎么解说我的观点?”)

所以说,咱们可以用“心境”来驱动 Live2D,赋予 AI 主播表情与动作。

那么,咱们又怎么让机器具有“心境”呢?事实上,在前文说到的 让 AI 看懂你的心境,并引荐应景的音乐,以一种简略的完结 一文的“中文文本情感剖析”末节中,咱们用一种及其简略的办法,完结了一种 文本 -> 心境 的模型。这儿咱们就沿袭其时的成果,不作额外介绍了,新读者可以先去阅读那篇文章的对应部分。

总归,咱们可以用“考虑”的输出,即主播要说的话,经过文本情感剖析技能,反推出主播的心境,然后依据心境,就可以操控 Live2D 模型做出适宜的表情、动作。

整体规划

小结一下上述规划,咱们用机器来模仿人类主播,完结与观众的闲聊交互,整个进程如下图所示:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

咱们把 muvtuber 分成了一个个独立的模块。每个模块运用独立的技能栈,完结自己的作业,并经过某种办法露出 API,供他人调用。终究,咱们会经过一个“驱动程序”muvtuberdriver 来调用各个模块,经过一个次序流程,完结上图所示的数据活动。

组件完结

本节介绍对上述 muvtuber 规划方案的完结。我会先写各个独立组件,再写怎么把他们拼装到一同完结作业。喜爱“自顶向下”的读者请自行安排阅读次序。

这是一个具有很多组件的项目,装起 numpy 从头撸好像不够正确。咱们会心胸感激地凭借一些开源项目。我期望供给一种最简略的完结——面向麻(my)瓜(self)编程——只写必要的、能了解的代码。

先修课程:这是个 Web 前端 + 后端 + 机器学习的综合项目,需求运用到 Vue(TypeScript)、Go、Python 等相关言语和技能。

文短码长,下文只给出必要的规划、中心的代码,疏忽错误处理、并发安全等等工程中实践的问题。实在的代码完结,请读者参考源码:

服务 阐明
xfgryujk/blivechat 获取直播间弹幕音讯
Live2dView 前端:显现 Live2D 模型
Live2dDriver 驱动前端 Live2D 模型动作表情
ChatGPTChatbot 根据 ChatGPT 的优质谈天机器人
MusharingChatbot 根据 ChatterBot 的简略谈天机
Emotext 中文文本情感剖析
muvtuberdriver 拼装各模块,驱动整个流程

弹幕姬

技能栈:Node,Python。

为了和其他模块对接,咱们需求那种能经过比较底层的办法输出弹幕的东西。咱们要给机器看的,而不是一个给人看的黑箱的图形界面。

好在也不用自己从头写。xfgryujk/blivedm 便是这样的一个东西。作者大大还顺别供给了显现给人看的界面 blivechat。

这儿咱们直接运用封装好的 blivechat。这个东西后端经过 WebSocket 把弹幕、礼物等音讯发送给前端的界面。拉取、编译、运转 blivechat(blivechat 的 README 里有具体的装置、运用办法,此处从简。):

# clone repo
git clone --recursive https://github.com/xfgryujk/blivechat.git
cd blivechat
# 编译前端
cd frontend
npm install
npm run build
cd ..
# 运转服务
pip3 install -r requirements.txt
python3 main.py

虽然没有文档,但略微抓包,就可以模仿出一个前端,经过 WebSocket 接口获取到弹幕:

// 树立链接
let ws = new WebSocket('ws://localhost:12450/api/chat');
// 订阅直播间
ws.send(JSON.stringify({
    "cmd": 1,
    "data": { "roomId": 000 }
}));
// 接纳弹幕音讯
ws.onmessage = (e) => {
  console.log(JSON.parse(e.data))
};
// KeepAlive:每 10 秒发一次 heartbeat
setInterval(() => {
  ws.send(`{"cmd":0,"data":{}}`)
}, 1000 * 10);

除此之外,咱们还可以运用它自带的图形界面(有没有一种或许,这才是这个项目的正确用法),这个弹幕框可以由 OBS 收集输出到直播间里:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

优质谈天机器人:ChatGPT

技能栈:Python

这应该是最有意思的部分,却也是最难的。幸亏,有 ChatGPT。

acheong08/ChatGPT 项目供给了一个很好的 ChatGPT 接口。

装置:

pip3 install revChatGPT

运用:

from revChatGPT.V1 import Chatbot
chatbot = Chatbot(config={
  "access_token": "浏览器拜访https://chat.openai.com/api/auth/session获取"
})
prompt = "你好"
response = ""
for data in chatbot.ask(prompt):
    response = data["message"]
print(response)

咱们只需编写一条适宜的 prompt,即可让 ChatGPT 化身“虚拟主播”,例如:

请运用女性化的、口语化的、抒发的、感性的、心爱的、调皮的、幽默的、害臊的、情绪傲娇的言语风格,扮演一个正在直播的 vtuber,之后我的输入均为用户评论,请用简略的一句话答复它们吧。

大概是这种作用:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

我并不拿手写 prompt,上面这段也改编自网络(原版大概是让做猫娘的),假如你有更好的 prompt,欢迎与我共享。

注:prompt 笑话一则:

嘿,知道吗?以后不需求程序员啦!只需描绘出需求,ChatGPT 就能写出代码!

——太棒了,那么怎么写出简练明晰无歧义的需求描绘呢?

呃,未来或许会有那么一种言语……

——你说的或许是:核算机程序规划言语 :)

咱们把这个东西简略封装出一个 HTTP API,便利其他组件调用:

$ curl -X POST localhost:9006/renew -d '{"access_token": "eyJhb***99A"}' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 2
ok
$ curl -X POST localhost:9006/ask -d '{"prompt": "你好"}'
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 45
你好!有什么我可以协助你的吗?

根本谈天机器人:ChatterBot

技能栈:Python

ChatGPT 很好用,输出质量十分高,可是:

  • 这不是一个开源的组件,或许廉价的公共服务。不能确保一直可用,也不能确认用它直播是否契合规矩。
  • OpenAI 有拜访约束:恳求太频频会拒绝服务。在实践中 30 秒/次会触发,60 秒/次 才比较安全。

总归,ChatGPT 硬用下去也不是个办法。咱们可以完结一个丐版谈天机器人作为弥补。在 ChatGPT 因为网络、约束等原因无法拜访时,也供给一点点对话才能。

在很多年前,我曾用过一个开箱即用的傻瓜式谈天机器人库:ChatterBot。练习、推理都十分简略,调用者无需任何机器学习知识:

from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer
chatbot = ChatBot('Charlie')
# 练习
trainer = ListTrainer(chatbot)
trainer.train([
    "Hi, can I help you?",
    "Sure, I'd like to book a flight to Iceland.",
    "Your flight has been booked."
])
# 推理:对话
response = chatbot.get_response('I would like to book a flight.')
print(response)

可以用列表传入对话进行练习。练习完后,调用 get_response 办法即可进行对话。同样简略封装成 HTTP API:

curl 'http://localhost:9007/chatbot/get_response?chat=文本内容'

它的对话作用十分差。但风趣的是,这个库可以在线学习。在你和他对话的进程中,它会持续学习。在测验时,我给它接上 blivechat,随意进一个直播间,让它旁听,和弹幕虚空交流。经过一上午它现已学会了包括但不限于“妙啊”“有点东西”“对对对”等直播弹幕常(抽)用(象)表达,可以在恰当语境下输出作为回应。(这样让它黑听学习也有弊端,它会顺别学到许多“别的女性”,然后满嘴 Lulu、白菜、社长什么什么的)

不能指望这个库做出 ChatGPT 那样的优质、有逻辑的答复。只把它作为兜底,让它快速跟着弹幕瞎附合吵吵仍是不错的,别冷场就好。

我在直播实测中的比照:

  • ChatGPTChatbot:由 ChatGPT 生成的答复
  • MusharingChatbot:本节介绍的 Chatterbot 库(我开始在一个叫做 musharing 的项目中运用了这个库,故此命名)

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现


注意:ChatterBot 库早已年久失修。但可以找到一些可以用的 fork,例如 RaSan147/ChatterBot_update(from issues #2287):

pip install https://github.com/RaSan147/ChatterBot_update/archive/refs/heads/master.zip  # 直接从 GitHub 而非 PyPI 库房装置
python -m spacy download en_core_web_sm  # 一个履行不到的依靠,可是不装跑不起来。

语音组成

技能栈:macOS,Go

用 Chatbot 生成了回应观众的文本,(暂时疏忽过滤),接下来就要送到 TTS 语音组成模块,让机器把话说出来。

这个要作用好好像必须上深度学习,PaddleSpeech 好像是一个不错的挑选,不过 i5-8700U + 核显大概跑不动便是了。

可以考虑用一点云服务:

  • Microsoft Speech Studio:要注意网络问题
  • 百度 AI 开方平台 – 短文本在线组成:要注意钱包问题

挑选心仪的服务照着文档写就好了,十分简略。但这仍是有点麻烦了,假如你运用 macOS,作业还可以更简略:

SAY(1) 指令运用 macOS 的 Speech Synthesis Manager 将输入文本转换为可听语音,并经过在“体系偏好设置”中挑选的声响输出设备播映它,或将其存储到 AIFF 文件中。 你可以经过 man say 查看这个指令的具体用法。

打开扬声器或许耳机,调节音量;打开终端,键入指令:

say '你好,这是体系自带的 TTS 语音组成。'

不出意外,你会听到 Siri 或许婷婷声情并茂地大声朗读。

假如你对听到的声响不胜满足,(例如体系默认装备的好像是低配婷婷,只能听个响),可以到「体系设置 > 辅助功用 > 朗读内容 > 体系声响」依照个人喜好修正(个人觉得简体中文系女声,讲的最好的便是「Siri 2」)。

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

简略在任何主流言语中调用这个东西,例如咱们之后会在 Go 言语中调用它:

package main
import "os/exec"
type Sayer interface {
    Say(text string) error
}
// sayer is a caller to the SAY(1) command on macOS.
type sayer struct {
    voice       string // voice to be used
    rate        string // words per minute
    audioDevice string // audio device to be used to play the audio
}
func (s *sayer) Say(text string) error {
    // say -v voice -r rate -a audioDevice text
    return exec.Command("say",
        "-v", s.voice, "-r", s.rate, "-a", s.audioDevice,
        text,
    ).Run()
}

文本情感剖析

技能栈:Python

在 murecom 项目中,咱们完结了一个名为 Emotext 的中文文本情感剖析模块。它的作用便是输入文本,输出其间蕴含的情感(Emotext = func (text) -> emotions):

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

关于这个东西的原理即完结详见让 AI 看懂你的心境,并引荐应景的音乐,以一种简略的完结 一文(说人话的介绍)。下面仅给出中心算法(不说人话的介绍)(某蒟蒻的毕业论文,我先喷:学术辣鸡):

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

其间词典 EE 运用大连理工情感本体库 2。大连理工情感本体库是较为成熟的一套中文情感词典方案,供给了对数万中文词汇的情感分类及强度剖析,在中文文本情感剖析范畴被广泛运用。该库的情感区分方案 与 表 3-1 保持一致。该库的情感强度表明为 1 ∼ 9 的数值,数值越大,以为对应情感越强烈。

界说整段输入文本的总情感为一切要害词情感强度的 tfidf 加权总和:

e(d)=∑t∈keywordstfidf(t,d,D)⋅Ite(d) = \sum_{t \in keywords} tfidf(t,d,D)\cdot I_t

在有必要的情况下,还可以对 e(d)e(d) 进行 softmax 核算,得到文本在各情感类别上的概率分布。

总归,这东西便是一个很简略的算法,抛开作用不谈(该算法仅作为非深度学习的 baseline),人人都能写、人人都能了解。

此处复用其时的代码,只做简略的 API 重新封装,开放如下 HTTP 接口:

$ curl -X POST localhost:9008/ --data '快乐'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 149
{"emotions": ..., "polarity": ...}

完结的作业示意图和中心类图如下:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

Live2D View

技能栈:TypeScriptVue.js,Quasar,Pinia,WebSocket

我从未设想过,一切组件中最麻烦的居然是 Live2D 这个看似除编外人员 OBS 之外最成熟的东西。

因为咱们的需求有一点刁钻。正常的 Live2D 运用办法是:

  1. 把模型作为皮套,经过面/动捕进行驱动。但咱们是机器作为中之人,机器没头没脸、无法自己动。所以一些封装的很好的、我们都在用的直播皮套 app 不能用。
  2. 网站上的看板娘,把 Live2D 模型加载到网页上,并全自动地招引你的目光(而她的目光却被你的鼠标招引),一起呼应你对她的点(抚)击(摸)等动作。这个东西为了对运用者友好,一般封装地十分严实,根本不露出什么接口,一切咱们也不能直接拿来用。

咱们要找那种露出了底层一点的接口,能通进程序调用操控的方案。事实上,我发现常用的 Live2DViewerEX 就供给了十分好的 ExAPI 功用。但考虑到它的接口仍是不够丰富,而且该 app 也不是开源完结,所以,我决议自己写。(绝对不是疼爱那 $4.99,仅仅恰好缺那 $4.99算了)

Live2D 官方 供给有各种 SDK。例如 Web、Unity 等等。出于能耗考虑,决议运用 Web 平台。可是打开文档,不太能看懂,搞了良久 Demo 都没跑起来(后来才知道或许是我 v4 sdk 上 v2 模型了)。好在我找到了一个对官方 SDK 的封装:

  • guansss/pixi-live2d-display: A PixiJS plugin to display Live2D models of any kind.

这个库供给了简略明晰的接口,可以导入、显现模型,并操控表情、动作。

import * as PIXI from 'pixi.js';
import { Live2DModel } from 'pixi-live2d-display';
window.PIXI = PIXI;
// 模型文件
const cubism2Model =
  "https://cdn.jsdelivr.net/gh/guansss/pixi-live2d-display/test/assets/shizuku/shizuku.model.json";
// 初始化 view
const app = new PIXI.Application({
    view: document.getElementById('canvas'),
});
// 读取模型
const model = await Live2DModel.from(cubism2Model);
// 显现模型
app.stage.addChild(model);
// 设置动作、表情
model.motion('tap_body');
model.expression('smile');

用 Vue 把上面的 Demo 封装成为 component,名曰 Live2DView。辅以一个 Live2DStore,用于操控 View 中 Live2D 的动作和表情。一个通用的、可复用的 Live2D 组件就完结了:

<template>
  <Live2DView />
</template>
<script setup lang="ts">
import Live2DView from './Live2DView.vue';
import { useLive2DStore } from 'stores/live2D-store';
const live2DStore = useLive2DStore();
// 经过 store 操控 view 中的 Live2D 皮套
live2DStore.setMotion({ group: 'tap_body' })
live2DStore.setExpression('smile')
</script>

运转一下(背面的调试功用仅仅简略地露出 store 的 actions,文中不作介绍):

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

再写一个 WsStore,用来接纳来自 WebSocket 的指令,进而调用 Live2DStore,完结呼应的动作、表情操控:

import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { useLive2DStore } from './live2D-store';
export const DEFAULT_WS_ADDR = 'ws://localhost:9001/live2d';
const live2DStore = useLive2DStore();
export const useWsStore = defineStore('ws', () => {
  const ws = ref<WebSocket>();
  function dialWebSocket(address: string = DEFAULT_WS_ADDR) {
    ws.value = new WebSocket(address);
    ws.value.onmessage = (event: MessageEvent) => {
      let data = JSON.parse(event.data);
      if (data.model) {  // 替换模型
        live2DStore.setModel(data.model);
      }
      if (data.motion) {  // 设置动作
        live2DStore.setMotion(data.motion);
      }
      if (data.expression) {  // 设置表情
        live2DStore.setExpression(data.expression);
      }
    };
  }
  return { dialWebSocket, };
});

这样,咱们就可以经过 WebSocket,发送 JSON 指令,从其他程序来操控她了:

{
    "model": "https://path/to/model.json",
    "motion": "tap_body",
    "expression": "smile"
}

这个用来操控她的程序是 live2ddriver——

Live2D Driver

技能栈:Go,HTTP,WebSocket

live2ddriver 要做的是:接纳来自 Chatbot 输出的文本;调用 Emotext 进行情感剖析;并依据得到的“心境”,驱动 Live2D View,使做出对应的表情、动作。

  • 输入:文本,Vtuber 要说的话。
    • 接纳自 HTTP 恳求。
  • 输出:JOSN,契合输入文本情感的表情、动作。
    • 发送到 WebSocket。

这个程序十分简略,不过多介绍,只给出要害代码(已去掉烦人的日志打印、错误处理和并发安全)。

拜访 Emotext:进行文本情感剖析:

var EmotextServer = "http://localhost:9008/"
var client = &http.Client{}
func Query(text string) (EmotionResult, error) {
	body := strings.NewReader(text)
	resp, _ := client.Post(EmotextServer, "text/plain", body)
	defer resp.Body.Close()
	var result EmotionResult
	json.NewDecoder(resp.Body).Decode(&result)
	return result, nil
}

驱动 Live2D 模型:文本 -> 驱动指令:

type Live2DDriver interface {
	Drive(textIn string) Live2DRequest
}
// DriveLive2DHTTP get text from request body.
// A Live2DRequest will be generated by the driver and sent to chOut
func DriveLive2DHTTP(driver Live2DDriver, addr string) (chOut chan []byte) {
	chOut = make(chan []byte, BufferSize)
	go func() {
		router := gin.Default()
		router.POST("/driver", func(c *gin.Context) {
			body := c.Request.Body
			defer body.Close()
			text, _ := ioutil.ReadAll(body)
			// 调用 driver 核算该做何中动作
			res := driver.Drive(string(text))
			j, _ := json.Marshal(res)
			chOut <- j
		})
		router.Run(addr)
	}()
	return chOut
}

咱们需求针对每个模型写对应的 Driver 具体完结(情感的转移实践上运用了一个一阶马尔可夫进程,这儿简化了):

type shizukuDriver struct {
	currentExpression shizukuExpression
	currentMotion     shizukuMotion
	emotions emotext.Emotions // emotion => motion
	polarity emotext.Polarity // polarity => expression
}
func (d *shizukuDriver) Drive(textIn string) Live2DRequest {
    // 恳求 Emotext
	emoResult, err := emotext.Query(text)
    d.emotions, d.polarity = emoResult.Emotions, emoResult.Polarity
    // 找得分最大的情感
	maxEmotion, maxPolarity := keyOfMaxValue(d.emotion), keyOfMaxValue(d.polarity)
    // 结构驱动指令
    var req Live2DRequest
	if d.currentExpression != maxPolarity {
		d.currentExpression = maxPolarity
		req.Expression = d.currentExpression
	}
	if d.currentMotion != maxEmotion {
		d.currentMotion = maxEmotion
		req.Motion = d.currentMotion
	}
	return req
}

WebSocket 音讯转发器:把音讯经过 WebSocket 发给前端,便是简略的开几个 goruntine 用 chan 来传数据:

// messageForwarder forwards messages to connected clients, that are, Live2DViews.
type messageForwarder struct {
	msgChans []chan []byte
}
func (f *messageForwarder) ForwardMessageTo(ws *websocket.Conn) {
	ch := make(chan []byte, BufferSize)
	// add
	f.msgChans = append(f.msgChans, ch)
	// forward
	for msg := range ch {
		ws.Write(msg)
	}
}
func (f *messageForwarder) ForwardMessageFrom(msgCh <-chan []byte) {
	for msg := range msgCh {
    	// send
    	for _, ch := range f.msgChans {
            ch <- msg
    	}
	}
}

main:

// WebSocket 转发器:把东西发到 WebSocket 中
forwarder := wsforwarder.NewMessageForwarder()
// 监听 :9004,获取来自 Chatbot 的 text
// 剖析情感,生成驱动指令,发给 WebSocket 转发器。
go func() {
    driver := live2ddriver.NewShizukuDriver()
    dh := live2ddriver.DriveLive2DHTTP(driver, ":9004")
    forwarder.ForwardMessageFrom(dh)
}()
// WebSocket 服务:转发 Live2D 驱动指令到前端
http.Handle("/live2d", websocket.Handler(func(c *websocket.Conn) {
    forwarder.ForwardMessageTo(c)
}))
http.ListenAndServe(":9001", nil)

组件小结

端口 服务 阐明
12450 Blivechat 获取直播间弹幕音讯
9000 Live2dView 前端:显现 Live2D 模型
9001 Live2dDriver::ws 发动作表情给前端的 WebSocket 服务
9004 Live2dDriver::driver 发文本给 live2ddriver 以驱动模型动作表情
9006 ChatGPTChatbot 优质谈天机器人
9007 MusharingChatbot 根据 ChatterBot 的简略谈天机
9008 Emotext 中文文本情感剖析
Say 文本语音组成:体系自带指令

总装,开播!

技能栈:Go

好了,把前面完结的具体组件放到最开始的规划图中:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

咱们经过 blivechat,从 B 站的直播间获取弹幕音讯;传递给 ChatGPT 或许备用的 ChatterBot 获取对话的回应;然后一方面把回应文本朗读出来,另一方面把回应文本传给 Live2DDriver,用 Emotext 进行情感剖析,然后视话中情感,向 Live2DView 宣布 WebSocket 指令;前端的 Live2DView 接纳到指令后,更新页面显现的模型动作表情。终究把弹幕框、Live2D以及 Sayer 朗读出的音频经过 OBS 收集,放到一个画面里,向 B 站直播间推流。这样一个简略的 AI 直播流程就完结了!一起为了确保直播质量,咱们或许还需求过滤掉一些含义不明的弹幕,以及过于大胆的讲话,这可以经过额外的 Filter 组件完结(本文不作介绍)。

终究的使命:把上面这段话,或许或上面那张图翻译成实践的代码。咱们凭借 Go 言语完结这个作业。

第一步,便是对上一节介绍的各个服务分别编写出客户端,这个十分简略,便是一些参数编码、http 恳求、解析呼应、错误处理这样繁琐但简略的调 API 的规范作业,大(GitHub)家(Copilot)都可以轻松完结。

第二步,也是终究一步,一个 main 函数,串起其一切组件,也便是完结规划图中的那些连线:

func main() {
	textInChan := make(chan *TextIn, BufSize)
	textOutChan := make(chan *TextOut, BufSize)
	// (blivedm) -> in
	go TextInFromDm(*roomid, textInChan)
	// in -> filter (目前没有) -> in
	textInFiltered := textInChan
	// in -> chatbot -> out
	chatbot := NewPrioritizedChatbot(map[Priority]Chatbot{
		PriorityLow:  NewMusharingChatbot(),
		PriorityHigh: NewChatGPTChatbot(),
	})
	go TextOutFromChatbot(chatbot, textInFiltered, textOutChan)
	// out -> filter (目前没有) -> out
	textOutFiltered := textOutChan
	// out -> (live2d) & (say) & (stdout)
	live2d := NewLive2DDriver()
	sayer := NewSayer()
	for textOut := range textOutFiltered {
		live2d.TextOutToLive2DDriver(textOut)
		sayer.Say(textOut.Content)
		fmt.Println(textOut)
	}
}

归功于 Go 言语的 goruntine 和 chan,悉数都十分直观。

终究一步,启动程序,打开 OBS,把输出的音视频收集进去:

  • 弹幕框:localhost:12450/...
  • Live2DView:localhost:9000
  • 音频(say)的输出:你运用的音频设备

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

在 B 站开始直播,OBS 推流,完结!

(具体的 OBS 运用办法以及怎么在 B 站敞开直播,我们可以在 B 站查找相关视频学习,此处不作介绍。)

想看看她直播的作用?这个项目不是什么复杂的深度学习大模型,不需求任何特别的硬/软件,一般装备的 Mac + Git + Python + Poetry + Node + Pnpm + Go + OBS 即可搭起全套环境,轻松复现。(我也会赶快完结容器化,届时便可直接一键运转。)

总结

我们或许很绝望,本文没有去练习某种奇幻的深度学习模型,从而完结悉数。这仅仅因为我不会。

我一直以为,核算机程序就应该是这样的。”Keep it simple, stupid!”。麻瓜模块的快速组合,而不是端到端的数学大魔法。强类型数据流、活动和状态,而不是高维浮点张量乘。。。又扯远了。

是这样的,我期望共享的是一个“从主意到 app”的进程。我怎么想到一个点子,又怎么把它变成可以程序化的需求,或许说把它变成可行的核算:我要的 = f(现有的),清晰输入(弹幕)、输出(直播)和约束(我的机器跑的动)。然后,我又怎么规划这个 f,把它拆分成一个个相对独立的小模块,每个模块完结一件作业,用最简略的代码去完结他们。终究我又怎么把这些模块拼在一同,让他们协同作业,完结一个风趣的大程序(AI 虚拟主播)。

总归,这个项目完结了一个灵活的框架,你可以任意替换其间的组件,从而不断提升这个 AI 主播的业务才能。请容我再次放出整体的规划图,并鄙人面增加一些我觉得有意思的 TODO:

写个AI虚拟主播:看懂弹幕,妙语连珠,悲欢形于色,以一种简单的实现

满打满算我只用了一周时间设(摸)计(鱼)、一周时间编码来完结了这个项目。所以她是十分粗糙的。换句话说,她有十分大的改进空间。例如我现在现已开始将其间的部分手写的 HTTP 通讯迁移到 gRPC,用更少的代码,带来更强的功能、更完善的错误处理。

我把这个项目当作自己的“学期(课外)学习方案”来不断优化:期望在功用上完结上图的各种 todo 的一起,也在完结上不断重构,从现在的天真模式,逐渐用上 RPC、容器编排、函数核算、devOps 等等现代云原生的办法。

终究这个项目或许会变得十分棒。假如有所发展,我会鄙人一篇文章“muvtuber:进化!从散装微服务到云原生”中介绍,请我们监督。

我不是一个老到的程序员,我写不出好的规划模式、刷不明白 Leetcode,我只有不断冒出的主意,和越来越长的 todo list。我的备忘录里躺满了 app 等待完结,但说到的总是做不到,美好的主意都等到了发霉。可也只能一行一行渐渐写,just for fun。我用十年写了满屏烂代码,就算越来越苍茫,也就只好这样持续吧……


终究再次给出悉数源码链接,望读者垂阅指正:

服务 阐明 根据
xfgryujk/blivechat 获取直播间弹幕音讯
Live2dView 前端:显现 Live2D 模型 guansss/pixi-live2d-display
Live2dDriver 驱动前端 Live2D 模型动作表情
ChatGPTChatbot 根据 ChatGPT 的优质谈天机器人 acheong08/ChatGPT
MusharingChatbot 根据 ChatterBot 的简略谈天机 RaSan147/ChatterBot_update
musharing-team/chatbot_api
Emotext 中文文本情感剖析 cdfmlr/murecom-verse-1
muvtuberdriver 拼装各模块,驱动整个流程

参考资料:

  • Cubism: Live2D Cubism SDK チュートリアル. docs.live2d.com/cubism-sdk-…
  • Gunther Cox: ChatterBot Docs. chatterbot.readthedocs.io/en/stable/
  • Pavo Studio: Live2DViewerEX 文档. live2d.pavostudio.com/doc/zh-cn/a…
  • CDFMLR: 让 AI 看懂你的心境,并引荐应景的音乐,以一种简略的完结. /post/707081…
  • guansss: pixi-live2d-display docs. guansss.github.io/pixi-live2d…
  • MDN: The WebSocket API (WebSockets). developer.mozilla.org/en-US/docs/…
  • OBS: Open Broadcaster Software. obsproject.com
  • 大连理工大学 – 信息检索研究室: 情感词汇本体-词典. ir.dlut.edu.cn/info/1013/1…
  • Bilibili 协助中心: 成为主播. link.bilibili.com/p/help/inde…

本项目、文章可以完结,还要特别感谢 GitHub Copilot 和 ChatGPT 的协助。

Footnotes

  1. 何谓“mu”:一说是 machine/you 的缩写:这个系列用于探索机器与人、人工智能与艺术。另有一说 mu… 是 make your … 的意思:保持简略,开源共享,人人都能做。(事实上 mu 开始的来历是 music,所以这些探索的切入点是:音乐、共享、感动。) ↩

  2. 徐琳宏 , 林鸿飞 , 潘宇 , et al. 情感词汇本体的结构 [J]. 情报学报, 2008, 27(2) : 6. ↩