use std::path::PathBuf;
use clap::Parser;
use multimux::config::{Config, Route};
use multimux::{MultimuxError, Result};
#[derive(Parser)]
#[command(
name = "multimux",
version,
about = "Live RTSP -> LL-HLS just-in-time repackaging HTTP origin",
long_about = "Pulls one or more live RTSP sources and serves each as LL-HLS \
(RFC 8216bis) from an in-process HTTP origin.\n\
Either point it at a JSON config file (--config) describing one or \
more routes, or use the single-route quick start (--rtsp + --name)."
)]
struct Cli {
#[arg(long, value_name = "FILE", conflicts_with_all = ["rtsp", "name"])]
config: Option<PathBuf>,
#[arg(long, value_name = "URL", requires = "name")]
rtsp: Option<String>,
#[arg(long, value_name = "NAME", requires = "rtsp")]
name: Option<String>,
#[arg(long, value_name = "ADDR", default_value_t = Config::default().bind)]
bind: String,
#[arg(long, value_name = "SECS", default_value_t = Config::default().target_duration_secs)]
target_duration: f64,
#[arg(long, value_name = "MS", default_value_t = Config::default().part_target_ms)]
part_ms: u32,
#[arg(long, value_name = "N", default_value_t = Config::default().window_segments)]
window: usize,
}
fn build_config(cli: Cli) -> Result<Config> {
if let Some(path) = cli.config {
return Config::from_json_file(&path);
}
let rtsp_url = cli.rtsp.ok_or_else(|| {
MultimuxError::Config(
"either --config <FILE> or --rtsp <URL> --name <NAME> is required".into(),
)
})?;
let name = cli
.name
.expect("clap requires --name whenever --rtsp is given");
let config = Config {
bind: cli.bind,
target_duration_secs: cli.target_duration,
part_target_ms: cli.part_ms,
window_segments: cli.window,
routes: vec![Route { name, rtsp_url }],
};
config.validate()?;
Ok(config)
}
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("error: {e}");
std::process::exit(1);
}
}
async fn run() -> Result<()> {
let cli = Cli::parse();
let config = build_config(cli)?;
multimux::origin::serve(config).await
}