use serde::Deserialize; use std::process::Command; use std::io::{BufRead, BufReader}; #[derive(Debug, Deserialize)] pub struct AIVideoRequest { pub image_path: String, pub prompt: String, pub duration: String, pub model_type: String, pub output_path: Option, pub timeout: Option, } #[derive(Debug, Deserialize)] pub struct BatchAIVideoRequest { pub image_folder: String, pub prompts: Vec, pub output_folder: String, pub duration: String, pub model_type: String, pub timeout: Option, } async fn execute_python_command(_app: tauri::AppHandle, args: &[String]) -> Result { println!("Executing Python command with args: {:?}", args); // Get project root directory let current_dir = std::env::current_dir() .map_err(|e| format!("Failed to get current directory: {}", e))?; let project_root = if current_dir.ends_with("src-tauri") { current_dir.parent().unwrap_or(¤t_dir).to_path_buf() } else { current_dir }; println!("Working directory: {:?}", project_root); // Try multiple Python commands in order of preference let mut child = None; let mut last_error = String::new(); let mut cmd = Command::new("python"); cmd.current_dir(&project_root); cmd.args(args); cmd.stdout(std::process::Stdio::piped()); cmd.stderr(std::process::Stdio::piped()); match cmd.spawn() { Ok(process) => { println!("Successfully started Python process"); child = Some(process); } Err(e) => { last_error = format!("Failed to process: {}", e); println!("{}", last_error); } } let mut child = child.ok_or_else(|| { format!("Failed to start Python process with any command. Last error: {}", last_error) })?; let stdout = child.stdout.take().unwrap(); let stderr = child.stderr.take().unwrap(); let stdout_reader = BufReader::new(stdout); let stderr_reader = BufReader::new(stderr); let mut progress_messages = Vec::new(); let mut error_messages = Vec::new(); let mut final_result: Option = None; // Read stdout for line in stdout_reader.lines() { let line = line.map_err(|e| format!("Failed to read stdout: {}", e))?; println!("Python stdout: {}", line); // Parse JSON-RPC messages if line.starts_with("JSONRPC:") { let json_str = &line[8..]; // Remove "JSONRPC:" prefix if let Ok(json_value) = serde_json::from_str::(json_str) { if let Some(method) = json_value.get("method") { if method == "progress" { progress_messages.push(json_str.to_string()); println!("Progress: {}", json_str); } } else if json_value.get("result").is_some() || json_value.get("error").is_some() { // This is a final result or error response - always update to get the latest final_result = Some(json_str.to_string()); println!("JSON-RPC result found: {}", json_str); } } } else if line.trim().starts_with('{') && line.trim().ends_with('}') { // Fallback: try to parse as direct JSON result if let Ok(json_value) = serde_json::from_str::(line.trim()) { // Check if this looks like a final result (has status field) if json_value.get("status").is_some() { final_result = Some(line.trim().to_string()); println!("Direct JSON result: {}", line.trim()); } } } } // Read stderr for line in stderr_reader.lines() { let line = line.map_err(|e| format!("Failed to read stderr: {}", e))?; println!("Python stderr: {}", line); error_messages.push(line); } // Wait for process to complete println!("Waiting for Python process to complete..."); let exit_status = child.wait() .map_err(|e| format!("Failed to wait for Python process: {}", e))?; println!("Python process terminated with code: {:?}", exit_status.code()); println!("Python script exit code: {:?}", exit_status.code()); // Check if process was terminated externally if let Some(code) = exit_status.code() { if code != 0 { println!("Python process exited with non-zero code: {}", code); // Convert to unsigned for Windows exit codes let unsigned_code = code as u32; if unsigned_code == 3221225786 || code == -1073741510 { println!("Process was likely terminated by antivirus or system security"); } else if code == 1 { println!("Process exited with error code 1 (general error)"); } } } else { println!("Python process was terminated by signal (killed externally)"); } // Return the final result if we found one if let Some(result) = final_result { println!("Extracted JSON-RPC result: {}", result); return Ok(result); } // If no JSON-RPC result found, provide detailed error information if exit_status.success() { // Process succeeded but no output - this is unusual let error_msg = if progress_messages.is_empty() { "Python script completed successfully but produced no output" } else { "Python script completed but did not return a valid JSON result" }; Err(error_msg.to_string()) } else { // Process failed - provide detailed error based on exit code let error_msg = if let Some(code) = exit_status.code() { let unsigned_code = code as u32; match code { 1 => "Python script failed with general error. Check if all dependencies are installed.".to_string(), -1073741510 => "Python process was terminated by antivirus or system security. Please add the application to your antivirus whitelist.".to_string(), _ if unsigned_code == 3221225786 => "Python process was terminated by antivirus or system security. Please add the application to your antivirus whitelist.".to_string(), _ => format!("Python script failed with exit code: {}. This may indicate a system or environment issue.", code) } } else { "Python process was killed by external signal. This may be caused by antivirus software or system security policies.".to_string() }; // Include any error output we captured let mut full_error = error_msg; if !progress_messages.is_empty() { full_error.push_str(&format!("\n\nLast stdout: {}", progress_messages.join("\n"))); } if !error_messages.is_empty() { full_error.push_str(&format!("\n\nStderr: {}", error_messages.join("\n"))); } Err(full_error) } } #[tauri::command] pub async fn generate_ai_video(app: tauri::AppHandle, request: AIVideoRequest) -> Result { let mut args = vec![ "-m".to_string(), "python_core.ai_video.video_generator".to_string(), "--action".to_string(), "single".to_string(), "--image".to_string(), request.image_path, "--prompt".to_string(), request.prompt, "--duration".to_string(), request.duration, "--model".to_string(), request.model_type, ]; if let Some(output_path) = request.output_path { args.push("--output".to_string()); args.push(output_path); } if let Some(timeout) = request.timeout { args.push("--timeout".to_string()); args.push(timeout.to_string()); } execute_python_command(app, &args).await } #[tauri::command] pub async fn batch_generate_ai_videos(app: tauri::AppHandle, request: BatchAIVideoRequest) -> Result { let prompts_json = serde_json::to_string(&request.prompts) .map_err(|e| format!("Failed to serialize prompts: {}", e))?; let mut args = vec![ "-m".to_string(), "python_core.ai_video.video_generator".to_string(), "--action".to_string(), "batch".to_string(), "--folder".to_string(), request.image_folder, "--prompts".to_string(), prompts_json, "--output".to_string(), request.output_folder, "--duration".to_string(), request.duration, "--model".to_string(), request.model_type, ]; if let Some(timeout) = request.timeout { args.push("--timeout".to_string()); args.push(timeout.to_string()); } execute_python_command(app, &args).await } #[tauri::command] pub async fn test_ai_video_environment(_app: tauri::AppHandle) -> Result { println!("Testing AI video environment..."); // Get project root directory let current_dir = std::env::current_dir() .map_err(|e| format!("Failed to get current directory: {}", e))?; let project_root = if current_dir.ends_with("src-tauri") { current_dir.parent().unwrap_or(¤t_dir).to_path_buf() } else { current_dir }; println!("Testing from project root: {:?}", project_root); // Try multiple Python commands in order of preference let python_commands = if cfg!(target_os = "windows") { vec!["python", "python3", "py"] } else { vec!["python3", "python"] }; for python_cmd in python_commands { println!("Testing Python command: {}", python_cmd); let output = Command::new(python_cmd) .current_dir(&project_root) .args(&["-c", "import sys; print(f'Python {sys.version}'); import requests; print('requests OK'); import PIL; print('PIL OK')"]) .output(); match output { Ok(output) => { if output.status.success() { let stdout = String::from_utf8_lossy(&output.stdout); return Ok(format!("Environment test passed with {}:\n{}", python_cmd, stdout)); } else { let stderr = String::from_utf8_lossy(&output.stderr); println!("Python {} test failed: {}", python_cmd, stderr); continue; } } Err(e) => { println!("Failed to execute Python {}: {}", python_cmd, e); continue; } } } Err("Failed to find a working Python installation".to_string()) }