use anyhow::{anyhow, Result}; use reqwest::Client; use std::time::Duration; use crate::data::models::video_generation::{ DifyApiConfig, DifyApiRequest, DifyApiResponse, DifyInputs, VideoGenerationResult }; /// 视频生成服务 /// 基于Dify API实现视频生成功能 pub struct VideoGenerationService { client: Client, config: DifyApiConfig, } impl VideoGenerationService { /// 创建新的视频生成服务实例 pub fn new() -> Self { let client = Client::builder() .timeout(Duration::from_secs(300)) // 5分钟超时 .build() .expect("Failed to create HTTP client"); Self { client, config: DifyApiConfig::default(), } } /// 使用自定义配置创建视频生成服务 pub fn with_config(config: DifyApiConfig) -> Self { let client = Client::builder() .timeout(Duration::from_secs(300)) .build() .expect("Failed to create HTTP client"); Self { client, config } } /// 生成视频 pub async fn generate_video( &self, product: String, scene: String, model_desc: String, image_url: String, template: String, duplicate: u32, ) -> Result { println!("🎬 开始生成视频..."); println!("产品: {}", product); println!("场景: {}", scene); println!("模特: {}", model_desc); println!("图片: {}", image_url); println!("模板: {}", template); println!("数量: {}", duplicate); let start_time = std::time::Instant::now(); // 构建请求数据 let request_data = DifyApiRequest { inputs: DifyInputs { product: product.clone(), scene: scene.clone(), model: model_desc.clone(), image: image_url.clone(), duplicate, template: template.clone(), }, response_mode: "blocking".to_string(), user: "mixvideo-desktop".to_string(), }; // 发送请求 let url = format!("{}/v1/workflows/run", self.config.host); let response = self .client .post(&url) .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("Content-Type", "application/json") .json(&request_data) .send() .await .map_err(|e| anyhow!("请求失败: {}", e))?; let status = response.status(); let response_text = response .text() .await .map_err(|e| anyhow!("读取响应失败: {}", e))?; println!("📡 API响应状态: {}", status); println!("📡 API响应内容: {}", response_text); if !status.is_success() { return Err(anyhow!("API请求失败: {} - {}", status, response_text)); } // 解析响应 let api_response: DifyApiResponse = serde_json::from_str(&response_text) .map_err(|e| anyhow!("解析响应失败: {} - 响应内容: {}", e, response_text))?; if let Some(error) = api_response.error { return Err(anyhow!("API返回错误: {}", error)); } let data = api_response .data .ok_or_else(|| anyhow!("API响应中缺少data字段"))?; let video_urls = data.outputs.output; if video_urls.is_empty() { return Err(anyhow!("API未返回任何视频URL")); } let generation_time = start_time.elapsed().as_millis() as u64; println!("✅ 视频生成成功!"); println!("🎥 生成的视频数量: {}", video_urls.len()); println!("⏱️ 生成耗时: {}ms", generation_time); // 处理视频路径(如果是file://协议,直接使用本地路径) let video_paths = self.process_video_paths(&video_urls).await?; Ok(VideoGenerationResult { video_urls, video_paths, generation_time, api_response: Some(response_text), }) } /// 处理视频路径(支持file://协议和HTTP URL) async fn process_video_paths(&self, video_urls: &[String]) -> Result> { let mut video_paths = Vec::new(); for url in video_urls.iter() { if url.starts_with("file://") { // 如果是file://协议,提取本地路径 let local_path = url.replace("file://", "").replace("file:///", ""); println!("📁 使用本地视频文件: {}", local_path); video_paths.push(local_path); } else if url.starts_with("http://") || url.starts_with("https://") { // 如果是HTTP URL,尝试下载 match self.download_single_video(url, video_paths.len()).await { Ok(path) => { println!("📥 视频下载成功: {}", path); video_paths.push(path); } Err(e) => { println!("⚠️ 视频下载失败: {} - {}", url, e); // 下载失败时使用原URL作为路径 video_paths.push(url.clone()); } } } else { // 其他情况直接使用原路径 println!("📄 使用原始路径: {}", url); video_paths.push(url.clone()); } } Ok(video_paths) } /// 下载视频到本地(保留原方法以备后用) async fn download_videos(&self, video_urls: &[String]) -> Result> { let mut video_paths = Vec::new(); for (index, url) in video_urls.iter().enumerate() { match self.download_single_video(url, index).await { Ok(path) => { println!("📥 视频下载成功: {}", path); video_paths.push(path); } Err(e) => { println!("⚠️ 视频下载失败: {} - {}", url, e); // 下载失败时使用原URL作为路径 video_paths.push(url.clone()); } } } Ok(video_paths) } /// 下载单个视频文件 async fn download_single_video(&self, url: &str, index: usize) -> Result { // 创建下载目录 let download_dir = std::env::current_dir()?.join("downloads").join("videos"); std::fs::create_dir_all(&download_dir)?; // 生成文件名 let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S").to_string(); let filename = format!("video_{}_{}.mp4", timestamp, index); let file_path = download_dir.join(&filename); // 下载文件 let response = self .client .get(url) .send() .await .map_err(|e| anyhow!("下载请求失败: {}", e))?; if !response.status().is_success() { return Err(anyhow!("下载失败: {}", response.status())); } let bytes = response .bytes() .await .map_err(|e| anyhow!("读取视频数据失败: {}", e))?; std::fs::write(&file_path, bytes) .map_err(|e| anyhow!("保存视频文件失败: {}", e))?; Ok(file_path.to_string_lossy().to_string()) } /// 验证图片URL是否可访问 pub async fn validate_image_url(&self, url: &str) -> Result { match self.client.head(url).send().await { Ok(response) => Ok(response.status().is_success()), Err(_) => Ok(false), } } /// 获取API配置 pub fn get_config(&self) -> &DifyApiConfig { &self.config } /// 更新API配置 pub fn update_config(&mut self, config: DifyApiConfig) { self.config = config; } /// 测试API连接 pub async fn test_connection(&self) -> Result { let url = format!("{}/health", self.config.host); match self.client.get(&url).send().await { Ok(response) => Ok(response.status().is_success()), Err(_) => Ok(false), } } } impl Default for VideoGenerationService { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn test_video_generation_service_creation() { let service = VideoGenerationService::new(); assert_eq!(service.config.host, "https://dify.bowongai.com"); assert_eq!(service.config.api_key, "app-Lm10XUBJKnE1bnG6VPfiD1se"); } #[tokio::test] async fn test_custom_config() { let custom_config = DifyApiConfig { host: "https://custom.api.com".to_string(), api_key: "custom-key".to_string(), }; let service = VideoGenerationService::with_config(custom_config.clone()); assert_eq!(service.config.host, custom_config.host); assert_eq!(service.config.api_key, custom_config.api_key); } #[tokio::test] async fn test_validate_image_url() { let service = VideoGenerationService::new(); // 测试一个公开的图片URL let result = service.validate_image_url("https://httpbin.org/status/200").await; assert!(result.is_ok()); } }