147 lines
5.1 KiB
Rust
147 lines
5.1 KiB
Rust
use std::sync::Arc;
|
|
|
|
use super::Extractor;
|
|
use eyre::{eyre, Result};
|
|
use lazy_static::lazy_static;
|
|
use upend_base::{
|
|
addressing::Address,
|
|
constants::{ATTR_IN, ATTR_KEY, ATTR_LABEL, ATTR_OF},
|
|
entry::{Entry, EntryValue, InvariantEntry},
|
|
};
|
|
use upend_db::{
|
|
jobs::{JobContainer, JobState},
|
|
stores::{fs::FILE_MIME_KEY, UpStore},
|
|
UpEndConnection,
|
|
};
|
|
|
|
lazy_static! {
|
|
pub static ref ID3_TYPE_INVARIANT: InvariantEntry = InvariantEntry {
|
|
attribute: String::from(ATTR_KEY),
|
|
value: "TYPE_ID3".into(),
|
|
};
|
|
pub static ref ID3_TYPE_LABEL: Entry = Entry {
|
|
entity: ID3_TYPE_INVARIANT.entity().unwrap(),
|
|
attribute: ATTR_LABEL.into(),
|
|
value: "ID3".into(),
|
|
..Default::default()
|
|
};
|
|
}
|
|
|
|
pub struct ID3Extractor;
|
|
|
|
impl Extractor for ID3Extractor {
|
|
fn get(
|
|
&self,
|
|
address: &Address,
|
|
_connection: &UpEndConnection,
|
|
store: Arc<Box<dyn UpStore + Send + Sync>>,
|
|
mut job_container: JobContainer,
|
|
) -> Result<Vec<Entry>> {
|
|
if let Address::Hash(hash) = address {
|
|
let files = store.retrieve(hash)?;
|
|
|
|
if let Some(file) = files.get(0) {
|
|
let file_path = file.get_file_path();
|
|
let mut job_handle = job_container.add_job(
|
|
None,
|
|
&format!(
|
|
r#"Getting ID3 info from "{:}""#,
|
|
file_path
|
|
.components()
|
|
.last()
|
|
.unwrap()
|
|
.as_os_str()
|
|
.to_string_lossy()
|
|
),
|
|
)?;
|
|
|
|
let tags = id3::Tag::read_from_path(file_path)?;
|
|
|
|
let mut result: Vec<Entry> = tags
|
|
.frames()
|
|
.flat_map(|frame| match frame.content() {
|
|
id3::Content::Text(text) => vec![
|
|
Entry {
|
|
entity: address.clone(),
|
|
attribute: format!("ID3_{}", frame.id()),
|
|
value: match frame.id() {
|
|
"TYER" | "TBPM" => EntryValue::guess_from(text),
|
|
_ => text.clone().into(),
|
|
},
|
|
provenance: "SYSTEM EXTRACTOR".to_string(),
|
|
timestamp: chrono::Utc::now().naive_utc(),
|
|
},
|
|
Entry {
|
|
entity: Address::Attribute(format!("ID3_{}", frame.id())),
|
|
attribute: ATTR_LABEL.into(),
|
|
value: format!("ID3: {}", frame.name()).into(),
|
|
provenance: "SYSTEM EXTRACTOR".to_string(),
|
|
timestamp: chrono::Utc::now().naive_utc(),
|
|
},
|
|
],
|
|
_ => vec![],
|
|
})
|
|
.collect();
|
|
|
|
if !result.is_empty() {
|
|
result.extend(
|
|
result
|
|
.iter()
|
|
.filter(|e| e.attribute != ATTR_LABEL)
|
|
.map(|e| Entry {
|
|
entity: Address::Attribute(e.attribute.clone()),
|
|
attribute: ATTR_OF.into(),
|
|
value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()),
|
|
..Default::default()
|
|
})
|
|
.collect::<Vec<Entry>>(),
|
|
);
|
|
result.extend(vec![
|
|
(&ID3_TYPE_INVARIANT as &InvariantEntry).try_into().unwrap(),
|
|
ID3_TYPE_LABEL.clone(),
|
|
Entry {
|
|
entity: address.clone(),
|
|
attribute: ATTR_IN.into(),
|
|
value: EntryValue::Address(ID3_TYPE_INVARIANT.entity().unwrap()),
|
|
..Default::default()
|
|
},
|
|
]);
|
|
}
|
|
|
|
let _ = job_handle.update_state(JobState::Done);
|
|
|
|
Ok(result)
|
|
} else {
|
|
Err(eyre!("Couldn't find file for {hash:?}!"))
|
|
}
|
|
} else {
|
|
Ok(vec![])
|
|
}
|
|
}
|
|
|
|
fn is_needed(&self, address: &Address, connection: &UpEndConnection) -> Result<bool> {
|
|
let is_audio = connection.retrieve_object(address)?.iter().any(|e| {
|
|
if e.attribute == FILE_MIME_KEY {
|
|
if let EntryValue::String(mime) = &e.value {
|
|
return mime.starts_with("audio") || mime == "application/x-riff";
|
|
}
|
|
}
|
|
false
|
|
});
|
|
|
|
if !is_audio {
|
|
return Ok(false);
|
|
}
|
|
|
|
let is_extracted = !connection
|
|
.query(format!("(matches @{} (contains \"ID3\") ?)", address).parse()?)?
|
|
.is_empty();
|
|
|
|
if is_extracted {
|
|
return Ok(false);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
}
|