pymouth
是基于Python的Live2D口型同步库. 你可以用音频文件, 甚至是AI模型输出的ndarray, 就能轻松的让你的Live2D形象开口
唱跳RAP v.
效果演示视频.
Demo video
- Python>=3.10
- VTubeStudio>=1.28.0 (可选)
pip install pymouth
-
你需要确定自己Live2D口型同步的支持参数.
请注意:下面提供一种简单的判断方式,但这种方式会修改(重置)Live2D模型口型部分参数,使用前请备份好自己的模型。
如果你对自己的模型了如指掌,可以跳过这步。
-
下面是两种基于不同方式的Demo.
你可以找一个音频文件替换some.wav
.
samplerate
:音频数据的采样率.
output_device
:输出设备Index. 可以参考audio_devices_utils.py-
基于分贝的口型同步
import time from pymouth import VTSAdapter, DBAnalyser def main(): with VTSAdapter(DBAnalyser) as a: a.action(audio='some.wav', samplerate=44100, output_device=2) time.sleep(100000) # do something if __name__ == "__main__": main()
-
基于元音的口型同步
import time from pymouth import VTSAdapter, VowelAnalyser def main(): with VTSAdapter(VowelAnalyser) as a: a.action(audio='some.wav', samplerate=44100, output_device=2) time.sleep(100000) # do something if __name__ == "__main__": main()
第一次运行程序时,
VTubeStudio
会弹出插件授权界面, 通过授权后, 插件会在runtime路径下生成pymouth_vts_token.txt
文件, 之后运行不会重复授权, 除非token文件丢失或在VTubeStudio
移除授权.
-
1.2.0版本之后,移除了所有函数的协程调用方式(async/await),协程调用具有传染性,不利于用户维护。
目前只提供阻塞与非阻塞调用方式,非阻塞方式由内部线程池单线程实现,即无论a.action
被调用多少次,都会按照调用的现后顺序播放音频。
- 如果你仍使用协程启动,可以参考下面的示例
import asyncio from pymouth import VTSAdapter, VowelAnalyser async def main(): with VTSAdapter(VowelAnalyser) as a: a.action(audio='aiueo.wav', samplerate=44100, output_device=2) # no-block # a.action_block(audio='aiueo.wav', samplerate=44100, output_device=2) # block await asyncio.sleep(100000) if __name__ == "__main__": asyncio.run(main())
下面是一个比较完整的使用pymouth作为AI TTS消费者的例子。
import queue
import threading
import time
from fish_speech import tts
from pymouth import VTSAdapter, DBAnalyser, VTSPluginInfo
class SpeakMsg:
def __init__(self, msg: str, required: bool):
self.msg = msg
self.required = required
self.create_timestamp = time.time()
self.create_datetime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.create_timestamp))
class Speaker:
def __init__(self):
self.queue = queue.Queue(1)
def start(self):
plugin_info = VTSPluginInfo(plugin_name='kanojyo2',
developer='organics',
authentication_token_path='./pymouth_vts_token.txt',
plugin_icon=None)
with VTSAdapter(DBAnalyser, plugin_info=plugin_info) as a:
while True:
msg: SpeakMsg = self.queue.get()
t0 = time.time()
audio, rate = tts.tts_ndarray(msg.msg)
print(f'speak time:{time.time() - t0:.02f}')
a.action(audio=audio, samplerate=rate, output_device=2)
def speak(self, msg: str, required=True):
if required:
self.queue.put(SpeakMsg(msg, required))
else:
try:
self.queue.put_nowait(SpeakMsg(msg, required))
except queue.Full:
print("Queue Full")
if __name__ == "__main__":
speakers = Speaker()
# 这里的实现只作为参考而不是建议。对于AI等CPU密集型场景,使用线程而不是协程可能会更好。
threading.Thread(target=speakers.start).start()
关键的代码只有两行:
with VTSAdapter(DBAnalyser) as a:
a.action(audio='some.wav', samplerate=44100, output_device=2) # no-block
# a.action_block(audio='aiueo.wav', samplerate=44100, output_device=2) # block
a.action()
非阻塞,会立即返回,由程序内部维护线程池和队列。
a.action_block()
阻塞,直到音频播放和处理完毕才会返回,纯同步代码无线程,线程由调用者维护。
VTSAdapter
以下是详细的参数说明:
param | required | default | describe |
---|---|---|---|
analyser |
Y | 分析仪,必须是 Analyser 的子类,目前支持DBAnalyser 和VowelAnalyser |
|
db_vts_mouth_param |
'MouthOpen' |
仅作用于DBAnalyser , VTS中控制mouth_input的参数, 如果不是默认值请自行修改. |
|
vowel_vts_mouth_param |
dict[str,str] |
仅作用于VowelAnalyser , VTS中控制mouth_input的参数, 如果不是默认值请自行修改. |
|
ws_uri |
str |
websocket uri 默认:ws://localhost:8001 | |
plugin_info |
VTSPluginInfo |
插件信息,可以自定义 |
a.action()
会开始处理音频数据. 以下是详细的参数说明:
param | required | default | describe |
---|---|---|---|
audio |
Y | 音频数据, 可以是文件path, 可以是SoundFile对象, 也可以是ndarray | |
samplerate |
Y | 采样率, 这取决与音频数据的采样率, 如果你无法获取到音频数据的采样率, 可以尝试输出设备的采样率. | |
output_device |
Y | 输出设备Index, 这取决与硬件或虚拟设备. 可用 audio_devices_utils.py 打印当前系统音频设备信息. | |
finished_callback |
None |
音频处理完成会回调这个方法. | |
auto_play |
True |
是否自动播放音频,默认为True,会播放音频(自动将audio写入指定output_device ) |
Get Started 演示了一种High Level API 如果你不使用 VTubeStudio
或者想更加灵活的使用, 可以尝试Low Level API. 下面是一个Demo.
import time
from pymouth import DBAnalyser
def callback(y: float, data):
# Y is the Y coordinate of the model's mouth.
# Like is 0.4212883452
print(y) # do something
with DBAnalyser() as a:
a.action_noblock('zh.wav', 44100, output_device=2, callback=callback) # no block
# a.action_block() # block
print("end")
time.sleep(1000000)
import time
from pymouth import VowelAnalyser
def callback(md: dict[str, float], data):
"""
md like is:
{
'VoiceSilence': 0,
'VoiceA': 0.6547555255,
'VoiceI': 0.2872873444,
'VoiceU': 0.1034789232,
'VoiceE': 0.3927834533,
'VoiceO': 0.1927834548,
}
"""
print(md) # do something
with VowelAnalyser() as a:
a.action_noblock('zh.wav', 44100, output_device=2, callback=callback) # no block
# a.action_block() # block
print("end")
time.sleep(1000000)
- 文档补全
- Test case