/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ use crate::LOCAL_VIDEO_TRANSCODING_TASKS; use jellybase::{ cache::{async_cache_file, CachePath}, common::stream::{StreamFormatInfo, TrackKind}, CONF, }; use log::{debug, info}; use std::process::Stdio; use tokio::{ io::copy, process::{ChildStdin, Command}, }; // TODO odd video resolutions can cause errors when transcoding to YUV42{0,2} // TODO with an implementation that cant handle it (SVT-AV1 is such an impl). pub async fn transcode( key: &str, kind: TrackKind, format: &StreamFormatInfo, input: impl FnOnce(ChildStdin), ) -> anyhow::Result { async_cache_file( &["frag-tc", key, &format!("{format:?}")], move |mut output| async move { let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?; debug!("transcoding fragment with {format:?}"); let template = match format.codec.as_str() { "V_MPEG4/ISO/AVC" => CONF.encoders.avc.as_ref(), "V_MPEGH/ISO/HEVC" => CONF.encoders.hevc.as_ref(), "V_VP8" => CONF.encoders.vp8.as_ref(), "V_VP9" => CONF.encoders.vp9.as_ref(), "V_AV1" => CONF.encoders.av1.as_ref(), _ => None, } .or(CONF.encoders.generic.as_ref()) .cloned() .unwrap_or("ffmpeg %i %f %e %o".to_owned()); let filter = match kind { TrackKind::Video => format!("-vf scale={}:-1", format.width.unwrap()), TrackKind::Audio => format!(""), TrackKind::Subtitle => String::new(), }; let typechar = match kind { TrackKind::Video => "v", TrackKind::Audio => "a", TrackKind::Subtitle => "s", }; let fallback_encoder = match format.codec.as_str() { "A_OPUS" => "libopus", "V_VP8" => "libvpx", "V_VP9" => "libvpx-vp9", "V_AV1" => "libaom", // svtav1 is x86 only :( "V_MPEG4/ISO/AVC" => "libx264", "V_MPEGH/ISO/HEVC" => "libx265", _ => "", }; let args = template .replace("%i", "-f matroska -i pipe:0 -copyts") .replace("%o", "-f matroska pipe:1") .replace("%f", &filter) .replace("%e", "-c:%t %c -b:%t %r") .replace("%t", typechar) .replace("%c", fallback_encoder) .replace("%r", &(format.bitrate as i64).to_string()) .replace(" ", " "); info!("encoding with {:?}", args); let mut args = args.split(" "); let mut proc = Command::new(args.next().unwrap()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .args(args) .spawn()?; let stdin = proc.stdin.take().unwrap(); let mut stdout = proc.stdout.take().unwrap(); input(stdin); copy(&mut stdout, &mut output).await?; proc.wait().await.unwrap().exit_ok()?; info!("done"); Ok(()) }, ) .await }