1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
use crate::LOCAL_IMAGE_TRANSCODING_TASKS;
use anyhow::Context;
use image::imageops::FilterType;
use jellybase::{cache::async_cache_file, AssetLocationExt};
use jellycommon::AssetLocation;
use log::{debug, info};
use rgb::FromSlice;
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
};
use tokio::io::AsyncWriteExt;
pub async fn transcode(
asset: AssetLocation,
quality: f32,
speed: u8,
width: usize,
) -> anyhow::Result<AssetLocation> {
let original_path = asset.path();
let asset = asset.clone();
Ok(async_cache_file(
&[
"image-tc",
original_path.as_os_str().to_str().unwrap(),
&format!("{width} {quality} {speed}"),
],
move |mut output| async move {
let _permit = LOCAL_IMAGE_TRANSCODING_TASKS.acquire().await?;
info!("encoding {asset:?} (speed={speed}, quality={quality}, width={width})");
let encoded = tokio::task::spawn_blocking(move || {
let original_path = asset.path();
let mut file =
BufReader::new(File::open(&original_path).context("opening source")?);
// TODO: use better image library that supports AVIF
let is_avif = {
let mut magic = [0u8; 12];
file.read_exact(&mut magic).context("reading 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
magic
== [
0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66,
]
};
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::io::Reader::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(())
},
)
.await?)
}
|