diff --git a/vidformer-cli/src/bench.rs b/vidformer-cli/src/bench.rs index 1a4be79..eac3bcb 100644 --- a/vidformer-cli/src/bench.rs +++ b/vidformer-cli/src/bench.rs @@ -3,7 +3,7 @@ use glob::glob; use serde::Deserialize; use std::collections::BTreeMap; use std::sync::Arc; -use vidformer::run_spec; +use vidformer::run; #[derive(Deserialize)] struct DveBench { @@ -69,11 +69,11 @@ pub(crate) fn cmd_benchmark(opt: &BenchmarkCmd) { println!("Running spec {} benches", entry.display()); for _i in 0..opt.warmup_runs { - run_spec(&spec, "/tmp/output.mp4", &context, &config, &None).unwrap(); + run(&spec, "/tmp/output.mp4", &context, &config, &None).unwrap(); } for i in 0..opt.runs { - let stat = run_spec(&spec, "/tmp/output.mp4", &context, &config, &None).unwrap(); + let stat = run(&spec, "/tmp/output.mp4", &context, &config, &None).unwrap(); let benchmark_stat = BenchmarkStat { bench: entry.display().to_string(), diff --git a/vidformer-cli/src/main.rs b/vidformer-cli/src/main.rs index 600e5c1..1cc4101 100644 --- a/vidformer-cli/src/main.rs +++ b/vidformer-cli/src/main.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand}; use num_rational::Rational64; use std::{collections::BTreeMap, fs::File, io::Write}; -use vidformer::{filter, run_spec, sir, source, spec}; +use vidformer::{filter, run, sir, source, spec}; mod bench; mod yrden; @@ -177,7 +177,7 @@ fn cmd_x() { let dve_config = std::sync::Arc::new(dve_config); println!("Running spec..."); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); dbg!(&stats); diff --git a/vidformer-cli/src/yrden.rs b/vidformer-cli/src/yrden.rs index 755b31b..0397f82 100644 --- a/vidformer-cli/src/yrden.rs +++ b/vidformer-cli/src/yrden.rs @@ -382,6 +382,7 @@ async fn yrden_http_req( serde_json::from_reader(spec_content).expect("Unable to parse JSON"); let spec: Box = Box::new(spec); + let spec = std::sync::Arc::new(spec); let mut filters = crate::default_filters(); for (name, filter) in request.filters { @@ -423,18 +424,34 @@ async fn yrden_http_req( }; let dve_config = std::sync::Arc::new(dve_config); + // Validate the spec + let validation_status = vidformer::validate(&spec, &context, &dve_config); + if let Err(err) = validation_status { + return Ok(hyper::Response::builder() + .status(hyper::StatusCode::BAD_REQUEST) + .header("Access-Control-Allow-Origin", "*") + .body(http_body_util::Full::new(hyper::body::Bytes::from( + format!("Error validating spec: {}", err), + ))) + .unwrap()); + } + let host_prefix = { let global: std::sync::MutexGuard<'_, YrdenGlobal> = global.lock().unwrap(); global.host_prefix.clone() }; - let (namespace_id, playlist, stream, ranges) = - vidformer::create_spec_hls(spec.as_ref(), &host_prefix, &context, &dve_config); + let (namespace_id, playlist, stream, ranges) = vidformer::create_spec_hls( + spec.as_ref().as_ref(), + &host_prefix, + &context, + &dve_config, + ); let namespace = YrdenNamespace { context, dve_config, - spec: std::sync::Arc::new(spec), + spec, playlist, stream, ranges, @@ -625,7 +642,7 @@ async fn yrden_http_req( let spec = std::sync::Arc::new(spec); let spec_result = tokio::task::spawn_blocking(move || { - vidformer::run_spec(&spec, &output_path2, &context, &dve_config, &None) + vidformer::run(&spec, &output_path2, &context, &dve_config, &None) }) .await .unwrap(); @@ -748,7 +765,7 @@ async fn yrden_http_req( // todo, don't unwrap let spec_result = tokio::task::spawn_blocking(move || { - vidformer::run_spec( + vidformer::run( &spec, &tmp_path_2, &context, @@ -847,7 +864,7 @@ async fn yrden_http_req( // todo, don't unwrap let spec_result = tokio::task::spawn_blocking(move || { - vidformer::run_spec( + vidformer::run( &spec, &tmp_path_2, &context, diff --git a/vidformer/src/dve.rs b/vidformer/src/dve.rs index b780b5f..dc54c64 100644 --- a/vidformer/src/dve.rs +++ b/vidformer/src/dve.rs @@ -1040,8 +1040,8 @@ impl ExecContext { } } -/// Execute a vidformer spec -pub fn run_spec( +/// Execute a spec, or a range of a spec. +pub fn run( spec: &Arc>, output_path: &str, context: &Arc, @@ -1117,6 +1117,31 @@ pub fn run_spec( exec_contex.run() } +/// Validate that a spec can be run. +pub fn validate( + spec: &Arc>, + context: &Arc, + config: &Arc, +) -> Result<(), Error> { + let expected_output_type = config.expected_output_type(); + + let process_span = Arc::new(crate::sir::ProcessSpan::create( + spec.as_ref().as_ref(), + context, + &None, + )); + + // Type check frames + for oframe in &process_span.frames { + let frame_type = type_frame(context, config, oframe)?; + if frame_type != expected_output_type { + return Err(Error::InvalidOutputFrameType); + } + } + + Ok(()) +} + fn encoder_thread( config: Arc, stat: Arc, diff --git a/vidformer/src/lib.rs b/vidformer/src/lib.rs index 4c20e99..8300d12 100644 --- a/vidformer/src/lib.rs +++ b/vidformer/src/lib.rs @@ -11,6 +11,7 @@ mod pool; mod util; pub use dve::{ - create_spec_hls, run_spec, Config, Context, EncoderConfig, Error, Range, RangeTsFormat, Stats, + create_spec_hls, run, validate, Config, Context, EncoderConfig, Error, Range, RangeTsFormat, + Stats, }; pub use util::{codecs, init, CodecDescriptor}; diff --git a/vidformer/tests/basic_tests.rs b/vidformer/tests/basic_tests.rs index 3b7a4e0..b631670 100644 --- a/vidformer/tests/basic_tests.rs +++ b/vidformer/tests/basic_tests.rs @@ -115,7 +115,7 @@ fn test_placeholder() { let spec = std::sync::Arc::new(spec); let context = std::sync::Arc::new(context); let dve_config = std::sync::Arc::new(dve_config); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert_eq!(stats.max_decoder_count, 0); assert_eq!(stats.frames_written, 24 * 3); @@ -169,7 +169,7 @@ fn test_bad_resolution() { let context = std::sync::Arc::new(context); let dve_config = std::sync::Arc::new(dve_config); assert!(matches!( - run_spec( + run( &spec, test_output_path!(test_bad_resolution), &context, @@ -227,7 +227,7 @@ fn test_non_existant_source() { let context = std::sync::Arc::new(context); let dve_config = std::sync::Arc::new(dve_config); - let ret = run_spec( + let ret = run( &spec, test_output_path!(test_non_existant_source), &context, @@ -299,7 +299,7 @@ fn test_no_source_file() { let context = std::sync::Arc::new(context); let dve_config = std::sync::Arc::new(dve_config); - let ret = run_spec( + let ret = run( &spec, test_output_path!(test_non_existant_source), &context, @@ -371,7 +371,7 @@ fn test_tos_transcode_1dec() { num_frames: NUM_FRAMES, })); let output_path = test_output_path!(test_tos_transcode_1dec); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert_eq!(stats.max_decoder_count, 1); assert_eq!(stats.frames_written, NUM_FRAMES as usize); @@ -400,7 +400,7 @@ fn test_tos_transcode_2dec() { num_frames: NUM_FRAMES, })); let output_path = test_output_path!(test_tos_transcode_2dec); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert!(stats.max_decoder_count >= 1 && stats.max_decoder_count <= 2); assert_eq!(stats.frames_written, NUM_FRAMES as usize); @@ -429,7 +429,7 @@ fn test_tos_transcode_4dec() { num_frames: NUM_FRAMES, })); let output_path = test_output_path!(test_tos_transcode_4dec); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert!(stats.max_decoder_count >= 1 && stats.max_decoder_count <= 4); assert_eq!(stats.frames_written, NUM_FRAMES as usize); @@ -458,7 +458,7 @@ fn test_tos_transcode_manydec() { num_frames: NUM_FRAMES, })); let output_path = test_output_path!(test_tos_transcode_manydec); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert!(stats.max_decoder_count >= 1 && stats.max_decoder_count <= 100); assert_eq!(stats.frames_written, NUM_FRAMES as usize); @@ -486,7 +486,7 @@ fn test_tos_transcode_1dec_1pool() { let spec: std::sync::Arc> = std::sync::Arc::new(Box::new(ClipSpec { num_frames: 2 * 24 })); // make sure we only need one source GOP let output_path = test_output_path!(test_tos_transcode_1dec_1pool); - let stats = run_spec(&spec, output_path, &context, &dve_config, &None).unwrap(); + let stats = run(&spec, output_path, &context, &dve_config, &None).unwrap(); assert_eq!(stats.max_decoder_count, 1); assert_eq!(stats.decoders_created, 1); // this is just a basic streaming edit. if our algorithm works it should just decode the one needed GOP