quoter/src/thumbnail.rs
Untone 7fc29f6b7e
All checks were successful
deploy / deploy (push) Successful in 1m1s
find-closest-fix
2024-10-23 17:13:45 +03:00

97 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use actix_web::error::ErrorInternalServerError;
use image::{imageops::FilterType, DynamicImage};
use std::{collections::HashMap, io::Cursor};
use image::guess_format;
pub const THUMB_WIDTHS: [u32; 7] = [10, 40, 110, 300, 600, 800, 1400];
/// Парсит путь к файлу, извлекая оригинальное имя, требуемую ширину и формат.
/// Примеры:
/// - "filename_150.ext" -> ("filename", 150, "ext")
/// - "unsafe/1440x/production/image/439efaa0-816f-11ef-b201-439da98539bc.jpg" -> ("439efaa0-816f-11ef-b201-439da98539bc", 1440, "jpg")
/// - "unsafe/production/image/5627e002-0c53-11ee-9565-0242ac110006.png" -> ("5627e002-0c53-11ee-9565-0242ac110006", 0, "png")
/// - "unsafe/development/image/439efaa0-816f-11ef-b201-439da98539bc.jpg/webp" -> ("439efaa0-816f-11ef-b201-439da98539bc", 0, "webp")
pub fn parse_file_path(requested_path: &str) -> (String, u32, String) {
let mut path = requested_path.to_string();
if requested_path.ends_with("/webp") {
path = path.replace("/webp", "");
}
let mut path_parts: Vec<&str> = path.split('/').collect();
let mut extension = String::new();
let mut width = 0;
let mut base_filename = String::new();
if path_parts.is_empty() {
return (path.to_string(), width, extension);
}
// пытаемся извлечь формат из имени файла
if let Some(filename_part) = path_parts.pop() {
if let Some((base, ext_part)) = filename_part.rsplit_once('.') {
extension = ext_part.to_lowercase();
base_filename = base.to_string(); // Устанавливаем base_filename без расширения
} else {
base_filename = filename_part.to_string();
}
}
// Если base_filename ещё не установлено, извлекаем его
if base_filename.is_empty() {
if let Some(filename_part) = path_parts.pop() {
if let Some((base, ext_part)) = filename_part.rsplit_once('.') {
extension = ext_part.to_lowercase();
base_filename = base.to_string();
} else {
base_filename = filename_part.to_string();
}
}
}
// Извлечение ширины из base_filename, если она есть
if let Some((name_part, width_str)) = base_filename.rsplit_once('_') {
if let Ok(w) = width_str.parse::<u32>() {
width = w;
base_filename = name_part.to_string();
}
}
// Проверка на старую ширину в путях, начинающихся с "unsafe"
if path.starts_with("unsafe") && width == 0 {
if path_parts.len() >= 2 {
if let Some(old_width_str) = path_parts.get(1) { // Получаем второй элемент
let old_width_str = old_width_str.trim_end_matches('x');
if let Ok(w) = old_width_str.parse::<u32>() {
width = w;
}
}
}
}
(base_filename, width, extension)
}
/// Генерирует миниатюры изображения.
pub async fn generate_thumbnails(image: &DynamicImage) -> Result<HashMap<u32, Vec<u8>>, actix_web::Error> {
let mut thumbnails = HashMap::new();
let format = guess_format(&image.as_bytes()).unwrap();
for &width in THUMB_WIDTHS.iter().filter(|&&w| w < image.width()) {
let thumbnail = image.resize(width, u32::MAX, FilterType::Lanczos3); // Ресайз изображения по ширине
let mut buffer = Vec::new();
thumbnail
.write_to(&mut Cursor::new(&mut buffer), format)
.map_err(|_| ErrorInternalServerError("Не удалось сгенерировать миниатюру"))?; // Сохранение изображения в указанном формате
thumbnails.insert(width, buffer);
}
Ok(thumbnails)
}
/// Выбирает ближайший подходящий размер из предопределённых.
pub fn find_closest_width(requested_width: u32) -> u32 {
*THUMB_WIDTHS
.iter()
.min_by_key(|&&width| (width as i32 - requested_width as i32).abs())
.unwrap_or(&THUMB_WIDTHS[0]) // Возвращаем самый маленький размер, если ничего не подошло
}