Unifi asset download and thumbnail download response type
Some checks failed
Integration Tests / integration-test (push) Failing after 19s
Some checks failed
Integration Tests / integration-test (push) Failing after 19s
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1016,6 +1016,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"image",
|
"image",
|
||||||
|
"mime",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ image = "0.25"
|
|||||||
url = "2.5"
|
url = "2.5"
|
||||||
bytes = "1.10"
|
bytes = "1.10"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
|
mime = "0.3.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio-test = "0.4"
|
tokio-test = "0.4"
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
//! Assets API - Manage photos and videos
|
//! Assets API - Manage photos and videos
|
||||||
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::{io::Cursor, str::FromStr};
|
||||||
|
|
||||||
|
use mime::Mime;
|
||||||
|
use reqwest::header::CONTENT_TYPE;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Client,
|
Client,
|
||||||
@@ -14,17 +17,17 @@ use crate::{
|
|||||||
|
|
||||||
/// Response from downloading a thumbnail containing image data and metadata
|
/// Response from downloading a thumbnail containing image data and metadata
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThumbnailResponse {
|
pub struct AssetDownload {
|
||||||
/// The image data as bytes
|
/// The asset data as bytes
|
||||||
pub data: bytes::Bytes,
|
pub data: bytes::Bytes,
|
||||||
/// The MIME type of the image (e.g., "image/jpeg", "image/png", "image/webp")
|
/// The MIME type of the asset (e.g., "image/jpeg", "image/png", "image/webp")
|
||||||
pub content_type: String,
|
pub mime: Mime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThumbnailResponse {
|
impl AssetDownload {
|
||||||
/// Get the file extension based on content type
|
/// Get the file extension based on content type
|
||||||
pub fn extension(&self) -> Option<&str> {
|
pub fn extension(&self) -> Option<&str> {
|
||||||
match self.content_type.as_str() {
|
match self.mime.essence_str() {
|
||||||
"image/jpeg" | "image/jpg" => Some("jpg"),
|
"image/jpeg" | "image/jpg" => Some("jpg"),
|
||||||
"image/png" => Some("png"),
|
"image/png" => Some("png"),
|
||||||
"image/webp" => Some("webp"),
|
"image/webp" => Some("webp"),
|
||||||
@@ -35,8 +38,8 @@ impl ThumbnailResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the content type
|
/// Get the content type
|
||||||
pub fn content_type(&self) -> &str {
|
pub fn content_type(&self) -> &Mime {
|
||||||
&self.content_type
|
&self.mime
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data
|
/// Get the data
|
||||||
@@ -52,7 +55,7 @@ impl ThumbnailResponse {
|
|||||||
/// Decode the image data into a DynamicImage
|
/// Decode the image data into a DynamicImage
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Returns an error if the content type is not supported or if decoding fails
|
/// Returns an error if the asset is not an image, content type is not supported, or if decoding fails
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
@@ -69,8 +72,8 @@ impl ThumbnailResponse {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn decode(&self) -> Result<image::DynamicImage> {
|
pub fn decode(&self) -> Result<image::DynamicImage> {
|
||||||
// Get image format from content type
|
// Get image format from content type
|
||||||
let format = image::ImageFormat::from_mime_type(&self.content_type).ok_or_else(|| {
|
let format = image::ImageFormat::from_mime_type(&self.mime).ok_or_else(|| {
|
||||||
ImmichError::Image(format!("Unsupported content type: {}", self.content_type))
|
ImmichError::Image(format!("Unsupported content type: {}", self.mime))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Create reader with the format and decode
|
// Create reader with the format and decode
|
||||||
@@ -303,7 +306,10 @@ impl UploadAssetBuilder {
|
|||||||
|
|
||||||
// Add file timestamps (required by Immich API v2)
|
// Add file timestamps (required by Immich API v2)
|
||||||
let now = chrono::Utc::now().to_rfc3339();
|
let now = chrono::Utc::now().to_rfc3339();
|
||||||
form = form.text("fileCreatedAt", self.file_created_at.unwrap_or_else(|| now.clone()));
|
form = form.text(
|
||||||
|
"fileCreatedAt",
|
||||||
|
self.file_created_at.unwrap_or_else(|| now.clone()),
|
||||||
|
);
|
||||||
form = form.text("fileModifiedAt", self.file_modified_at.unwrap_or(now));
|
form = form.text("fileModifiedAt", self.file_modified_at.unwrap_or(now));
|
||||||
|
|
||||||
let req = self.client.post("/assets").multipart(form);
|
let req = self.client.post("/assets").multipart(form);
|
||||||
@@ -386,7 +392,7 @@ impl DownloadAssetBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the download
|
/// Execute the download
|
||||||
pub async fn execute(self) -> Result<bytes::Bytes> {
|
pub async fn execute(self) -> Result<AssetDownload> {
|
||||||
let path = format!("/assets/{}/original", self.id);
|
let path = format!("/assets/{}/original", self.id);
|
||||||
let mut req = self.client.get(&path);
|
let mut req = self.client.get(&path);
|
||||||
|
|
||||||
@@ -395,8 +401,18 @@ impl DownloadAssetBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = self.client.execute(req.build()?).await?;
|
let response = self.client.execute(req.build()?).await?;
|
||||||
let bytes = response.bytes().await?;
|
|
||||||
Ok(bytes)
|
// Get content type from response headers
|
||||||
|
let mime = response
|
||||||
|
.headers()
|
||||||
|
.get(CONTENT_TYPE)
|
||||||
|
.and_then(|ct| ct.to_str().ok())
|
||||||
|
.and_then(|ct| Mime::from_str(ct).ok())
|
||||||
|
.ok_or(ImmichError::Image("Missing or invalid content-type".into()))?;
|
||||||
|
|
||||||
|
let data = response.bytes().await?;
|
||||||
|
|
||||||
|
Ok(AssetDownload { data, mime })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +449,7 @@ impl ThumbnailBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the request
|
/// Execute the request
|
||||||
pub async fn execute(self) -> Result<ThumbnailResponse> {
|
pub async fn execute(self) -> Result<AssetDownload> {
|
||||||
let path = format!("/assets/{}/thumbnail", self.id);
|
let path = format!("/assets/{}/thumbnail", self.id);
|
||||||
let mut req = self.client.get(&path);
|
let mut req = self.client.get(&path);
|
||||||
|
|
||||||
@@ -454,15 +470,15 @@ impl ThumbnailBuilder {
|
|||||||
let response = self.client.execute(req.build()?).await?;
|
let response = self.client.execute(req.build()?).await?;
|
||||||
|
|
||||||
// Get content type from response headers
|
// Get content type from response headers
|
||||||
let content_type = response
|
let mime = response
|
||||||
.headers()
|
.headers()
|
||||||
.get("content-type")
|
.get(CONTENT_TYPE)
|
||||||
.and_then(|ct| ct.to_str().ok())
|
.and_then(|ct| ct.to_str().ok())
|
||||||
.map(String::from)
|
.and_then(|ct| Mime::from_str(ct).ok())
|
||||||
.unwrap_or_else(|| "application/octet-stream".to_string());
|
.ok_or(ImmichError::Image("Missing or invalid content-type".into()))?;
|
||||||
|
|
||||||
let data = response.bytes().await?;
|
let data = response.bytes().await?;
|
||||||
|
|
||||||
Ok(ThumbnailResponse { data, content_type })
|
Ok(AssetDownload { data, mime })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user