add create voice
Browse files- api.py +89 -5
- app.py +398 -161
- i18n/characters_en.csv +0 -0
- i18n/characters_ja.csv +0 -0
- i18n/characters_ko.csv +0 -0
- i18n/characters_zh.csv +0 -0
- i18n/translations.json +40 -0
api.py
CHANGED
@@ -1,11 +1,40 @@
|
|
1 |
import asyncio
|
|
|
2 |
import aiohttp
|
3 |
-
import
|
4 |
-
|
|
|
|
|
5 |
from utils import normalize_audio_loudness
|
6 |
|
7 |
-
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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(
|
19 |
-
logging.getLogger("gradio").setLevel(logging.WARNING)
|
20 |
|
21 |
# 带有时间的log
|
22 |
-
logging.basicConfig(
|
|
|
|
|
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,
|
34 |
-
new_characters = load_characters_csv(lang)
|
35 |
-
initial_characters = get_characters(kind=
|
36 |
-
return
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
def get_characters(
|
|
|
|
|
42 |
# 使用传入的 all_characters 参数
|
43 |
filtered_characters = all_characters[all_characters["类别"] == kind]
|
44 |
-
|
45 |
if query:
|
46 |
# 使用拼音和汉字进行搜索
|
47 |
filtered_characters = filtered_characters[
|
48 |
-
filtered_characters[
|
49 |
]
|
50 |
-
if filtered_characters.empty and lang ==
|
51 |
filtered_characters = all_characters[all_characters["类别"] == kind]
|
52 |
filtered_characters = filtered_characters[
|
53 |
-
filtered_characters[
|
|
|
|
|
54 |
]
|
55 |
-
|
56 |
# 按名称分组,并选择每组的第一个记录
|
57 |
-
unique_characters =
|
|
|
|
|
|
|
|
|
|
|
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(
|
64 |
-
|
65 |
-
|
|
|
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 =
|
|
|
|
|
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[
|
137 |
-
|
138 |
# 按情绪去重并获取完整的角色信息
|
139 |
-
character_infos = character_records.drop_duplicates(subset=[
|
140 |
-
|
|
|
|
|
141 |
# 如果没有找到角色信息,返回一个包含默认值的字典
|
142 |
-
return
|
|
|
|
|
|
|
|
|
|
|
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[
|
|
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
if existing_char:
|
170 |
# 如果情绪不同,更新情绪
|
171 |
-
if existing_char[
|
172 |
-
existing_char[
|
173 |
else:
|
174 |
selected_characters.insert(0, current_character)
|
175 |
-
|
176 |
-
updated_characters = get_characters(
|
|
|
|
|
177 |
# ! 取消gallery选中状态,返回个新的gallery是必要的,否则会保留上一次的选中状态。这里sonnet很喜欢改成返回一个数组,但这不能清空gallery的选中状态
|
178 |
-
updated_gallery = gr.Gallery(
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
|
|
|
|
197 |
else:
|
198 |
-
updates.extend(
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
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(
|
213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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[
|
233 |
-
character_dict[
|
234 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
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 = [
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
273 |
with gr.Blocks():
|
274 |
gallery = gr.Gallery(
|
275 |
-
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(
|
|
|
|
|
|
|
|
|
287 |
info_type = gr.Dropdown(choices=[], label=gettext("Select emotion"))
|
288 |
with gr.Row():
|
289 |
-
add_voice_button = gr.Button(
|
290 |
-
|
291 |
-
|
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(
|
299 |
-
|
|
|
|
|
|
|
|
|
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(
|
306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
with gr.Column():
|
308 |
-
prompt_audio = gr.Audio(
|
309 |
-
|
310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
|
|
|
|
|
|
329 |
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
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 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
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__ ==
|
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 |
}
|