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",
|
"openapi": "3.0.0",
|
||||||
"info": {
|
"info": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "4.0",
|
"version": "5.0",
|
||||||
"title": "Polaris",
|
"title": "Polaris",
|
||||||
"termsOfService": ""
|
"termsOfService": ""
|
||||||
},
|
},
|
||||||
|
@ -444,13 +444,13 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/serve/{file}": {
|
"/audio/{file}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Collection"
|
"Collection"
|
||||||
],
|
],
|
||||||
"summary": "Access a media file in the collection",
|
"summary": "Access a media file in the collection",
|
||||||
"operationId": "getServe",
|
"operationId": "getAudio",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "file",
|
"name": "file",
|
||||||
|
@ -465,12 +465,53 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successful operation",
|
"description": "Successful operation",
|
||||||
"content": {
|
"content": {
|
||||||
"image/*": {
|
"audio/*": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"format": "binary"
|
"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": {
|
"schema": {
|
||||||
"format": "binary"
|
"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 API_MINOR_VERSION: i32 = 0;
|
||||||
pub const COOKIE_SESSION: &str = "session";
|
pub const COOKIE_SESSION: &str = "session";
|
||||||
pub const COOKIE_USERNAME: &str = "username";
|
pub const COOKIE_USERNAME: &str = "username";
|
||||||
|
|
|
@ -4,6 +4,7 @@ use rocket::request::{self, FromParam, FromRequest, Request};
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::Html;
|
||||||
use rocket::{delete, get, post, put, routes, Outcome, State};
|
use rocket::{delete, get, post, put, routes, Outcome, State};
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
use std::default::Default;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -23,7 +24,6 @@ use crate::service::dto;
|
||||||
use crate::service::error::APIError;
|
use crate::service::error::APIError;
|
||||||
use crate::thumbnails;
|
use crate::thumbnails;
|
||||||
use crate::user;
|
use crate::user;
|
||||||
use crate::utils;
|
|
||||||
use crate::vfs::VFSSource;
|
use crate::vfs::VFSSource;
|
||||||
|
|
||||||
pub fn get_routes() -> Vec<rocket::Route> {
|
pub fn get_routes() -> Vec<rocket::Route> {
|
||||||
|
@ -44,7 +44,8 @@ pub fn get_routes() -> Vec<rocket::Route> {
|
||||||
recent,
|
recent,
|
||||||
search_root,
|
search_root,
|
||||||
search,
|
search,
|
||||||
serve,
|
audio,
|
||||||
|
thumbnail,
|
||||||
list_playlists,
|
list_playlists,
|
||||||
save_playlist,
|
save_playlist,
|
||||||
read_playlist,
|
read_playlist,
|
||||||
|
@ -305,21 +306,25 @@ fn search(
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/serve/<path>")]
|
#[get("/audio/<path>")]
|
||||||
fn serve(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>> {
|
fn audio(db: State<'_, DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>> {
|
||||||
let vfs = db.get_vfs()?;
|
let vfs = db.get_vfs()?;
|
||||||
let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
|
let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
|
||||||
|
let file = File::open(&real_path)?;
|
||||||
let serve_path = if utils::is_image(&real_path) {
|
|
||||||
thumbnails::get_thumbnail(&real_path, 400)?
|
|
||||||
} else {
|
|
||||||
real_path
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = File::open(serve_path)?;
|
|
||||||
Ok(serve::RangeResponder::new(file))
|
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")]
|
#[get("/playlists")]
|
||||||
fn list_playlists(db: State<'_, DB>, auth: Auth) -> Result<Json<Vec<dto::ListPlaylistsEntry>>> {
|
fn list_playlists(db: State<'_, DB>, auth: Auth) -> Result<Json<Vec<dto::ListPlaylistsEntry>>> {
|
||||||
let playlist_names = playlist::list_playlists(&auth.username, db.deref().deref())?;
|
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 mut service = ServiceType::new(function_name!());
|
||||||
let response = service.get_json::<dto::Version>("/api/version");
|
let response = service.get_json::<dto::Version>("/api/version");
|
||||||
let version = response.body();
|
let version = response.body();
|
||||||
assert_eq!(version, &dto::Version { major: 4, minor: 0 });
|
assert_eq!(version, &dto::Version { major: 5, minor: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[named]
|
#[named]
|
||||||
|
@ -389,7 +389,7 @@ fn test_service_serve() {
|
||||||
path.push("Hunted");
|
path.push("Hunted");
|
||||||
path.push("02 - Candlelight.mp3");
|
path.push("02 - Candlelight.mp3");
|
||||||
let uri = format!(
|
let uri = format!(
|
||||||
"/api/serve/{}",
|
"/api/audio/{}",
|
||||||
percent_encode(path.to_string_lossy().as_ref().as_bytes(), NON_ALPHANUMERIC)
|
percent_encode(path.to_string_lossy().as_ref().as_bytes(), NON_ALPHANUMERIC)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,31 @@ use crate::utils;
|
||||||
|
|
||||||
const THUMBNAILS_PATH: &str = "thumbnails";
|
const THUMBNAILS_PATH: &str = "thumbnails";
|
||||||
|
|
||||||
fn hash(path: &Path, dimension: u32) -> u64 {
|
#[derive(Debug, Hash)]
|
||||||
let path_string = path.to_string_lossy();
|
pub struct Options {
|
||||||
let hash_input = format!("{}:{}", path_string, dimension.to_string());
|
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();
|
let mut hasher = DefaultHasher::new();
|
||||||
hash_input.hash(&mut hasher);
|
path.hash(&mut hasher);
|
||||||
|
options.hash(&mut hasher);
|
||||||
hasher.finish()
|
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()?;
|
let mut out_path = utils::get_data_root()?;
|
||||||
out_path.push(THUMBNAILS_PATH);
|
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_image = image::open(real_path)?;
|
||||||
let (source_width, source_height) = source_image.dimensions();
|
let (source_width, source_height) = source_image.dimensions();
|
||||||
let largest_dimension = cmp::max(source_width, source_height);
|
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()));
|
out_path.push(format!("{}.jpg", hash.to_string()));
|
||||||
|
|
||||||
if !out_path.exists() {
|
if !out_path.exists() {
|
||||||
let quality = 80;
|
let quality = 80;
|
||||||
let source_aspect_ratio: f32 = source_width as f32 / source_height as f32;
|
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;
|
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 =
|
let scaled_image =
|
||||||
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||||
let (scaled_width, scaled_height) = scaled_image.dimensions();
|
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,
|
(out_dimension - scaled_height) / 2,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final_image =
|
final_image = source_image.resize(out_dimension, out_dimension, FilterType::Lanczos3);
|
||||||
source_image.resize_exact(out_dimension, out_dimension, FilterType::Lanczos3);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut out_file = File::create(&out_path)?;
|
let mut out_file = File::create(&out_path)?;
|
||||||
final_image.write_to(&mut out_file, ImageOutputFormat::JPEG(quality))?;
|
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)
|
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…
Add table
Reference in a new issue