Xinonria commited on
Commit
6915670
1 Parent(s): 40f2a20

add create voice

Browse files
api.py CHANGED
@@ -1,11 +1,40 @@
1
  import asyncio
 
2
  import aiohttp
3
- import io
4
- import os
 
 
5
  from utils import normalize_audio_loudness
6
 
7
- BASE_URL = os.getenv("BASE_URL")
8
- AUDIO_URL = os.getenv("AUDIO_URL")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  async def generate_api(voice_ids, text):
11
  timeout = aiohttp.ClientTimeout(total=10) # 设置10秒的总超时时间
@@ -16,7 +45,6 @@ async def generate_api(voice_ids, text):
16
  # 读取响应内容
17
  audio_data = await response.read()
18
  # print(type(audio_data))
19
- # 创建一个字节流对象
20
  audio_data = normalize_audio_loudness(audio_data)
21
  return audio_data
22
  else:
@@ -40,3 +68,59 @@ async def get_audio(voice_id):
40
  return "请求超时,请稍后重试"
41
  except aiohttp.ClientError as e:
42
  return f"网络错误: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import asyncio
2
+ from datetime import datetime
3
  import aiohttp
4
+ import pickle
5
+
6
+ import pandas as pd
7
+
8
  from utils import normalize_audio_loudness
9
 
10
+ import os
11
+ from dotenv import load_dotenv
12
+ from pymongo import MongoClient
13
+ from bson import Binary, ObjectId
14
+
15
+ # 尝试直接获取环境变量
16
+ BASE_URL = os.environ.get("BASE_URL")
17
+ AUDIO_URL = os.environ.get("AUDIO_URL")
18
+ MONGO_URI = os.environ.get("MONGO_URI")
19
+ DATABASE_NAME = os.environ.get("DATABASE_NAME")
20
+ COLLECTION_NAME = os.environ.get("COLLECTION_NAME")
21
+ CREATE_COLLECTION = os.environ.get("CREATE_COLLECTION")
22
+
23
+ # 如果直接获取不到,则从.env文件加载
24
+ if BASE_URL is None or AUDIO_URL is None or MONGO_URI is None or DATABASE_NAME is None or COLLECTION_NAME is None or CREATE_COLLECTION is None:
25
+ print("从.env文件加载环境变量")
26
+ load_dotenv()
27
+ BASE_URL = os.getenv("BASE_URL")
28
+ AUDIO_URL = os.getenv("AUDIO_URL")
29
+ MONGO_URI = os.getenv("MONGO_URI")
30
+ DATABASE_NAME = os.getenv("DATABASE_NAME")
31
+ COLLECTION_NAME = os.getenv("COLLECTION_NAME")
32
+ CREATE_COLLECTION = os.getenv("CREATE_COLLECTION")
33
+
34
+ client = MongoClient(MONGO_URI)
35
+ db = client[DATABASE_NAME]
36
+ collection = db[COLLECTION_NAME]
37
+ create_collection = db[CREATE_COLLECTION]
38
 
39
  async def generate_api(voice_ids, text):
40
  timeout = aiohttp.ClientTimeout(total=10) # 设置10秒的总超时时间
 
45
  # 读取响应内容
46
  audio_data = await response.read()
47
  # print(type(audio_data))
 
48
  audio_data = normalize_audio_loudness(audio_data)
49
  return audio_data
50
  else:
 
68
  return "请求超时,请稍后重试"
69
  except aiohttp.ClientError as e:
70
  return f"网络错误: {str(e)}"
71
+
72
+ def load_characters_csv(lang):
73
+ # 从MongoDB集合中获取数据
74
+ cursor = collection.find({"language": lang, "is_public": True})
75
+
76
+ # 将查询结果转换为列表
77
+ data = list(cursor)
78
+
79
+ # 创建一个空的DataFrame
80
+ df = pd.DataFrame(columns=["类别", "id", "名称", "情绪", "头像", "voice_id"])
81
+
82
+ # 遍历数据并填充DataFrame
83
+ for item in data:
84
+ df = pd.concat([df, pd.DataFrame({
85
+ "类别": [item["category"]],
86
+ "id": [str(item["id"])], # 确保id是字符串类型
87
+ "名称": [item["name"]],
88
+ "情绪": [item["emotion"]],
89
+ "头像": [item["avatar"]],
90
+ "voice_id": [item["voice_id"]]
91
+ })], ignore_index=True)
92
+
93
+ 指定顺序 = {
94
+ "zh": ["原神", "崩坏星穹铁道", "绝区零", "鸣潮"],
95
+ "en": ["Genshin Impact", "Honkai: Star Rail", "Zenless Zone Zero", "Wuthering Waves"],
96
+ "ja": ["原神[げんしん", "崩壊:スターレイル", "ゼンレスゾーンゼロ", "Wuthering Waves"],
97
+ "ko": ["원신", "붕괴: 스타레일", "젠레스 존 제로", "Wuthering Waves"]
98
+ }
99
+ 当前语言顺序 = 指定顺序.get(lang, 指定顺序["en"])
100
+ 其他类别 = sorted(set(df['类别'].unique()) - set(当前语言顺序))
101
+ unique_categories = 当前语言顺序 + 其他类别
102
+
103
+ return df, unique_categories
104
+
105
+
106
+
107
+ async def generate_voice(avatar, name, emotion, tags, gender, audio_data, language):
108
+ # 将图像数据转换为二进制
109
+ avatar_binary = pickle.dumps(avatar)
110
+ # 将音频数据转换为二进制
111
+ audio_binary = pickle.dumps(audio_data)
112
+
113
+ # 创建声音对象
114
+ voice = {
115
+ "avatar": Binary(avatar_binary),
116
+ "name": name,
117
+ "emotion": emotion,
118
+ "tags": tags,
119
+ "gender": gender,
120
+ "audio_data": Binary(audio_binary),
121
+ "language": language,
122
+ "create_at": datetime.now().isoformat(),
123
+ "is_public": False
124
+ }
125
+ result = create_collection.insert_one(voice)
126
+ return result.inserted_id
app.py CHANGED
@@ -1,68 +1,101 @@
1
  import time
2
  import os
3
  import logging
4
- from io import StringIO
5
 
6
  import gradio as gr
 
7
  import pandas as pd
8
  from pypinyin import lazy_pinyin
9
- from gradio_i18n import gettext, Translate
10
 
11
- from api import generate_api, get_audio
12
  from utils import get_length
13
 
14
  # 翻译文件位置
15
- trans_file = os.path.join(os.path.dirname(__file__),"i18n", "translations.json")
16
 
17
  # 关闭aiohttp的DEBUG日志
18
- logging.getLogger('aiohttp').setLevel(logging.WARNING)
19
- logging.getLogger("gradio").setLevel(logging.WARNING)
20
 
21
  # 带有时间的log
22
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
23
 
24
 
25
- header = "header"
26
 
27
  terms = "terms"
28
 
29
- def load_characters_csv(lang):
30
- name = os.path.join(os.path.dirname(__file__), "i18n", f"characters_{lang}.csv")
31
- return pd.read_csv(name)
32
 
33
- def update_all_characters(lang, current_all_characters):
34
- new_characters = load_characters_csv(lang)
35
- initial_characters = get_characters(kind="原神", all_characters=new_characters)
36
- return new_characters, initial_characters, gr.Gallery(value=[[char['头像'], char['名称']] for char in initial_characters],
37
- show_label=False, elem_id="character_gallery", columns=[11],
38
- object_fit="contain", height="auto", interactive=False,
39
- allow_preview=False, selected_index=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
- def get_characters(query=None, page=1, per_page=400, kind="原神", lang="zh", all_characters=None):
 
 
42
  # 使用传入的 all_characters 参数
43
  filtered_characters = all_characters[all_characters["类别"] == kind]
44
-
45
  if query:
46
  # 使用拼音和汉字进行搜索
47
  filtered_characters = filtered_characters[
48
- filtered_characters['名称'].str.contains(query, case=False)
49
  ]
50
- if filtered_characters.empty and lang == 'zh':
51
  filtered_characters = all_characters[all_characters["类别"] == kind]
52
  filtered_characters = filtered_characters[
53
- filtered_characters['名称'].apply(lambda x: ''.join(lazy_pinyin(x))).str.contains(query, case=False)
 
 
54
  ]
55
-
56
  # 按名称分组,并选择每组的第一个记录
57
- unique_characters = filtered_characters.groupby('名称').first().reset_index().sort_values(by='id')
 
 
 
 
 
58
 
 
 
 
 
 
 
 
 
 
 
59
  # 应用分页
60
  start_index = (page - 1) * per_page
61
  end_index = start_index + per_page
62
-
63
- return unique_characters.iloc[start_index:end_index].to_dict('records')
64
-
65
- async def generate(selected_character = None, selected_characters = [], text = "", lang="zh"):
 
66
  # print("-------",selected_character)
67
  # print("-------",selected_characters)
68
  if selected_character:
@@ -79,10 +112,10 @@ async def generate(selected_character = None, selected_characters = [], text = "
79
  elif lang == "ko":
80
  raise gr.Error("먼저 캐릭터를 선택하세요")
81
  voice_ids = [char.get("voice_id") for char in characters if char.get("voice_id")]
82
-
83
  if not voice_ids:
84
  raise gr.Error("所选角色没有关联的 voice_id")
85
-
86
  start_time = time.time()
87
  # 假设我们只使用第一个选择的角色的名称
88
  if voice_ids == "1":
@@ -94,7 +127,7 @@ async def generate(selected_character = None, selected_characters = [], text = "
94
  raise gr.Error("そのキャラクターの音声はまだ作成されていません")
95
  elif lang == "ko":
96
  raise gr.Error("해당 캐릭터의 음성이 아직 생성되지 않았습니다")
97
-
98
  if text == "":
99
  if lang == "zh":
100
  raise gr.Error("请输入需要合成的文本")
@@ -104,7 +137,7 @@ async def generate(selected_character = None, selected_characters = [], text = "
104
  raise gr.Error("合成するテキストを入力してください")
105
  elif lang == "ko":
106
  raise gr.Error("합성할 텍스트를 입력하세요")
107
-
108
  if get_length(text) > 1024:
109
  if lang == "zh":
110
  raise gr.Error("长度请控制在1024个字符以内")
@@ -114,13 +147,18 @@ async def generate(selected_character = None, selected_characters = [], text = "
114
  raise gr.Error("テキストの長さが1024文字を超えています")
115
  elif lang == "ko":
116
  raise gr.Error("텍스트 길이가 1024자를 초과합니다")
117
-
 
 
 
118
  audio = await generate_api(voice_ids, text)
119
  end_time = time.time()
120
  if lang == "zh":
121
  cost_time = f"合成共花费{end_time - start_time:.2f}秒"
122
  elif lang == "en":
123
- cost_time = f"Total time spent synthesizing: {end_time - start_time:.2f} seconds"
 
 
124
  elif lang == "ja":
125
  cost_time = f"合成にかかった時間: {end_time - start_time:.2f}秒"
126
  elif lang == "ko":
@@ -131,25 +169,37 @@ async def generate(selected_character = None, selected_characters = [], text = "
131
  else:
132
  return audio, cost_time
133
 
 
134
  def get_character_emotions(character, all_characters):
135
  # 从all_characters中筛选出与当前角色名称相同的所有记录
136
- character_records = all_characters[all_characters['名称'] == character['名称']]
137
-
138
  # 按情绪去重并获取完整的角色信息
139
- character_infos = character_records.drop_duplicates(subset=['情绪']).to_dict('records')
140
-
 
 
141
  # 如果没有找到角色信息,返回一个包含默认值的字典
142
- return character_infos if character_infos else [{"名称": character['名称'], "情绪": "默认情绪"}]
 
 
 
 
 
143
 
144
  def update_character_info(character_name, emotion, current_character, all_characters):
145
  character_info = None
146
  if character_name and emotion:
147
- character_info = all_characters[(all_characters['名称'] == character_name) & (all_characters['情绪'] == emotion)]
 
 
 
148
  if character_name == "":
149
  return None
150
  character_info = character_info.iloc[0].to_dict()
151
  return character_info, all_characters
152
 
 
153
  def add_new_voice(current_character, selected_characters, kind, lang, all_characters):
154
  if not current_character:
155
  if lang == "zh":
@@ -160,78 +210,128 @@ def add_new_voice(current_character, selected_characters, kind, lang, all_charac
160
  raise gr.Error("まず、キャラクターを選択してください")
161
  elif lang == "ko":
162
  raise gr.Error("먼저 캐릭터를 선택하세요")
163
-
164
  if len(selected_characters) >= 5:
165
  raise gr.Error("已达到最大选择数(5个)")
166
-
167
  # 检查是否已存在相同角色
168
- existing_char = next((char for char in selected_characters if char['名称'] == current_character['名称']), None)
 
 
 
 
 
 
 
169
  if existing_char:
170
  # 如果情绪不同,更新情绪
171
- if existing_char['情绪'] != current_character['情绪']:
172
- existing_char['情绪'] = current_character['情绪']
173
  else:
174
  selected_characters.insert(0, current_character)
175
-
176
- updated_characters = get_characters(kind=kind, lang=lang, all_characters=all_characters)
 
 
177
  # ! 取消gallery选中状态,返回个新的gallery是必要的,否则会保留上一次的选中状态。这里sonnet很喜欢改成返回一个数组,但这不能清空gallery的选中状态
178
- updated_gallery = gr.Gallery(value=[[char['头像'], char['名称']] for char in updated_characters],
179
- show_label=False, elem_id="character_gallery", columns=[11],
180
- object_fit="contain", height="auto", interactive=False,
181
- allow_preview=False, selected_index=None)
182
-
183
- return (None, gr.update(value=""), gr.update(choices=[]), selected_characters,
184
- updated_characters, updated_gallery, gr.update(visible=True), all_characters)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
  def update_selected_chars_display(selected_characters):
187
  updates = []
188
  for i, (name, emotion, _, row) in enumerate(selected_chars_rows):
189
  if i < len(selected_characters):
190
  char = selected_characters[i]
191
- updates.extend([
192
- gr.update(value=char['名称'], visible=True),
193
- gr.update(value=char['情绪'], visible=True),
194
- gr.update(visible=True),
195
- gr.update(visible=True)
196
- ])
 
 
197
  else:
198
- updates.extend([
199
- gr.update(value="", visible=False),
200
- gr.update(value="", visible=False),
201
- gr.update(visible=False),
202
- gr.update(visible=False)
203
- ])
 
 
204
  return updates
205
 
 
206
  def remove_character(index, selected_characters):
207
  if 0 <= index < len(selected_characters):
208
- del selected_characters[index]
209
  return selected_characters, gr.update(visible=True)
210
 
 
211
  def update_gallery(kind, query, all_characters):
212
- updated_characters = get_characters(kind=kind, query=query, lang=lang, all_characters=all_characters)
213
- return updated_characters, [[char['头像'], char['名称']] for char in updated_characters], all_characters
 
 
 
 
 
 
 
214
 
215
  def on_select(evt: gr.SelectData, characters, selected_characters, all_characters):
216
  # 如果没有选择角色,换人的时候清空
217
  if len(selected_characters) == 0:
218
  selected_characters = []
219
-
220
  selected = characters[evt.index]
221
  emotions = get_character_emotions(selected, all_characters)
222
  normal_index = 0
223
  for index, emotion in enumerate(emotions):
224
- if emotion["情绪"] == "正常" or emotion["情绪"] == "보통" or emotion["情绪"] == "normal":
 
 
 
 
225
  normal_index = index
226
  break
227
-
228
  default_emotion = emotions[normal_index]["情绪"] if emotions else ""
229
  default_voice_id = emotions[normal_index]["voice_id"] if emotions else ""
230
-
231
  character_dict = selected.copy()
232
- character_dict['情绪'] = default_emotion
233
- character_dict['voice_id'] = default_voice_id
234
- return selected["名称"], gr.Dropdown(choices=[emotion["情绪"] for emotion in emotions], value=default_emotion), character_dict, selected_characters
 
 
 
 
 
 
 
 
235
 
236
  async def update_prompt_audio(current_character):
237
  if current_character:
@@ -239,40 +339,102 @@ async def update_prompt_audio(current_character):
239
  else:
240
  return None
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  head = """
243
  <title>Free Online Text to Speech (TTS) | Convert Text to Audio</title>
244
  <meta name="description" content="Text to Speech(TTS) for free! 5-second voice cloning, no sign-up required.">
245
  <meta name="keywords" content="text to speech, TTS, free TTS, online TTS, speech synthesis, voice generator">
246
  """
247
-
248
  with gr.Blocks(title="Online Free TTS", theme=gr.themes.Soft(), head=head) as demo:
249
  gr.Markdown(
250
  "Online Free TTS(Text-to-Speech). Ultra-low latency, 5-second voice cloning."
251
  )
252
- lang = gr.Radio(choices=[("中文", "zh"), ("English", "en"), ("日本語", "ja"), ("한국인", "ko")], label=gettext("Language"), value="en", scale=1)
253
- all_characters_state = gr.State(load_characters_csv("en"))
 
 
 
 
 
 
254
 
255
  with Translate(trans_file, lang, placeholder_langs=["en", "zh", "ja", "ko"]):
256
- gr.Markdown(
257
- value=gettext(header))
258
  with gr.Group():
259
- initial_characters = get_characters(kind="原神", lang="zh", all_characters=all_characters_state.value)
 
 
260
  characters = gr.State(initial_characters)
261
  selected_characters = gr.State([])
262
  current_character = gr.State(None)
263
-
 
264
  with gr.Blocks():
265
  with gr.Row():
266
  # kind = gr.Dropdown(choices=["原神", "崩坏星穹铁道","鸣潮","明日方舟","其他"], value="原神", label="请选择角色类别")
267
- choices = [(gettext("Genshin Impact"),"原神"),
268
- (gettext("Honkai: Star Rail"),"崩坏星穹铁道"),
269
- (gettext("ZenZenless Zone Zero"),"绝区零"),
270
- (gettext("Wuthering Waves"),"鸣潮")]
271
- kind = gr.Dropdown(choices=choices, value="原神", label=gettext("Select character category"))
272
- query = gr.Textbox(label=gettext("Search character"), value="", lines=1, max_lines=1, interactive=True)
 
 
 
 
 
 
 
 
 
 
 
 
273
  with gr.Blocks():
274
  gallery = gr.Gallery(
275
- value=[[char['头像'], char['名称']] for char in characters.value],
 
 
276
  show_label=False,
277
  elem_id="character_gallery",
278
  columns=[11],
@@ -280,34 +442,64 @@ with gr.Blocks(title="Online Free TTS", theme=gr.themes.Soft(), head=head) as de
280
  height="auto",
281
  interactive=False,
282
  allow_preview=False,
283
- selected_index=None
284
  )
285
  with gr.Row():
286
- character_name = gr.Textbox(label=gettext("Currently selected character"), interactive=False, max_lines=1)
 
 
 
 
287
  info_type = gr.Dropdown(choices=[], label=gettext("Select emotion"))
288
  with gr.Row():
289
- add_voice_button = gr.Button(gettext("Add new voice"), variant="primary")
290
-
291
- selected_chars_container = gr.Column(elem_id="selected_chars_container", visible=False)
292
-
 
 
 
 
293
  with selected_chars_container:
294
  gr.Markdown(gettext("### Selected characters"))
295
  selected_chars_rows = []
296
  for i in range(5): # 假设最多选择5个角色
297
  with gr.Row() as row:
298
- name = gr.Textbox(label=gettext("Name"), interactive=False, max_lines=1)
299
- emotion = gr.Textbox(label=gettext("Emotion"), interactive=False, max_lines=1)
 
 
 
 
300
  delete_btn = gr.Button(gettext("Delete"), scale=0)
301
  selected_chars_rows.append((name, emotion, delete_btn, row))
302
 
303
  with gr.Row():
304
  with gr.Column():
305
- text = gr.Textbox(label=gettext("Text to synthesize"), value="", lines=10, max_lines=10)
306
- inference_button = gr.Button(gettext("🎉 Synthesize Voice 🎉"), variant="primary", size='lg')
 
 
 
 
 
 
 
307
  with gr.Column():
308
- prompt_audio = gr.Audio(label=gettext("Reference audio for synthesis"), interactive=False, type="numpy")
309
- output = gr.Audio(label=gettext("Output audio"), interactive=False, type="numpy")
310
- cost_time = gr.Textbox(label=gettext("Synthesis time"), interactive=False, show_label=False, max_lines=1)
 
 
 
 
 
 
 
 
 
 
 
311
  try:
312
  inference_button.click(
313
  fn=generate,
@@ -319,74 +511,119 @@ with gr.Blocks(title="Online Free TTS", theme=gr.themes.Soft(), head=head) as de
319
  except Exception as e:
320
  pass
321
 
322
- # -------------- 绑定事件 --------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- lang.change(
325
- fn=update_all_characters,
326
- inputs=[lang, all_characters_state],
327
- outputs=[all_characters_state, characters, gallery]
328
- )
 
 
 
329
 
330
- add_voice_button.click(
331
- fn=add_new_voice,
332
- inputs=[current_character, selected_characters, kind, lang, all_characters_state],
333
- outputs=[current_character, character_name, info_type, selected_characters,
334
- characters, gallery, selected_chars_container, all_characters_state]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  ).then(
336
  fn=update_selected_chars_display,
337
  inputs=[selected_characters],
338
- outputs=[item for row in selected_chars_rows for item in row]
339
  )
340
 
 
 
 
 
 
341
 
342
- gallery.select(
343
- fn=on_select,
344
- inputs=[characters, selected_characters, all_characters_state],
345
- outputs=[character_name, info_type, current_character, selected_characters]
346
- ).then(
347
- fn=update_prompt_audio,
348
- inputs=[current_character],
349
- outputs=[prompt_audio]
350
- )
351
-
352
- info_type.change(
353
- fn=update_character_info,
354
- inputs=[character_name, info_type, current_character, all_characters_state],
355
- outputs=[current_character, all_characters_state]
356
- ).then(
357
- fn=update_prompt_audio,
358
- inputs=[current_character],
359
- outputs=[prompt_audio]
360
- )
361
 
362
- for i, (_, _, delete_btn, _) in enumerate(selected_chars_rows):
363
- delete_btn.click(
364
- fn=remove_character,
365
- inputs=[gr.Number(value=i, visible=False), selected_characters],
366
- outputs=[selected_characters, selected_chars_container]
367
- ).then(
368
- fn=update_selected_chars_display,
369
- inputs=[selected_characters],
370
- outputs=[item for row in selected_chars_rows for item in row]
371
- )
372
-
373
- kind.change(
374
- fn=update_gallery,
375
- inputs=[kind, query, all_characters_state],
376
- outputs=[characters, gallery, all_characters_state]
377
- )
378
-
379
- query.change(
380
- fn=update_gallery,
381
- inputs=[kind, query, all_characters_state],
382
- outputs=[characters, gallery, all_characters_state]
383
- )
384
- gr.Markdown(gettext(terms))
385
 
386
 
387
- if __name__ == '__main__':
388
  demo.queue(default_concurrency_limit=None).launch(
389
- # server_name="0.0.0.0",
390
- # server_port=80,
391
  show_api=False
392
  )
 
1
  import time
2
  import os
3
  import logging
 
4
 
5
  import gradio as gr
6
+ import numpy as np
7
  import pandas as pd
8
  from pypinyin import lazy_pinyin
9
+ from i18n import gettext, Translate
10
 
11
+ from api import generate_api, get_audio, generate_voice, load_characters_csv
12
  from utils import get_length
13
 
14
  # 翻译文件位置
15
+ trans_file = os.path.join(os.path.dirname(__file__), "i18n", "translations.json")
16
 
17
  # 关闭aiohttp的DEBUG日志
18
+ logging.getLogger("aiohttp").setLevel(logging.WARNING)
19
+ # logging.getLogger("gradio").setLevel(logging.WARNING)
20
 
21
  # 带有时间的log
22
+ logging.basicConfig(
23
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
24
+ )
25
 
26
 
27
+ header = """header"""
28
 
29
  terms = "terms"
30
 
 
 
 
31
 
32
+ def update_all_characters(lang, category):
33
+ new_characters, category = load_characters_csv(lang)
34
+ initial_characters = get_characters(kind=category[0], all_characters=new_characters)
35
+ return (
36
+ new_characters,
37
+ initial_characters,
38
+ gr.Gallery(
39
+ value=[[char["头像"], char["名称"]] for char in initial_characters],
40
+ show_label=False,
41
+ elem_id="character_gallery",
42
+ columns=[11],
43
+ object_fit="contain",
44
+ height="auto",
45
+ interactive=False,
46
+ allow_preview=False,
47
+ selected_index=None,
48
+ ),
49
+ category,
50
+ gr.update(choices=category, value=category[0])
51
+ )
52
+
53
 
54
+ def get_characters(
55
+ query=None, page=1, per_page=400, kind="原神", lang="zh", all_characters=None
56
+ ):
57
  # 使用传入的 all_characters 参数
58
  filtered_characters = all_characters[all_characters["类别"] == kind]
59
+
60
  if query:
61
  # 使用拼音和汉字进行搜索
62
  filtered_characters = filtered_characters[
63
+ filtered_characters["名称"].str.contains(query, case=False)
64
  ]
65
+ if filtered_characters.empty and lang == "zh":
66
  filtered_characters = all_characters[all_characters["类别"] == kind]
67
  filtered_characters = filtered_characters[
68
+ filtered_characters["名称"]
69
+ .apply(lambda x: "".join(lazy_pinyin(x)))
70
+ .str.contains(query, case=False)
71
  ]
72
+
73
  # 按名称分组,并选择每组的第一个记录
74
+ unique_characters = (
75
+ filtered_characters.groupby("名称").first().reset_index().sort_values(by="id")
76
+ )
77
+
78
+ # 处理头像数据
79
+ import pickle
80
 
81
+ def process_avatar(avatar):
82
+ if not isinstance(avatar, str):
83
+ try:
84
+ return pickle.loads(bytes(avatar))
85
+ except:
86
+ return avatar
87
+ return avatar
88
+
89
+ unique_characters['头像'] = unique_characters['头像'].apply(process_avatar)
90
+
91
  # 应用分页
92
  start_index = (page - 1) * per_page
93
  end_index = start_index + per_page
94
+
95
+ return unique_characters.iloc[start_index:end_index].to_dict("records")
96
+
97
+
98
+ async def generate(selected_character=None, selected_characters=[], text="", lang="zh"):
99
  # print("-------",selected_character)
100
  # print("-------",selected_characters)
101
  if selected_character:
 
112
  elif lang == "ko":
113
  raise gr.Error("먼저 캐릭터를 선택하세요")
114
  voice_ids = [char.get("voice_id") for char in characters if char.get("voice_id")]
115
+
116
  if not voice_ids:
117
  raise gr.Error("所选角色没有关联的 voice_id")
118
+
119
  start_time = time.time()
120
  # 假设我们只使用第一个选择的角色的名称
121
  if voice_ids == "1":
 
127
  raise gr.Error("そのキャラクターの音声はまだ作成されていません")
128
  elif lang == "ko":
129
  raise gr.Error("해당 캐릭터의 음성이 아직 생성되지 않았습니다")
130
+
131
  if text == "":
132
  if lang == "zh":
133
  raise gr.Error("请输入需要合成的文本")
 
137
  raise gr.Error("合成するテキストを入力してください")
138
  elif lang == "ko":
139
  raise gr.Error("합성할 텍스트를 입력하세요")
140
+
141
  if get_length(text) > 1024:
142
  if lang == "zh":
143
  raise gr.Error("长度请控制在1024个字符以内")
 
147
  raise gr.Error("テキストの長さが1024文字を超えています")
148
  elif lang == "ko":
149
  raise gr.Error("텍스트 길이가 1024자를 초과합니다")
150
+
151
+ logging.info(
152
+ f"选择角色: {characters[0].get('名称')}, 文本: {text}, voice_id: {voice_ids}"
153
+ )
154
  audio = await generate_api(voice_ids, text)
155
  end_time = time.time()
156
  if lang == "zh":
157
  cost_time = f"合成共花费{end_time - start_time:.2f}秒"
158
  elif lang == "en":
159
+ cost_time = (
160
+ f"Total time spent synthesizing: {end_time - start_time:.2f} seconds"
161
+ )
162
  elif lang == "ja":
163
  cost_time = f"合成にかかった時間: {end_time - start_time:.2f}秒"
164
  elif lang == "ko":
 
169
  else:
170
  return audio, cost_time
171
 
172
+
173
  def get_character_emotions(character, all_characters):
174
  # 从all_characters中筛选出与当前角色名称相同的所有记录
175
+ character_records = all_characters[all_characters["名称"] == character["名称"]]
176
+
177
  # 按情绪去重并获取完整的角色信息
178
+ character_infos = character_records.drop_duplicates(subset=["情绪"]).to_dict(
179
+ "records"
180
+ )
181
+
182
  # 如果没有找到角色信息,返回一个包含默认值的字典
183
+ return (
184
+ character_infos
185
+ if character_infos
186
+ else [{"名称": character["名称"], "情绪": "默认情绪"}]
187
+ )
188
+
189
 
190
  def update_character_info(character_name, emotion, current_character, all_characters):
191
  character_info = None
192
  if character_name and emotion:
193
+ character_info = all_characters[
194
+ (all_characters["名称"] == character_name)
195
+ & (all_characters["情绪"] == emotion)
196
+ ]
197
  if character_name == "":
198
  return None
199
  character_info = character_info.iloc[0].to_dict()
200
  return character_info, all_characters
201
 
202
+
203
  def add_new_voice(current_character, selected_characters, kind, lang, all_characters):
204
  if not current_character:
205
  if lang == "zh":
 
210
  raise gr.Error("まず、キャラクターを選択してください")
211
  elif lang == "ko":
212
  raise gr.Error("먼저 캐릭터를 선택하세요")
213
+
214
  if len(selected_characters) >= 5:
215
  raise gr.Error("已达到最大选择数(5个)")
216
+
217
  # 检查是否已存在相同角色
218
+ existing_char = next(
219
+ (
220
+ char
221
+ for char in selected_characters
222
+ if char["名称"] == current_character["名称"]
223
+ ),
224
+ None,
225
+ )
226
  if existing_char:
227
  # 如果情绪不同,更新情绪
228
+ if existing_char["情绪"] != current_character["情绪"]:
229
+ existing_char["情绪"] = current_character["情绪"]
230
  else:
231
  selected_characters.insert(0, current_character)
232
+
233
+ updated_characters = get_characters(
234
+ kind=kind, lang=lang, all_characters=all_characters
235
+ )
236
  # ! 取消gallery选中状态,返回个新的gallery是必要的,否则会保留上一次的选中状态。这里sonnet很喜欢改成返回一个数组,但这不能清空gallery的选中状态
237
+ updated_gallery = gr.Gallery(
238
+ value=[[char["头像"], char["名称"]] for char in updated_characters],
239
+ show_label=False,
240
+ elem_id="character_gallery",
241
+ columns=[11],
242
+ object_fit="contain",
243
+ height="auto",
244
+ interactive=False,
245
+ allow_preview=False,
246
+ selected_index=None,
247
+ )
248
+
249
+ return (
250
+ None,
251
+ gr.update(value=""),
252
+ gr.update(choices=[]),
253
+ selected_characters,
254
+ updated_characters,
255
+ updated_gallery,
256
+ gr.update(visible=True),
257
+ all_characters,
258
+ )
259
+
260
 
261
  def update_selected_chars_display(selected_characters):
262
  updates = []
263
  for i, (name, emotion, _, row) in enumerate(selected_chars_rows):
264
  if i < len(selected_characters):
265
  char = selected_characters[i]
266
+ updates.extend(
267
+ [
268
+ gr.update(value=char["名称"], visible=True),
269
+ gr.update(value=char["情绪"], visible=True),
270
+ gr.update(visible=True),
271
+ gr.update(visible=True),
272
+ ]
273
+ )
274
  else:
275
+ updates.extend(
276
+ [
277
+ gr.update(value="", visible=False),
278
+ gr.update(value="", visible=False),
279
+ gr.update(visible=False),
280
+ gr.update(visible=False),
281
+ ]
282
+ )
283
  return updates
284
 
285
+
286
  def remove_character(index, selected_characters):
287
  if 0 <= index < len(selected_characters):
288
+ del selected_characters[index]
289
  return selected_characters, gr.update(visible=True)
290
 
291
+
292
  def update_gallery(kind, query, all_characters):
293
+ updated_characters = get_characters(
294
+ kind=kind, query=query, lang=lang, all_characters=all_characters
295
+ )
296
+ return (
297
+ updated_characters,
298
+ [[char["头像"], char["名称"]] for char in updated_characters],
299
+ all_characters,
300
+ )
301
+
302
 
303
  def on_select(evt: gr.SelectData, characters, selected_characters, all_characters):
304
  # 如果没有选择角色,换人的时候清空
305
  if len(selected_characters) == 0:
306
  selected_characters = []
307
+
308
  selected = characters[evt.index]
309
  emotions = get_character_emotions(selected, all_characters)
310
  normal_index = 0
311
  for index, emotion in enumerate(emotions):
312
+ if (
313
+ emotion["情绪"] == "正常"
314
+ or emotion["情绪"] == "보통"
315
+ or emotion["情绪"] == "normal"
316
+ ):
317
  normal_index = index
318
  break
319
+
320
  default_emotion = emotions[normal_index]["情绪"] if emotions else ""
321
  default_voice_id = emotions[normal_index]["voice_id"] if emotions else ""
322
+
323
  character_dict = selected.copy()
324
+ character_dict["情绪"] = default_emotion
325
+ character_dict["voice_id"] = default_voice_id
326
+ return (
327
+ selected["名称"],
328
+ gr.Dropdown(
329
+ choices=[emotion["情绪"] for emotion in emotions], value=default_emotion
330
+ ),
331
+ character_dict,
332
+ selected_characters,
333
+ )
334
+
335
 
336
  async def update_prompt_audio(current_character):
337
  if current_character:
 
339
  else:
340
  return None
341
 
342
+ async def create_voice(avatar, name, emotion, tags, gender, audio_data, lang):
343
+ updates = {}
344
+ for field, value in [("avatar", avatar), ("name", name), ("emotion", emotion), ("tags", tags), ("gender", gender), ("audio_data", audio_data)]:
345
+ if field in ["avatar", "audio_data"]:
346
+ if value is None or (isinstance(value, np.ndarray) and value.size == 0):
347
+ updates[field] = gr.update(value=None)
348
+ elif value == "":
349
+ updates[field] = gr.update(value="")
350
+
351
+ if updates:
352
+ if lang == "zh":
353
+ gr.Warning("请填写完整信息")
354
+ elif lang == "en":
355
+ gr.Warning("Please fill in all the information")
356
+ elif lang == "ja":
357
+ gr.Warning("すべての情報を入力してください")
358
+ elif lang == "ko":
359
+ gr.Warning("모든 정보를 입력하세요")
360
+ return tuple(updates.get(field, gr.update()) for field in ["avatar", "name", "emotion", "tags", "gender", "audio_data"])
361
+ duration = len(audio_data[1]) / audio_data[0]
362
+ if duration < 3.2 or duration > 8:
363
+ if lang == "zh":
364
+ gr.Warning("音频时长请控制在3.2-8秒之间")
365
+ elif lang == "en":
366
+ gr.Warning("The audio duration should be between 3.2 and 8 seconds")
367
+ elif lang == "ja":
368
+ gr.Warning("音声の長さは3.2秒から8秒の間にしてください")
369
+ elif lang == "ko":
370
+ gr.Warning("음성 길이는 3.2초에서 8초 사이로 설정해야 합니다")
371
+ return avatar, name, emotion, tags, gender, audio_data
372
+ await generate_voice(avatar, name, emotion, tags, gender, audio_data, lang)
373
+ if lang == "zh":
374
+ gr.Info("创建成功")
375
+ elif lang == "en":
376
+ gr.Info("Create successfully")
377
+ elif lang == "ja":
378
+ gr.Info("作成に成功しました")
379
+ elif lang == "ko":
380
+ gr.Info("생성 성공")
381
+ return avatar, name, emotion, tags, gender, audio_data
382
+
383
  head = """
384
  <title>Free Online Text to Speech (TTS) | Convert Text to Audio</title>
385
  <meta name="description" content="Text to Speech(TTS) for free! 5-second voice cloning, no sign-up required.">
386
  <meta name="keywords" content="text to speech, TTS, free TTS, online TTS, speech synthesis, voice generator">
387
  """
 
388
  with gr.Blocks(title="Online Free TTS", theme=gr.themes.Soft(), head=head) as demo:
389
  gr.Markdown(
390
  "Online Free TTS(Text-to-Speech). Ultra-low latency, 5-second voice cloning."
391
  )
392
+ lang = gr.Radio(
393
+ choices=[("中文", "zh"), ("English", "en"), ("日本語", "ja"), ("한국인", "ko")],
394
+ label=gettext("Language"),
395
+ value="en",
396
+ scale=1,
397
+ )
398
+ all_characters_state = gr.State(load_characters_csv("en")[0])
399
+ category = gr.State(load_characters_csv("en")[1])
400
 
401
  with Translate(trans_file, lang, placeholder_langs=["en", "zh", "ja", "ko"]):
402
+ gr.Markdown(value=gettext(header))
 
403
  with gr.Group():
404
+ initial_characters = get_characters(
405
+ kind="原神", lang="zh", all_characters=all_characters_state.value
406
+ )
407
  characters = gr.State(initial_characters)
408
  selected_characters = gr.State([])
409
  current_character = gr.State(None)
410
+
411
+ with gr.Tab(gettext("Synthesis Voice")):
412
  with gr.Blocks():
413
  with gr.Row():
414
  # kind = gr.Dropdown(choices=["原神", "崩坏星穹铁道","鸣潮","明日方舟","其他"], value="原神", label="请选择角色类别")
415
+ # choices = [
416
+ # (gettext("Genshin Impact"), "原神"),
417
+ # (gettext("Honkai: Star Rail"), "崩坏星穹铁道"),
418
+ # (gettext("ZenZenless Zone Zero"), "绝区零"),
419
+ # (gettext("Wuthering Waves"), "鸣潮"),
420
+ # ]
421
+ kind = gr.Dropdown(
422
+ choices=category.value,
423
+ value="原神",
424
+ label=gettext("Select character category"),
425
+ )
426
+ query = gr.Textbox(
427
+ label=gettext("Search character"),
428
+ value="",
429
+ lines=1,
430
+ max_lines=1,
431
+ interactive=True,
432
+ )
433
  with gr.Blocks():
434
  gallery = gr.Gallery(
435
+ value=[
436
+ [char["头像"], char["名称"]] for char in characters.value
437
+ ],
438
  show_label=False,
439
  elem_id="character_gallery",
440
  columns=[11],
 
442
  height="auto",
443
  interactive=False,
444
  allow_preview=False,
445
+ selected_index=None,
446
  )
447
  with gr.Row():
448
+ character_name = gr.Textbox(
449
+ label=gettext("Currently selected character"),
450
+ interactive=False,
451
+ max_lines=1,
452
+ )
453
  info_type = gr.Dropdown(choices=[], label=gettext("Select emotion"))
454
  with gr.Row():
455
+ add_voice_button = gr.Button(
456
+ gettext("Add new voice"), variant="primary"
457
+ )
458
+
459
+ selected_chars_container = gr.Column(
460
+ elem_id="selected_chars_container", visible=False
461
+ )
462
+
463
  with selected_chars_container:
464
  gr.Markdown(gettext("### Selected characters"))
465
  selected_chars_rows = []
466
  for i in range(5): # 假设最多选择5个角色
467
  with gr.Row() as row:
468
+ name = gr.Textbox(
469
+ label=gettext("Name"), interactive=False, max_lines=1
470
+ )
471
+ emotion = gr.Textbox(
472
+ label=gettext("Emotion"), interactive=False, max_lines=1
473
+ )
474
  delete_btn = gr.Button(gettext("Delete"), scale=0)
475
  selected_chars_rows.append((name, emotion, delete_btn, row))
476
 
477
  with gr.Row():
478
  with gr.Column():
479
+ text = gr.Textbox(
480
+ label=gettext("Text to synthesize"),
481
+ value="",
482
+ lines=10,
483
+ max_lines=10,
484
+ )
485
+ inference_button = gr.Button(
486
+ gettext("🎉 Synthesize Voice 🎉"), variant="primary", size="lg"
487
+ )
488
  with gr.Column():
489
+ prompt_audio = gr.Audio(
490
+ label=gettext("Reference audio for synthesis"),
491
+ interactive=False,
492
+ type="numpy",
493
+ )
494
+ output = gr.Audio(
495
+ label=gettext("Output audio"), interactive=False, type="numpy"
496
+ )
497
+ cost_time = gr.Textbox(
498
+ label=gettext("Synthesis time"),
499
+ interactive=False,
500
+ show_label=False,
501
+ max_lines=1,
502
+ )
503
  try:
504
  inference_button.click(
505
  fn=generate,
 
511
  except Exception as e:
512
  pass
513
 
514
+ with gr.Tab(gettext("Create Voice")):
515
+ with gr.Row():
516
+ avatar = gr.Image(label=gettext("Avatar"), interactive=True, type="pil", image_mode="RGBA")
517
+ with gr.Column():
518
+ with gr.Row():
519
+ name = gr.Textbox(
520
+ label=gettext("Name"), interactive=True, max_lines=1
521
+ )
522
+ emotion = gr.Textbox(
523
+ label=gettext("Emotion\n(Happy, Sad, Angry)"), interactive=True, max_lines=1
524
+ )
525
+ tags = gr.Textbox(
526
+ label=gettext("Tags\n(Genshin, Cute, Girl, Boy, etc.)"), interactive=True, max_lines=1
527
+ )
528
+ gender = gr.Dropdown(
529
+ label=gettext("Gender"),
530
+ choices=[
531
+ (gettext("Male"), "male"),
532
+ (gettext("Female"), "female"),
533
+ (gettext("Non-Binary"), "non-binary"),
534
+ ],
535
+ interactive=True,
536
+ )
537
+ audio_data = gr.Audio(label=gettext("Prompt Audio(min 3.2s, max 8s)"), interactive=True)
538
+ create_button = gr.Button(gettext("Create Voice"), variant="primary")
539
+ # gr.Examples(
540
+ # examples=[
541
+ # ["https://ttscdn.rubii.ai/public/tts_avatars/原神/0491eeea-f82d-42dc-a6bf-b57e38e9c148.png", "Rubii", "开心", "原神", "female", (32000, np.zeros(10000)), lang],
542
+ # ],
543
+ # inputs=[avatar, name, emotion, tags, gender, audio_data],
544
+ # )
545
 
546
+ gr.Markdown(gettext(terms))
547
+ # -------------- 绑定事件 --------------
548
+
549
+ lang.change(
550
+ fn=update_all_characters,
551
+ inputs=[lang, category],
552
+ outputs=[all_characters_state, characters, gallery, category, kind],
553
+ )
554
 
555
+ add_voice_button.click(
556
+ fn=add_new_voice,
557
+ inputs=[
558
+ current_character,
559
+ selected_characters,
560
+ kind,
561
+ lang,
562
+ all_characters_state,
563
+ ],
564
+ outputs=[
565
+ current_character,
566
+ character_name,
567
+ info_type,
568
+ selected_characters,
569
+ characters,
570
+ gallery,
571
+ selected_chars_container,
572
+ all_characters_state,
573
+ ],
574
+ ).then(
575
+ fn=update_selected_chars_display,
576
+ inputs=[selected_characters],
577
+ outputs=[item for row in selected_chars_rows for item in row],
578
+ )
579
+
580
+ gallery.select(
581
+ fn=on_select,
582
+ inputs=[characters, selected_characters, all_characters_state],
583
+ outputs=[character_name, info_type, current_character, selected_characters],
584
+ ).then(
585
+ fn=update_prompt_audio, inputs=[current_character], outputs=[prompt_audio]
586
+ )
587
+
588
+ info_type.change(
589
+ fn=update_character_info,
590
+ inputs=[character_name, info_type, current_character, all_characters_state],
591
+ outputs=[current_character, all_characters_state],
592
+ ).then(
593
+ fn=update_prompt_audio, inputs=[current_character], outputs=[prompt_audio]
594
+ )
595
+
596
+ for i, (_, _, delete_btn, _) in enumerate(selected_chars_rows):
597
+ delete_btn.click(
598
+ fn=remove_character,
599
+ inputs=[gr.Number(value=i, visible=False), selected_characters],
600
+ outputs=[selected_characters, selected_chars_container],
601
  ).then(
602
  fn=update_selected_chars_display,
603
  inputs=[selected_characters],
604
+ outputs=[item for row in selected_chars_rows for item in row],
605
  )
606
 
607
+ kind.change(
608
+ fn=update_gallery,
609
+ inputs=[kind, query, all_characters_state],
610
+ outputs=[characters, gallery, all_characters_state],
611
+ )
612
 
613
+ query.change(
614
+ fn=update_gallery,
615
+ inputs=[kind, query, all_characters_state],
616
+ outputs=[characters, gallery, all_characters_state],
617
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
+ create_button.click(
620
+ fn=create_voice,
621
+ inputs=[avatar, name, emotion, tags, gender, audio_data, lang],
622
+ outputs=[avatar, name, emotion, tags, gender, audio_data],
623
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
 
626
+ if __name__ == "__main__":
627
  demo.queue(default_concurrency_limit=None).launch(
 
 
628
  show_api=False
629
  )
i18n/characters_en.csv DELETED
The diff for this file is too large to render. See raw diff
 
i18n/characters_ja.csv DELETED
The diff for this file is too large to render. See raw diff
 
i18n/characters_ko.csv DELETED
The diff for this file is too large to render. See raw diff
 
i18n/characters_zh.csv DELETED
The diff for this file is too large to render. See raw diff
 
i18n/translations.json CHANGED
@@ -20,6 +20,16 @@
20
  "Reference audio for synthesis": "Reference audio for synthesis",
21
  "Output audio": "Output audio",
22
  "Synthesis time": "Synthesis time",
 
 
 
 
 
 
 
 
 
 
23
  "terms": "### **[Technical discussions, problem-solving, casual chats - welcome to our Discord](https://discord.gg/DyECTyqhkC)**\n\n ## Disclaimer\n\nThe voice synthesis service provided by this website (hereinafter referred to as the \"Service\") is intended for personal use and entertainment purposes. Before using this Service, please carefully read and fully understand the following terms:\n\n1. **Character Copyright**: The character images used on this website may involve third-party intellectual property rights. This website does not own the copyrights to these characters. Users should respect the intellectual property rights of the relevant characters when using the Service and ensure that their actions do not infringe upon any third-party intellectual property rights.\n\n2. **User-Generated Content (UGC)**: The voice content generated by users through this platform (hereinafter referred to as \"UGC\") is the sole responsibility of the users and is not related to this platform. This platform cannot control or review the specific content generated by users and does not assume any responsibility for the accuracy, completeness, or legality of UGC.\n\n3. **Usage Restrictions**: The voices and their UGC generated by this Service are limited to personal use only and may not be used for any commercial purposes. It is prohibited to use the generated content for any commercial activities without prior written consent from this platform.\n\n4. **Legal Responsibility**: Any legal responsibility arising from the use of this Service by users shall be borne by the users themselves and is not related to this platform. This platform does not assume any responsibility for any disputes or losses caused by users' use of the Service or their UGC.\n\n5. **Copyright Statement**: Users should respect original creations and must not use this Service to generate content that infringes upon others' copyrights. If user-generated content is found to infringe upon others' copyrights, this platform reserves the right to immediately cease providing services to them and reserves the right to pursue legal action.\n\n6. **Content Moderation**: Although this platform cannot control UGC, once content that violates this disclaimer or laws and regulations is discovered, this platform will take necessary measures, including but not limited to deleting the violating content and cooperating with relevant authorities in investigations.\n\n7. **Attribution Requirement**: Users should, where possible, prominently indicate \"This content was generated by AI and this website \" or a similar statement in the generated content. Users should ensure that the attribution complies with the requirements of these terms.\n\n By using this website, users agree to the above disclaimer. If you have any questions, please contact us at contact@yomio.ai.\n\n**The final interpretation right belongs to this website.**"
24
  },
25
  "zh": {
@@ -43,6 +53,16 @@
43
  "Reference audio for synthesis": "当前使用的参考音频",
44
  "Output audio": "输出的语音",
45
  "Synthesis time": "合成时间",
 
 
 
 
 
 
 
 
 
 
46
  "terms": "### **[技术讨论,有任何问题,或闲聊交友?欢迎加入我们的Discord](https://discord.gg/DyECTyqhkC)**\n\n ## 免责声明\n\n本网站提供的语音合成服务(以下简称\"服务\")仅供个人使用和娱乐目的。在使用本服务之前,请仔细阅读并充分理解以下条款:\n\n1. **角色版权**:本网站使用的角色图像可能涉及第三方知识产权。本网站不拥有这些角色的版权。用户在使用服务时应尊重相关角色的知识产权,并确保其行为不侵犯任何第三方知识产权。\n\n2. **用户生成内容(UGC)**:用户通过本平台生成的语音内容(以下简称\"UGC\")由用户自行负责,与本平台无关。本平台无法控制或审核用户生成的具体内容,不对UGC的准确性、完整性或合法性承担任何责任。\n\n3. **使用限制**:本服务生成的语音及其UGC仅��个人使用,不得用于任何商业目的。未经本平台事先书面同意,禁止将生成的内容用于任何商业活动。\n\n4. **法律责任**:用户使用本服务所产生的任何法律责任由用户自行承担,与本平台无关。本平台不对用户使用服务或其UGC造成的任何纠纷或损失承担任何责任。\n\n5. **版权声明**:用户应尊重原创作品,不得使用本服务生成侵犯他人版权的内容。如发现用户生成的内容侵犯他人版权,本平台保留立即停止向其提供服务的权利,并保留追究法律责任的权利。\n\n6. **内容审核**:尽管本平台无法控制UGC,但一旦发现违反本免责声明或法律法规的内容,本平台将采取必要措施,包括但不限于删除违规内容,并配合相关部门调查。\n\n7. **归属要求**:用户应在可能的情况下,在生成的内容中标明\"本内容由AI和本网站生成\"或类似声明。用户应确保归属符合这些条款的要求。\n\n 使用本网站即表示用户同意上述免责声明。如有任何疑问,请联系我们:contact@yomio.ai。\n\n**本网站拥有最终解释权。**"
47
  },
48
  "ja": {
@@ -66,6 +86,16 @@
66
  "Reference audio for synthesis": "合成に使用する参考音声",
67
  "Output audio": "出力された音声",
68
  "Synthesis time": "合成時間",
 
 
 
 
 
 
 
 
 
 
69
  "terms": "### **[技術的な議論、問題解決、カジュアルなチャットはDiscordへようこそ](https://discord.gg/DyECTyqhkC)**\n\n ## 免責事項\n\nこのウェブサイトが提供する音声合成サービス(以下「本サービス」)は、個人使用および娯楽目的のためのものです。本サービスを使用する前に、以下の条項を注意深く読み、十分に理解してください:\n\n1. **キャラクターの著作権**:このウェブサイトで使用されているキャラクター画像は、第三者の知的財産権を含む場合があります。このウェブサイトはこれらのキャラクターの著作権を所有していません。ユーザーは本サービスを使用する際、関連するキャラクターの知的財産権を尊重し、第三者の知的財産権を侵害しないよう行動する必要があります。\n\n2. **ユーザー生成コンテンツ(UGC)**:このプラットフォームを通じてユーザーが生成した音声コンテンツ(以下「UGC」)は、ユーザー自身の責任であり、このプラットフォームとは関係ありません。このプラットフォームはユーザーが生成した具体的なコンテンツを制御または審査することはできず、UGCの正確性、完全性、または合法性について一切の責任を負いません。\n\n3. **使用制限**:本サービスで生成された音声およびそのUGCは個人使用に限定され、商業目的で使用することはできません。このプラットフォームの事前の書面による同意なしに、生成されたコンテンツを商業活動に使用することは禁止されています。\n\n4. **法的責任**:ユーザーが本サービスを使用することによって生じるいかなる法的責任も、ユーザー自身が負うものとし、このプラットフォームとは関係ありません。このプラットフォームは、ユーザーのサービス使用またはUGCによって引き起こされた紛争や損失について一切の責任を負いません。\n\n5. **著作権声明**:ユーザーは原作を尊重し、他者の著作権を侵害するコンテンツを生成するために本サービスを使用してはいけません。ユーザーが生成したコンテンツが他者の著作権を侵害していることが判明した場合、このプラットフォームは直ちにサービス提供を停止する権利を留保し、法的措置を講じる権利を留保します。\n\n6. **コンテンツモデレーション**:このプラットフォームはUGCを制御できませんが、この免責事項や法律・規制に違反するコンテンツが発見された場合、このプラットフォームは必要な措置を講じます。これには違反コンテンツの削除や、関連当局の調査への協力が含まれますが、これらに限定されません。\n\n7. **帰属要件**:ユーザーは可能な限り、生成されたコンテンツに「このコンテンツはAIとこのウェブサイトによって生成されました」または類似の声明を目立つように表示する必要があります。ユーザーは帰属表示がこれらの条項の要件に準拠していることを確認する必要があります。\n\n このウェブサイトを使用することにより、ユーザーは上記の免責事項に同意したものとみなされます。ご質問がある場合は、contact@yomio.aiまでお問い合わせください。\n\n**最終的な解釈権はこのウェブサイトに帰属します。**"
70
  },
71
  "ko": {
@@ -89,6 +119,16 @@
89
  "Reference audio for synthesis": "합성에 사용되는 참조 오디오",
90
  "Output audio": "출력된 음성",
91
  "Synthesis time": "합성 시간",
 
 
 
 
 
 
 
 
 
 
92
  "terms": "### **[기술 토론, 문제 해결, 일상 대화 - 우리의 Discord에 오신 것을 환영합니다](https://discord.gg/DyECTyqhkC)**\n\n ## 면책 조항\n\n이 웹사이트가 제공하는 음성 합성 서비스(이하 \"서비스\")는 개인 사용 및 엔터테인먼트 목적으로 제공됩니다. 이 서비스를 사용하기 전에 다음 조항을 주의 깊게 읽고 완전히 이해해 주시기 바랍니다:\n\n1. **캐릭터 저작권**: 이 웹사이트에서 사용되는 캐릭터 이미지는 제3자의 지적 재산권과 관련될 수 있습니다. 이 웹사이트는 이러한 캐릭터들의 저작권을 소유하고 있지 않습니다. 사용자는 서비스를 사용할 때 관련 캐릭터의 지적 재산권을 존중하고 제3자의 지적 재산권을 침해하지 않도록 해야 합니다.\n\n2. **사용자 생성 콘텐츠(UGC)**: 이 플랫폼을 통해 사용자가 생성한 음성 콘텐츠(이하 \"UGC\")는 사용자의 단독 책임이며 이 플랫폼과는 관련이 없습니다. 이 플랫폼은 사용자가 생성한 특정 콘텐츠를 통제하거나 검토할 수 없으며 UGC의 정확성, 완전성 또는 합법성에 대해 어떠한 책임도 지지 않습니다.\n\n3. **사용 제한**: 이 서비스로 생성된 음성과 그 UGC는 개인 사용으로만 제한되며 상업적 목적으로 사용될 수 없습니다. 이 플랫폼의 사전 서면 동의 없이 생성된 콘텐츠를 상업적 활동에 사용하는 것은 금지됩니다.\n\n4. **법적 책임**: 사용자가 이 서비스를 사용함으로써 발생하는 모든 법적 책임은 사용자 자신이 부담하며 이 플랫폼과는 관련이 없습니다. 이 플랫폼은 사용자의 서비스 사용 또는 UGC로 인해 발생하는 어떠한 분쟁이나 손실에 대해서도 책임을 지지 않습니다.\n\n5. **저작권 성명**: 사용자는 원작을 존중해야 하며 이 서비스를 사용하여 타인의 저작권을 침해하는 콘텐츠를 생성해서는 안 됩니다. 사용자가 생성한 콘텐츠가 타인의 저작권을 침해한 것으로 밝혀진 경우, 이 플랫폼은 즉시 서비스 제공을 중단할 권리를 보유하며 법적 조치를 취할 권리를 보유합니다.\n\n6. **콘텐츠 관리**: 이 플랫폼은 UGC를 통제할 수 없지만, 이 면책 조항이나 법률 및 규정을 위반하는 콘텐츠가 발견되면 이 플랫폼은 필요한 조치를 취할 것입니다. 여기에는 위반 콘텐츠 삭제 및 관련 당국의 조사에 협조하는 것이 포함되지만 이에 국한되지 않습니다.\n\n7. **귀속 요구 사항**: 사용자는 가능한 한 생성된 콘텐츠에 \"이 콘텐츠는 AI와 이 웹사이트에 의해 생성되었습니다\" 또는 유사한 문구를 눈에 띄게 표시해야 합니다. 사용자는 귀속 표시가 이 약관의 요구 사항을 준수하는지 확인해야 합니다.\n\n 이 웹사이트를 사용함으로써 사용자는 위의 면책 조항에 동의하는 것으로 간주됩니다. 질문이 있으시면 contact@yomio.ai로 문의해 주시기 바랍니다.\n\n**최종 해석권은 이 웹사이트에 있습니다.**"
93
  }
94
  }
 
20
  "Reference audio for synthesis": "Reference audio for synthesis",
21
  "Output audio": "Output audio",
22
  "Synthesis time": "Synthesis time",
23
+ "Synthesis Voice": "Synthesis Voice",
24
+ "Avatar": "Avatar",
25
+ "Emotion\n(Happy, Sad, Angry)": "Emotion\n(Happy, Sad, Angry)",
26
+ "Tags\n(Genshin, Cute, Girl, Boy, etc.)": "Tags\n(Genshin, Cute, Girl, Boy, etc.)",
27
+ "Male": "Male",
28
+ "Female": "Female",
29
+ "Non-Binary": "Non-Binary",
30
+ "Gender": "Gender",
31
+ "Prompt Audio(min 3.2s, max 8s)": "Prompt Audio(min 3.2s, max 8s)",
32
+ "Create Voice": "Create Voice",
33
  "terms": "### **[Technical discussions, problem-solving, casual chats - welcome to our Discord](https://discord.gg/DyECTyqhkC)**\n\n ## Disclaimer\n\nThe voice synthesis service provided by this website (hereinafter referred to as the \"Service\") is intended for personal use and entertainment purposes. Before using this Service, please carefully read and fully understand the following terms:\n\n1. **Character Copyright**: The character images used on this website may involve third-party intellectual property rights. This website does not own the copyrights to these characters. Users should respect the intellectual property rights of the relevant characters when using the Service and ensure that their actions do not infringe upon any third-party intellectual property rights.\n\n2. **User-Generated Content (UGC)**: The voice content generated by users through this platform (hereinafter referred to as \"UGC\") is the sole responsibility of the users and is not related to this platform. This platform cannot control or review the specific content generated by users and does not assume any responsibility for the accuracy, completeness, or legality of UGC.\n\n3. **Usage Restrictions**: The voices and their UGC generated by this Service are limited to personal use only and may not be used for any commercial purposes. It is prohibited to use the generated content for any commercial activities without prior written consent from this platform.\n\n4. **Legal Responsibility**: Any legal responsibility arising from the use of this Service by users shall be borne by the users themselves and is not related to this platform. This platform does not assume any responsibility for any disputes or losses caused by users' use of the Service or their UGC.\n\n5. **Copyright Statement**: Users should respect original creations and must not use this Service to generate content that infringes upon others' copyrights. If user-generated content is found to infringe upon others' copyrights, this platform reserves the right to immediately cease providing services to them and reserves the right to pursue legal action.\n\n6. **Content Moderation**: Although this platform cannot control UGC, once content that violates this disclaimer or laws and regulations is discovered, this platform will take necessary measures, including but not limited to deleting the violating content and cooperating with relevant authorities in investigations.\n\n7. **Attribution Requirement**: Users should, where possible, prominently indicate \"This content was generated by AI and this website \" or a similar statement in the generated content. Users should ensure that the attribution complies with the requirements of these terms.\n\n By using this website, users agree to the above disclaimer. If you have any questions, please contact us at contact@yomio.ai.\n\n**The final interpretation right belongs to this website.**"
34
  },
35
  "zh": {
 
53
  "Reference audio for synthesis": "当前使用的参考音频",
54
  "Output audio": "输出的语音",
55
  "Synthesis time": "合成时间",
56
+ "Synthesis Voice": "合成语音",
57
+ "Avatar": "头像",
58
+ "Emotion\n(Happy, Sad, Angry)": "情绪\n(快乐、悲伤、愤怒)",
59
+ "Tags\n(Genshin, Cute, Girl, Boy, etc.)": "标签\n(原神、可爱、少女、正太等)",
60
+ "Male": "男性",
61
+ "Female": "女性",
62
+ "Non-Binary": "非二元性别",
63
+ "Gender": "性别",
64
+ "Prompt Audio(min 3.2s, max 8s)": "提示音频(最短3.2秒,最长8秒)",
65
+ "Create Voice": "创建语音",
66
  "terms": "### **[技术讨论,有任何问题,或闲聊交友?欢迎加入我们的Discord](https://discord.gg/DyECTyqhkC)**\n\n ## 免责声明\n\n本网站提供的语音合成服务(以下简称\"服务\")仅供个人使用和娱乐目的。在使用本服务之前,请仔细阅读并充分理解以下条款:\n\n1. **角色版权**:本网站使用的角色图像可能涉及第三方知识产权。本网站不拥有这些角色的版权。用户在使用服务时应尊重相关角色的知识产权,并确保其行为不侵犯任何第三方知识产权。\n\n2. **用户生成内容(UGC)**:用户通过本平台生成的语音内容(以下简称\"UGC\")由用户自行负责,与本平台无关。本平台无法控制或审核用户生成的具体内容,不对UGC的准确性、完整性或合法性承担任何责任。\n\n3. **使用限制**:本服务生成的语音及其UGC仅��个人使用,不得用于任何商业目的。未经本平台事先书面同意,禁止将生成的内容用于任何商业活动。\n\n4. **法律责任**:用户使用本服务所产生的任何法律责任由用户自行承担,与本平台无关。本平台不对用户使用服务或其UGC造成的任何纠纷或损失承担任何责任。\n\n5. **版权声明**:用户应尊重原创作品,不得使用本服务生成侵犯他人版权的内容。如发现用户生成的内容侵犯他人版权,本平台保留立即停止向其提供服务的权利,并保留追究法律责任的权利。\n\n6. **内容审核**:尽管本平台无法控制UGC,但一旦发现违反本免责声明或法律法规的内容,本平台将采取必要措施,包括但不限于删除违规内容,并配合相关部门调查。\n\n7. **归属要求**:用户应在可能的情况下,在生成的内容中标明\"本内容由AI和本网站生成\"或类似声明。用户应确保归属符合这些条款的要求。\n\n 使用本网站即表示用户同意上述免责声明。如有任何疑问,请联系我们:contact@yomio.ai。\n\n**本网站拥有最终解释权。**"
67
  },
68
  "ja": {
 
86
  "Reference audio for synthesis": "合成に使用する参考音声",
87
  "Output audio": "出力された音声",
88
  "Synthesis time": "合成時間",
89
+ "Synthesis Voice": "音声合成",
90
+ "Avatar": "アバター",
91
+ "Emotion\n(Happy, Sad, Angry)": "感情\n(嬉しい、悲しい、怒り)",
92
+ "Tags\n(Genshin, Cute, Girl, Boy, etc.)": "タグ\n(原神、かわいい、女の子、男の子など)",
93
+ "Male": "男性",
94
+ "Female": "女性",
95
+ "Non-Binary": "ノンバイナリー",
96
+ "Gender": "性別",
97
+ "Prompt Audio(min 3.2s, max 8s)": "プロンプト音声(最小3.2秒、最大8秒)",
98
+ "Create Voice": "音声を作成",
99
  "terms": "### **[技術的な議論、問題解決、カジュアルなチャットはDiscordへようこそ](https://discord.gg/DyECTyqhkC)**\n\n ## 免責事項\n\nこのウェブサイトが提供する音声合成サービス(以下「本サービス」)は、個人使用および娯楽目的のためのものです。本サービスを使用する前に、以下の条項を注意深く読み、十分に理解してください:\n\n1. **キャラクターの著作権**:このウェブサイトで使用されているキャラクター画像は、第三者の知的財産権を含む場合があります。このウェブサイトはこれらのキャラクターの著作権を所有していません。ユーザーは本サービスを使用する際、関連するキャラクターの知的財産権を尊重し、第三者の知的財産権を侵害しないよう行動する必要があります。\n\n2. **ユーザー生成コンテンツ(UGC)**:このプラットフォームを通じてユーザーが生成した音声コンテンツ(以下「UGC」)は、ユーザー自身の責任であり、このプラットフォームとは関係ありません。このプラットフォームはユーザーが生成した具体的なコンテンツを制御または審査することはできず、UGCの正確性、完全性、または合法性について一切の責任を負いません。\n\n3. **使用制限**:本サービスで生成された音声およびそのUGCは個人使用に限定され、商業目的で使用することはできません。このプラットフォームの事前の書面による同意なしに、生成されたコンテンツを商業活動に使用することは禁止されています。\n\n4. **法的責任**:ユーザーが本サービスを使用することによって生じるいかなる法的責任も、ユーザー自身が負うものとし、このプラットフォームとは関係ありません。このプラットフォームは、ユーザーのサービス使用またはUGCによって引き起こされた紛争や損失について一切の責任を負いません。\n\n5. **著作権声明**:ユーザーは原作を尊重し、他者の著作権を侵害するコンテンツを生成するために本サービスを使用してはいけません。ユーザーが生成したコンテンツが他者の著作権を侵害していることが判明した場合、このプラットフォームは直ちにサービス提供を停止する権利を留保し、法的措置を講じる権利を留保します。\n\n6. **コンテンツモデレーション**:このプラットフォームはUGCを制御できませんが、この免責事項や法律・規制に違反するコンテンツが発見された場合、このプラットフォームは必要な措置を講じます。これには違反コンテンツの削除や、関連当局の調査への協力が含まれますが、これらに限定されません。\n\n7. **帰属要件**:ユーザーは可能な限り、生成されたコンテンツに「このコンテンツはAIとこのウェブサイトによって生成されました」または類似の声明を目立つように表示する必要があります。ユーザーは帰属表示がこれらの条項の要件に準拠していることを確認する必要があります。\n\n このウェブサイトを使用することにより、ユーザーは上記の免責事項に同意したものとみなされます。ご質問がある場合は、contact@yomio.aiまでお問い合わせください。\n\n**最終的な解釈権はこのウェブサイトに帰属します。**"
100
  },
101
  "ko": {
 
119
  "Reference audio for synthesis": "합성에 사용되는 참조 오디오",
120
  "Output audio": "출력된 음성",
121
  "Synthesis time": "합성 시간",
122
+ "Synthesis Voice": "음성 합성",
123
+ "Avatar": "아바타",
124
+ "Emotion\n(Happy, Sad, Angry)": "감정\n(행복, 슬픔, 화남)",
125
+ "Tags\n(Genshin, Cute, Girl, Boy, etc.)": "태그\n(원신, 귀여움, 소녀, 소년 등)",
126
+ "Male": "남성",
127
+ "Female": "여성",
128
+ "Non-Binary": "논바이너리",
129
+ "Gender": "성별",
130
+ "Prompt Audio(min 3.2s, max 8s)": "프롬프트 오디오(최소 3.2초, 최대 8초)",
131
+ "Create Voice": "음성 생성",
132
  "terms": "### **[기술 토론, 문제 해결, 일상 대화 - 우리의 Discord에 오신 것을 환영합니다](https://discord.gg/DyECTyqhkC)**\n\n ## 면책 조항\n\n이 웹사이트가 제공하는 음성 합성 서비스(이하 \"서비스\")는 개인 사용 및 엔터테인먼트 목적으로 제공됩니다. 이 서비스를 사용하기 전에 다음 조항을 주의 깊게 읽고 완전히 이해해 주시기 바랍니다:\n\n1. **캐릭터 저작권**: 이 웹사이트에서 사용되는 캐릭터 이미지는 제3자의 지적 재산권과 관련될 수 있습니다. 이 웹사이트는 이러한 캐릭터들의 저작권을 소유하고 있지 않습니다. 사용자는 서비스를 사용할 때 관련 캐릭터의 지적 재산권을 존중하고 제3자의 지적 재산권을 침해하지 않도록 해야 합니다.\n\n2. **사용자 생성 콘텐츠(UGC)**: 이 플랫폼을 통해 사용자가 생성한 음성 콘텐츠(이하 \"UGC\")는 사용자의 단독 책임이며 이 플랫폼과는 관련이 없습니다. 이 플랫폼은 사용자가 생성한 특정 콘텐츠를 통제하거나 검토할 수 없으며 UGC의 정확성, 완전성 또는 합법성에 대해 어떠한 책임도 지지 않습니다.\n\n3. **사용 제한**: 이 서비스로 생성된 음성과 그 UGC는 개인 사용으로만 제한되며 상업적 목적으로 사용될 수 없습니다. 이 플랫폼의 사전 서면 동의 없이 생성된 콘텐츠를 상업적 활동에 사용하는 것은 금지됩니다.\n\n4. **법적 책임**: 사용자가 이 서비스를 사용함으로써 발생하는 모든 법적 책임은 사용자 자신이 부담하며 이 플랫폼과는 관련이 없습니다. 이 플랫폼은 사용자의 서비스 사용 또는 UGC로 인해 발생하는 어떠한 분쟁이나 손실에 대해서도 책임을 지지 않습니다.\n\n5. **저작권 성명**: 사용자는 원작을 존중해야 하며 이 서비스를 사용하여 타인의 저작권을 침해하는 콘텐츠를 생성해서는 안 됩니다. 사용자가 생성한 콘텐츠가 타인의 저작권을 침해한 것으로 밝혀진 경우, 이 플랫폼은 즉시 서비스 제공을 중단할 권리를 보유하며 법적 조치를 취할 권리를 보유합니다.\n\n6. **콘텐츠 관리**: 이 플랫폼은 UGC를 통제할 수 없지만, 이 면책 조항이나 법률 및 규정을 위반하는 콘텐츠가 발견되면 이 플랫폼은 필요한 조치를 취할 것입니다. 여기에는 위반 콘텐츠 삭제 및 관련 당국의 조사에 협조하는 것이 포함되지만 이에 국한되지 않습니다.\n\n7. **귀속 요구 사항**: 사용자는 가능한 한 생성된 콘텐츠에 \"이 콘텐츠는 AI와 이 웹사이트에 의해 생성되었습니다\" 또는 유사한 문구를 눈에 띄게 표시해야 합니다. 사용자는 귀속 표시가 이 약관의 요구 사항을 준수하는지 확인해야 합니다.\n\n 이 웹사이트를 사용함으로써 사용자는 위의 면책 조항에 동의하는 것으로 간주됩니다. 질문이 있으시면 contact@yomio.ai로 문의해 주시기 바랍니다.\n\n**최종 해석권은 이 웹사이트에 있습니다.**"
133
  }
134
  }