Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent image resolution from being lowered when already low. #93

Merged
merged 1 commit into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/core/api/tv_maze/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::io::Write;

use bytes::Bytes;
use tracing::error;

const POSTER_WIDTH: u32 = 480;
const POSTER_HEIGHT: u32 = 853;
const BACKGROUND_WIDTH: u32 = 1280;
const BACKGROUND_HEIGHT: u32 = 720;

pub enum ImageResolution {
Original(ImageKind),
Medium,
}

#[derive(Clone, Copy)]
pub enum ImageKind {
Poster,
Background,
}

/// Loads the image from the provided url
///
/// Since Original images from TvMaze may have extremely high resolution up to 4k which can cause `wgpu` to crash,
/// this function will thumbnail the original image to the size that is good enough to be displayed in the GUI.
pub async fn load_image(image_url: String, image_resolution: ImageResolution) -> Option<Bytes> {
loop {
match reqwest::get(&image_url).await {
Ok(response) => {
if let Ok(bytes) = response.bytes().await {
let image = image::load_from_memory(&bytes)
.map_err(|err| error!("failed to load image from the api: {}", err))
.ok()?;

break match image_resolution {
ImageResolution::Original(image_kind) => {
if should_lower_resolution(&image, image_kind) {
lower_image_resolution(image, image_kind)
} else {
Some(bytes)
}
}
ImageResolution::Medium => Some(bytes),
};
}
}
Err(ref err) => {
if err.is_request() {
super::random_async_sleep().await;
} else {
break None;
}
}
}
}
}

fn should_lower_resolution(image: &image::DynamicImage, image_kind: ImageKind) -> bool {
match image_kind {
ImageKind::Poster => image.height() > POSTER_HEIGHT || image.width() > POSTER_WIDTH,
ImageKind::Background => {
image.height() > BACKGROUND_HEIGHT || image.width() > BACKGROUND_WIDTH
}
}
}

fn lower_image_resolution(
image: image::DynamicImage,
image_kind: ImageKind,
) -> Option<bytes::Bytes> {
let img = match image_kind {
ImageKind::Poster => image.thumbnail(POSTER_WIDTH, POSTER_WIDTH),
ImageKind::Background => image.thumbnail(BACKGROUND_WIDTH, BACKGROUND_HEIGHT),
};

let mut writer = std::io::BufWriter::new(vec![]);

let mut jpeg_encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut writer, 100);

jpeg_encoder
.encode_image(&img)
.map_err(|err| error!("failed to encode image: {}", err))
.ok()?;

writer
.flush()
.map_err(|err| error!("failed to flush image bytes: {}", err))
.ok()?;

Some(bytes::Bytes::copy_from_slice(writer.get_ref()))
}
66 changes: 1 addition & 65 deletions src/core/api/tv_maze/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::io::Write;

use bytes::Bytes;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::error;

pub mod episodes_information;
pub mod image;
pub mod seasons_list;
pub mod series_information;
pub mod series_searching;
Expand Down Expand Up @@ -50,67 +47,6 @@ pub struct Image {
pub medium_image_url: String,
}

pub enum ImageType {
Original(OriginalType),
Medium,
}

pub enum OriginalType {
Poster,
Background,
}

/// Loads the image from the provided url
///
/// Since Original images from TvMaze may have extremely high resolution up to 4k which can cause `wgpu` to crash,
/// this function will thumbnail the original image to the size that is good enough to be displayed in the GUI.
pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Bytes> {
loop {
match reqwest::get(&image_url).await {
Ok(response) => {
if let Ok(bytes) = response.bytes().await {
break Some(if let ImageType::Original(original_type) = image_type {
let img = image::load_from_memory(&bytes)
.map_err(|err| error!("failed to load image from the api: {}", err))
.ok()?;

let img = match original_type {
OriginalType::Poster => img.thumbnail(480, 853),
OriginalType::Background => img.thumbnail(1280, 720),
};

let mut writer = std::io::BufWriter::new(vec![]);

let mut jpeg_encoder =
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut writer, 100);

jpeg_encoder
.encode_image(&img)
.map_err(|err| error!("failed to encode image: {}", err))
.ok()?;

writer
.flush()
.map_err(|err| error!("failed to flush image bytes: {}", err))
.ok()?;

bytes::Bytes::copy_from_slice(writer.get_ref())
} else {
bytes
});
}
}
Err(ref err) => {
if err.is_request() {
random_async_sleep().await;
} else {
break None;
}
}
}
}
}

