'commit'
This commit is contained in:
@@ -1,11 +1,16 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
from typing import Optional
|
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 fastapi.responses import JSONResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from Util.ObsUtil import ObsUploader
|
||||||
|
from Config.Config import OBS_BUCKET, OBS_SERVER
|
||||||
|
|
||||||
from Config import Config
|
from Config import Config
|
||||||
from Util.VideoRetalk import VideoRetalk
|
from Util.VideoRetalk import VideoRetalk
|
||||||
@@ -41,7 +46,7 @@ class VideoRetalkResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/generate", response_model=VideoRetalkResponse)
|
@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
|
head_move_strength=request.head_move_strength
|
||||||
)
|
)
|
||||||
|
|
||||||
if video_result and video_result['video_url']:
|
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(
|
return VideoRetalkResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message="视频生成成功",
|
message="视频生成成功",
|
||||||
video_url=video_result['video_url'],
|
video_url=obs_url,
|
||||||
task_id=str(uuid.uuid4()),
|
task_id=str(uuid.uuid4()),
|
||||||
video_duration=video_result['video_duration'],
|
video_duration=video_result['video_duration'],
|
||||||
video_ratio=video_result['video_ratio'],
|
video_ratio=video_result['video_ratio'],
|
||||||
request_id=str(uuid.uuid4())
|
request_id=str(uuid.uuid4())
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
return VideoRetalkResponse(
|
|
||||||
success=False,
|
|
||||||
message="视频生成失败"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"视频生成接口错误: {e}")
|
logger.error(f"视频生成接口错误: {e}")
|
||||||
|
Binary file not shown.
@@ -314,11 +314,20 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 生成按钮点击事件
|
// 生成按钮点击事件
|
||||||
document.getElementById('generate-btn').addEventListener('click', function() {
|
document.getElementById('generate-btn').addEventListener('click', async function() {
|
||||||
const generateBtn = this;
|
const generateBtn = this;
|
||||||
const loading = document.getElementById('loading');
|
const loading = document.getElementById('loading');
|
||||||
const videoResult = document.getElementById('video-result');
|
const videoResult = document.getElementById('video-result');
|
||||||
const emptyResult = document.getElementById('empty-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;
|
generateBtn.disabled = true;
|
||||||
@@ -326,13 +335,35 @@
|
|||||||
videoResult.style.display = 'none';
|
videoResult.style.display = 'none';
|
||||||
emptyResult.style.display = 'none';
|
emptyResult.style.display = 'none';
|
||||||
|
|
||||||
// 模拟生成过程
|
try {
|
||||||
setTimeout(() => {
|
// 实际API调用
|
||||||
loading.classList.remove('active');
|
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';
|
videoResult.style.display = 'block';
|
||||||
generateBtn.disabled = false;
|
|
||||||
layer.msg('视频合成成功', {icon: 1});
|
layer.msg('视频合成成功', {icon: 1});
|
||||||
}, 2000);
|
} 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');
|
||||||
|
generateBtn.disabled = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
Reference in New Issue
Block a user