File size: 19,677 Bytes
76d8c82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
from io import StringIO
import time
import os
import logging

import gradio as gr
import pandas as pd
from pypinyin import lazy_pinyin
from gradio_i18n import , Translate

from api import generate_api

# 翻译文件位置
trans_file = os.path.join(os.path.dirname(__file__),"i18n", "translations.json")

# 关闭aiohttp的DEBUG日志
logging.getLogger('aiohttp').setLevel(logging.WARNING)
logging.getLogger("gradio").setLevel(logging.WARNING)

# 带有时间的log
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


terms = r"""

## 免责声明



本网站提供的语音合成服务(以下简称“服务”)旨在供个人使用和娱乐目的。在使用本服务前,请用户仔细阅读并充分理解以下条款:



1. **角色版权**:本网站可能使用的角色形象涉及第三方知识产权。本网站不拥有这些角色的版权。用户在使用服务时应尊重相关角色的知识产权,并确保其行为不侵犯任何第三方的知识产权。



2. **用户生成内容(UGC)**:用户通过本平台生成的语音内容(以下简称“UGC”)由用户自行负责,与本平台无关。本平台无法控制或审核用户生成的具体内容,且不对UGC的准确性、完整性或合法性承担任何责任。



3. **使用限制**:本服务生成的语音及其UGC仅限于个人使用,不得用于任何商业目的。未经本平台事先书面同意,禁止将生成内容用于任何商业活动。



4. **法律责任**:用户使用本服务所产生的任何法律责任由用户自行承担,与本平台无关。如因用户使用服务或其UGC导致的任何纠纷或损失,本平台不承担任何责任。



5. **版权声明**:用户应尊重原创,不得使用本服务生成侵犯他人著作权的内容。如发现用户生成内容侵犯他人版权,本平台有权立即停止对其提供服务,并保留追究法律责任的权利。



6. **内容监管**:尽管本平台无法控制UGC,但一旦发现违反本免责声明或法律法规的内容,本平台将采取必要措施,包括但不限于删除违规内容,并配合有关部门进行调查。



7. **注明要求**:用户应在生成内容的显著位置,如可能的话,注明“此内容由RubiiTTS生成”或类似的说明。用户应确保注明行为符合本条款的要求。



用户使用本网站即表示同意以上免责声明。如有疑问,请联系我们[email protected]


**最终解释权归本网站所有。**



"""

terms_js = r"alert('本网站提供的语音合成服务仅供个人使用和娱乐目的。请注意以下几点:\n1. 角色版权:本网站使用的角色形象可能涉及第三方知识产权,我们不拥有这些角色的版权。\n2. 生成内容:用户通过本平台生成的语音内容由用户自行负责,与本平台无关。我们无法控制或审核用户生成的内容。\n3. 使用限制:生成的语音仅限个人使用,不得用于任何商业目的。\n4. 法律责任:用户使用本服务所产生的任何法律责任由用户自行承担,与本平台无关。\n5. 版权声明:请尊重原创,不要使用本服务生成侵犯他人著作权的内容。\n使用本网站即表示您同意以上免责声明。如有疑问,请联系我们。')"

def load_characters_csv(lang):
    name = f"characters_{lang}"
    return pd.read_csv(StringIO(os.getenv(name)))

def update_all_characters(lang, current_all_characters):
    new_characters = load_characters_csv(lang)
    initial_characters = get_characters(kind="原神", all_characters=new_characters)
    return new_characters, initial_characters, gr.Gallery(value=[[char['头像'], char['名称']] for char in initial_characters], 
                                 show_label=False, elem_id="character_gallery", columns=[11], 
                                 object_fit="contain", height="auto", interactive=False, 
                                 allow_preview=False, selected_index=None)

def get_characters(query=None, page=1, per_page=400, kind="原神", lang="zh", all_characters=None):
    # 使用传入的 all_characters 参数
    filtered_characters = all_characters[all_characters["类别"] == kind]
    
    if query:
        # 使用拼音和汉字进行搜索
        filtered_characters = filtered_characters[
            filtered_characters['名称'].str.contains(query, case=False)
        ]
        if filtered_characters.empty and lang == 'zh':
            filtered_characters = all_characters[all_characters["类别"] == kind]
            filtered_characters = filtered_characters[
                filtered_characters['名称'].apply(lambda x: ''.join(lazy_pinyin(x))).str.contains(query, case=False)
            ]
    
    # 按名称分组,并选择每组的第一个记录
    unique_characters = filtered_characters.groupby('名称').first().reset_index().sort_values(by='id')
    
    # 应用分页
    start_index = (page - 1) * per_page
    end_index = start_index + per_page
    
    return unique_characters.iloc[start_index:end_index].to_dict('records')
        
