From 2918f9597d9f6c2e91c4ab3bde481e6232cc5506 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Thu, 21 Aug 2025 14:16:13 +0800 Subject: [PATCH] 'commit' --- dsLightRag/Routes/SunoRoute.py | 256 +++++++++++++++++ .../__pycache__/SunoRoute.cpython-310.pyc | Bin 0 -> 5115 bytes .../suno_music_generator.cpython-310.pyc | Bin 0 -> 5684 bytes .../{Suno => Routes}/suno_music_generator.py | 0 dsLightRag/Start.py | 15 +- dsLightRag/static/Suno/index.html | 271 ++++++++++++------ 6 files changed, 452 insertions(+), 90 deletions(-) create mode 100644 dsLightRag/Routes/SunoRoute.py create mode 100644 dsLightRag/Routes/__pycache__/SunoRoute.cpython-310.pyc create mode 100644 dsLightRag/Routes/__pycache__/suno_music_generator.cpython-310.pyc rename dsLightRag/{Suno => Routes}/suno_music_generator.py (100%) diff --git a/dsLightRag/Routes/SunoRoute.py b/dsLightRag/Routes/SunoRoute.py new file mode 100644 index 00000000..3de2678f --- /dev/null +++ b/dsLightRag/Routes/SunoRoute.py @@ -0,0 +1,256 @@ +import logging +import json +import time +import datetime +import requests +from typing import Optional + +import fastapi +from fastapi import APIRouter, HTTPException, Query +from pydantic import BaseModel + +from Routes.suno_music_generator import SunoMusicGenerator +from Config import Config + +# 创建路由路由器 +router = APIRouter(prefix="/api/suno", tags=["音乐"]) + +# 配置日志 +logger = logging.getLogger(__name__) + +# 获取API密钥 +AK = Config.GPTNB_API_KEY + +# 请求模型 +class MusicGenerateRequest(BaseModel): + prompt: str + make_instrumental: Optional[bool] = True + +# 初始化音乐生成器 +music_generator = SunoMusicGenerator(AK) + +# 任务状态存储(实际应用中可使用数据库) +music_tasks = {} + + +@router.post("/prompt_input") +async def prompt_input(request: MusicGenerateRequest): + """ + 生成音乐任务接口 + :param request: 包含提示词和是否纯音乐的请求体 + :return: 任务ID和状态信息 + """ + prompt = request.prompt + make_instrumental = request.make_instrumental + + if not prompt: + raise HTTPException(status_code=400, detail="缺少提示词参数") + + try: + logger.info(f"开始生成音乐任务,提示词: {prompt}") + + # 调用音乐生成器生成音乐 + # 注意:我们只执行生成请求,不等待结果,因为这是一个异步过程 + # 构建JSON请求体 + request_json = { + "gpt_description_prompt": prompt, + "mv": "chirp-v3-5", + "prompt": "", + "make_instrumental": make_instrumental + } + + # 设置请求头 + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {AK}" + } + + # 执行生成请求 + response = requests.post( + SunoMusicGenerator.GENERATE_URL, + headers=headers, + json=request_json, + timeout=30 + ) + response.raise_for_status() + + # 解析响应 + generate_json = response.json() + logger.info(f"音乐生成响应: {generate_json}") + + # 提取任务ID + task_id = None + if "id" in generate_json: + task_id = generate_json["id"] + elif "task_id" in generate_json: + task_id = generate_json["task_id"] + elif "clip_id" in generate_json: + task_id = generate_json["clip_id"] + + if task_id is None: + raise HTTPException(status_code=500, detail="无法从响应中提取任务ID") + + # 存储任务信息 + music_tasks[task_id] = { + "status": "processing", + "prompt": prompt, + "create_time": datetime.datetime.now().isoformat(), + "response": generate_json + } + + logger.info(f"音乐生成任务已提交,任务ID: {task_id}") + + return { + "code": 200, + "message": "音乐生成任务已提交", + "data": { + "task_id": task_id, + "status": "processing" + } + } + + except json.JSONDecodeError as e: + logger.error(f"解析音乐生成响应失败: {e}") + raise HTTPException(status_code=500, detail=f"解析音乐生成响应失败: {str(e)}") + except Exception as e: + logger.error(f"音乐生成过程中发生错误: {e}") + raise HTTPException(status_code=500, detail=f"音乐生成过程中发生错误: {str(e)}") + + +@router.get("/check_task_status") +async def check_task_status(task_id: str = Query(..., description="音乐生成任务ID")): + """ + 检查音乐生成任务状态接口 + :param task_id: 音乐生成任务ID + :return: 任务状态信息 + """ + if not task_id: + raise HTTPException(status_code=400, detail="缺少任务ID参数") + + # 检查任务是否存在 + if task_id not in music_tasks: + raise HTTPException(status_code=404, detail=f"任务ID不存在: {task_id}") + + try: + logger.info(f"检查任务状态,任务ID: {task_id}") + + # 获取任务信息 + task_info = music_tasks[task_id] + generate_json = task_info["response"] + + # 构建查询URL + url_builder = [SunoMusicGenerator.FEED_URL, "?"] + + # 尝试从生成响应中获取clips的ID + clip_ids = [] + if "clips" in generate_json: + clips_array = generate_json["clips"] + for clip in clips_array: + if "id" in clip: + clip_ids.append(clip["id"]) + + # 添加ids参数 + if clip_ids: + ids_param = ",".join(clip_ids) + url_builder.append(f"ids={ids_param}") + logger.info(f"使用clips ID查询: {ids_param}") + else: + url_builder.append(f"ids={task_id}") + logger.info(f"使用任务ID查询: {task_id}") + + url = "".join(url_builder) + logger.info(f"查询URL: {url}") + + # 设置请求头 + headers = { + "Authorization": f"Bearer {AK}", + "Accept": "application/json" + } + + # 执行查询请求 + response = requests.get(url, headers=headers, timeout=30) + response.raise_for_status() + + # 解析查询响应 + json_response = response.json() + clips = json_response.get("clips", []) + + if clips: + # 遍历所有返回的音乐片段 + for clip in clips: + clip_id = clip.get("id") + status = clip.get("status") + title = clip.get("title") + audio_url = clip.get("audio_url") + + logger.info(f"查询结果:") + logger.info(f"ID: {clip_id}") + logger.info(f"标题: {title}") + logger.info(f"状态: {status}") + + # 更新任务状态 + task_info["status"] = status + task_info["last_check_time"] = datetime.datetime.now().isoformat() + task_info["title"] = title + + if status == "complete": + # 确保audio_url字段存在 + if audio_url: + # 移除URL中可能存在的反引号 + audio_url = audio_url.replace("`", "").strip() + task_info["audio_url"] = audio_url + logger.info("音乐生成已完成!") + logger.info(f"音频URL: {audio_url}") + + # 下载音频文件 + file_name = f"suno_music_{int(time.time())}.mp3" + save_path = music_generator.base_path / file_name + if music_generator.download_audio(audio_url, str(save_path)): + task_info["local_path"] = str(save_path) + else: + logger.warning("音乐生成已完成,但未找到音频URL!") + + elif status == "failed": + logger.error("音乐生成失败!") + task_info["error_message"] = clip.get("error_message", "未知错误") + + # 更新任务存储 + music_tasks[task_id] = task_info + + return { + "code": 200, + "message": "查询成功", + "data": { + "task_id": task_id, + "status": status, + "title": title, + "audio_url": audio_url if status == "complete" else None, + "local_path": task_info.get("local_path") if status == "complete" else None, + "error_message": task_info.get("error_message") if status == "failed" else None + } + } + else: + logger.info("未找到音乐片段") + return { + "code": 200, + "message": "查询成功", + "data": { + "task_id": task_id, + "status": "processing", + "title": None, + "audio_url": None, + "local_path": None, + "error_message": None + } + } + + except fastapi.Requests.exceptions.RequestException as e: + logger.error(f"查询任务状态失败: {e}") + raise HTTPException(status_code=500, detail=f"查询任务状态失败: {str(e)}") + except json.JSONDecodeError as e: + logger.error(f"解析查询响应失败: {e}") + raise HTTPException(status_code=500, detail=f"解析查询响应失败: {str(e)}") + except Exception as e: + logger.error(f"查询任务状态过程中发生错误: {e}") + raise HTTPException(status_code=500, detail=f"查询任务状态过程中发生错误: {str(e)}") + diff --git a/dsLightRag/Routes/__pycache__/SunoRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/SunoRoute.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ee093021d0347d716a39b371c39135975111942 GIT binary patch literal 5115 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 XJdVl2)9S;1QH5BI5xfK7%0K@L9V1Cw literal 0 HcmV?d00001 diff --git a/dsLightRag/Suno/suno_music_generator.py b/dsLightRag/Routes/suno_music_generator.py similarity index 100% rename from dsLightRag/Suno/suno_music_generator.py rename to dsLightRag/Routes/suno_music_generator.py diff --git a/dsLightRag/Start.py b/dsLightRag/Start.py index 06a4f8ce..94c0c299 100644 --- a/dsLightRag/Start.py +++ b/dsLightRag/Start.py @@ -20,6 +20,7 @@ from Routes.TeachingModel.api.DocumentController import router as document_route from Routes.TeachingModel.api.TeachingModelController import router as teaching_model_router from Routes.QA import router as qa_router from Routes.JiMengRoute import router as jimeng_router +from Routes.SunoRoute import router as suno_router from Util.LightRagUtil import * from contextlib import asynccontextmanager @@ -33,17 +34,19 @@ logger.addHandler(handler) @asynccontextmanager -async def lifespan(_: FastAPI): +async def lifespan(_: FastAPI): pool = await init_postgres_pool() app.state.pool = pool asyncio.create_task(train_document_task()) - + try: - yield + yield finally: # 应用关闭时销毁连接池 await close_postgres_pool(pool) + + app = FastAPI(lifespan=lifespan) # 挂载静态文件目录 @@ -56,8 +59,9 @@ app.include_router(rag_router) # LightRAG路由 app.include_router(knowledge_router) # 知识图谱路由 app.include_router(oss_router) # 阿里云OSS路由 app.include_router(llm_router) # 大模型路由 -app.include_router(qa_router) # 答疑路由 -app.include_router(jimeng_router) # 即梦路由 +app.include_router(qa_router) # 答疑路由 +app.include_router(jimeng_router) # 即梦路由 +app.include_router(suno_router) # Suno路由 # Teaching Model 相关路由 # 登录相关(不用登录) @@ -75,6 +79,5 @@ app.include_router(teaching_model_router, prefix="/api/teaching/model", tags=["t # 教学答疑 app.include_router(teaching_model_router, prefix="/api/teaching/model", tags=["teacher_model"]) - if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8100) diff --git a/dsLightRag/static/Suno/index.html b/dsLightRag/static/Suno/index.html index 77453110..75ae4f65 100644 --- a/dsLightRag/static/Suno/index.html +++ b/dsLightRag/static/Suno/index.html @@ -134,7 +134,7 @@ - +
示例:「夏日海边」「深夜治愈」「热血励志」
@@ -199,6 +199,7 @@
+
@@ -220,93 +221,195 @@ \ No newline at end of file