mxivideo/src-tauri/src/commands/ai_video.rs

293 lines
10 KiB
Rust

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<String>,
pub timeout: Option<u32>,
}
#[derive(Debug, Deserialize)]
pub struct BatchAIVideoRequest {
pub image_folder: String,
pub prompts: Vec<String>,
pub output_folder: String,
pub duration: String,
pub model_type: String,
pub timeout: Option<u32>,
}
async fn execute_python_command(_app: tauri::AppHandle, args: &[String]) -> Result<String, String> {
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(&current_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<String> = 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::<serde_json::Value>(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::<serde_json::Value>(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<String, String> {
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<String, String> {
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<String, String> {
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(&current_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())
}