aboutsummaryrefslogtreecommitdiff
path: root/transcoder/src
diff options
context:
space:
mode:
Diffstat (limited to 'transcoder/src')
-rw-r--r--transcoder/src/fragment.rs120
-rw-r--r--transcoder/src/image.rs14
-rw-r--r--transcoder/src/thumbnail.rs41
3 files changed, 83 insertions, 92 deletions
diff --git a/transcoder/src/fragment.rs b/transcoder/src/fragment.rs
index 88a311e..3e07ad7 100644
--- a/transcoder/src/fragment.rs
+++ b/transcoder/src/fragment.rs
@@ -3,7 +3,6 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-
use crate::LOCAL_VIDEO_TRANSCODING_TASKS;
use jellybase::{
cache::{async_cache_file, CachePath},
@@ -21,78 +20,75 @@ use tokio::{
// 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_key: &str,
input: impl FnOnce(ChildStdin),
) -> anyhow::Result<CachePath> {
- 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 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 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(" ", " ");
- 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(" ", " ");
+ async_cache_file("frag-tc", (input_key, &args), async |mut output| {
+ let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?;
+ debug!("transcoding fragment with {format:?}");
- info!("encoding with {:?}", args);
+ 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 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();
+ let stdin = proc.stdin.take().unwrap();
+ let mut stdout = proc.stdout.take().unwrap();
- input(stdin);
- copy(&mut stdout, &mut output).await?;
+ input(stdin);
+ copy(&mut stdout, &mut output).await?;
- proc.wait().await.unwrap().exit_ok()?;
- info!("done");
- Ok(())
- },
- )
+ proc.wait().await.unwrap().exit_ok()?;
+ info!("done");
+ Ok(())
+ })
.await
}
diff --git a/transcoder/src/image.rs b/transcoder/src/image.rs
index 28b253a..c6e1367 100644
--- a/transcoder/src/image.rs
+++ b/transcoder/src/image.rs
@@ -12,25 +12,23 @@ use rgb::FromSlice;
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
- path::PathBuf,
+ path::Path,
};
use tokio::io::AsyncWriteExt;
pub async fn transcode(
- path: PathBuf,
+ path: &Path,
quality: f32,
speed: u8,
width: usize,
) -> anyhow::Result<CachePath> {
async_cache_file(
- &[
- "image-tc",
- path.clone().as_os_str().to_str().unwrap(),
- &format!("{width} {quality} {speed}"),
- ],
- move |mut output| async move {
+ "image-tc",
+ (path, width, quality as i32, speed),
+ |mut output| async move {
let _permit = LOCAL_IMAGE_TRANSCODING_TASKS.acquire().await?;
info!("encoding {path:?} (speed={speed}, quality={quality}, width={width})");
+ let path = path.to_owned();
let encoded = tokio::task::spawn_blocking(move || {
let mut file = BufReader::new(File::open(&path).context("opening source")?);
diff --git a/transcoder/src/thumbnail.rs b/transcoder/src/thumbnail.rs
index c8bfb1c..caef397 100644
--- a/transcoder/src/thumbnail.rs
+++ b/transcoder/src/thumbnail.rs
@@ -5,30 +5,27 @@ use std::{path::Path, process::Stdio};
use tokio::{io::copy, process::Command};
pub async fn create_thumbnail(path: &Path, time: f64) -> anyhow::Result<CachePath> {
- async_cache_file(
- &["thumb", path.to_str().unwrap(), &format!("{time}")],
- move |mut output| async move {
- let _permit = LOCAL_IMAGE_TRANSCODING_TASKS.acquire().await?;
- info!("creating thumbnail of {path:?} at {time}s",);
+ async_cache_file("thumb", (path, time as i64), move |mut output| async move {
+ let _permit = LOCAL_IMAGE_TRANSCODING_TASKS.acquire().await?;
+ info!("creating thumbnail of {path:?} at {time}s",);
- let mut proc = Command::new("ffmpeg")
- .stdout(Stdio::piped())
- .args(["-ss", &format!("{time}")])
- .args(["-f", "matroska", "-i", path.to_str().unwrap()])
- .args(["-frames:v", "1"])
- .args(["-c:v", "qoi"])
- .args(["-f", "image2"])
- .args(["-update", "1"])
- .arg("pipe:1")
- .spawn()?;
+ let mut proc = Command::new("ffmpeg")
+ .stdout(Stdio::piped())
+ .args(["-ss", &format!("{time}")])
+ .args(["-f", "matroska", "-i", path.to_str().unwrap()])
+ .args(["-frames:v", "1"])
+ .args(["-c:v", "qoi"])
+ .args(["-f", "image2"])
+ .args(["-update", "1"])
+ .arg("pipe:1")
+ .spawn()?;
- let mut stdout = proc.stdout.take().unwrap();
- copy(&mut stdout, &mut output).await?;
+ let mut stdout = proc.stdout.take().unwrap();
+ copy(&mut stdout, &mut output).await?;
- proc.wait().await.unwrap().exit_ok()?;
- info!("done");
- Ok(())
- },
- )
+ proc.wait().await.unwrap().exit_ok()?;
+ info!("done");
+ Ok(())
+ })
.await
}