Implement AI gallery example 🎉
It displays the most popular daily images of Civitai!
This commit is contained in:
parent
890d852e05
commit
e2c52c9728
8 changed files with 714 additions and 121 deletions
148
examples/gallery/src/civitai.rs
Normal file
148
examples/gallery/src/civitai.rs
Normal file
|
|
@ -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<Vec<Self>, Error> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Response {
|
||||
items: Vec<Image>,
|
||||
}
|
||||
|
||||
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<Rgba, Error> {
|
||||
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::<Vec<_>>()
|
||||
.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<reqwest::Error>),
|
||||
IOFailed(Arc<io::Error>),
|
||||
JoinFailed(Arc<task::JoinError>),
|
||||
ImageDecodingFailed(Arc<image::ImageError>),
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Self::RequestFailed(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(error: io::Error) -> Self {
|
||||
Self::IOFailed(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<task::JoinError> for Error {
|
||||
fn from(error: task::JoinError) -> Self {
|
||||
Self::JoinFailed(Arc::new(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<image::ImageError> for Error {
|
||||
fn from(error: image::ImageError) -> Self {
|
||||
Self::ImageDecodingFailed(Arc::new(error))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue