Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@ SING_CUDA_DEVICE="0"
SONG_CACHE_SIZE=100
SONG_CACHE_DAYS=30

# 网易云音乐账号配置,添加配置以支持会员歌曲
#NCM_PHONE=
#NCM_EMAIL=
#NCM_PASSWORD=
#NCM_CTCODE=86


# chat 功能相关配置

# 请参考 https://zhuanlan.zhihu.com/p/618011122 配置 strategy
#CHAT_STRATEGY=cpu fp32

# tts 中翻日
# 百度翻译:https://api.fanyi.baidu.com
# 有道翻译:https://fanyi.youdao.com/openapi/
# 百度与有道均拥有每月100万字符免费额度
# 在控制台创建应用获取ID与密钥
# 默认使用百度翻译

TRANSLATOR_ENABLE= False
BAIDU_APP_ID = ""
BAIDU_SECRET_KEY = ""
YOUDAO_APP_KEY = ""
YOUDAO_APP_SECRET = ""
DEFAULT_TRANSLATOR = "baidu" # "youdao"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,5 @@ cython_debug/
# User defined
log
*.log
data
# resource
7 changes: 7 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class Settings(BaseSettings):
ncm_password: str = ""
ncm_ctcode: int = 86

translator_enable: bool = False
baidu_app_id: str = ""
baidu_secret_key: str = ""
youdao_app_key: str = ""
youdao_app_secret: str = ""
default_translator: str = "baidu"

chat_strategy: str = "cpu fp32"

model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
Expand Down
57 changes: 28 additions & 29 deletions app/services/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,33 @@ async def send_callback(url: str, data: dict, files: dict = None):
return resp.json()


async def callback_failed(request_id: str):
async def callback(
request_id: str,
status: str = "success",
text: str = None,
audio: bytes = None,
song_id: str = None,
chunk_index: int = None,
key: int = None,
):
callback_url = f"{CALLBACK_URL}/{request_id}"
await send_callback(callback_url, {"status": "failed"})


async def callback_text(request_id: str, text: str):
callback_url = f"{CALLBACK_URL}/{request_id}"
await send_callback(callback_url, {"status": "success", "text": text})


async def callback_audio(request_id: str, audio: bytes):
callback_url = f"{CALLBACK_URL}/{request_id}"
await send_callback(
callback_url,
{"status": "success"},
files={"file": audio},
)


async def callback_audio_with_info(request_id: str, audio: bytes, song_id: str, chunk_index: int, key: int):
callback_url = f"{CALLBACK_URL}/{request_id}"
await send_callback(
callback_url,
{
"status": "success",
"song_id": song_id,
"chunk_index": chunk_index,
"key": key,
},
files={"file": audio},
)
data = {"status": status}

if status == "failed":
await send_callback(callback_url, data)
return

if text:
data["text"] = text
if song_id:
data["song_id"] = song_id
if chunk_index is not None:
data["chunk_index"] = chunk_index
if key is not None:
data["key"] = key

if audio:
await send_callback(callback_url, data, files={"file": audio})
else:
await send_callback(callback_url, data)
110 changes: 110 additions & 0 deletions app/services/translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import hashlib
import random
import time
import uuid

import requests

from app.core.config import settings


class BaiduTranslator:
def __init__(self, app_id, secret_key):
self.app_id = app_id
self.secret_key = secret_key
self.url = "http://api.fanyi.baidu.com/api/trans/vip/translate"

def translate(self, text, from_lang="zh", to_lang="jp"):
salt = random.randint(32768, 65536)
sign_str = self.app_id + text + str(salt) + self.secret_key
sign = hashlib.md5(sign_str.encode("utf-8")).hexdigest()

params = {
"q": text,
"from": from_lang,
"to": to_lang,
"appid": self.app_id,
"salt": salt,
"sign": sign,
}

try:
response = requests.get(self.url, params=params, timeout=5)
result = response.json()

if "trans_result" in result:
return result["trans_result"][0]["dst"]
else:
print(f"翻译出错: {result}")
return text # 出错时返回原文
except Exception as e:
print(f"翻译请求异常: {e}")
return text # 异常时返回原文


class YoudaoTranslator:
def __init__(self, app_key, app_secret):
self.app_key = app_key
self.app_secret = app_secret
self.url = "https://openapi.youdao.com/api"

def translate(self, text, from_lang="zh-CHS", to_lang="ja"):
salt = str(uuid.uuid1())
curtime = str(int(time.time()))
sign = self._calculate_sign(text, salt, curtime)

params = {
"q": text,
"from": from_lang,
"to": to_lang,
"appKey": self.app_key,
"salt": salt,
"sign": sign,
"signType": "v3",
"curtime": curtime,
}

try:
response = requests.post(self.url, data=params, timeout=5)
result = response.json()

if result.get("errorCode") == "0" and "translation" in result:
return result["translation"][0]
else:
print(f"翻译出错: {result}")
return text # 出错时返回原文
except Exception as e:
print(f"翻译请求异常: {e}")
return text # 异常时返回原文

def _calculate_sign(self, q, salt, curtime):
input_str = self._get_input(q)
sign_str = self.app_key + input_str + salt + curtime + self.app_secret
hash_algorithm = hashlib.sha256()
hash_algorithm.update(sign_str.encode("utf-8"))
return hash_algorithm.hexdigest()

def _get_input(self, text):
if text is None:
return text
text_len = len(text)
return text if text_len <= 20 else text[0:10] + str(text_len) + text[text_len - 10 : text_len]


BAIDU_APP_ID = settings.baidu_app_id
BAIDU_SECRET_KEY = settings.baidu_secret_key

