diff --git a/src/imageprovider.rs b/src/imageprovider.rs new file mode 100644 index 0000000..bc6a8c2 --- /dev/null +++ b/src/imageprovider.rs @@ -0,0 +1,86 @@ +// provides NASA image of the day. +// + +use std::io::Cursor; + +use anyhow::{Context, Result}; +use bytes::Buf; +use image::{ + imageops::{crop_imm, resize, FilterType}, + ImageReader, RgbImage, +}; +use std::time::Duration; +use tokio::time::sleep; + +use rss::Channel; +use tracing::{info, instrument}; + +use crate::{app::AppState, dither::DitherMethod, dither::DitheredImage, eink::Palette}; + +const NASA_IOTD_RSS_FEED: &str = "https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss"; + +const TARGET_ASPECT: f32 = 800.0 / 480.0; + +async fn get_latest_image() -> Result> { + let content = reqwest::get(NASA_IOTD_RSS_FEED).await?.bytes().await?; + let channel = Channel::read_from(content.reader())?; + + let latest_item = &channel.items()[0]; + let image_enc = latest_item + .enclosure() + .context("no enclosure in first item")?; + + // now we have an image url + info!( + "found image with type {}, size {}", + image_enc.mime_type(), + image_enc.length() + ); + + let image_request = reqwest::get(image_enc.url()).await?; + let reader = + ImageReader::new(Cursor::new(image_request.bytes().await?)).with_guessed_format()?; + + let image = reader.decode()?; + Ok(Box::new(image.into())) +} + +async fn get_nasa() -> Result { + let img = get_latest_image().await?; + let (width, height) = img.dimensions(); + + #[allow(clippy::cast_precision_loss)] + let input_aspect = width as f32 / height as f32; + let resize = if input_aspect > TARGET_ASPECT { + let new_width = (height as f32 * TARGET_ASPECT) as u32; + let offset = (width - new_width) / 2; + (offset, 0, new_width, height) + } else { + let new_height = (width as f32 / TARGET_ASPECT) as u32; + let offset = (height - new_height) / 2; + (0, offset, width, new_height) + }; + + let cropped = crop_imm(&*img, resize.0, resize.1, resize.2, resize.3).to_image(); + + Ok(cropped) +} + +#[instrument(skip(ctx))] +pub async fn nasa_task(ctx: AppState) { + info!("Starting NASA IOTD task"); + loop { + if let Ok(img) = get_nasa().await { + let mut buf = DitheredImage::new(800, 480, Palette::Default.value().to_vec()); + let resized = resize(&img, 800, 480, FilterType::Lanczos3); + { + let mut dither = DitherMethod::Atkinson.get_ditherer(); + dither.dither(&resized, &mut buf); + } + ctx.display_channel.send(Box::new(buf)); + info!("Nasa image updated"); + } + + sleep(Duration::from_secs(60 * 60 * 24)).await; + } +}