-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
scanner: refactor most of the movies scanner
- Loading branch information
Showing
7 changed files
with
228 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
use anitomy::Anitomy; | ||
use anitomy::ElementCategory; | ||
use torrent_name_parser::Metadata as TorrentMetadata; | ||
|
||
#[derive(Clone, Debug, Eq, PartialEq, Hash)] | ||
pub struct Metadata { | ||
pub name: String, | ||
pub year: Option<i64>, | ||
pub season: Option<i64>, | ||
pub episode: Option<i64>, | ||
} | ||
|
||
pub trait FilenameMetadata { | ||
fn from_str(s: &str) -> Option<Metadata>; | ||
} | ||
|
||
impl FilenameMetadata for TorrentMetadata { | ||
fn from_str(s: &str) -> Option<Metadata> { | ||
let metadata = TorrentMetadata::from(s).ok()?; | ||
|
||
Some(Metadata { | ||
name: metadata.title().to_owned(), | ||
year: metadata.year().map(|x| x as i64), | ||
season: metadata.season().map(|x| x as i64), | ||
episode: metadata.episode().map(|x| x as i64), | ||
}) | ||
} | ||
} | ||
|
||
impl FilenameMetadata for Anitomy { | ||
fn from_str(s: &str) -> Option<Metadata> { | ||
let metadata = match Anitomy::new().parse(s) { | ||
Ok(v) | Err(v) => v, | ||
}; | ||
|
||
Some(Metadata { | ||
name: metadata.get(ElementCategory::AnimeTitle)?.to_string(), | ||
year: metadata | ||
.get(ElementCategory::AnimeYear) | ||
.and_then(|x| x.parse().ok()), | ||
season: metadata | ||
.get(ElementCategory::AnimeSeason) | ||
.and_then(|x| x.parse().ok()), | ||
episode: metadata | ||
.get(ElementCategory::EpisodeNumber) | ||
.and_then(|x| x.parse().ok()), | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
pub trait ResultExt<T, E> { | ||
fn inspect_err(self, f: impl FnOnce(&E)) -> Result<T, E>; | ||
} | ||
|
||
impl<T, E> ResultExt<T, E> for Result<T, E> { | ||
fn inspect_err(self, f: impl FnOnce(&E)) -> Result<T, E> { | ||
if let Err(ref e) = self { | ||
f(e); | ||
} | ||
|
||
self | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
use crate::external::ExternalMedia; | ||
use crate::external::ExternalQuery; | ||
use crate::inspect::ResultExt; | ||
|
||
use super::MediaMatcher; | ||
use super::WorkUnit; | ||
|
||
use async_trait::async_trait; | ||
use chrono::prelude::Utc; | ||
use chrono::Datelike; | ||
|
||
use database::library::MediaType; | ||
use database::media::InsertableMedia; | ||
use database::media::Media; | ||
use database::mediafile::UpdateMediaFile; | ||
use database::mediafile::MediaFile; | ||
use database::Transaction; | ||
|
||
use std::sync::Arc; | ||
use tracing::error; | ||
|
||
pub struct MovieMatcher; | ||
|
||
impl MovieMatcher { | ||
/// Method will match a mediafile to a new media. Caller must ensure that the mediafile | ||
/// supplied is not coupled to a media object. If it is coupled we will assume that we can | ||
/// replace the metadata supplied to it. | ||
async fn match_to_result( | ||
&self, | ||
tx: &mut Transaction<'_>, | ||
file: MediaFile, | ||
provided: ExternalMedia, | ||
) -> Result<(), Box<dyn std::error::Error>> { | ||
// TODO: Push posters and backdrops to download queue. | ||
|
||
let media = InsertableMedia { | ||
media_type: MediaType::Movie, | ||
library_id: file.library_id, | ||
name: provided.title, | ||
description: provided.description, | ||
rating: provided.rating, | ||
year: provided.release_date.map(|x| x.year() as _), | ||
added: Utc::now().to_string(), | ||
poster: None, | ||
backdrop: None, | ||
}; | ||
|
||
// NOTE: If the mediafile is coupled to a media we assume that we want to reuse the media | ||
// object, but replace its metadata in-place. This is useful when rematching a media. | ||
let media_id = if file.media_id.is_some() { | ||
media.insert(tx, file.media_id).await.inspect_err(|error| { | ||
error!( | ||
?error, | ||
?file.media_id, | ||
"Failed to assign mediafile to media." | ||
) | ||
})? | ||
} else { | ||
// Maybe a media object that can be linked against this file already exists and we want | ||
// to bind to it? | ||
match Media::get_id_by_name(tx, &media.name) | ||
.await | ||
.inspect_err(|error| error!(?error, %media.name, "Failed to get a media by name"))? | ||
{ | ||
Some(id) => id, | ||
None => media | ||
.insert(tx, None) | ||
.await | ||
.inspect_err(|error| error!(?error, "Failed to insert media object."))?, | ||
} | ||
}; | ||
|
||
// NOTE: Previous scanner had a `InsertableMovie::insert`. Honestly no clue if we need | ||
// that, or if that is a remnant from the openflix days. | ||
|
||
// Update mediafile to point to a new parent media_id. We also want to set raw_name and | ||
// raw_year to what its parent has so that when we refresh metadata, files that were | ||
// matched manually (due to bogus filenames) dont get unmatched, or matched wrongly. | ||
UpdateMediaFile { | ||
media_id: Some(media_id), | ||
raw_name: Some(media.name), | ||
raw_year: media.year, | ||
..Default::default() | ||
} | ||
.update(tx, file.id) | ||
.await | ||
.inspect_err(|error| { | ||
error!(?error, "Failed to update mediafile to point to new parent.") | ||
})?; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl MediaMatcher for MovieMatcher { | ||
async fn batch_match(self: Arc<Self>, provider: Arc<dyn ExternalQuery>, work: Vec<WorkUnit>) { | ||
let metadata_futs = work | ||
.into_iter() | ||
.map(|WorkUnit(file, metadata)| async { | ||
for meta in metadata { | ||
match provider.search(meta.name.as_ref(), meta.year.map(|x| x as _)).await { | ||
Ok(provided) => return Some((file, provided)), | ||
Err(e) => error!(?meta, "Failed to find a movie match."), | ||
} | ||
} | ||
|
||
None | ||
}) | ||
.collect::<Vec<_>>(); | ||
|
||
let metadata = futures::future::join_all(metadata_futs).await; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters