From 575e606ee8d09d07582c0e0274635ba0a1dc274b Mon Sep 17 00:00:00 2001 From: MrSn0wy <61592704+MrSn0wy@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:43:10 +0100 Subject: [PATCH] Implemented github parsing and images re-encoding and resizing --- backend/image.png | Bin 0 -> 8239 bytes backend/src/config.rs | 0 backend/src/images.rs | 138 +++++++++++++++++++++++ backend/src/macros.rs | 29 +++++ backend/src/networking.rs | 26 +++++ backend/src/parsing.rs | 230 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 backend/image.png create mode 100644 backend/src/config.rs create mode 100644 backend/src/images.rs create mode 100644 backend/src/macros.rs create mode 100644 backend/src/networking.rs create mode 100644 backend/src/parsing.rs diff --git a/backend/image.png b/backend/image.png new file mode 100644 index 0000000000000000000000000000000000000000..1a951c509dac6c2061cfddb5132d78e4cf73c0b6 GIT binary patch literal 8239 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 literal 0 HcmV?d00001 diff --git a/backend/src/config.rs b/backend/src/config.rs new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/images.rs b/backend/src/images.rs new file mode 100644 index 0000000..2fbcf74 --- /dev/null +++ b/backend/src/images.rs @@ -0,0 +1,138 @@ +use fast_image_resize::images::Image; +use fast_image_resize::{FilterType, PixelType, ResizeAlg, ResizeOptions, Resizer, SrcCropping}; +use load_image::export::imgref::ImgVec; +use ravif::{AlphaColorMode, ColorSpace, EncodedImage, Encoder, Img, RGBA8}; +use load_image::ImageData; + +const RESIZE_OPTIONS: ResizeOptions = ResizeOptions { + algorithm: ResizeAlg::Convolution(FilterType::Lanczos3), + cropping: SrcCropping::None, + mul_div_alpha: true, +}; + +pub(crate) fn convert_image(image_data: Vec) -> anyhow::Result> { + + let quality: f32 = 60f32; + let alpha_quality = ((quality + 100.)/2.).min(quality + quality/4. + 2.); + let speed: u8 = 1; + let color_space = ColorSpace::YCbCr; + let depth = Some(10); + + let img = load_rgba(image_data)?; + + let cropped_image = crop_image(img)?; + + // let img: Img> = Img::new(cropped_image, 128,128); + + let rgba_pixels: Vec = cropped_image + .chunks(4) + .map(|chunk| RGBA8 { + r: chunk[0], + g: chunk[1], + b: chunk[2], + a: chunk[3], + }) + .collect(); + + let img = Img::new(rgba_pixels, 128,128); + // let img = load_rgba(cropped_image)?; + + // let img_vec = bytemuck::cast_slice(&img.clone().into_buf()); + // fs::write("./cropped.png", dst_image.buffer())?; + // let resized_img = load_rgba(dst_image.buffer())?; + + + let enc = Encoder::new() + .with_quality(quality) + .with_depth(depth) + .with_speed(speed) + .with_alpha_quality(alpha_quality) + .with_internal_color_space(color_space) + .with_alpha_color_mode(AlphaColorMode::UnassociatedClean) + .with_num_threads(None); + + let EncodedImage { avif_file, color_byte_size, alpha_byte_size , .. } = enc.encode_rgba(img.as_ref())?; + + Ok(avif_file) +} + +fn load_rgba(data: Vec) -> anyhow::Result> { + use rgb::prelude::*; + + let img = load_image::load_data(&data)?.into_imgvec(); + + let img = match img { + load_image::export::imgref::ImgVecKind::RGB8(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.with_alpha(255)).collect()), + load_image::export::imgref::ImgVecKind::RGBA8(img) => img, + load_image::export::imgref::ImgVecKind::RGB16(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.map(|c| (c >> 8) as u8).with_alpha(255)).collect()), + load_image::export::imgref::ImgVecKind::RGBA16(img) => img.map_buf(|buf| buf.into_iter().map(|px| px.map(|c| (c >> 8) as u8)).collect()), + load_image::export::imgref::ImgVecKind::GRAY8(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = g.0; RGBA8::new(c,c,c,255) }).collect()), + load_image::export::imgref::ImgVecKind::GRAY16(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = (g.0>>8) as u8; RGBA8::new(c,c,c,255) }).collect()), + load_image::export::imgref::ImgVecKind::GRAYA8(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = g.0; RGBA8::new(c,c,c,g.1) }).collect()), + load_image::export::imgref::ImgVecKind::GRAYA16(img) => img.map_buf(|buf| buf.into_iter().map(|g| { let c = (g.0>>8) as u8; RGBA8::new(c,c,c,(g.1>>8) as u8) }).collect()), + }; + + Ok(img) +} +fn load_rgba_test() -> anyhow::Result<(Vec, usize, usize)> { + let image = load_image::load_path("./image.png")?; + + let width = image.width; + let height = image.height; + + + let img = match image.bitmap { + ImageData::RGBA8(img) => img, + ImageData::RGB8(img) => img.iter().map(|buf| buf.with_alpha(255)).collect(), + _ => todo!() + }; + + Ok((img,width,height)) +} + + +fn crop_image(image_data: ImgVec) -> anyhow::Result> { + + let width: u32 = image_data.width() as u32; + let height: u32 = image_data.height() as u32; + + let img_bytes: Vec = image_data.into_buf().iter().flat_map(|pixel| vec![pixel.r, pixel.g, pixel.b, pixel.a]).collect(); + + let src_image = Image::from_vec_u8( + width, + height, + img_bytes, + PixelType::U8x4, // RGBA8 + )?; + + + let mut cropped_image = Image::new( + 128, + 128, + PixelType::U8x4, // RGBA8 + ); + + let mut resizer = Resizer::new(); + resizer.resize(&src_image, &mut cropped_image, &RESIZE_OPTIONS)?; + + // let file = File::create("./cropped.png")?; + // let ref mut w = BufWriter::new(file); + // let mut encoder = PngEncoder::new(w, 128u32, 128u32); + // encoder.set_color(png::ColorType::Rgba); + // encoder.set_depth(png::BitDepth::Eight); + // + // let mut writer = encoder.write_header()?; + // + // // Write the image data (RGBA) + // writer.write_image_data(cropped_image.buffer())?; + + + // let (image, width, height) = load_rgba_test().unwrap(); + // + // let raw_pixels: &[u8] = bytemuck::cast_slice(&image); + // + // assert_eq!(raw_pixels.len(), (width * height * 4) as usize, "Invalid buffer size!"); + // + + Ok(cropped_image.into_vec()) +} \ No newline at end of file diff --git a/backend/src/macros.rs b/backend/src/macros.rs new file mode 100644 index 0000000..5a4ddb4 --- /dev/null +++ b/backend/src/macros.rs @@ -0,0 +1,29 @@ +// purely cosmetic +#[macro_export] +macro_rules! panic_red { + ($($arg:tt)*) => {{ + eprintln!("\x1b[31;1m{}\x1b[0m", format!($($arg)*)); + std::process::exit(1); + }}; +} + +#[macro_export] +macro_rules! eprintln_red { + ($($arg:tt)*) => {{ + eprintln!("\x1b[31;1m{}\x1b[0m", format!($($arg)*)); + }}; +} + +#[macro_export] +macro_rules! println_green { + ($($arg:tt)*) => {{ + println!("\x1b[32m{}\x1b[0m", format!($($arg)*)); + }}; +} + +#[macro_export] +macro_rules! println_cyan { + ($($arg:tt)*) => {{ + println!("\x1b[36;1m{}\x1b[0m", format!($($arg)*)); + }}; +} \ No newline at end of file diff --git a/backend/src/networking.rs b/backend/src/networking.rs new file mode 100644 index 0000000..39e773a --- /dev/null +++ b/backend/src/networking.rs @@ -0,0 +1,26 @@ +use serde_json::Value; +use crate::panic_red; + +fn github_request(url: &str, token: &str) -> Value { + match ureq::get(url) + .set("User-Agent", "fpps4.net") + .set("Accept", "application/vnd.github+json") + .set("Authorization", &format!("Bearer {}", token)) + .call() + { + Ok(response) => response.into_json().expect("Failed to parse JSON"), + Err(response) => panic_red!("Github request failed: {}", response), + } +} + +// fn image_downloader(url: String, user_ffagent: &str, location: String) -> Result<(), anyhow::Error> { +// let response = ureq::get(&url).set("User-Agent", user_agent).call()?; +// let mut reader = response.into_reader(); +// let mut buffer = Vec::new(); +// reader.read_to_end(&mut buffer)?; +// +// let img = image::load_from_memory(&buffer)?; +// let resized_img = img.resize_exact(256, 256, image::imageops::Lanczos3); +// resized_img.save_with_format(location, image::ImageFormat::Avif)?; +// Ok(()) +// } \ No newline at end of file diff --git a/backend/src/parsing.rs b/backend/src/parsing.rs new file mode 100644 index 0000000..39f3822 --- /dev/null +++ b/backend/src/parsing.rs @@ -0,0 +1,230 @@ +use anyhow::{bail, Context}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +struct Issues { + issues: Vec, +} + +// #[derive(Serialize, Deserialize)] +struct Issue { + id: u64, + code: String, + title: String, + labels: Vec, + status: Status, + issue_type: GameType, + created: String, + updated: String, + // image: bool, +} + +// #[derive(Serialize, Deserialize)] +enum GameType { + Game, + Homebrew, + Ps2game, + SystemFw505, + SystemFwUnknown, +} + +// #[derive(Serialize, Deserialize)] +enum Status { + Playable, + Ingame, + Menus, + Boots, + Nothing, +} + +fn parse_github_issue(issue: Value, code_regex: Regex) -> anyhow::Result<(Issue, Vec)> { + let mut warning: Vec = vec![]; + + let id: u64 = issue + .get("number") + .and_then(Value::as_u64) + .context("Failed to parse id")?; + + let code: String = { + let title: &str = issue + .get("title") + .and_then(Value::as_str) + .context("Failed to parse title")?; + + if code_regex.captures(&title).iter().len() != 1 { + warning.push(String::from("More than one title id has been matched")); // add a warning + } + + code_regex + .find(&title) + .map(|x| x.as_str().to_uppercase()) + .context("Failed to get code using regex")? + }; + + let title: String = { + let mut title: String = issue + .get("title") + .and_then(Value::as_str) + .context("Failed to parse title")? + .to_string(); + + title = title.replace(&code, ""); + + if title.contains("(Homebrew)") + || title.contains("- HOMEBREW") + || title.contains("Homebrew") + || title.contains("[]") + { + warning.push(String::from("Title not correctly formatted")); // add a warning + + title = title.replace("(Homebrew)", ""); + title = title.replace("- HOMEBREW", ""); + title = title.replace("Homebrew", ""); + title = title.replace("[]", ""); + } + + title + }; + + let mut labels: Vec<&str> = issue + .get("labels") + .and_then(Value::as_array) + .context("Failed to get labels")? + .iter() + .map(|label| label + .get("name") + .and_then(Value::as_str) + .unwrap_or_default()) + .collect::>(); + + + let status: Status = { + + // I agree that the .retain is a bit silly, but I want to be able to give a warning when an issue has more than one "status-" label + let status = match labels.iter().find(|label| label.starts_with("status-")) { + Some(label) => match *label { + "status-playable" => { + labels.retain(|label| label != &"status-playable"); + Status::Playable + } + "status-ingame" => { + labels.retain(|label| label != &"status-ingame"); + Status::Ingame + } + "status-menus" => { + labels.retain(|label| label != &"status-menus"); + Status::Menus + } + "status-boots" => { + labels.retain(|label| label != &"status-boots"); + Status::Boots + } + "status-nothing" => { + labels.retain(|label| label != &"status-nothing"); + Status::Nothing + } + _ => bail!("No status label found or recognized"), + } + None => bail!("No status label found or recognized"), + }; + + // clean labels + if labels.contains(&"status-") + { + warning.push(String::from("More than one status label")); // add a warning + labels = labels.into_iter().filter(|x| !x.starts_with("status-")).collect(); + } + + status + }; + + + let issue_type: GameType = { + let issue_type = match labels.iter().find(|label| label.starts_with("app-")) { + Some(label) => match *label { + "app-system-fw505" => { + labels.retain(|label| *label != "app-system-fw505"); + GameType::SystemFw505 + } + "app-system-fw_unknown" => { + labels.retain(|label| *label != "app-system-fw_unknown"); + GameType::SystemFwUnknown + } + "app-ps2game" => { + labels.retain(|label| *label != "app-ps2game"); + GameType::Ps2game + } + "app-homebrew" => { + labels.retain(|label| *label != "app-homebrew"); + GameType::Homebrew + } + + _ => + if code.starts_with("CUSA") + || code.starts_with("PCJS") + || code.starts_with("PLJM") + || code.starts_with("PLJS") + { + GameType::Game + } else { + bail!("couldn't determine the game-type") + } + + } + + None => + if code.starts_with("CUSA") + || code.starts_with("PCJS") + || code.starts_with("PLJM") + || code.starts_with("PLJS") + { + GameType::Game + } else { + bail!("couldn't determine the game-type") + } + + }; + + // clean labels + if labels.contains(&"app-system-fw505") + || labels.contains(&"app-system-fw_unknown") + || labels.contains(&"app-ps2game") + || labels.contains(&"app-homebrew") + { + warning.push(String::from("More than one app label")); // add a warning + labels = labels.into_iter().filter(|x| !x.starts_with("app-")).collect(); + } + + issue_type + }; + + // RFC3339 String + let created: String = issue + .get("created_at") + .and_then(Value::as_str) + .context("Failed to parse updated_at")? + .to_string(); + + let updated: String = issue + .get("updated_at") + .and_then(Value::as_str) + .context("Failed to parse updated_at")? + .to_string(); + + let new_issue: Issue = Issue { + id, + code, + title, + labels: labels + .iter_mut() + .map(|label| label.to_string()) + .collect(), + status, + issue_type, + created, + updated, + }; + + Ok((new_issue, warning)) +} \ No newline at end of file -- 2.51.2