async def generate(selected_character = None, selected_characters = [], text = "", lang="zh"):
    # print("-------",selected_character)
    # print("-------",selected_characters)
    if selected_character:
        characters = [selected_character] + selected_characters
    else:
        characters = selected_characters
    if not selected_character and not selected_characters:
        if lang == "zh":
            raise gr.Error("请先选择一个角色")
        elif lang == "en":
            raise gr.Error("Please select a character first")
        elif lang == "ja":
            raise gr.Error("まず、キャラクターを選択してください")
        elif lang == "ko":
            raise gr.Error("먼저 캐릭터를 선택하세요")
    voice_ids = [char.get("voice_id") for char in characters if char.get("voice_id")]
    
    if not voice_ids:
        raise gr.Error("所选角色没有关联的 voice_id")
    
    start_time = time.time()
    # 假设我们只使用第一个选择的角色的名称
    if voice_ids == "1":
        if lang == "zh":
            raise gr.Error("该角色暂未创建语音")
        elif lang == "en":
            raise gr.Error("The character has not been created yet")
        elif lang == "ja":
            raise gr.Error("そのキャラクターの音声はまだ作成されていません")
        elif lang == "ko":
            raise gr.Error("해당 캐릭터의 음성이 아직 생성되지 않았습니다")
        
    if text == "":
        if lang == "zh":
            raise gr.Error("请输入需要合成的文本")
        elif lang == "en":
            raise gr.Error("Please enter the text to be synthesized")
        elif lang == "ja":
            raise gr.Error("合成するテキストを入力してください")
        elif lang == "ko":
            raise gr.Error("합성할 텍스트를 입력하세요")
    
    if (lang == "en" and len(text.split()) > 200) or len(text) > 512:
        if lang == "zh":
            raise gr.Error("长度请控制在512个字符以内")
        elif lang == "en":
            raise gr.Error("The text length exceeds 200 words")
        elif lang == "ja":
            raise gr.Error("テキストの長さが512文字を超えています")
        elif lang == "ko":
            raise gr.Error("텍스트 길이가 512자를 초과합니다")
    
    # logging.info(f"选择角色: {characters[0].get('名称')}, 文本: {text}, voice_id: {voice_ids}")
    audio = await generate_api(voice_ids, text)
    end_time = time.time()
    if lang == "zh":
        cost_time = f"合成共花费{end_time - start_time:.2f}秒"
    elif lang == "en":
        cost_time = f"Total time spent synthesizing: {end_time - start_time:.2f} seconds"
    elif lang == "ja":
        cost_time = f"合成にかかった時間: {end_time - start_time:.2f}秒"
    elif lang == "ko":
        cost_time = f"합성에 소요된 시간: {end_time - start_time:.2f}초"
    if isinstance(audio, str):
        print(audio)
        raise gr.Error(audio)
    else:
        return audio, cost_time

def get_character_emotions(character, all_characters):
    # 从all_characters中筛选出与当前角色名称相同的所有记录
    character_records = all_characters[all_characters['名称'] == character['名称']]
    
    # 获取所有不重复的情绪
    emotions = character_records['情绪'].unique().tolist()
    
    # 如果没有找到情绪,返回一个默认值
    return emotions if emotions else ["默认情绪"]

def update_character_info(character_name, emotion, current_character, all_characters):
    character_info = None
    if character_name and emotion:
        character_info = all_characters[(all_characters['名称'] == character_name) & (all_characters['情绪'] == emotion)]
    if character_name == "":
        return None
    character_info = character_info.iloc[0].to_dict()
    return character_info, all_characters

