use crate::error::Result;
use crate::job::{BatchJob, BatchOperation, InputSpec, OutputSpec};
use crate::operations::{FileOperation, OutputFormat};
use crate::{BatchEngine, JobId};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Parser, Debug)]
#[command(name = "oximedia-batch")]
#[command(about = "Batch processing engine for OxiMedia", long_about = None)]
pub struct CliArgs {
#[arg(long, default_value = "batch.db")]
pub db_path: String,
#[arg(long, default_value = "4")]
pub workers: usize,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Submit {
#[arg(long)]
name: String,
#[arg(long)]
operation: String,
#[arg(long)]
input: String,
#[arg(long)]
output: Option<String>,
#[arg(long)]
template: Option<String>,
},
List {
#[arg(long)]
status: Option<String>,
},
Status {
job_id: String,
},
Cancel {
job_id: String,
},
Watch {
#[arg(long)]
folder: PathBuf,
#[arg(long)]
template: String,
},
Serve {
#[arg(long, default_value = "0.0.0.0:3000")]
addr: String,
},
}
pub async fn execute(args: CliArgs) -> Result<()> {
let engine = Arc::new(BatchEngine::new(&args.db_path, args.workers)?);
engine.start().await?;
match args.command {
Commands::Submit {
name,
operation,
input,
output,
template: _,
} => {
let op = parse_operation(&operation)?;
let mut job = BatchJob::new(name, op);
job.add_input(InputSpec::new(input));
if let Some(output_path) = output {
job.add_output(OutputSpec::new(output_path, OutputFormat::Mp4));
}
let job_id = engine.submit_job(job).await?;
println!("Job submitted: {job_id}");
}
Commands::List { status: _ } => {
let jobs = engine.list_jobs()?;
for job in jobs {
println!("{} - {}", job.id, job.name);
}
}
Commands::Status { job_id } => {
let job_id = JobId::from_string(job_id);
let status = engine.get_job_status(&job_id).await?;
println!("Status: {status}");
}
Commands::Cancel { job_id } => {
let job_id = JobId::from_string(job_id);
engine.cancel_job(&job_id).await?;
println!("Job cancelled");
}
Commands::Watch { folder, template } => {
println!("Watching folder: {}", folder.display());
let watch_config = crate::watch::WatchConfig::new(folder)
.with_template(template)
.with_pattern("*.mp4".to_string());
let watch_folder = crate::watch::WatchFolder::new(watch_config, engine);
watch_folder.start().await?;
}
Commands::Serve { addr } => {
println!("Starting API server on {addr}");
crate::api::start_server(engine, &addr).await?;
}
}
Ok(())
}
fn parse_operation(operation: &str) -> Result<BatchOperation> {
match operation {
"copy" => Ok(BatchOperation::FileOp {
operation: FileOperation::Copy { overwrite: false },
}),
"move" => Ok(BatchOperation::FileOp {
operation: FileOperation::Move { overwrite: false },
}),
"transcode" => Ok(BatchOperation::Transcode {
preset: "default".to_string(),
}),
"qc" => Ok(BatchOperation::QualityCheck {
profile: "default".to_string(),
}),
_ => Err(crate::error::BatchError::ValidationError(format!(
"Unknown operation: {operation}"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_operation_copy() {
let result = parse_operation("copy");
assert!(result.is_ok());
}
#[test]
fn test_parse_operation_move() {
let result = parse_operation("move");
assert!(result.is_ok());
}
#[test]
fn test_parse_operation_transcode() {
let result = parse_operation("transcode");
assert!(result.is_ok());
}
#[test]
fn test_parse_operation_qc() {
let result = parse_operation("qc");
assert!(result.is_ok());
}
#[test]
fn test_parse_operation_unknown() {
let result = parse_operation("unknown");
assert!(result.is_err());
}
}