diff --git a/add_generation_quota.sql b/add_generation_quota.sql new file mode 100644 index 0000000..22700f7 --- /dev/null +++ b/add_generation_quota.sql @@ -0,0 +1,12 @@ +-- 为用户 ID=91, username=18888888888 增加 1000 次生成次数 +-- 执行时间: 2026-03-06 + +-- 更新用户的视频生成次数,增加 1000 次 +UPDATE nf_user +SET video_gen_remaining = video_gen_remaining + 1000 +WHERE id = 91 AND username = '18888888888'; + +-- 验证更新结果 +SELECT id, username, nickname, video_gen_remaining, video_gen_reset_date +FROM nf_user +WHERE id = 91; diff --git a/check_song_library.sql b/check_song_library.sql new file mode 100644 index 0000000..911fa3a --- /dev/null +++ b/check_song_library.sql @@ -0,0 +1,42 @@ +-- 检查歌曲库中的音频文件 URL +-- 查找可能有问题的歌曲记录 + +-- 1. 查看所有歌曲的音频 URL +SELECT + id, + title, + audio_url, + status, + gender, + duration_sec, + createtime +FROM nf_song_library +WHERE deletetime IS NULL +ORDER BY id DESC +LIMIT 20; + +-- 2. 查找最近失败的任务相关的歌曲 +SELECT + gt.id as task_id, + gt.status as task_status, + gt.payload, + gt.error_message, + sl.id as song_id, + sl.title as song_title, + sl.audio_url, + gt.created_at +FROM nf_generation_task gt +LEFT JOIN nf_song_library sl ON JSON_EXTRACT(gt.payload, '$.song_id') = sl.id +WHERE gt.task_type = 'sing' + AND gt.status = 'failed' +ORDER BY gt.created_at DESC +LIMIT 10; + +-- 3. 统计有问题的 URL 模式 +SELECT + SUBSTRING_INDEX(audio_url, '/', 5) as url_prefix, + COUNT(*) as count +FROM nf_song_library +WHERE deletetime IS NULL + AND status = 1 +GROUP BY url_prefix; diff --git a/fix_song_audio_urls.sql b/fix_song_audio_urls.sql new file mode 100644 index 0000000..7ae1a63 --- /dev/null +++ b/fix_song_audio_urls.sql @@ -0,0 +1,46 @@ +-- 修复歌曲库中不存在的音频文件 +-- 方案:临时禁用这些有问题的歌曲,或者更新为可用的音频 URL + +-- 1. 查看当前有问题的歌曲(audio_url 包含 20260126 或 20260117) +SELECT + id, + title, + artist, + audio_url, + status, + gender +FROM nf_song_library +WHERE deletetime IS NULL + AND (audio_url LIKE '%/20260126/%' OR audio_url LIKE '%/20260117/%') +ORDER BY id; + +-- 2. 临时禁用这些歌曲(推荐方案) +-- 取消下面的注释来执行 +/* +UPDATE nf_song_library +SET status = 0 +WHERE deletetime IS NULL + AND (audio_url LIKE '%/20260126/%' OR audio_url LIKE '%/20260117/%'); +*/ + +-- 3. 或者使用外部 URL(如果有的话) +-- 例如:使用 www.bensound.com 的测试音频 +/* +UPDATE nf_song_library +SET audio_url = 'https://www.bensound.com/bensound-music/bensound-dreams.mp3' +WHERE id = 11 AND title = 'Dreams'; +*/ + +-- 4. 查看可用的歌曲(使用外部 URL 的) +SELECT + id, + title, + artist, + audio_url, + status, + gender +FROM nf_song_library +WHERE deletetime IS NULL + AND status = 1 + AND (audio_url LIKE 'http%' AND audio_url NOT LIKE '%/uploads/%') +ORDER BY id; diff --git a/lover/.env b/lover/.env index 781ad8d..940f89d 100644 --- a/lover/.env +++ b/lover/.env @@ -1,5 +1,12 @@ -DATABASE_URL=mysql+pymysql://root:rootx77@localhost:3306/fastadmin?charset=utf8mb4 -USER_INFO_API=http://127.0.0.1:30100/api/user_basic/get_user_basic +DATABASE_URL=mysql+pymysql://fastadmin:root@1.15.149.240:3306/fastadmin?charset=utf8mb4 +USER_INFO_API=http://1.15.149.240:30100/api/user_basic/get_user_basic # 语音通话超时设置(秒)- 增加到120秒以适应ASR+LLM+TTS处理时间 -VOICE_CALL_IDLE_TIMEOUT=120 \ No newline at end of file +VOICE_CALL_IDLE_TIMEOUT=120 + +# OSS 配置 - 用于文件存储和访问 +ALIYUN_OSS_ACCESS_KEY_ID=LTAI5tBzjogJDx4JzRYoDyEM +ALIYUN_OSS_ACCESS_KEY_SECRET=43euicRkkzlLjGTYzFYkTupcW7N5w3 +ALIYUN_OSS_BUCKET_NAME=hello12312312 +ALIYUN_OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com +ALIYUN_OSS_CDN_DOMAIN=https://hello12312312.oss-cn-hangzhou.aliyuncs.com diff --git a/lover/routers/sing.py b/lover/routers/sing.py index 7fc160d..6ebce96 100644 --- a/lover/routers/sing.py +++ b/lover/routers/sing.py @@ -174,13 +174,35 @@ def _semaphore_guard(semaphore: threading.BoundedSemaphore): def _cdnize(url: Optional[str]) -> Optional[str]: """ - 将相对路径补全为可访问 URL。优先使用 CDN,其次 bucket+endpoint,最后兜底固定域名。 + 将相对路径补全为可访问 URL。 + 策略: + 1. 如果已经是完整 URL,直接返回 + 2. 如果是相对路径,先检查本地文件是否存在 + 3. 如果本地文件存在,返回本地路径(以 /uploads/ 开头) + 4. 如果本地文件不存在,转换为 OSS URL """ + import os + if not url: return url cleaned = url.strip() + + # 如果已经是完整 URL,直接返回 if cleaned.startswith("http://") or cleaned.startswith("https://"): return cleaned + + # 检查本地文件是否存在 + local_base_path = "/www/wwwroot/1.15.149.240_30100/public" + local_file_path = os.path.join(local_base_path, cleaned.lstrip("/")) + + if os.path.exists(local_file_path): + # 本地文件存在,返回本地路径标识 + logger.info(f"找到本地文件: {local_file_path},使用本地路径") + return cleaned if cleaned.startswith("/") else f"/{cleaned}" + + # 本地文件不存在,转换为 OSS URL + logger.info(f"本地文件不存在: {local_file_path},尝试使用 OSS URL") + # 去掉首个斜杠,防止双斜杠 cleaned = cleaned.lstrip("/") if settings.ALIYUN_OSS_CDN_DOMAIN: @@ -270,6 +292,31 @@ def _resolve_sing_prompts(model: str) -> tuple[str, str]: def _download_to_path(url: str, target_path: str): + import os + import shutil + + # 检查是否是本地文件路径(以 /uploads/ 开头) + if url.startswith("/uploads/") or url.startswith("uploads/"): + # 本地文件路径,直接复制 + # 假设本地文件存储在 /www/wwwroot/1.15.149.240_30100/public/ 目录下 + local_base_path = "/www/wwwroot/1.15.149.240_30100/public" + source_path = os.path.join(local_base_path, url.lstrip("/")) + + logger.info(f"检测到本地文件路径,从 {source_path} 复制到 {target_path}") + + if not os.path.exists(source_path): + logger.error(f"本地文件不存在: {source_path}") + raise HTTPException(status_code=404, detail=f"本地文件不存在: {url}") + + try: + shutil.copy2(source_path, target_path) + logger.info(f"本地文件复制成功: {source_path} -> {target_path}") + return + except Exception as exc: + logger.error(f"本地文件复制失败: {exc}") + raise HTTPException(status_code=500, detail="本地文件复制失败") from exc + + # HTTP/HTTPS URL,通过网络下载 try: logger.info(f"开始下载文件: {url}") resp = requests.get(url, stream=True, timeout=30) @@ -372,8 +419,13 @@ def _ensure_emo_detect_cache( def _probe_media_duration(path: str) -> Optional[float]: + ffprobe_path = _ffprobe_bin() + if not ffprobe_path: + logger.error("ffprobe 命令未找到,无法获取音频时长") + return None + command = [ - _ffprobe_bin(), + ffprobe_path, "-v", "error", "-show_entries", @@ -382,21 +434,34 @@ def _probe_media_duration(path: str) -> Optional[float]: "default=noprint_wrappers=1:nokey=1", path, ] + + logger.info(f"执行 ffprobe 命令: {' '.join(command)}") + try: result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except FileNotFoundError: + except FileNotFoundError as e: + logger.error(f"ffprobe 命令未找到: {e}") return None - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as e: + logger.error(f"ffprobe 执行失败: {e}, stderr: {e.stderr.decode('utf-8', errors='ignore')}") return None + raw = result.stdout.decode("utf-8", errors="ignore").strip() + logger.info(f"ffprobe 输出: {raw}") + if not raw: + logger.error("ffprobe 返回空结果") return None try: duration = float(raw) - except ValueError: + except ValueError as e: + logger.error(f"无法解析时长值: {raw}, 错误: {e}") return None if duration <= 0: + logger.error(f"时长值无效: {duration}") return None + + logger.info(f"音频时长: {duration} 秒") return duration diff --git a/xuniYou/utils/request.js b/xuniYou/utils/request.js index 3900195..194ff55 100644 --- a/xuniYou/utils/request.js +++ b/xuniYou/utils/request.js @@ -1,8 +1,8 @@ // Windows 本地开发 - 混合架构 -export const baseURL = 'http://192.168.1.141:30100' // PHP 处理用户管理和界面 -// export const baseURL = 'http://1.15.149.240:30100' // PHP 处理用户管理和界面 -export const baseURLPy = 'http://192.168.1.141:30101' // FastAPI 处理 AI 功能 -// export const baseURLPy = 'http://1.15.149.240:30101' // FastAPI 处理 AI 功能 +// export const baseURL = 'http://192.168.1.141:30100' // PHP 处理用户管理和界面 +export const baseURL = 'http://1.15.149.240:30100' // PHP 处理用户管理和界面 +// export const baseURLPy = 'http://192.168.1.141:30101' // FastAPI 处理 AI 功能 +export const baseURLPy = 'http://1.15.149.240:30101' // FastAPI 处理 AI 功能 // 远程服务器 - 需要时取消注释 // export const baseURL = 'http://1.15.149.240:30100'