From e2c52c9728cb03d01b33388a8159db9bd9eac04c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Jan 2025 05:01:43 +0100 Subject: Implement AI `gallery` example :tada: It displays the most popular daily images of Civitai! --- examples/gallery/src/civitai.rs | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 examples/gallery/src/civitai.rs (limited to 'examples/gallery/src/civitai.rs') diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs new file mode 100644 index 00000000..d1163013 --- /dev/null +++ b/examples/gallery/src/civitai.rs @@ -0,0 +1,148 @@ +use bytes::Bytes; +use serde::Deserialize; +use tokio::task; + +use std::fmt; +use std::io; +use std::sync::Arc; + +#[derive(Debug, Clone, Deserialize)] +pub struct Image { + pub id: Id, + url: String, +} + +impl Image { + pub async fn list() -> Result, Error> { + let client = reqwest::Client::new(); + + #[derive(Deserialize)] + struct Response { + items: Vec, + } + + let response: Response = client + .get(endpoint("/images")) + .query(&[ + ("sort", "Most Reactions"), + ("period", "Day"), + ("nsfw", "None"), + ("limit", "99"), + ]) + .send() + .await? + .error_for_status()? + .json() + .await?; + + Ok(response.items) + } + + pub async fn download(self, size: Size) -> Result { + let client = reqwest::Client::new(); + + let bytes = client + .get(match size { + Size::Original => self.url, + Size::Thumbnail => self + .url + .split("/") + .map(|part| { + if part.starts_with("width=") { + "width=640" + } else { + part + } + }) + .collect::>() + .join("/"), + }) + .send() + .await? + .error_for_status()? + .bytes() + .await?; + + let image = task::spawn_blocking(move || { + Ok::<_, Error>( + image::ImageReader::new(io::Cursor::new(bytes)) + .with_guessed_format()? + .decode()? + .to_rgba8(), + ) + }) + .await??; + + Ok(Rgba { + width: image.width(), + height: image.height(), + pixels: Bytes::from(image.into_raw()), + }) + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, +)] +pub struct Id(u32); + +#[derive(Clone)] +pub struct Rgba { + pub width: u32, + pub height: u32, + pub pixels: Bytes, +} + +impl fmt::Debug for Rgba { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Rgba") + .field("width", &self.width) + .field("height", &self.height) + .finish() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Size { + Original, + Thumbnail, +} + +fn endpoint(path: &str) -> String { + const API_URL: &str = "https://civitai.com/api/v1"; + + format!("{API_URL}{path}") +} + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub enum Error { + RequestFailed(Arc), + IOFailed(Arc), + JoinFailed(Arc), + ImageDecodingFailed(Arc), +} + +impl From for Error { + fn from(error: reqwest::Error) -> Self { + Self::RequestFailed(Arc::new(error)) + } +} + +impl From for Error { + fn from(error: io::Error) -> Self { + Self::IOFailed(Arc::new(error)) + } +} + +impl From for Error { + fn from(error: task::JoinError) -> Self { + Self::JoinFailed(Arc::new(error)) + } +} + +impl From for Error { + fn from(error: image::ImageError) -> Self { + Self::ImageDecodingFailed(Arc::new(error)) + } +} -- cgit From e1ad0910f0a383372a481cab4545c4e20805b41c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Jan 2025 20:46:47 +0100 Subject: Change `period` to `Week` in `gallery` example --- examples/gallery/src/civitai.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples/gallery/src/civitai.rs') diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs index d1163013..5844fdfc 100644 --- a/examples/gallery/src/civitai.rs +++ b/examples/gallery/src/civitai.rs @@ -25,7 +25,7 @@ impl Image { .get(endpoint("/images")) .query(&[ ("sort", "Most Reactions"), - ("period", "Day"), + ("period", "Week"), ("nsfw", "None"), ("limit", "99"), ]) -- cgit From 086b06553b1b792577e9f663ae7721385696483e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 27 Jan 2025 20:47:59 +0100 Subject: Remove unnecessary `endpoint` function in `gallery` example --- examples/gallery/src/civitai.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'examples/gallery/src/civitai.rs') diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs index 5844fdfc..7a8f1c45 100644 --- a/examples/gallery/src/civitai.rs +++ b/examples/gallery/src/civitai.rs @@ -22,7 +22,7 @@ impl Image { } let response: Response = client - .get(endpoint("/images")) + .get("https://civitai.com/api/v1/images") .query(&[ ("sort", "Most Reactions"), ("period", "Week"), @@ -108,12 +108,6 @@ pub enum Size { Thumbnail, } -fn endpoint(path: &str) -> String { - const API_URL: &str = "https://civitai.com/api/v1"; - - format!("{API_URL}{path}") -} - #[derive(Debug, Clone)] #[allow(dead_code)] pub enum Error { -- cgit From cd445f758f08f97a719a3320d54df30620aa4434 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 28 Jan 2025 03:25:18 +0100 Subject: Display grid placeholder when loading `gallery` example --- examples/gallery/src/civitai.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'examples/gallery/src/civitai.rs') diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs index 7a8f1c45..986b6bf2 100644 --- a/examples/gallery/src/civitai.rs +++ b/examples/gallery/src/civitai.rs @@ -13,6 +13,8 @@ pub struct Image { } impl Image { + pub const LIMIT: usize = 99; + pub async fn list() -> Result, Error> { let client = reqwest::Client::new(); @@ -27,7 +29,7 @@ impl Image { ("sort", "Most Reactions"), ("period", "Week"), ("nsfw", "None"), - ("limit", "99"), + ("limit", &Image::LIMIT.to_string()), ]) .send() .await? -- cgit