src/media/images.rs
Rama main · réplica de lectura del repositorio original.
use chrono::{DateTime, Utc};
use color_eyre::{
Result,
eyre::{Context, eyre},
};
use exif::{Exif, In, Tag, Value};
use image::DynamicImage;
use std::{fs, path::Path};
pub struct ImageProcessor {
exif: Option<Exif>,
image: DynamicImage,
}
impl ImageProcessor {
pub fn new(path: &Path) -> Result<Self> {
let media_bytes = fs::read(path)?;
let mut exif_buf_reader = std::io::Cursor::new(media_bytes);
let exif_reader = exif::Reader::new();
let exif = exif_reader.read_from_container(&mut exif_buf_reader).ok();
let image = image::open(path).wrap_err("Failed to open image")?;
Ok(Self { exif, image })
}
pub fn get_timestamp(&self) -> Result<DateTime<Utc>> {
let exif = self.exif.as_ref().ok_or_else(|| eyre!("no exif data"))?;
let timestamp = exif_get_str(exif, Tag::DateTimeOriginal)
.or_else(|_| exif_get_str(exif, Tag::DateTime))
.wrap_err("Failed to get DateTime/DateTimeOriginal tag from exif data")?;
let timestamp_tz = exif_get_str(exif, Tag::OffsetTimeOriginal);
let mut timestamp_modified = timestamp.to_string().replace('-', ":");
timestamp_modified.push(' ');
match ×tamp_tz {
Ok(tz) => timestamp_modified.push_str(tz.as_ref()),
Err(_) => timestamp_modified.push_str("+02:00"), // TODO: Make configurable
}
let timestamp = DateTime::parse_from_str(×tamp_modified, "%Y:%m:%d %H:%M:%S %z")
.wrap_err(format!(
"Failed to parse timestamp:{timestamp:?} tz:{timestamp_tz:?}"
))?
.with_timezone(&Utc);
Ok(timestamp)
}
pub fn get_thumbnail(&self, size: u32) -> Result<DynamicImage> {
let mut image = self.image.thumbnail(size, size);
if let Some(exif) = &self.exif {
image = match exif_get_u32(exif, Tag::Orientation) {
// http://sylvana.net/jpegcrop/exif_orientation.html
// Ok(1) => image,
Ok(2) => image.flipv(),
Ok(3) => image.rotate180(),
Ok(4) => image.rotate180().flipv(),
Ok(5) => image.rotate90().flipv(),
Ok(6) => image.rotate90(),
Ok(7) => image.rotate270().flipv(),
Ok(8) => image.rotate270(),
_ => image,
};
}
Ok(crop(image))
}
}
fn exif_get_u32(exif: &exif::Exif, tag: Tag) -> Result<u32> {
#[allow(clippy::option_if_let_else)]
match exif.get_field(tag, In::PRIMARY) {
Some(field) => match field.value.get_uint(0) {
Some(v) => Ok(v),
_ => Err(eyre!("Failed getting number")),
},
None => Err(eyre!("Value not found")),
}
}
fn exif_get_str(exif: &exif::Exif, tag: Tag) -> Result<&str> {
match exif.get_field(tag, In::PRIMARY) {
Some(field) => match &field.value {
Value::Ascii(bytes) => {
let bytes = bytes.first().ok_or_else(|| eyre!("Something is wrong"))?;
Ok(std::str::from_utf8(bytes)?)
}
_ => Err(eyre!("Not Ascii value")),
},
None => Err(eyre!("Value not found")),
}
}
#[must_use]
pub fn crop(mut image: DynamicImage) -> DynamicImage {
let image_height = image.height();
let image_width = image.width();
if image_height > image_width {
image.crop(
0,
(image_height - image_width) / 2,
image_width,
image_width,
)
} else {
image.crop(
(image_width - image_height) / 2,
0,
image_height,
image_height,
)
}
}