293 lines
10 KiB
Rust
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(¤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<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(¤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())
|
|
}
|