package com.rj.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.rj.dto.VideoSynthesisRequestDto; import com.rj.entity.VideoSynthesisLog; import com.rj.mapper.VideoSynthesisLogMapper; import com.rj.service.IVideoSynthesisService; import com.rj.utils.MinIOUrlGenerator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 视频合成服务实现类 * * @author rj * @date 2025-01-02 */ @Slf4j @Service public class VideoSynthesisServiceImpl implements IVideoSynthesisService { @Autowired private VideoSynthesisLogMapper videoSynthesisLogMapper; @Autowired private RestTemplate restTemplate; @Autowired MinIOUrlGenerator minIOUrlGenerator ; @Value("${dashscope.api.key}") private String apiKey; private static final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis/"; @Override public VideoSynthesisLog synthesizeVideoAndSave(VideoSynthesisRequestDto request) { LocalDateTime startTime = LocalDateTime.now(); String requestId = UUID.randomUUID().toString(); VideoSynthesisLog synthesisLog = new VideoSynthesisLog(); synthesisLog.setRequestId(requestId); synthesisLog.setRequestTime(startTime); synthesisLog.setSuccess(false); // 使用BeanUtils复制相同名称的属性 BeanUtils.copyProperties(request, synthesisLog); // 设置视频名称(如果请求中有提供) if (request.getVideoName() != null && !request.getVideoName().trim().isEmpty()) { synthesisLog.setVideoName(request.getVideoName()); log.info("设置视频名称: {}", request.getVideoName()); } else { // 如果没有提供视频名称,使用默认名称 synthesisLog.setVideoName("视频合成_" + requestId.substring(0, 8)); log.info("使用默认视频名称: {}", synthesisLog.getVideoName()); } // 设置图像区域坐标(如果请求中有提供) if (request.getFaceBbox() != null && !request.getFaceBbox().trim().isEmpty()) { synthesisLog.setFaceBbox(request.getFaceBbox()); log.info("设置图像区域坐标: {}", request.getFaceBbox()); } // 设置动态区域坐标(如果请求中有提供) if (request.getExtBbox() != null && !request.getExtBbox().trim().isEmpty()) { synthesisLog.setExtBbox(request.getExtBbox()); log.info("设置动态区域坐标: {}", request.getExtBbox()); } try { log.info("开始视频合成,请求ID: {}, 图片URL: {}, 音频URL: {}", requestId, request.getImageUrl(), request.getAudioUrl()); // 构建请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("X-DashScope-Async", "enable"); headers.set("Authorization", "Bearer " + apiKey); request.buildParam(); // 构建请求体 HttpEntity entity = new HttpEntity<>(request, headers); log.info("开始视频合成,entity : {} ",entity); // 发送请求 ResponseEntity response = restTemplate.postForEntity(API_URL, entity, String.class); LocalDateTime endTime = LocalDateTime.now(); synthesisLog.setResponseTime(endTime); synthesisLog.setProcessingTimeMs(java.time.Duration.between(startTime, endTime).toMillis()); // 解析响应 if (response.getStatusCode() == HttpStatus.OK) { // 直接解析JSON响应 String responseBody = response.getBody(); log.info("API响应: {}", responseBody); // 解析JSON响应 if (responseBody != null && !responseBody.trim().isEmpty()) { try { // 使用fastjson解析JSON响应 JSONObject responseJson = JSON.parseObject(responseBody); // 提取task_id if (responseJson.containsKey("output")) { JSONObject output = responseJson.getJSONObject("output"); if (output != null && output.containsKey("task_id")) { String taskId = output.getString("task_id"); synthesisLog.setTaskId(taskId); log.info("提取到任务ID: {}", taskId); } } // 设置基本状态 synthesisLog.setTaskStatus("PENDING"); synthesisLog.setSuccess(true); // 生成临时访问URL(7天有效期) try { log.info("即将用音频名称: {}, 生成临时访问URL", synthesisLog.getVideoName()); String tempUrl = generateTempVideoUrl(synthesisLog.getVideoName(), requestId); synthesisLog.setVideoTempUrl(tempUrl); log.info("生成临时访问URL: {}", tempUrl); } catch (Exception e) { log.warn("生成临时访问URL失败: {}", e.getMessage()); // 临时URL生成失败不影响主流程 } } catch (Exception e) { log.error("解析API响应JSON失败: {}", e.getMessage(), e); synthesisLog.setErrorMessage("解析API响应失败: " + e.getMessage()); synthesisLog.setSuccess(false); } } else { synthesisLog.setErrorMessage("API响应为空"); synthesisLog.setSuccess(false); } log.info("视频合成请求成功,任务ID: {}, 状态: {}", synthesisLog.getTaskId(), synthesisLog.getTaskStatus()); } else { synthesisLog.setErrorMessage("HTTP请求失败,状态码: " + response.getStatusCode()); log.error("视频合成请求失败,状态码: {}, 响应: {}", response.getStatusCode(), response.getBody()); } } catch (Exception e) { LocalDateTime endTime = LocalDateTime.now(); synthesisLog.setResponseTime(endTime); synthesisLog.setProcessingTimeMs(java.time.Duration.between(startTime, endTime).toMillis()); synthesisLog.setErrorMessage("视频合成异常: " + e.getMessage()); log.error("视频合成失败: {}", e.getMessage(), e); } // 保存日志 saveVideoSynthesisLog(synthesisLog); return synthesisLog; } @Override public boolean saveVideoSynthesisLog(VideoSynthesisLog synthesisLog) { try { synthesisLog.setCreatedAt(LocalDateTime.now()); synthesisLog.setUpdatedAt(LocalDateTime.now()); int result = videoSynthesisLogMapper.insert(synthesisLog); log.info("视频合成日志保存{}: 请求ID={}, 任务ID={}, 成功={}", result > 0 ? "成功" : "失败", synthesisLog.getRequestId(), synthesisLog.getTaskId(), synthesisLog.getSuccess()); return result > 0; } catch (Exception e) { log.error("保存视频合成日志失败: {}", e.getMessage(), e); return false; } } @Override public VideoSynthesisLog getVideoSynthesisLogByRequestId(String requestId) { try { return videoSynthesisLogMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper() .eq("request_id", requestId) ); } catch (Exception e) { log.error("根据请求ID查询视频合成日志失败: {}", e.getMessage(), e); return null; } } @Override public VideoSynthesisLog getVideoSynthesisLogByTaskId(String taskId) { try { return videoSynthesisLogMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper() .eq("task_id", taskId) ); } catch (Exception e) { log.error("根据任务ID查询视频合成日志失败: {}", e.getMessage(), e); return null; } } /** * 生成临时视频访问URL * * @param videoName 视频名称 * @param requestId 请求ID * @return 临时访问URL */ private String generateTempVideoUrl(String videoName, String requestId) { try { return minIOUrlGenerator.generateTempUrl(videoName, 7, TimeUnit.DAYS).getUrl(); } catch (Exception e) { log.error("生成临时视频URL失败: {}", e.getMessage(), e); return null; } } }