aboutsummaryrefslogtreecommitdiff
path: root/import/src/tmdb.rs
diff options
context:
space:
mode:
Diffstat (limited to 'import/src/tmdb.rs')
-rw-r--r--import/src/tmdb.rs194
1 files changed, 140 insertions, 54 deletions
diff --git a/import/src/tmdb.rs b/import/src/tmdb.rs
index 95ebef4..b651223 100644
--- a/import/src/tmdb.rs
+++ b/import/src/tmdb.rs
@@ -1,3 +1,5 @@
+use std::{fmt::Display, sync::Arc};
+
/*
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.
@@ -5,11 +7,143 @@
*/
use anyhow::Context;
use bincode::{Decode, Encode};
-use jellycommon::chrono::{format::Parsed, Utc};
+use jellybase::cache::{async_cache_file, async_cache_memory};
+use jellycommon::{
+ chrono::{format::Parsed, Utc},
+ AssetLocation,
+};
use log::info;
+use reqwest::{
+ header::{HeaderMap, HeaderName, HeaderValue},
+ Client, ClientBuilder,
+};
use serde::Deserialize;
+use tokio::io::AsyncWriteExt;
+
+pub struct Tmdb {
+ client: Client,
+ key: String,
+}
+
+impl Tmdb {
+ 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"),
+ )]))
+ .build()
+ .unwrap();
+ Self {
+ client,
+ key: api_key.to_owned(),
+ }
+ }
+ pub async fn search(&self, kind: TmdbKind, query: &str) -> anyhow::Result<Arc<TmdbQuery>> {
+ async_cache_memory(
+ &["api-tmdb-search", query, &format!("{kind}")],
+ || async move {
+ info!("searching tmdb: {query:?}");
+ Ok(self
+ .client
+ .get(&format!(
+ "https://api.themoviedb.org/3/search/{kind}?query={}?api_key={}",
+ query.replace(" ", "+"),
+ self.key
+ ))
+ .send()
+ .await?
+ .error_for_status()?
+ .json::<TmdbQuery>()
+ .await?)
+ },
+ )
+ .await
+ }
+ pub async fn details(&self, kind: TmdbKind, id: u64) -> anyhow::Result<Arc<TmdbDetails>> {
+ async_cache_memory(
+ &["api-tmdb-details", &format!("{kind} {id}")],
+ || async move {
+ info!("fetching details: {id:?}");
+ Ok(self
+ .client
+ .get(&format!(
+ "https://api.themoviedb.org/3/{kind}/{id}?api_key={}",
+ self.key,
+ ))
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?)
+ },
+ )
+ .await
+ }
+ pub async fn person_image(&self, id: u64) -> anyhow::Result<Arc<TmdbPersonImage>> {
+ async_cache_memory(&["api-tmdb-search", &format!("{id}")], || async move {
+ Ok(self
+ .client
+ .get(&format!(
+ "https://api.themoviedb.org/3/person/{id}/images?api_key={}",
+ self.key,
+ ))
+ .send()
+ .await?
+ .error_for_status()?
+ .json()
+ .await?)
+ })
+ .await
+ }
+ pub async fn image(&self, path: &str) -> anyhow::Result<AssetLocation> {
+ async_cache_file(&["api-tmdb-image", path], |mut file| async move {
+ info!("downloading image {path:?}");
+ let mut res = reqwest::get(&format!("https://image.tmdb.org/t/p/original{path}"))
+ .await?
+ .error_for_status()?;
+ while let Some(chunk) = res.chunk().await? {
+ file.write_all(&chunk).await?;
+ }
+ Ok(())
+ })
+ .await
+ }
+}
+
+pub fn parse_release_date(d: &str) -> anyhow::Result<i64> {
+ let (year, month, day) = (&d[0..4], &d[5..7], &d[8..10]);
+ let (year, month, day) = (
+ year.parse().context("parsing year")?,
+ month.parse().context("parsing month")?,
+ day.parse().context("parsing day")?,
+ );
+
+ let mut p = Parsed::new();
+ p.year = Some(year);
+ p.month = Some(month);
+ p.day = Some(day);
+ p.hour_div_12 = Some(0);
+ p.hour_mod_12 = Some(0);
+ p.minute = Some(0);
+ p.second = Some(0);
+ Ok(p.to_datetime_with_timezone(&Utc)?.timestamp_millis())
+}
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Deserialize, Encode, Decode)]
+pub struct TmdbPersonImage {
+ pub id: u64,
+ pub profiles: Vec<TmdbPersonImageProfile>,
+}
+#[derive(Debug, Clone, Deserialize, Encode, Decode)]
+pub struct TmdbPersonImageProfile {
+ pub aspect_ratio: f64,
+ pub height: u32,
+ pub width: u32,
+ pub file_path: String,
+}
+
+#[derive(Debug, Clone, Deserialize, Encode, Decode)]
pub struct TmdbQuery {
pub page: usize,
pub results: Vec<TmdbQueryResult>,
@@ -77,59 +211,11 @@ pub enum TmdbKind {
Tv,
Movie,
}
-impl TmdbKind {
- pub fn as_str(&self) -> &'static str {
- match self {
+impl Display for TmdbKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
TmdbKind::Tv => "tv",
TmdbKind::Movie => "movie",
- }
+ })
}
}
-
-pub async fn tmdb_search(kind: TmdbKind, query: &str, key: &str) -> anyhow::Result<TmdbQuery> {
- info!("searching tmdb: {query:?}");
- Ok(reqwest::get(&format!(
- "https://api.themoviedb.org/3/search/{}?query={}&api_key={key}",
- kind.as_str(),
- query.replace(" ", "+")
- ))
- .await?
- .json::<TmdbQuery>()
- .await?)
-}
-
-pub async fn tmdb_details(kind: TmdbKind, id: u64, key: &str) -> anyhow::Result<TmdbDetails> {
- info!("fetching details: {id:?}");
- Ok(reqwest::get(&format!(
- "https://api.themoviedb.org/3/{}/{id}?api_key={key}",
- kind.as_str()
- ))
- .await?
- .json()
- .await?)
-}
-
-pub async fn tmdb_image(path: &str) -> anyhow::Result<Vec<u8>> {
- info!("downloading image {path:?}");
- let res = reqwest::get(&format!("https://image.tmdb.org/t/p/original{path}")).await?;
- Ok(res.bytes().await?.to_vec())
-}
-
-pub fn parse_release_date(d: &str) -> anyhow::Result<i64> {
- let (year, month, day) = (&d[0..4], &d[5..7], &d[8..10]);
- let (year, month, day) = (
- year.parse().context("parsing year")?,
- month.parse().context("parsing month")?,
- day.parse().context("parsing day")?,
- );
-
- let mut p = Parsed::new();
- p.year = Some(year);
- p.month = Some(month);
- p.day = Some(day);
- p.hour_div_12 = Some(0);
- p.hour_mod_12 = Some(0);
- p.minute = Some(0);
- p.second = Some(0);
- Ok(p.to_datetime_with_timezone(&Utc)?.timestamp_millis())
-}