77 lines
1.7 KiB
Rust
77 lines
1.7 KiB
Rust
use epub::doc::EpubDoc;
|
|
use mime::Mime;
|
|
use pile_io::SyncReadBridge;
|
|
use std::sync::{Arc, OnceLock};
|
|
use tracing::trace;
|
|
|
|
use crate::{
|
|
extract::traits::ExtractState,
|
|
value::{Item, PileValue},
|
|
};
|
|
|
|
pub struct EpubCoverExtractor {
|
|
item: Item,
|
|
output: OnceLock<Option<(Mime, Vec<u8>)>>,
|
|
}
|
|
|
|
impl EpubCoverExtractor {
|
|
pub fn new(item: &Item) -> Self {
|
|
Self {
|
|
item: item.clone(),
|
|
output: OnceLock::new(),
|
|
}
|
|
}
|
|
|
|
async fn get_inner(&self) -> Result<Option<&(Mime, Vec<u8>)>, std::io::Error> {
|
|
if let Some(x) = self.output.get() {
|
|
return Ok(x.as_ref());
|
|
}
|
|
|
|
let reader = SyncReadBridge::new_current(self.item.read().await?);
|
|
let result = tokio::task::spawn_blocking(move || {
|
|
let mut doc = EpubDoc::from_reader(reader)?;
|
|
let cover_id = match doc.get_cover_id() {
|
|
Ok(id) => id,
|
|
Err(_) => return Ok::<_, anyhow::Error>(None),
|
|
};
|
|
|
|
let mime: Mime = doc
|
|
.resources
|
|
.get(&cover_id)
|
|
.and_then(|(_, mime_str)| mime_str.parse().ok())
|
|
.unwrap_or(mime::IMAGE_JPEG);
|
|
|
|
let bytes = doc.get_cover()?;
|
|
Ok(Some((mime, bytes)))
|
|
})
|
|
.await?;
|
|
|
|
let result = match result {
|
|
Ok(x) => x,
|
|
Err(error) => match error.downcast::<std::io::Error>() {
|
|
Ok(x) => return Err(x),
|
|
Err(error) => {
|
|
trace!(message = "Could not extract epub cover", ?error, key = ?self.item.key());
|
|
None
|
|
}
|
|
},
|
|
};
|
|
|
|
Ok(self.output.get_or_init(|| result).as_ref())
|
|
}
|
|
|
|
pub async fn get(&self, state: &ExtractState) -> Result<Option<PileValue>, std::io::Error> {
|
|
if !state.ignore_mime && self.item.mime().essence_str() != "application/epub+zip" {
|
|
return Ok(None);
|
|
}
|
|
|
|
Ok(self
|
|
.get_inner()
|
|
.await?
|
|
.map(|(mime, bytes)| PileValue::Blob {
|
|
mime: mime.clone(),
|
|
bytes: Arc::new(bytes.clone()),
|
|
}))
|
|
}
|
|
}
|