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 66e6b23d..97df0822 100644 Binary files a/dsLightRag/Routes/__pycache__/VideoRetalkRoute.cpython-310.pyc and b/dsLightRag/Routes/__pycache__/VideoRetalkRoute.cpython-310.pyc differ diff --git a/dsLightRag/static/video-retalk.html b/dsLightRag/static/video-retalk.html index 5d44d511..75498293 100644 --- a/dsLightRag/static/video-retalk.html +++ b/dsLightRag/static/video-retalk.html @@ -314,11 +314,20 @@ }); // 生成按钮点击事件 - document.getElementById('generate-btn').addEventListener('click', function() { + document.getElementById('generate-btn').addEventListener('click', async function() { const generateBtn = this; const loading = document.getElementById('loading'); const videoResult = document.getElementById('video-result'); const emptyResult = document.getElementById('empty-result'); + const videoPlayer = document.getElementById('video-player'); + + // 获取参数 + const imageUrl = document.getElementById('image-url').value.trim(); + const audioUrl = document.getElementById('audio-url').value.trim(); + const eyeMovement = document.getElementById('eye-movement').value; + + // 映射眼睛移动频率 + const eyeMoveFreqMap = {low: 0.3, medium: 0.5, high: 0.7}; // 显示加载状态 generateBtn.disabled = true; @@ -326,13 +335,35 @@ videoResult.style.display = 'none'; emptyResult.style.display = 'none'; - // 模拟生成过程 - setTimeout(() => { + 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); + } }); });