use anyhow::Context;
use crate::Net;
use crate::args::MoqSide;
use hang::moq_net;
#[derive(clap::Args, Clone)]
pub struct Args {
#[arg(long)]
pub output: Option<String>,
#[arg(long = "rung", value_parser = parse_rung)]
pub rungs: Vec<moq_transcode::Rung>,
#[arg(long, default_value = "auto")]
pub encoder: String,
#[arg(long, default_value = "auto")]
pub decoder: String,
}
fn parse_rung(arg: &str) -> Result<moq_transcode::Rung, String> {
let (height, bitrate) = arg
.split_once(':')
.ok_or_else(|| format!("expected height:bitrate, got `{arg}`"))?;
let height: u32 = height.parse().map_err(|e| format!("invalid height `{height}`: {e}"))?;
let bitrate: u64 = bitrate
.parse()
.map_err(|e| format!("invalid bitrate `{bitrate}`: {e}"))?;
Ok(moq_transcode::Rung::new(height, bitrate))
}
pub async fn run(moq: MoqSide, args: Args, net: Net) -> anyhow::Result<()> {
let source_path = moq
.broadcast
.clone()
.filter(|name| !name.is_empty())
.context("`transcode` requires the source broadcast: pass --broadcast <name>")?;
let output_path = args
.output
.clone()
.unwrap_or_else(|| format!("{source_path}/transcode.hang"));
let url = moq
.client
.connect
.clone()
.context("`transcode` requires a relay: pass --client-connect <url>")?;
let publish = moq_net::Origin::random().produce();
let remote = moq_net::Origin::random().produce();
let mut session = net
.client(moq.client.clone())?
.with_publisher(&publish)
.with_subscriber(remote.clone())
.reconnect(url);
while !matches!(session.status().await?, moq_native::Status::Connected) {}
let source = remote
.consume()
.request_broadcast(&source_path)
.await
.context("source broadcast unavailable")?;
let mut config = moq_transcode::Config::default();
if !args.rungs.is_empty() {
config.rungs = args.rungs.clone();
}
config.encoder = match args.encoder.as_str() {
"auto" => moq_video::encode::Kind::Auto,
"hardware" => moq_video::encode::Kind::Hardware,
"software" => moq_video::encode::Kind::Software,
name => moq_video::encode::Kind::Named(name.to_string()),
};
config.decoder = match args.decoder.as_str() {
"auto" => moq_video::decode::Kind::Auto,
"hardware" => moq_video::decode::Kind::Hardware,
"software" => moq_video::decode::Kind::Software,
name => moq_video::decode::Kind::Named(name.to_string()),
};
config.source = output_path.strip_prefix(&format!("{source_path}/")).map(|rest| {
let depth = rest.split('/').count();
moq_net::PathRelativeOwned::from(vec![".."; depth].join("/"))
});
let output = publish
.create_broadcast(&output_path, moq_net::broadcast::Route::new().with_announce(true))
.context("failed to create the derivative broadcast")?;
tracing::info!(source = %source_path, output = %output_path, "transcoding");
tokio::select! {
res = moq_transcode::run(source, output, config) => Ok(res?),
res = session.closed() => Ok(res?),
}
}