diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-02-19 18:06:21 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-02-19 18:06:21 +0100 |
| commit | 962309ddcb033e0032258d6badebb90415a34e3d (patch) | |
| tree | 64a0bde3c6ddfefa334c71314d699b9a00fad0bf /import | |
| parent | c545bdbc10ae5a55f991e03260e6a74b92a75fda (diff) | |
| download | jellything-962309ddcb033e0032258d6badebb90415a34e3d.tar jellything-962309ddcb033e0032258d6badebb90415a34e3d.tar.bz2 jellything-962309ddcb033e0032258d6badebb90415a34e3d.tar.zst | |
omdb import plugin
Diffstat (limited to 'import')
| -rw-r--r-- | import/Cargo.toml | 2 | ||||
| -rw-r--r-- | import/src/lib.rs | 2 | ||||
| -rw-r--r-- | import/src/plugins/infojson.rs | 4 | ||||
| -rw-r--r-- | import/src/plugins/misc.rs | 2 | ||||
| -rw-r--r-- | import/src/plugins/mod.rs | 4 | ||||
| -rw-r--r-- | import/src/plugins/omdb.rs | 170 |
6 files changed, 179 insertions, 5 deletions
diff --git a/import/Cargo.toml b/import/Cargo.toml index c62ef9e..ad5a39d 100644 --- a/import/Cargo.toml +++ b/import/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jellyimport" -version = "0.1.0" +version = "0.2.0" edition = "2024" [dependencies] diff --git a/import/src/lib.rs b/import/src/lib.rs index 441fd0a..9129a70 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -5,9 +5,9 @@ */ #![feature(duration_constants)] +pub mod helpers; pub mod plugins; pub mod reporting; -pub mod helpers; use crate::{ plugins::{ImportPlugin, PluginContext, infojson::is_info_json, init_plugins, misc::is_cover}, diff --git a/import/src/plugins/infojson.rs b/import/src/plugins/infojson.rs index abfadc0..3928e7a 100644 --- a/import/src/plugins/infojson.rs +++ b/import/src/plugins/infojson.rs @@ -9,7 +9,7 @@ use chrono::{Utc, format::Parsed}; use jellycommon::*; use jellydb::RowNum; use jellyremuxer::matroska::{AttachedFile, Segment}; -use log::info; +use log::debug; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs::File, io::BufReader, path::Path}; @@ -171,7 +171,7 @@ impl ImportPlugin for Infojson { return Ok(()); } - info!("import channel info.json at {path:?}"); + debug!("import channel info.json at {path:?}"); let data = serde_json::from_reader::<_, YVideo>(BufReader::new(File::open(path)?))?; let title = clean_uploader_name(&data.title); diff --git a/import/src/plugins/misc.rs b/import/src/plugins/misc.rs index e71106a..1699f6d 100644 --- a/import/src/plugins/misc.rs +++ b/import/src/plugins/misc.rs @@ -34,10 +34,10 @@ impl ImportPlugin for ImageFiles { "backdrop.jpeg" | "backdrop.webp" | "backdrop.png" => PICT_BACKDROP, _ => return Ok(()), }; - info!("import {slot} at {path:?}"); let asset = ct.ic.cache.store( format!("media/literal/{}-{slot}.image", HashKey(path)), || { + info!("import {slot} at {path:?}"); let mut data = Vec::new(); File::open(path)?.read_to_end(&mut data)?; Ok(data) diff --git a/import/src/plugins/mod.rs b/import/src/plugins/mod.rs index a948c48..60bf09b 100644 --- a/import/src/plugins/mod.rs +++ b/import/src/plugins/mod.rs @@ -8,6 +8,7 @@ pub mod infojson; pub mod media_info; pub mod misc; pub mod musicbrainz; +pub mod omdb; pub mod tags; pub mod tmdb; pub mod trakt; @@ -74,6 +75,9 @@ pub fn init_plugins(secrets: &ApiSecrets) -> Vec<Box<dyn ImportPlugin>> { if let Some(api_key) = &secrets.tmdb { plugins.push(Box::new(tmdb::Tmdb::new(&api_key))); // deps: trakt } + if let Some(api_key) = &secrets.omdb { + plugins.push(Box::new(omdb::Omdb::new(&api_key))); // deps: trakt + } if let Some(api_key) = &secrets.acoustid { plugins.push(Box::new(acoustid::AcoustID::new(&api_key))); } diff --git a/import/src/plugins/omdb.rs b/import/src/plugins/omdb.rs new file mode 100644 index 0000000..bb58c6b --- /dev/null +++ b/import/src/plugins/omdb.rs @@ -0,0 +1,170 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use std::sync::Arc; + +use anyhow::{Context, Result, anyhow}; +use jellycache::Cache; +use jellycommon::{ + IDENT_IMDB, NO_IDENTIFIERS, NO_RATINGS, RTYP_IMDB, RTYP_METACRITIC, RTYP_ROTTEN_TOMATOES, +}; +use jellydb::RowNum; +use log::info; +use reqwest::{ + Client, ClientBuilder, + header::{HeaderMap, HeaderName, HeaderValue}, +}; +use serde::{Deserialize, Serialize}; +use tokio::runtime::Handle; + +use crate::{ + USER_AGENT, + plugins::{ImportPlugin, PluginContext, PluginInfo}, +}; + +pub struct Omdb { + client: Client, + key: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct OmdbMovie { + title: String, + year: String, + rated: String, + released: String, + runtime: String, + director: String, + writer: String, + actors: String, + plot: String, + language: String, + country: String, + awards: String, + poster: String, + ratings: Vec<OmdbRating>, + metascore: String, + #[serde(rename = "imdbRating")] + imdb_rating: String, + #[serde(rename = "imdbVotes")] + imdb_votes: String, + #[serde(rename = "imdbID")] + imdb_id: String, + r#type: String, + #[serde(rename = "DVD")] + dvd: Option<String>, + box_office: Option<String>, + production: Option<String>, + website: Option<String>, + response: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct OmdbRating { + source: String, + value: String, +} + +impl Omdb { + pub fn new(api_key: &str) -> Self { + let client = ClientBuilder::new() + .default_headers(HeaderMap::from_iter([ + ( + HeaderName::from_static("accept"), + HeaderValue::from_static("application/json"), + ), + ( + HeaderName::from_static("user-agent"), + HeaderValue::from_static(USER_AGENT), + ), + ])) + .build() + .unwrap(); + Self { + client, + key: api_key.to_owned(), + } + } + fn lookup(&self, cache: &Cache, id: &str, rt: &Handle) -> Result<Arc<OmdbMovie>> { + cache + .cache_memory(&format!("ext/omdb/movie/{id}.json"), move || { + rt.block_on(async { + info!("lookup {id}"); + let res = self + .client + .get(format!( + "http://www.omdbapi.com/?apikey={}&i={id}", + self.key + )) + .send() + .await? + .error_for_status()?; + Ok(res.json().await?) + }) + }) + .context("omdb lookup") + } +} + +impl ImportPlugin for Omdb { + fn info(&self) -> PluginInfo { + PluginInfo { + name: "omdb", + handle_process: true, + ..Default::default() + } + } + fn process(&self, ct: &PluginContext, node: RowNum) -> Result<()> { + let data = ct.ic.get_node(node)?.unwrap(); + let data = data.as_object(); + + let Some(id) = data.get(NO_IDENTIFIERS).unwrap_or_default().get(IDENT_IMDB) else { + return Ok(()); + }; + + let entry = self.lookup(&ct.ic.cache, id, ct.rt)?; + + let imdb = match entry.imdb_rating.as_str() { + "N/A" => None, + v => Some(v.parse::<f64>().context("parse imdb rating")?), + }; + let metascore = match entry.metascore.as_str() { + "N/A" => None, + v => Some(v.parse::<f64>().context("parse metascore rating")?), + }; + let rotten_tomatoes = entry + .ratings + .iter() + .find(|r| r.source == "Rotten Tomatoes") + .map(|r| { + r.value + .strip_suffix("%") + .ok_or(anyhow!("% missing"))? + .parse::<f64>() + .context("parse rotten tomatoes rating") + }) + .transpose()?; + + ct.ic.update_node(node, |mut node| { + for (typ, val) in [ + (RTYP_METACRITIC, metascore), + (RTYP_IMDB, imdb), + (RTYP_ROTTEN_TOMATOES, rotten_tomatoes), + ] { + if let Some(x) = val { + node = node + .as_object() + .update(NO_RATINGS, |rts| rts.insert(typ, x)); + } + } + node + })?; + + Ok(()) + } +} |