#![allow(dead_code)]
use crate::config::{ExtraParams, JxlifyConfig};
use crate::helper::{hash_bytes, hash_file, hash_string};
use image::GenericImageView;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ImageMeta {
pub width: u32,
pub height: u32,
pub format: String,
pub size: usize,
pub num_pages: usize,
pub blurhash: String,
pub colorspace: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MetaFile {
pub id: String,
pub path: String,
pub checksum: String,
#[serde(flatten)]
pub meta: ImageMeta,
}
pub fn get_metadata_id(
req_path: &str,
extra: &ExtraParams,
config: &JxlifyConfig,
subdir: &str,
) -> (String, PathBuf) {
let sanitized_query = format!(
"{}?width={}&height={}&max_width={}&max_height={}",
req_path,
extra.width.map(|v| v.to_string()).unwrap_or_default(),
extra.height.map(|v| v.to_string()).unwrap_or_default(),
extra.max_width.map(|v| v.to_string()).unwrap_or_default(),
extra.max_height.map(|v| v.to_string()).unwrap_or_default(),
);
let id = hash_string(&sanitized_query);
let meta_path = crate::helper::get_sharded_path(
&config.metadata_cache_path(),
subdir,
&id,
"json",
);
(id, meta_path)
}
pub fn read_or_build_metadata(
req_path: &str,
raw_path: &Path,
extra: &ExtraParams,
config: &JxlifyConfig,
subdir: &str,
) -> MetaFile {
let (id, meta_json_path) = get_metadata_id(req_path, extra, config, subdir);
if meta_json_path.exists() {
if let Ok(file) = File::open(&meta_json_path) {
let reader = BufReader::new(file);
if let Ok(meta_file) = serde_json::from_reader::<_, MetaFile>(reader) {
if let Ok(current_checksum) = hash_file(raw_path) {
if meta_file.checksum == current_checksum {
return meta_file;
}
debug!("Source file checksum changed for {}, rebuilding metadata", req_path);
} else {
return meta_file;
}
}
}
}
let meta_file = build_metadata(&id, req_path, raw_path);
save_metadata(&meta_json_path, &meta_file);
meta_file
}
pub fn build_metadata(id: &str, req_path: &str, raw_path: &Path) -> MetaFile {
let checksum = hash_file(raw_path).unwrap_or_default();
let file_size = std::fs::metadata(raw_path).map(|m| m.len() as usize).unwrap_or(0);
let mut image_meta = ImageMeta {
size: file_size,
format: crate::helper::get_extension(&raw_path.to_string_lossy()),
colorspace: "sRGB".to_string(),
num_pages: 1,
..Default::default()
};
if let Ok(img) = crate::encoder::load_dynamic_image(raw_path) {
let (w, h) = img.dimensions();
image_meta.width = w;
image_meta.height = h;
let small = img.thumbnail_exact(32, 32);
let rgba = small.to_rgba8();
if let Ok(hash) = blurhash::encode(4, 3, 32, 32, rgba.as_raw()) {
image_meta.blurhash = hash;
}
}
MetaFile {
id: id.to_string(),
path: req_path.to_string(),
checksum,
meta: image_meta,
}
}
pub fn build_metadata_from_bytes(id: &str, req_path: &str, bytes: &[u8]) -> MetaFile {
let checksum = hash_bytes(bytes);
let ext = crate::helper::get_extension(req_path);
let mut image_meta = ImageMeta {
size: bytes.len(),
format: ext.clone(),
colorspace: "sRGB".to_string(),
num_pages: 1,
..Default::default()
};
let decoded_img = crate::encoder::load_dynamic_image_from_bytes(bytes, Some(&ext)).ok();
if let Some(img) = decoded_img {
let (w, h) = img.dimensions();
image_meta.width = w;
image_meta.height = h;
let small = img.thumbnail_exact(32, 32);
let rgba = small.to_rgba8();
if let Ok(hash) = blurhash::encode(4, 3, 32, 32, rgba.as_raw()) {
image_meta.blurhash = hash;
}
}
MetaFile {
id: id.to_string(),
path: req_path.to_string(),
checksum,
meta: image_meta,
}
}
pub fn save_metadata(meta_path: &Path, meta_file: &MetaFile) {
if let Some(parent) = meta_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(json_str) = serde_json::to_string(meta_file) {
let _ = std::fs::write(meta_path, json_str.as_bytes());
}
}
pub fn delete_metadata(meta_path: &Path) {
if meta_path.exists() {
let _ = std::fs::remove_file(meta_path);
}
}