mirror of
https://github.com/agersant/polaris
synced 2024-11-10 02:04:13 +00:00
Made thumbnail padding optional
This commit is contained in:
parent
86d61dd964
commit
6e3f439d8a
6 changed files with 97 additions and 57 deletions
|
@ -2,7 +2,7 @@
|
|||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"description": "",
|
||||
"version": "4.0",
|
||||
"version": "5.0",
|
||||
"title": "Polaris",
|
||||
"termsOfService": ""
|
||||
},
|
||||
|
@ -444,13 +444,13 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/serve/{file}": {
|
||||
"/audio/{file}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Collection"
|
||||
],
|
||||
"summary": "Access a media file in the collection",
|
||||
"operationId": "getServe",
|
||||
"operationId": "getAudio",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "file",
|
||||
|
@ -465,12 +465,53 @@
|
|||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"image/*": {
|
||||
"audio/*": {
|
||||
"schema": {
|
||||
"format": "binary"
|
||||
}
|
||||
},
|
||||
"audio/*": {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"auth_http_header": [],
|
||||
"auth_cookie": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/thumbnail/{file}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Collection"
|
||||
],
|
||||
"summary": "Generate an image thumbnail for a media file in the collection",
|
||||
"operationId": "getServe",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "file",
|
||||
"in": "path",
|
||||
"description": "Path to the desired file",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pad",
|
||||
"in": "query",
|
||||
"description": "Indicates whether the thumbnail should be padded to a square aspect-ratio",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation",
|
||||
"content": {
|
||||
"image/*": {
|
||||
"schema": {
|
||||
"format": "binary"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub const API_MAJOR_VERSION: i32 = 4;
|
||||
pub const API_MAJOR_VERSION: i32 = 5;
|
||||
pub const API_MINOR_VERSION: i32 = 0;
|
||||
pub const COOKIE_SESSION: &str = "session";
|
||||
pub const COOKIE_USERNAME: &str = "username";
|
||||
|
|
|
@ -4,6 +4,7 @@ use rocket::request::{self, FromParam, FromRequest, Request};
|
|||
use rocket::response::content::Html;
|
||||
use rocket::{delete, get, post, put, routes, Outcome, State};
|
||||
use rocket_contrib::json::Json;
|
||||
use std::default::Default;
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
|
@ -23,7 +24,6 @@ use crate::service::dto;
|
|||
use crate::service::error::APIError;
|
||||
use crate::thumbnails;
|
||||
use crate::user;
|
||||
use crate::utils;
|
||||
use crate::vfs::VFSSource;
|
||||
|
||||
pub fn get_routes() -> Vec<rocket::Route> {
|
||||
|
@ -44,7 +44,8 @@ pub fn get_routes() -> Vec<rocket::Route> {
|
|||
recent,
|
||||
search_root,
|
||||
search,
|
||||
serve,
|
||||
audio,
|
||||
thumbnail,
|
||||
list_playlists,
|
||||
save_playlist,
|
||||
read_playlist,
|
||||
|
@ -305,21 +306,25 @@ fn search(
|
|||
Ok(Json(result))
|
||||
}
|
||||
|
||||
#[get("/serve/<path>")]
|
||||
fn serve(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>> {
|
||||
#[get("/audio/<path>")]
|
||||
fn audio(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>> {
|
||||
let vfs = db.get_vfs()?;
|
||||
let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
|
||||
|
||||
let serve_path = if utils::is_image(&real_path) {
|
||||
thumbnails::get_thumbnail(&real_path, 400)?
|
||||
} else {
|
||||
real_path
|
||||
};
|
||||
|
||||
let file = File::open(serve_path)?;
|
||||
let file = File::open(&real_path)?;
|
||||
Ok(serve::RangeResponder::new(file))
|
||||
}
|
||||
|
||||
#[get("/thumbnail/<path>?<pad>")]
|
||||
fn thumbnail(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf, pad: Option<bool>) -> Result<File> {
|
||||
let vfs = db.get_vfs()?;
|
||||
let image_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
|
||||
let mut options = thumbnails::Options::default();
|
||||
options.pad_to_square = pad.unwrap_or(options.pad_to_square);
|
||||
let thumbnail_path = thumbnails::get_thumbnail(&image_path, &options)?;
|
||||
let file = File::open(thumbnail_path)?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
#[get("/playlists")]
|
||||
fn list_playlists(db: State<'_, DB>, auth: Auth) -> Result<Json<Vec<dto::ListPlaylistsEntry>>> {
|
||||
let playlist_names = playlist::list_playlists(&auth.username, db.deref().deref())?;
|
||||
|
|
|
@ -107,7 +107,7 @@ fn test_service_version() {
|
|||
let mut service = ServiceType::new(function_name!());
|
||||
let response = service.get_json::<dto::Version>("/api/version");
|
||||
let version = response.body();
|
||||
assert_eq!(version, &dto::Version { major: 4, minor: 0 });
|
||||
assert_eq!(version, &dto::Version { major: 5, minor: 0 });
|
||||
}
|
||||
|
||||
#[named]
|
||||
|
@ -389,7 +389,7 @@ fn test_service_serve() {
|
|||
path.push("Hunted");
|
||||
path.push("02 - Candlelight.mp3");
|
||||
let uri = format!(
|
||||
"/api/serve/{}",
|
||||
"/api/audio/{}",
|
||||
percent_encode(path.to_string_lossy().as_ref().as_bytes(), NON_ALPHANUMERIC)
|
||||
);
|
||||
|
||||
|
|
|
@ -16,15 +16,31 @@ use crate::utils;
|
|||
|
||||
const THUMBNAILS_PATH: &str = "thumbnails";
|
||||
|
||||
fn hash(path: &Path, dimension: u32) -> u64 {
|
||||
let path_string = path.to_string_lossy();
|
||||
let hash_input = format!("{}:{}", path_string, dimension.to_string());
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Options {
|
||||
pub max_dimension: u32,
|
||||
pub resize_if_almost_square: bool,
|
||||
pub pad_to_square: bool,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
fn default() -> Options {
|
||||
Options {
|
||||
max_dimension: 400,
|
||||
resize_if_almost_square: true,
|
||||
pad_to_square: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(path: &Path, options: &Options) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
hash_input.hash(&mut hasher);
|
||||
path.hash(&mut hasher);
|
||||
options.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
||||
pub fn get_thumbnail(real_path: &Path, options: &Options) -> Result<PathBuf> {
|
||||
let mut out_path = utils::get_data_root()?;
|
||||
out_path.push(THUMBNAILS_PATH);
|
||||
|
||||
|
@ -35,17 +51,21 @@ pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
|||
let source_image = image::open(real_path)?;
|
||||
let (source_width, source_height) = source_image.dimensions();
|
||||
let largest_dimension = cmp::max(source_width, source_height);
|
||||
let out_dimension = cmp::min(max_dimension, largest_dimension);
|
||||
let out_dimension = cmp::min(options.max_dimension, largest_dimension);
|
||||
|
||||
let hash = hash(real_path, out_dimension);
|
||||
let hash = hash(real_path, options);
|
||||
out_path.push(format!("{}.jpg", hash.to_string()));
|
||||
|
||||
if !out_path.exists() {
|
||||
let quality = 80;
|
||||
let source_aspect_ratio: f32 = source_width as f32 / source_height as f32;
|
||||
let is_almost_square = source_aspect_ratio > 0.8 && source_aspect_ratio < 1.2;
|
||||
|
||||
let mut final_image;
|
||||
if source_aspect_ratio < 0.8 || source_aspect_ratio > 1.2 {
|
||||
if is_almost_square && options.resize_if_almost_square {
|
||||
final_image =
|
||||
source_image.resize_exact(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||
} else if options.pad_to_square {
|
||||
let scaled_image =
|
||||
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||
let (scaled_width, scaled_height) = scaled_image.dimensions();
|
||||
|
@ -61,9 +81,8 @@ pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
|
|||
(out_dimension - scaled_height) / 2,
|
||||
);
|
||||
} else {
|
||||
final_image =
|
||||
source_image.resize_exact(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||
};
|
||||
final_image = source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||
}
|
||||
|
||||
let mut out_file = File::create(&out_path)?;
|
||||
final_image.write_to(&mut out_file, ImageOutputFormat::JPEG(quality))?;
|
||||
|
|
25
src/utils.rs
25
src/utils.rs
|
@ -60,28 +60,3 @@ fn test_get_audio_format() {
|
|||
Some(AudioFormat::FLAC)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn is_image(path: &Path) -> bool {
|
||||
let extension = match path.extension() {
|
||||
Some(e) => e,
|
||||
_ => return false,
|
||||
};
|
||||
let extension = match extension.to_str() {
|
||||
Some(e) => e,
|
||||
_ => return false,
|
||||
};
|
||||
match extension.to_lowercase().as_str() {
|
||||
"png" => true,
|
||||
"gif" => true,
|
||||
"jpg" => true,
|
||||
"jpeg" => true,
|
||||
"bmp" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_image() {
|
||||
assert!(!is_image(Path::new("animals/🐷/my🐖file.mp3")));
|
||||
assert!(is_image(Path::new("animals/🐷/my🐖file.jpg")));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue