diff options
| author | metamuffin <metamuffin@disroot.org> | 2025-11-30 12:32:44 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2025-11-30 12:32:44 +0100 |
| commit | 8174d129fbabd2d39323678d11d868893ddb429a (patch) | |
| tree | 7979a528114cd5fb827f748f678a916e8e8eeddc /transcoder/src | |
| parent | 5db15c323d76dca9ae71b0204d63dcb09fbbcbc5 (diff) | |
| download | jellything-8174d129fbabd2d39323678d11d868893ddb429a.tar jellything-8174d129fbabd2d39323678d11d868893ddb429a.tar.bz2 jellything-8174d129fbabd2d39323678d11d868893ddb429a.tar.zst | |
new sync cache
Diffstat (limited to 'transcoder/src')
| -rw-r--r-- | transcoder/src/fragment.rs | 77 | ||||
| -rw-r--r-- | transcoder/src/image.rs | 140 | ||||
| -rw-r--r-- | transcoder/src/lib.rs | 2 | ||||
| -rw-r--r-- | transcoder/src/thumbnail.rs | 56 |
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") } |