From dab80a61d21d8624fc82a720a1c8b3a9a0119a4c Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Tue, 2 Sep 2025 08:58:30 +0800 Subject: [PATCH] 'commit' --- dsLightRag/Routes/VideoRetalkRoute.py | 63 ++++++++++++++---- .../VideoRetalkRoute.cpython-310.pyc | Bin 3630 -> 4793 bytes dsLightRag/static/video-retalk.html | 43 ++++++++++-- 3 files changed, 87 insertions(+), 19 deletions(-) diff --git a/dsLightRag/Routes/VideoRetalkRoute.py b/dsLightRag/Routes/VideoRetalkRoute.py index d267535e..f86c1b2f 100644 --- a/dsLightRag/Routes/VideoRetalkRoute.py +++ b/dsLightRag/Routes/VideoRetalkRoute.py @@ -1,11 +1,16 @@ import datetime import logging import uuid +import os +import time +import requests from typing import Optional -from fastapi import APIRouter, HTTPException, Query, Request +from fastapi import APIRouter, HTTPException, Query, Request, BackgroundTasks from fastapi.responses import JSONResponse from pydantic import BaseModel +from Util.ObsUtil import ObsUploader +from Config.Config import OBS_BUCKET, OBS_SERVER from Config import Config from Util.VideoRetalk import VideoRetalk @@ -41,7 +46,7 @@ class VideoRetalkResponse(BaseModel): @router.post("/generate", response_model=VideoRetalkResponse) -async def generate_video(request: VideoRetalkRequest): +async def generate_video(request: VideoRetalkRequest, background_tasks: BackgroundTasks): """ 生成人物朗读视频接口 根据输入的人物图片和音频,生成口型匹配的朗读视频 @@ -62,21 +67,53 @@ async def generate_video(request: VideoRetalkRequest): head_move_strength=request.head_move_strength ) - if video_result and video_result['video_url']: - return VideoRetalkResponse( - success=True, - message="视频生成成功", - video_url=video_result['video_url'], - task_id=str(uuid.uuid4()), - video_duration=video_result['video_duration'], - video_ratio=video_result['video_ratio'], - request_id=str(uuid.uuid4()) - ) - else: + if not video_result or not video_result['video_url']: return VideoRetalkResponse( success=False, message="视频生成失败" ) + + # 下载视频到本地临时文件 + timestamp = int(time.time()) + filename = f"video_{uuid.uuid4().hex[:8]}_{timestamp}.mp4" + output_dir = "static/video" + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, filename) + + # 从API下载视频文件 + response = requests.get(video_result['video_url'], stream=True) + if response.status_code != 200: + raise HTTPException(status_code=500, detail="视频文件下载失败") + + with open(output_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + # 上传到OBS + obs_uploader = ObsUploader() + obs_object_key = f"HuangHai/video/{filename}" + upload_success, upload_result = obs_uploader.upload_file(obs_object_key, output_path) + + if not upload_success: + # 上传失败,清理文件并抛出异常 + background_tasks.add_task(os.remove, output_path) if os.path.exists(output_path) else None + raise HTTPException(status_code=500, detail=f"OBS上传失败: {upload_result.get('errorMessage', '未知错误')}") + + # 构造OBS访问URL + obs_url = f"https://{OBS_BUCKET}.{OBS_SERVER}/{obs_object_key}" + + # 安排后台任务删除本地文件 + background_tasks.add_task(os.remove, output_path) if os.path.exists(output_path) else None + + return VideoRetalkResponse( + success=True, + message="视频生成成功", + video_url=obs_url, + task_id=str(uuid.uuid4()), + video_duration=video_result['video_duration'], + video_ratio=video_result['video_ratio'], + request_id=str(uuid.uuid4()) + ) except Exception as e: logger.error(f"视频生成接口错误: {e}") diff --git a/dsLightRag/Routes/__pycache__/VideoRetalkRoute.cpython-310.pyc b/dsLightRag/Routes/__pycache__/VideoRetalkRoute.cpython-310.pyc index 66e6b23dc2128aaf0670cb5bd64d3fdac480fac0..97df08229c19aef8054d29151fcb349f61faf755 100644 GIT binary patch delta 2870 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 y3hoeP&Q)r(5ry*vGV1tgn2JeEI1hcQ4QK { + try { + // 实际API调用 + const response = await fetch('/api/video/generate', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + image_url: imageUrl, + audio_url: audioUrl, + eye_move_freq: eyeMoveFreqMap[eyeMovement] + }) + }); + + const data = await response.json(); + if (data.success && data.video_url) { + videoPlayer.src = data.video_url; + videoResult.style.display = 'block'; + layer.msg('视频合成成功', {icon: 1}); + } else { + emptyResult.style.display = 'block'; + layer.msg('视频合成失败: ' + data.message, {icon: 2}); + } + } catch (error) { + console.error('API调用失败:', error); + emptyResult.style.display = 'block'; + layer.msg('网络错误,无法连接服务器', {icon: 2}); + } finally { loading.classList.remove('active'); - videoResult.style.display = 'block'; generateBtn.disabled = false; - layer.msg('视频合成成功', {icon: 1}); - }, 2000); + } }); });