def add_new_voice(current_character, selected_characters, kind, lang, all_characters):
    if not current_character:
        if lang == "zh":
            raise gr.Error("请先选择一个角色")
        elif lang == "en":
            raise gr.Error("Please select a character first")
        elif lang == "ja":
            raise gr.Error("まず、キャラクターを選択してください")
        elif lang == "ko":
            raise gr.Error("먼저 캐릭터를 선택하세요")
    
    if len(selected_characters) >= 5:
        raise gr.Error("已达到最大选择数(5个)")
    
    # 检查是否已存在相同角色
    existing_char = next((char for char in selected_characters if char['名称'] == current_character['名称']), None)
    if existing_char:
        # 如果情绪不同,更新情绪
        if existing_char['情绪'] != current_character['情绪']:
            existing_char['情绪'] = current_character['情绪']
    else:
        selected_characters.insert(0, current_character)
    
    updated_characters = get_characters(kind=kind, lang=lang, all_characters=all_characters)
    # ! 取消gallery选中状态,返回个新的gallery是必要的,否则会保留上一次的选中状态。这里sonnet很喜欢改成返回一个数组,但这不能清空gallery的选中状态
    updated_gallery = gr.Gallery(value=[[char['头像'], char['名称']] for char in updated_characters], 
                                 show_label=False, elem_id="character_gallery", columns=[11], 
                                 object_fit="contain", height="auto", interactive=False, 
                                 allow_preview=False, selected_index=None)
    
    return (None, gr.update(value=""), gr.update(choices=[]), selected_characters, 
            updated_characters, updated_gallery, gr.update(visible=True), all_characters)

def update_selected_chars_display(selected_characters):
    updates = []
    for i, (name, emotion, _, row) in enumerate(selected_chars_rows):
        if i < len(selected_characters):
            char = selected_characters[i]
            updates.extend([
                gr.update(value=char['名称'], visible=True),
                gr.update(value=char['情绪'], visible=True),
                gr.update(visible=True),
                gr.update(visible=True)
            ])
        else:
            updates.extend([
                gr.update(value="", visible=False),
                gr.update(value="", visible=False),
                gr.update(visible=False),
                gr.update(visible=False)
            ])
    return updates

def remove_character(index, selected_characters):
    if 0 <= index < len(selected_characters):
        del selected_characters[index]  
    return selected_characters, gr.update(visible=True)

def update_gallery(kind, query, all_characters):
    updated_characters = get_characters(kind=kind, query=query, lang=lang, all_characters=all_characters)
    return updated_characters, [[char['头像'], char['名称']] for char in updated_characters], all_characters

def on_select(evt: gr.SelectData, characters, selected_characters, all_characters):
    # 如果没有选择角色,换人的时候清空
    if len(selected_characters) == 0:
        selected_characters = []
    
    selected = characters[evt.index]
    emotions = get_character_emotions(selected, all_characters)
    default_emotion = emotions[0] if emotions else ""
    
    character_dict = selected.copy()
    character_dict['情绪'] = default_emotion

    return selected["名称"], gr.Dropdown(choices=emotions, value=default_emotion), character_dict, selected_characters

