main
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3from pathlib import Path
4
5from pyrogram.client import Client
6from pyrogram.types import Message
7
8from config import CAPTION_LENGTH, DOWNLOAD_DIR, PREFIX, TTS
9from messages.parser import parse_msg
10from messages.sender import send2tg
11from messages.utils import blockquote, equal_prefix, set_reaction, smart_split, startswith_prefix
12from tts.edge import edge_tts
13from tts.engines import get_tts_config, list_engines
14from tts.gemini import gemini_tts
15from tts.qwen import qwen_tts
16from tts.sambert import sambert_tts
17from utils import markdown_to_text, read_text
18
19HELP = f"""🗣**文字转语音**
20使用说明:
211. `{PREFIX.TTS}` + 文字或txt文件
222. `{PREFIX.TTS}` 回复 `文字消息` 或 `txt文件消息`
233. `{PREFIX.TTS} @音色名` 可以指定音色, 默认音色: {TTS.EDGE_VOICE}
24
25特殊用法:
26- `{PREFIX.TTS}` + @男 或 @male: 随机一款男声
27- `{PREFIX.TTS}` + @女 或 @female: 随机一款女声
28- `{PREFIX.TTS} @edge`: 随机一款MS Edge音色
29- `{PREFIX.TTS} @gemini`: 随机一款Gemini音色
30- `{PREFIX.TTS} @qwen`: 随机一款通义千问音色
31- `{PREFIX.TTS} @sambert`: 随机一款阿里Sambert音色
32{blockquote(list_engines())}
33"""
34
35
36async def text_to_speech(client: Client, message: Message, **kwargs):
37 info = parse_msg(message, silent=True)
38 if not startswith_prefix(info["text"], prefix=PREFIX.TTS):
39 return
40 # send docs if message == "/tts", without reply
41 if info["mtype"] == "text" and equal_prefix(message.text, prefix=PREFIX.TTS) and not message.reply_to_message:
42 await send2tg(client, message, texts=HELP, **kwargs)
43 return
44
45 voice_name, engine, model, texts = get_tts_config(info["text"].removeprefix(PREFIX.TTS).lstrip())
46 reaction_msg = message
47 if message.reply_to_message:
48 message = message.reply_to_message
49 info = parse_msg(message, silent=True, use_cache=False) # parse again
50
51 # file
52 if info["mtype"] == "document":
53 if info["mime_type"].startswith("text/") or Path(info["file_name"]).suffix.lower() in [".txt", ".md"]:
54 fpath: str = await client.download_media(message, f"{DOWNLOAD_DIR}/{info['file_name']}") # type: ignore
55 texts = read_text(fpath).strip()
56 else:
57 await reaction_msg.reply(text="不支持该文件格式, 请以 `.txt` 格式发送", quote=True)
58 return
59 elif reaction_msg.id != info["mid"]:
60 texts = info["text"]
61 await set_reaction(client, reaction_msg, reaction="👌")
62 if engine == "gemini":
63 resp = await gemini_tts(texts, model, voice_name)
64 elif engine == "qwen":
65 resp = await qwen_tts(texts, model, voice_name)
66 elif engine == "sambert":
67 resp = await sambert_tts(texts, model, voice_name)
68 elif engine == "edge":
69 resp = await edge_tts(texts, model, voice_name)
70 else:
71 msg = f"Unknown engine: {engine}"
72 raise ValueError(msg)
73
74 path = Path(resp.get("voice", ""))
75 if not path.is_file():
76 await set_reaction(client, reaction_msg, reaction="💔")
77 return
78
79 duration = round(resp["duration"])
80 caption = f"🗣音色: {resp['voice_name']}\n🤖引擎: {resp['model']}\n{blockquote(markdown_to_text(texts))}"
81 if len(await smart_split(caption, CAPTION_LENGTH)) == 1:
82 await message.reply_voice(path.as_posix(), duration=duration, caption=caption, quote=True)
83 else:
84 await message.reply_voice(path.as_posix(), duration=duration, caption=f"🗣音色: {resp['voice_name']}\n🤖引擎: {resp['model']}", quote=True)
85 await set_reaction(client, reaction_msg, reaction="")
86 path.unlink(missing_ok=True)