diff --git a/src/image_description.rs b/src/image_description.rs index 640fd19..967110c 100644 --- a/src/image_description.rs +++ b/src/image_description.rs @@ -1,12 +1,10 @@ -use base64::{Engine as _, engine::general_purpose::STANDARD}; +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use log::{debug, error, info, log_enabled, Level}; use serde::Deserialize; use serde::Serialize; use serde_json::json; -use std::time::Duration; -use log::{debug, error, log_enabled, info, Level}; use std::str; - - +use std::time::Duration; // module to hold all code for generating/fetching image descriptions // Input is the image name @@ -15,15 +13,14 @@ pub struct ChatGPTConfig { pub openai_api_key: String, pub openai_api_url: String, pub openai_model: String, - pub openai_prompt: String - + pub openai_prompt: String, } pub struct OllamaConfig { pub ollama_api_key: String, pub ollama_api_url: String, pub ollama_model: String, - pub ollama_prompt: String + pub ollama_prompt: String, } pub struct FileConfig { @@ -38,89 +35,98 @@ struct LlamaModel { format: String, suffix: String, images: Vec, - keep_alive: i8 + keep_alive: i8, } -// fetch the imagedescription from a file named like the Image -pub fn get_description_from_file(image_name: String , file_config: FileConfig) -> Result> { - //read image caption from a local file that +// fetch the imagedescription from a file named like the Image +pub fn get_description_from_file( + image_name: String, + file_config: FileConfig, +) -> Result> { + //read image caption from a local file that //has the same name than the image with the extension ".caption.txt" let caption_extension = file_config.caption_extension; - let captionname = format!("{}{}", image_name, caption_extension); - + let captionname = format!("{}{}", image_name, caption_extension); + debug!("Looking for {}", &captionname); let caption_data = std::fs::read_to_string(captionname); - - + + println!("Description fetched successfully from FILE"); + Ok(caption_data.unwrap()) } // fetch image description from ChatGPT -pub fn get_description_from_chatgpt(image_name: String, chatgpt_config: self::ChatGPTConfig) -> Result> { +pub fn get_description_from_chatgpt( + image_name: String, + chatgpt_config: self::ChatGPTConfig, +) -> Result> { // Read and encode image let image_data = std::fs::read(image_name)?; - - - + // Base64 encode the image for ChatGTP API let base64_image = STANDARD.encode(image_data); - + // Create ChatGPT API request let client = reqwest::blocking::Client::new(); let response = client .post(chatgpt_config.openai_api_url) - .header("Authorization", format!("Bearer {}", chatgpt_config.openai_api_key)) + .header( + "Authorization", + format!("Bearer {}", chatgpt_config.openai_api_key), + ) .header("Content-Type", "application/json") .json(&super::json!({ - "model": chatgpt_config.openai_model, - "max_tokens": 300, - "messages": [ - { - "role": "user", - "content": [ - { - "type": "text", - "text": chatgpt_config.openai_prompt.to_string(), - }, - { - "type": "image_url", - "image_url": { - "url": format!("data:image/jpeg;base64,{}", base64_image) - } - } - ] - } - ] - })) - .send(); - + "model": chatgpt_config.openai_model, + "max_tokens": 300, + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": chatgpt_config.openai_prompt.to_string(), + }, + { + "type": "image_url", + "image_url": { + "url": format!("data:image/jpeg;base64,{}", base64_image) + } + } + ] + } + ] + })) + .send(); + // Improved error handling for API response //if !response.unwrap().status().is_success() { // let error_text = response.unwrap_err(); // return Err(format!("OpenAI API error: ", std::error.box(error_text)); //} - + let result: super::Value = response.unwrap().json()?; - + // More detailed error handling for JSON parsing - let description = result["choices"] - .get(0) - .ok_or("No choices in response")? - ["message"]["content"] - .as_str() - .ok_or("Invalid content format in response")? - .to_string(); - + let description = result["choices"].get(0).ok_or("No choices in response")?["message"] + ["content"] + .as_str() + .ok_or("Invalid content format in response")? + .to_string(); + println!("Description generated successfully from ChatGPT"); + Ok(description) } // fetch images description from own OLLAMA server -pub fn get_description_from_ollama(image_name: String, ollama_config: OllamaConfig) -> Result> { +pub fn get_description_from_ollama( + image_name: String, + ollama_config: OllamaConfig, +) -> Result> { // Read and encode image - let image_data = std::fs::read(image_name)?; + let image_data = std::fs::read(image_name)?; // Base64 encode the image for ChatGTP API let base64_image = STANDARD.encode(image_data); - - + // Create the JSON payload let payload = json!({ "model": ollama_config.ollama_model.to_string(), @@ -128,24 +134,23 @@ pub fn get_description_from_ollama(image_name: String, ollama_config: OllamaCon "stream": false, "images": [base64_image] }); - - debug!("Payload for image OLLAMA API: \n{}", payload.clone()); + debug!("Payload for image OLLAMA API: \n{}", payload.clone()); // println!("JSON output:\n{}", json.clone()); // Create ChatGPT API request // let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::ClientBuilder::new() .connect_timeout(Duration::new(30, 0)) - .timeout(Duration::new(300,0)) - .connection_verbose(true).build()?; - - + .timeout(Duration::new(300, 0)) + .connection_verbose(true) + .build()?; + let response = client .post(ollama_config.ollama_api_url) .header("Content-Type", "application/json") - .json(&payload).send(); - - + .json(&payload) + .send(); + let response = match response { Ok(response) => { if response.status().is_success() { @@ -174,7 +179,6 @@ pub fn get_description_from_ollama(image_name: String, ollama_config: OllamaCon }; info!("Description generated by OLLAMA: {}", &description); - + println!("Description generated successfully from OLLAMA"); Ok(description) - } diff --git a/src/pixelfed.rs b/src/pixelfed.rs index a584acf..40b2e08 100644 --- a/src/pixelfed.rs +++ b/src/pixelfed.rs @@ -31,6 +31,8 @@ pub fn bulk_upload_images( ) -> Result<(), Box> { let mut media_ids = Vec::new(); let mut media_descriptions = Vec::new(); + + // generate our Pixelfed specific Configuration from the given global config let pxl_config = PixelfedConfig { pixelfed_url: config.pixelfed_url.clone(), pixelfed_access_token: config.pixelfed_access_token.clone(), @@ -38,8 +40,11 @@ pub fn bulk_upload_images( pixelfed_default_text: config.pixelfed_default_text.clone(), pixelfed_batch_size: config.pixelfed_batch_size.clone(), }; - let url = format!("{}/api/v1/media", config.pixelfed_url.clone()); + // construct the full URL for the Pixelfed Upload + let url = format!("{}/api/v1/media", pxl_config.pixelfed_url.clone()); + + // Iterate over all the images we were given for image_path in images { let client = match reqwest::blocking::ClientBuilder::new() .connect_timeout(Duration::new(30, 0)) @@ -69,10 +74,16 @@ pub fn bulk_upload_images( "Fetching image description from ChatGPT for {}", &image_path.to_string() ); - description = super::image_description::get_description_from_chatgpt( + description = match super::image_description::get_description_from_chatgpt( image_path.clone().to_string(), im_config, - )?; + ) { + Ok(description) => description, + Err(e) => { + error!("Failed to fetch image description from ChatGPT for {}", e); + return Err(Box::from(e)); + } + }; media_descriptions.push(description.clone()); } super::Mode::File => { @@ -83,10 +94,17 @@ pub fn bulk_upload_images( "Fetching image description from File for {}", &image_path.to_string() ); - description = super::image_description::get_description_from_file( + description = match super::image_description::get_description_from_file( image_path.clone().to_string(), im_config, - )?; + ) { + Ok(description) => description, + Err(e) => { + error!("Failed to fetch image description from File for {}", e); + return Err(Box::from(e)); + } + }; + info!("Description generated by ChatGPT: {}", &description.clone()); media_descriptions.push(description.clone()); } super::Mode::Local => { @@ -100,11 +118,17 @@ pub fn bulk_upload_images( "Fetching image description from OLLAMA for {}", &image_path.to_string() ); - description = super::image_description::get_description_from_ollama( + description = match super::image_description::get_description_from_ollama( image_path.clone().to_string(), im_config, - )?; - debug!("Description generated by OLLAMA: {}", &description.clone()); + ) { + Ok(description) => description, + Err(e) => { + error!("Failed to fetch image description from OLLAMA for {}", e); + return Err(Box::from(e)); + } + }; + info!("Description generated by OLLAMA: {}", &description.clone()); media_descriptions.push(description.clone()); } } @@ -114,13 +138,14 @@ pub fn bulk_upload_images( // construct the upload form for Pixelfed Upload of a single image including image description let form = match reqwest::blocking::multipart::Form::new() .text("description", description.clone()) - .file("file", image_path) { + .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 let res = client @@ -132,7 +157,7 @@ pub fn bulk_upload_images( let res_json: serde_json::Value = match res { Ok(response) => { if response.status().is_success() { - let rsp: serde_json::Value = response.json()?; + let rsp: serde_json::Value = response.json()?; info!("Image uploaded successfully! {}", &rsp.to_string()); rsp.into() } else {