aboutsummaryrefslogtreecommitdiff
path: root/transcoder/src
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-11-30 12:32:44 +0100
committermetamuffin <metamuffin@disroot.org>2025-11-30 12:32:44 +0100
commit8174d129fbabd2d39323678d11d868893ddb429a (patch)
tree7979a528114cd5fb827f748f678a916e8e8eeddc /transcoder/src
parent5db15c323d76dca9ae71b0204d63dcb09fbbcbc5 (diff)
downloadjellything-8174d129fbabd2d39323678d11d868893ddb429a.tar
jellything-8174d129fbabd2d39323678d11d868893ddb429a.tar.bz2
jellything-8174d129fbabd2d39323678d11d868893ddb429a.tar.zst
new sync cache
Diffstat (limited to 'transcoder/src')
-rw-r--r--transcoder/src/fragment.rs77
-rw-r--r--transcoder/src/image.rs140
-rw-r--r--transcoder/src/lib.rs2
-rw-r--r--transcoder/src/thumbnail.rs56
4 files changed, 124 insertions, 151 deletions
diff --git a/transcoder/src/fragment.rs b/transcoder/src/fragment.rs
index 152e163..564c94d 100644
--- a/transcoder/src/fragment.rs
+++ b/transcoder/src/fragment.rs
@@ -5,17 +5,17 @@
*/
use crate::{Config, CONF, LOCAL_VIDEO_TRANSCODING_TASKS};
use anyhow::Result;
-use jellycache::cache_file;
+use jellycache::{cache, CacheContentType, CacheKey};
use jellyremuxer::{demuxers::create_demuxer, muxers::write_fragment, ContainerFormat};
use jellystream_types::{StreamFormatInfo, TrackKind};
use log::info;
-use std::fmt::Write;
-use std::fs::File;
-use std::io::{copy, Write as W2};
-use std::process::{Command, Stdio};
-use std::thread::spawn;
-use winter_matroska::block::Block;
-use winter_matroska::{Cluster, Segment, TrackEntry as MatroskaTrackEntry};
+use std::{
+ fmt::Write,
+ io::{Cursor, Read, Write as W2},
+ process::{Command, Stdio},
+ thread::spawn,
+};
+use winter_matroska::{block::Block, Cluster, Segment, TrackEntry as MatroskaTrackEntry};
// 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).
@@ -38,41 +38,40 @@ pub fn transcode(
let input_duration = input.info.duration;
let had_next_kf = next_kf.is_some();
- let output = cache_file("frag-tc", (input_key, &command), |mut output| {
- let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.lock().unwrap();
- info!("encoding with {command:?}");
- let mut args = command.split(" ");
- let mut proc = Command::new(args.next().unwrap())
- .stdin(Stdio::piped())
- .stdout(Stdio::piped())
- .args(args)
- .spawn()?;
+ let output = cache(
+ CacheKey::new(CacheContentType::Unknown, ("frag-tc", input_key, &command)),
+ || {
+ let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.lock().unwrap();
+ info!("encoding with {command:?}");
+ let mut args = command.split(" ");
+ let mut proc = Command::new(args.next().unwrap())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .args(args)
+ .spawn()?;
- let mut stdin = proc.stdin.take().unwrap();
- let mut stdout = proc.stdout.take().unwrap();
+ let mut stdin = proc.stdin.take().unwrap();
+ let mut stdout = proc.stdout.take().unwrap();
- spawn(move || {
- copy(&mut stdout, &mut output).unwrap();
- });
+ spawn(move || {
+ input.clusters.extend(next_kf.map(|kf| Cluster {
+ simple_blocks: vec![kf],
+ ..Default::default()
+ }));
+ write_fragment(ContainerFormat::Matroska, &mut stdin, input).unwrap(); // TODO
+ stdin.flush().unwrap();
+ drop(stdin);
+ });
- input.clusters.extend(next_kf.map(|kf| Cluster {
- simple_blocks: vec![kf],
- ..Default::default()
- }));
+ let mut output = Vec::new();
+ stdout.read_to_end(&mut output)?;
+ proc.wait().unwrap().exit_ok()?;
+ info!("done");
+ Ok(output)
+ },
+ )?;
- write_fragment(ContainerFormat::Matroska, &mut stdin, input)?;
- stdin.flush()?;
- drop(stdin);
-
- proc.wait().unwrap().exit_ok()?;
- info!("done");
- Ok(())
- })?;
-
- let mut demuxer = create_demuxer(
- ContainerFormat::Matroska,
- Box::new(File::open(output.abs())?),
- );
+ let mut demuxer = create_demuxer(ContainerFormat::Matroska, Box::new(Cursor::new(output)));
let mut info = demuxer.info()?;
info.duration = input_duration;
diff --git a/transcoder/src/image.rs b/transcoder/src/image.rs
index 6a7f693..8366ece 100644
--- a/transcoder/src/image.rs
+++ b/transcoder/src/image.rs
@@ -3,96 +3,66 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
-use crate::LOCAL_IMAGE_TRANSCODING_TASKS;
-use anyhow::Context;
+use anyhow::{anyhow, Context, Result};
use image::imageops::FilterType;
-use jellycache::{async_cache_file, CachePath};
+use jellycache::{cache, cache_read, CacheKey};
use log::{debug, info};
use rgb::FromSlice;
-use std::{
- fs::File,
- io::{BufReader, Read, Seek, SeekFrom},
- path::Path,
-};
-use tokio::io::AsyncWriteExt;
+use std::io::Cursor;
-pub async fn transcode(
- path: &Path,
- quality: f32,
- speed: u8,
- width: usize,
-) -> anyhow::Result<CachePath> {
- async_cache_file(
- "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")?);
+pub fn transcode(key: CacheKey, quality: f32, speed: u8, width: usize) -> Result<Vec<u8>> {
+ cache(
+ CacheKey::new_image(("image-tc", key, width, quality as i32, speed)),
+ move || {
+ let input = cache_read(key)?.ok_or(anyhow!("transcode cache key missing"))?;
+ info!("encoding image (speed={speed}, quality={quality}, width={width})");
- // TODO: use better image library that supports AVIF
- let is_avif = {
- let mut magic = [0u8; 12];
- let _ = file.read_exact(&mut magic);
- file.seek(SeekFrom::Start(0))
- .context("seeking back to start")?;
- // TODO: magic experimentally found and probably not working in all cases but fine as long as our avif enc uses that
- matches!(
- magic,
- [
- 0x00,
- 0x00,
- 0x00,
- _,
- b'f',
- b't',
- b'y',
- b'p',
- b'a',
- b'v',
- b'i',
- b'f',
- ]
- )
- };
- let original = if is_avif {
- let mut buf = Vec::new();
- file.read_to_end(&mut buf).context("reading image")?;
- libavif_image::read(&buf).unwrap().to_rgba8()
- } else {
- let reader = image::ImageReader::new(file);
- let reader = reader.with_guessed_format().context("guessing format")?;
- debug!("guessed format (or fallback): {:?}", reader.format());
- reader.decode().context("decoding image")?.to_rgba8()
- };
- let image = image::imageops::resize(
- &original,
- width as u32,
- width as u32 * original.height() / original.width(),
- FilterType::Lanczos3,
- );
- let pixels = image.to_vec();
- let encoded = ravif::Encoder::new()
- .with_speed(speed.clamp(1, 10))
- .with_quality(quality.clamp(1., 100.))
- .encode_rgba(imgref::Img::new(
- pixels.as_rgba(),
- image.width() as usize,
- image.height() as usize,
- ))
- .context("avif encoding")?;
- info!("transcode finished");
- Ok::<_, anyhow::Error>(encoded)
- })
- .await??;
- output
- .write_all(&encoded.avif_file)
- .await
- .context("writing encoded image")?;
- Ok(())
+ // TODO: use better image library that supports AVIF
+ // TODO: magic experimentally found and probably not working in all cases but fine as long as our avif enc uses that
+ let is_avif = matches!(
+ input[0..input.len().min(12)],
+ [
+ 0x00,
+ 0x00,
+ 0x00,
+ _,
+ b'f',
+ b't',
+ b'y',
+ b'p',
+ b'a',
+ b'v',
+ b'i',
+ b'f',
+ ]
+ );
+ let original = if is_avif {
+ libavif_image::read(&input).unwrap().to_rgba8()
+ } else {
+ let reader = image::ImageReader::new(Cursor::new(input));
+ let reader = reader.with_guessed_format().context("guessing format")?;
+ debug!("guessed format (or fallback): {:?}", reader.format());
+ reader.decode().context("decoding image")?.to_rgba8()
+ };
+ let image = image::imageops::resize(
+ &original,
+ width as u32,
+ width as u32 * original.height() / original.width(),
+ FilterType::Lanczos3,
+ );
+ let pixels = image.to_vec();
+ let encoded = ravif::Encoder::new()
+ .with_speed(speed.clamp(1, 10))
+ .with_quality(quality.clamp(1., 100.))
+ .encode_rgba(imgref::Img::new(
+ pixels.as_rgba(),
+ image.width() as usize,
+ image.height() as usize,
+ ))
+ .context("avif encoding")?;
+
+ info!("transcode finished");
+ Ok(encoded.avif_file)
},
)
- .await
}
diff --git a/transcoder/src/lib.rs b/transcoder/src/lib.rs
index 1eac15b..cd8edbf 100644
--- a/transcoder/src/lib.rs
+++ b/transcoder/src/lib.rs
@@ -7,7 +7,6 @@
use serde::{Deserialize, Serialize};
use std::sync::{LazyLock, Mutex};
-use tokio::sync::Semaphore;
pub mod fragment;
pub mod image;
@@ -36,5 +35,4 @@ static CONF: LazyLock<Config> = LazyLock::new(|| {
.expect("transcoder config not preloaded. logic error")
});
-static LOCAL_IMAGE_TRANSCODING_TASKS: Semaphore = Semaphore::const_new(8);
static LOCAL_VIDEO_TRANSCODING_TASKS: Mutex<()> = Mutex::new(());
diff --git a/transcoder/src/thumbnail.rs b/transcoder/src/thumbnail.rs
index 8cefac3..eda9e04 100644
--- a/transcoder/src/thumbnail.rs
+++ b/transcoder/src/thumbnail.rs
@@ -1,31 +1,37 @@
-use crate::LOCAL_IMAGE_TRANSCODING_TASKS;
-use jellycache::{async_cache_file, CachePath};
+use anyhow::{Context, Result};
+use jellycache::{cache_store, CacheKey};
use log::info;
-use std::{path::Path, process::Stdio};
-use tokio::{io::copy, process::Command};
+use std::{
+ io::Read,
+ path::Path,
+ process::{Command, Stdio},
+};
-pub async fn create_thumbnail(path: &Path, time: f64) -> anyhow::Result<CachePath> {
- 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",);
+pub fn create_thumbnail(path: &Path, time: f64) -> Result<CacheKey> {
+ cache_store(
+ CacheKey::new_image(("thumbnail", path, time as i64)),
+ move || {
+ 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();
+ let mut output = Vec::new();
+ stdout.read_to_end(&mut output)?;
- proc.wait().await.unwrap().exit_ok()?;
- info!("done");
- Ok(())
- })
- .await
+ proc.wait().unwrap().exit_ok()?;
+ info!("done");
+ Ok(output)
+ },
+ )
+ .context("creating thumbnail")
}