/// T
fn try_bad_json(json_string: &str) -> Option<(String, String)> {
if let Ok(bad_response) = serde_json::from_str::<BadResponse>(json_string) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use bytes::Bytes;
use std::io::{self, ErrorKind};
use std::path;

pub use super::api::tv_maze::image::{ImageKind, ImageResolution};
use super::api::tv_maze::{series_information::SeriesMainInformation, ApiError};
pub use super::api::tv_maze::{ImageType, OriginalType};
use super::paths;
use crate::core::api::tv_maze::{self, deserialize_json};
use lazy_static::lazy_static;
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Cacher {
}

/// Loads the image from the provided url
pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Bytes> {
pub async fn load_image(image_url: String, image_type: ImageResolution) -> Option<Bytes> {
// Hashing the image url as a file name as the forward slashes in web urls
// mimic paths
use sha2::{Digest, Sha256};
Expand All @@ -153,7 +153,7 @@ pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Byte
Err(err) => {
if err.kind() == ErrorKind::NotFound {
info!("falling back online for image with link {}", image_url);
if let Some(image_bytes) = tv_maze::load_image(image_url, image_type).await {
if let Some(image_bytes) = tv_maze::image::load_image(image_url, image_type).await {
write_cache(&image_bytes, &image_path).await;
Some(image_bytes)
} else {
Expand Down
7 changes: 3 additions & 4 deletions src/core/caching/show_images.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::io::ErrorKind;

use super::{
load_image, read_cache, write_cache, CacheFilePath, ImageType as ImageKind, OriginalType,
CACHER,
load_image, read_cache, write_cache, CacheFilePath, ImageKind, ImageResolution, CACHER,
};
use crate::core::api::tv_maze::{
deserialize_json,
Expand Down Expand Up @@ -45,7 +44,7 @@ pub async fn get_recent_banner(series_id: u32) -> Option<bytes::Bytes> {
{
return load_image(
recent_background.resolutions.original.url.clone(),
ImageKind::Original(OriginalType::Background),
ImageResolution::Original(ImageKind::Background),
)
.await;
};
Expand All @@ -58,7 +57,7 @@ pub async fn get_recent_banner(series_id: u32) -> Option<bytes::Bytes> {

load_image(
recent_banner.resolutions.original.url,
ImageKind::Original(OriginalType::Background),
ImageResolution::Original(ImageKind::Background),
)
.await
}
4 changes: 2 additions & 2 deletions src/gui/series_page/series/cast_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ mod cast_poster {
fn load_person_image(image: Option<Image>) -> Command<Message> {
if let Some(image) = image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::PersonImageLoaded,
)
} else {
Expand All @@ -358,7 +358,7 @@ mod cast_poster {
fn load_character_image(image: Option<Image>) -> Command<Message> {
if let Some(image) = image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::CharacterImageLoaded,
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/gui/series_page/series/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ fn load_images(series_info_image: Option<Image>, series_id: u32) -> [Command<Mes
Command::perform(
caching::load_image(
image_url.original_image_url,
caching::ImageType::Original(caching::OriginalType::Poster),
caching::ImageResolution::Original(caching::ImageKind::Poster),
),
Message::SeriesImageLoaded,
)
Expand Down
5 changes: 4 additions & 1 deletion src/gui/tabs/discover_tab/searching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ mod search_result {
image_url
.map(|url| {
Command::perform(
caching::load_image(url.medium_image_url, caching::ImageType::Medium),
caching::load_image(
url.medium_image_url,
caching::ImageResolution::Medium,
),
Message::ImageLoaded,
)
.map(move |message| IndexedMessage::new(index, message))
Expand Down
9 changes: 6 additions & 3 deletions src/gui/troxide_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub mod episode_widget {

let command = if let Some(image) = episode_image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::ImageLoaded,
)
.map(move |message| IndexedMessage::new(index, message))
Expand Down Expand Up @@ -321,8 +321,11 @@ pub mod series_poster {
if let Some(image) = image {
Command::perform(
async move {
caching::load_image(image.medium_image_url, caching::ImageType::Medium)
.await
caching::load_image(
image.medium_image_url,
caching::ImageResolution::Medium,
)
.await
},
GenericPosterMessage::ImageLoaded,
)
Expand Down
Loading