with gr.Blocks(title="Rubii TTS", theme=gr.themes.Soft()) as demo:
    lang = gr.Radio(choices=[("中文", "zh"), ("English", "en"), ("日本語", "ja"), ("한국인", "ko")], label=("Language"), value="zh", scale=1)
    all_characters_state = gr.State(load_characters_csv("zh"))
    
    # with Translate(trans_file, lang, placeholder_langs=["en", "zh", "ja", "ko"]):
    gr.Markdown(
        value=("""## 🎉 欢迎使用Rubii语音合成系统 🎉

            

    #### [🗣️ 不想只是听到角色的声音,还想与他们进行互动交流吗?快点击我来体验与这些角色的生动对话吧!(中国大陆暂不可用) 🌟](https://rubii.ai)



    📝 使用说明:

    1. 选择角色类别 🎭

    2. 从图库中选择一个或多个角色(最多5个) 👥。当选择多个角色时,系统会自动进行声线融合(以第一个角色为主音色,其他角色为辅助音色),您可以尝试不同的组合来获得独特的声音效果。

    3. 选择角色的情绪 😊😢😠

    4. 输入要合成的文本 ✍️

    5. 点击"合成语音"按钮 🔊

    """
        ))
    with gr.Group():
        initial_characters = get_characters(kind="原神", lang="zh", all_characters=all_characters_state.value)
        characters = gr.State(initial_characters)
        selected_characters = gr.State([])
        current_character = gr.State(None)
        
        with gr.Blocks():
            with gr.Row():
                # kind = gr.Dropdown(choices=["原神", "崩坏星穹铁道","鸣潮","明日方舟","其他"], value="原神", label="请选择角色类别")
                choices = ["原神", "崩坏星穹铁道", "鸣潮"]
                kind = gr.Dropdown(choices=[((name), name) for name in choices], value="原神", label=("选择角色类别"))
                query = gr.Textbox(label=("搜索角色"), value="", lines=1, max_lines=1, interactive=True)
            with gr.Blocks():
                gallery = gr.Gallery(
                    value=[[char['头像'], char['名称']] for char in characters.value],
                    show_label=False,
                    elem_id="character_gallery",
                    columns=[11],
                    object_fit="contain",
                    height="auto",
                    interactive=False,
                    allow_preview=False,
                    selected_index=None
                )
            with gr.Row():
                character_name = gr.Textbox(label=("当前选择的角色"), interactive=False, max_lines=1)
                info_type = gr.Dropdown(choices=[], label=("选择情绪"))
            with gr.Row():
                add_voice_button = gr.Button(("添加新的声音"), variant="primary")
            
            selected_chars_container = gr.Column(elem_id="selected_chars_container", visible=False)
            
            with selected_chars_container:
                gr.Markdown(("### 已选择的角色"))
                selected_chars_rows = []
                for i in range(5):  # 假设最多选择5个角色
                    with gr.Row() as row:
                        name = gr.Textbox(label=("名称"), interactive=False, max_lines=1)
                        emotion = gr.Textbox(label=("情绪"), interactive=False, max_lines=1)
                        delete_btn = gr.Button(("删除"), scale=0)
                    selected_chars_rows.append((name, emotion, delete_btn, row))


        # -------------- 绑定事件 --------------
        
        lang.change(
            fn=update_all_characters,
            inputs=[lang, all_characters_state],
            outputs=[all_characters_state, characters, gallery]
        )

        add_voice_button.click(
            fn=add_new_voice,
            inputs=[current_character, selected_characters, kind, lang, all_characters_state],
            outputs=[current_character, character_name, info_type, selected_characters, 
                    characters, gallery, selected_chars_container, all_characters_state]
        ).then(
            fn=update_selected_chars_display,
            inputs=[selected_characters],
            outputs=[item for row in selected_chars_rows for item in row]
        )


        gallery.select(
            fn=on_select,
            inputs=[characters, selected_characters, all_characters_state],
            outputs=[character_name, info_type, current_character, selected_characters]
        )

        info_type.change(
            fn=update_character_info,
            inputs=[character_name, info_type, current_character, all_characters_state],
            outputs=[current_character, all_characters_state]
        )

        for i, (_, _, delete_btn, _) in enumerate(selected_chars_rows):
            delete_btn.click(
                fn=remove_character,
                inputs=[gr.Number(value=i, visible=False), selected_characters],
                outputs=[selected_characters, selected_chars_container]
            ).then(
                fn=update_selected_chars_display,
                inputs=[selected_characters],
                outputs=[item for row in selected_chars_rows for item in row]
            )
        
        kind.change(
            fn=update_gallery,
            inputs=[kind, query, all_characters_state],
            outputs=[characters, gallery, all_characters_state]
        )
        
        query.change(
            fn=update_gallery,
            inputs=[kind, query, all_characters_state],
            outputs=[characters, gallery, all_characters_state]
        )

        with gr.Row():
            with gr.Column():
                text = gr.Textbox(label=("需要合成的文本"), value="", lines=10, max_lines=10)
                inference_button = gr.Button(("🎉 合成语音 🎉"), variant="primary", size='lg')
            with gr.Column():
                output = gr.Audio(label=("输出的语音"), interactive=False, type="numpy")
                cost_time = gr.Textbox(label=("合成时间"), interactive=False, show_label=False, max_lines=1)
        try:
            inference_button.click(
                fn=generate,
                inputs=[current_character, selected_characters, text, lang],
                outputs=[output, cost_time],
            )
        except gr.Error as e:
            gr.Error(e)
        except Exception as e:
            pass
    gr.Markdown((terms))

if __name__ == '__main__':
    demo.queue(default_concurrency_limit=8).launch(
        server_name="0.0.0.0",
        server_port=80,
        show_api=False
    )