baidu_translator = BaiduTranslator(BAIDU_APP_ID, BAIDU_SECRET_KEY)


YOUDAO_APP_KEY = settings.youdao_app_key
YOUDAO_APP_SECRET = settings.youdao_app_secret

youdao_translator = YoudaoTranslator(YOUDAO_APP_KEY, YOUDAO_APP_SECRET)

# 默认使用百度翻译
active_translator = settings.default_translator
if active_translator == "baidu":
active_translator = baidu_translator
elif active_translator == "youdao":
active_translator = youdao_translator
8 changes: 4 additions & 4 deletions app/tasks/chat/chat_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from app.core.celery import celery_app
from app.core.config import settings
from app.services.callback import callback_audio, callback_failed, callback_text
from app.services.callback import callback
from app.tasks.tts.tts_tasks import tts_req
from app.utils.gpu_locker import GPULockManager

Expand Down Expand Up @@ -41,11 +41,11 @@ async def _chat_task_async(request_id: str, session: str, text: str, token_count
chat = ChatManager.get_chat()
ans = chat.chat(session, text, token_count)
if not ans:
await callback_failed(request_id)
await callback(request_id, status="failed")
return
if tts:
audio = tts_req(ans)
if audio:
await callback_audio(request_id, audio)
await callback(request_id, text=ans, audio=audio)
return
await callback_text(request_id, ans)
await callback(request_id, text=ans)
10 changes: 5 additions & 5 deletions app/tasks/sing/play_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import anyio

from app.core.celery import celery_app
from app.services.callback import callback_audio_with_info, callback_failed
from app.services.callback import callback

SONG_PATH = "resource/sing/splices/"
MUSIC_PATH = "resource/music/"
Expand Down Expand Up @@ -41,7 +41,7 @@ async def _play_task_async(request_id: str, speaker: str = ""):
try:
rand_music = get_random_song(speaker)
if not rand_music:
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

if "_spliced" in rand_music:
Expand Down Expand Up @@ -72,11 +72,11 @@ async def _play_task_async(request_id: str, speaker: str = ""):
async with await anyio.open_file(rand_music, "rb") as f:
audio_content = await f.read()
except Exception:
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

await callback_audio_with_info(request_id, audio_content, song_id=song_id, chunk_index=chunk_index, key=key)
await callback(request_id, audio=audio_content, song_id=song_id, chunk_index=chunk_index, key=key)
return True
except Exception:
await callback_failed(request_id)
await callback(request_id, status="failed")
return False
20 changes: 10 additions & 10 deletions app/tasks/sing/sing_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from app.core.celery import celery_app
from app.core.config import settings
from app.core.logger import logger
from app.services.callback import callback_audio, callback_failed
from app.services.callback import callback
from app.utils.gpu_locker import GPULockManager

from .mixer import mix, splice
Expand Down Expand Up @@ -40,12 +40,12 @@ async def _sing_task_async(request_id: str, speaker: str, song_id: int, sing_len
if cache_path.name.startswith(f"{song_id}_full_"):
async with await anyio.open_file(cache_path, "rb") as f:
file = await f.read()
await callback_audio(request_id, file)
await callback(request_id, audio=file)
return True
elif cache_path.name.startswith(f"{song_id}_spliced"):
async with await anyio.open_file(cache_path, "rb") as f:
file = await f.read()
await callback_audio(request_id, file)
await callback(request_id, audio=file)
return True
else:
cache_path = Path("resource/sing/mix") / f"{song_id}_chunk{chunk_index}_{key}key_{speaker}.mp3"
Expand All @@ -55,14 +55,14 @@ async def _sing_task_async(request_id: str, speaker: str, song_id: int, sing_len
)
async with await anyio.open_file(cache_path, "rb") as f:
file = await f.read()
await callback_audio(request_id, file)
await callback(request_id, audio=file)
return True

# 从网易云下载
origin = await asyncify(download)(song_id)
if not origin:
logger.error("download failed", song_id)
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

# 音频切片
Expand All @@ -73,7 +73,7 @@ async def _sing_task_async(request_id: str, speaker: str, song_id: int, sing_len
Path("NotExists"), Path("resource/sing/splices"), True, song_id, chunk_index, speaker, key=key
)
logger.error("slice failed", song_id)
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

chunk = slices_list[chunk_index]
Expand All @@ -82,7 +82,7 @@ async def _sing_task_async(request_id: str, speaker: str, song_id: int, sing_len
separated = await asyncify(separate)(chunk, Path("resource/sing"), locker=gpu_locker, key=key)
if not separated:
logger.error("separate failed", song_id)
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

vocals, no_vocals = separated
Expand All @@ -91,22 +91,22 @@ async def _sing_task_async(request_id: str, speaker: str, song_id: int, sing_len
svc = await asyncify(inference)(vocals, Path("resource/sing/svc"), key=key, speaker=speaker, locker=gpu_locker)
if not svc:
logger.error("svc failed", song_id)
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

# 混合人声和伴奏
result = await asyncify(mix)(svc, no_vocals, vocals, Path("resource/sing/mix"), svc.stem)
if not result:
logger.error("mix failed", song_id)
await callback_failed(request_id)
await callback(request_id, status="failed")
return False

# 混音后合并混音结果
finished = chunk_index == len(slices_list) - 1
await asyncify(splice)(result, Path("resource/sing/splices"), finished, song_id, chunk_index, speaker, key=key)
async with await anyio.open_file(result, "rb") as f:
file = await f.read()
await callback_audio(request_id, file)
await callback(request_id, audio=file)
return True


Expand Down
Loading
Loading