more error handling
This commit is contained in:
parent
abefe95a83
commit
b4a3acd2d0
4 changed files with 168 additions and 66 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pixelfed_batch_uploader"
|
name = "pixelfed_batch_uploader"
|
||||||
version = "1.0.3"
|
version = "1.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
|
@ -13,4 +13,5 @@ reqwest = { version = "0.12", features = ["blocking", "json", "multipart", "stre
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
clap = { version = "4.5.3", features = ["derive"] }
|
clap = { version = "4.5.3", features = ["derive"] }
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use log::{debug, error, log_enabled, info, Level};
|
use log::{debug, error, log_enabled, info, Level};
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +129,8 @@ pub fn get_description_from_ollama(image_name: String, ollama_config: OllamaCon
|
||||||
"images": [base64_image]
|
"images": [base64_image]
|
||||||
});
|
});
|
||||||
|
|
||||||
debug!("Payload for image OLLAMA API: \n{}", &payload.to_string());
|
|
||||||
|
debug!("Payload for image OLLAMA API: \n{}", payload.clone());
|
||||||
// println!("JSON output:\n{}", json.clone());
|
// println!("JSON output:\n{}", json.clone());
|
||||||
// Create ChatGPT API request
|
// Create ChatGPT API request
|
||||||
// let client = reqwest::blocking::Client::new();
|
// let client = reqwest::blocking::Client::new();
|
||||||
|
@ -143,30 +145,35 @@ pub fn get_description_from_ollama(image_name: String, ollama_config: OllamaCon
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.json(&payload).send();
|
.json(&payload).send();
|
||||||
|
|
||||||
// Improved error handling for API response
|
|
||||||
// Check for HTTP errors
|
|
||||||
if response.as_ref().unwrap().status().is_success() {
|
|
||||||
debug!("success!");
|
|
||||||
} else if response.as_ref().unwrap().status().is_server_error() {
|
|
||||||
error!("server error!");
|
|
||||||
} else {
|
|
||||||
error!("Something else happened. Status: {:?}", response.as_ref().unwrap().status());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let response = match response {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status().is_success() {
|
||||||
|
info!("success!");
|
||||||
|
} else if response.status().is_server_error() {
|
||||||
|
error!("server error!");
|
||||||
|
} else {
|
||||||
|
error!("Something else happened. Status: {:?}", response.status());
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to send request: {}", e);
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Extract response text
|
// Extract response text
|
||||||
let result: super::Value = response.unwrap().json()?;
|
let result: super::Value = response.json()?;
|
||||||
let description: String;
|
let description = if let Some(response_text) = result["response"].as_str() {
|
||||||
if !result["response"].is_null() {
|
response_text.replace("\\n", "\n").replace("\\\"", "\"")
|
||||||
description = result["response"].to_string();
|
} else if let Some(error_text) = result["error"].as_str() {
|
||||||
} else if !result["error"].is_null() {
|
error_text.replace("\\n", "\n").replace("\\\"", "\"")
|
||||||
description = result["error"].to_string();
|
|
||||||
} else {
|
} else {
|
||||||
description = "Could not find response or error from OLLAMA".to_string();
|
"Could not find response or error from OLLAMA".to_string()
|
||||||
}
|
};
|
||||||
|
|
||||||
|
info!("Description generated by OLLAMA: {}", &description);
|
||||||
debug!("Description generated by OLLAMA: {}", &description.clone());
|
|
||||||
|
|
||||||
Ok(description)
|
Ok(description)
|
||||||
|
|
||||||
|
|
|
@ -119,12 +119,12 @@ fn get_jpeg_files(directory: &str) -> Vec<String> {
|
||||||
None => {my_config = "config.json".to_string()},
|
None => {my_config = "config.json".to_string()},
|
||||||
|
|
||||||
}
|
}
|
||||||
debug!("effective config file: {}", &my_config);
|
info!("effective config file: {}", &my_config);
|
||||||
|
|
||||||
|
|
||||||
let mut config = load_config(my_config).unwrap();
|
let mut config = load_config(my_config).unwrap();
|
||||||
|
|
||||||
debug!("Config OK?: " );
|
info!("Config OK?: " );
|
||||||
|
|
||||||
// Overwrite config values with command line arguments
|
// Overwrite config values with command line arguments
|
||||||
match args.visibility {
|
match args.visibility {
|
||||||
|
@ -137,7 +137,7 @@ fn get_jpeg_files(directory: &str) -> Vec<String> {
|
||||||
|
|
||||||
// get list of all the images in the gives path
|
// get list of all the images in the gives path
|
||||||
let images = get_jpeg_files(&args.image_path);
|
let images = get_jpeg_files(&args.image_path);
|
||||||
debug!("Images empty? {}", &images.is_empty().to_string());
|
info!("Images empty? {}", &images.is_empty().to_string());
|
||||||
|
|
||||||
// knowing now the total number of images, calculate the number of batches
|
// knowing now the total number of images, calculate the number of batches
|
||||||
let total_batches = (images.len() + config.pixelfed_batch_size - 1) / config.pixelfed_batch_size;
|
let total_batches = (images.len() + config.pixelfed_batch_size - 1) / config.pixelfed_batch_size;
|
||||||
|
|
172
src/pixelfed.rs
172
src/pixelfed.rs
|
@ -1,27 +1,36 @@
|
||||||
|
use log::{debug, error, info, log_enabled, Level};
|
||||||
use reqwest::{self};
|
use reqwest::{self};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use log::{debug, error, log_enabled, info, Level};
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
||||||
struct PixelfedConfig {
|
struct PixelfedConfig {
|
||||||
pixelfed_url: String,
|
pixelfed_url: String,
|
||||||
pixelfed_access_token: String,
|
pixelfed_access_token: String,
|
||||||
pixelfed_visibility: String, // Should be "unlisted"
|
pixelfed_visibility: String, // Should be "unlisted"
|
||||||
pixelfed_default_text: String,
|
pixelfed_default_text: String,
|
||||||
pixelfed_batch_size: usize,
|
pixelfed_batch_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_post_text(template: &str, batch_num: usize, total_batches: usize, title: &str) -> String {
|
fn format_post_text(template: &str, batch_num: usize, total_batches: usize, title: &str) -> String {
|
||||||
template
|
template
|
||||||
.replace("@batch@", &format!("Batch {} out of {}", batch_num, total_batches))
|
.replace(
|
||||||
|
"@batch@",
|
||||||
|
&format!("Batch {} out of {}", batch_num, total_batches),
|
||||||
|
)
|
||||||
.replace("@title@", title)
|
.replace("@title@", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload a single image to pixelfed
|
// upload a single image to pixelfed
|
||||||
pub fn bulk_upload_images(config: &super::Config, images: &[String], batch_num: usize, total_batches: usize, title: &str, caption_mode: &super::Mode) -> Result<(), Box<dyn Error>> {
|
pub fn bulk_upload_images(
|
||||||
|
config: &super::Config,
|
||||||
|
images: &[String],
|
||||||
|
batch_num: usize,
|
||||||
|
total_batches: usize,
|
||||||
|
title: &str,
|
||||||
|
caption_mode: &super::Mode,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut media_ids = Vec::new();
|
let mut media_ids = Vec::new();
|
||||||
let mut media_descriptions = Vec::new();
|
let mut media_descriptions = Vec::new();
|
||||||
let client = reqwest::blocking::Client::new();
|
|
||||||
let pxl_config = PixelfedConfig {
|
let pxl_config = PixelfedConfig {
|
||||||
pixelfed_url: config.pixelfed_url.clone(),
|
pixelfed_url: config.pixelfed_url.clone(),
|
||||||
pixelfed_access_token: config.pixelfed_access_token.clone(),
|
pixelfed_access_token: config.pixelfed_access_token.clone(),
|
||||||
|
@ -32,6 +41,19 @@ pub fn bulk_upload_images(config: &super::Config, images: &[String], batch_num:
|
||||||
let url = format!("{}/api/v1/media", config.pixelfed_url.clone());
|
let url = format!("{}/api/v1/media", config.pixelfed_url.clone());
|
||||||
|
|
||||||
for image_path in images {
|
for image_path in images {
|
||||||
|
let client = match reqwest::blocking::ClientBuilder::new()
|
||||||
|
.connect_timeout(Duration::new(30, 0))
|
||||||
|
.timeout(Duration::new(300, 0))
|
||||||
|
.connection_verbose(true)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to build HTTP client: {}", e);
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let description: String;
|
let description: String;
|
||||||
debug!("Handling image {}", &image_path.to_string());
|
debug!("Handling image {}", &image_path.to_string());
|
||||||
// get image description depending on the caption_mode
|
// get image description depending on the caption_mode
|
||||||
|
@ -41,20 +63,32 @@ pub fn bulk_upload_images(config: &super::Config, images: &[String], batch_num:
|
||||||
openai_model: config.openai_model.clone(),
|
openai_model: config.openai_model.clone(),
|
||||||
openai_api_key: config.openai_api_key.clone(),
|
openai_api_key: config.openai_api_key.clone(),
|
||||||
openai_api_url: config.openai_api_url.clone(),
|
openai_api_url: config.openai_api_url.clone(),
|
||||||
openai_prompt: config.prompt.clone()
|
openai_prompt: config.prompt.clone(),
|
||||||
};
|
};
|
||||||
info!("Fetching image description from ChatGPT for {}", &image_path.to_string());
|
info!(
|
||||||
description = super::image_description::get_description_from_chatgpt(image_path.to_string(), im_config)?;
|
"Fetching image description from ChatGPT for {}",
|
||||||
|
&image_path.to_string()
|
||||||
|
);
|
||||||
|
description = super::image_description::get_description_from_chatgpt(
|
||||||
|
image_path.clone().to_string(),
|
||||||
|
im_config,
|
||||||
|
)?;
|
||||||
media_descriptions.push(description.clone());
|
media_descriptions.push(description.clone());
|
||||||
},
|
}
|
||||||
super::Mode::File => {
|
super::Mode::File => {
|
||||||
let im_config = super::image_description::FileConfig {
|
let im_config = super::image_description::FileConfig {
|
||||||
caption_extension: config.caption_extension.clone(),
|
caption_extension: config.caption_extension.clone(),
|
||||||
};
|
};
|
||||||
info!("Fetching image description from File for {}", &image_path.to_string());
|
info!(
|
||||||
description = super::image_description::get_description_from_file(image_path.to_string(), im_config)?;
|
"Fetching image description from File for {}",
|
||||||
|
&image_path.to_string()
|
||||||
|
);
|
||||||
|
description = super::image_description::get_description_from_file(
|
||||||
|
image_path.clone().to_string(),
|
||||||
|
im_config,
|
||||||
|
)?;
|
||||||
media_descriptions.push(description.clone());
|
media_descriptions.push(description.clone());
|
||||||
},
|
}
|
||||||
super::Mode::Local => {
|
super::Mode::Local => {
|
||||||
let im_config = super::image_description::OllamaConfig {
|
let im_config = super::image_description::OllamaConfig {
|
||||||
ollama_api_key: config.ollama_api_key.clone(),
|
ollama_api_key: config.ollama_api_key.clone(),
|
||||||
|
@ -62,60 +96,120 @@ pub fn bulk_upload_images(config: &super::Config, images: &[String], batch_num:
|
||||||
ollama_model: config.ollama_model.clone(),
|
ollama_model: config.ollama_model.clone(),
|
||||||
ollama_prompt: config.prompt.clone(),
|
ollama_prompt: config.prompt.clone(),
|
||||||
};
|
};
|
||||||
info!("Fetching image description from OLLAMA for {}", &image_path.to_string());
|
info!(
|
||||||
description = super::image_description::get_description_from_ollama(image_path.to_string(), im_config)?;
|
"Fetching image description from OLLAMA for {}",
|
||||||
|
&image_path.to_string()
|
||||||
|
);
|
||||||
|
description = super::image_description::get_description_from_ollama(
|
||||||
|
image_path.clone().to_string(),
|
||||||
|
im_config,
|
||||||
|
)?;
|
||||||
debug!("Description generated by OLLAMA: {}", &description.clone());
|
debug!("Description generated by OLLAMA: {}", &description.clone());
|
||||||
media_descriptions.push(description.clone());
|
media_descriptions.push(description.clone());
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Uploading image {} to Pixelfed", &image_path.to_string());
|
println!("Uploading image {} to Pixelfed", &image_path.to_string());
|
||||||
|
|
||||||
// construct the upload form for Pixelfed Upload of a single image including image description
|
// construct the upload form for Pixelfed Upload of a single image including image description
|
||||||
let form = reqwest::blocking::multipart::Form::new().text("description", description.clone())
|
let form = match reqwest::blocking::multipart::Form::new()
|
||||||
.file("file", image_path)?;
|
.text("description", description.clone())
|
||||||
|
.file("file", image_path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to construct multipart form: {}", e);
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// upload the form to Pixelfed
|
// upload the form to Pixelfed
|
||||||
let res = client.post(&url)
|
let res = client
|
||||||
|
.post(&url)
|
||||||
.bearer_auth(&pxl_config.pixelfed_access_token)
|
.bearer_auth(&pxl_config.pixelfed_access_token)
|
||||||
.multipart(form)
|
.multipart(form)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
let res_json: serde_json::Value = match res {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status().is_success() {
|
||||||
|
let rsp: serde_json::Value = response.json()?;
|
||||||
|
info!("Image uploaded successfully! {}", &rsp.to_string());
|
||||||
|
rsp.into()
|
||||||
|
} else {
|
||||||
|
error!("Failed to upload image. Status: {:?}", &response.status());
|
||||||
|
let error_message = response
|
||||||
|
.text()
|
||||||
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
||||||
|
error!("Error message: {}", error_message);
|
||||||
|
return Err(Box::from("Failed to upload image"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to send request: {}", e);
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Improved error handling for API response
|
|
||||||
// Check for HTTP errors
|
|
||||||
if res.as_ref().unwrap().status().is_success() {
|
|
||||||
debug!("success!");
|
|
||||||
} else if res.as_ref().unwrap().status().is_server_error() {
|
|
||||||
error!("server error!");
|
|
||||||
} else {
|
|
||||||
error!("Something else happened. Status: {:?}", res.as_ref().unwrap().status());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let res_json: serde_json::Value = res.unwrap().json()?;
|
|
||||||
let image_id = res_json["id"].as_str().unwrap().to_string();
|
let image_id = res_json["id"].as_str().unwrap().to_string();
|
||||||
media_ids.push(image_id);
|
media_ids.push(image_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let client = match reqwest::blocking::ClientBuilder::new()
|
||||||
|
.connect_timeout(Duration::new(30, 0))
|
||||||
|
.timeout(Duration::new(300, 0))
|
||||||
|
.connection_verbose(true)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Ok(client) => client,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to build HTTP client: {}", e);
|
||||||
|
return Err(Box::from(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
if !media_ids.is_empty() {
|
if !media_ids.is_empty() {
|
||||||
let post_url = format!("{}/api/v1/statuses", pxl_config.pixelfed_url);
|
let post_url = format!("{}/api/v1/statuses", pxl_config.pixelfed_url);
|
||||||
let post_text = format_post_text(&pxl_config.pixelfed_default_text, batch_num, total_batches, title);
|
let post_text = format_post_text(
|
||||||
|
&pxl_config.pixelfed_default_text,
|
||||||
|
batch_num,
|
||||||
|
total_batches,
|
||||||
|
title,
|
||||||
|
);
|
||||||
let body = serde_json::json!({
|
let body = serde_json::json!({
|
||||||
"status": post_text,
|
"status": post_text,
|
||||||
"media_ids": media_ids,
|
"media_ids": media_ids,
|
||||||
"alt_texts": media_descriptions,
|
"alt_texts": media_descriptions,
|
||||||
"visibility": pxl_config.pixelfed_visibility,
|
"visibility": pxl_config.pixelfed_visibility,
|
||||||
});
|
});
|
||||||
debug!("Body: \n{}", &body.to_string());
|
info!("Body: \n{}", &body.to_string());
|
||||||
debug!("MediaIDs: {}", &media_ids.len());
|
info!("MediaIDs: {}", &media_ids.len());
|
||||||
debug!("Alt_texts: {}", &media_descriptions.len());
|
info!("Alt_texts: {}", &media_descriptions.len());
|
||||||
println!("Posting batch {} out of {} with media {}", batch_num, total_batches, media_ids.len());
|
println!(
|
||||||
let res = client.post(&post_url)
|
"Posting batch {} out of {} with media {}",
|
||||||
|
batch_num,
|
||||||
|
total_batches,
|
||||||
|
media_ids.len()
|
||||||
|
);
|
||||||
|
let res = client
|
||||||
|
.post(&post_url)
|
||||||
.bearer_auth(&pxl_config.pixelfed_access_token)
|
.bearer_auth(&pxl_config.pixelfed_access_token)
|
||||||
.json(&body)
|
.json(&body)
|
||||||
.send();
|
.send();
|
||||||
|
match res {
|
||||||
|
Ok(response) => {
|
||||||
|
if response.status().is_success() {
|
||||||
|
info!("Post created successfully!");
|
||||||
|
} else {
|
||||||
|
error!("Failed to create post. Status: {:?}", response.status());
|
||||||
|
let error_message = response
|
||||||
|
.text()
|
||||||
|
.unwrap_or_else(|_| "Unknown error".to_string());
|
||||||
|
error!("Error message: {}", error_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to send request: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue