mirror of
https://github.com/mza921/Plex-Auto-Collections
synced 2024-11-14 00:07:18 +00:00
Merge pull request #115 from meisnate12/PerformanceUpdate
2.7.0 Created a mapping for TMDb ID to Plex Rating Key once each run instead of every time tmdb, imdb, tvdb, or trakt list was run.
This commit is contained in:
commit
c52c81a993
11 changed files with 756 additions and 765 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ app/config.yml
|
|||
config/config-*
|
||||
config/*.pickle
|
||||
**/cache.db*
|
||||
**/*.cache*
|
||||
|
|
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.7.0] - 2020-11-26 - [#115](https://github.com/mza921/Plex-Auto-Collections/pull/115)
|
||||
### Added
|
||||
- Added `tmdb_trending_daily` and `tmdb_trending_weekly`
|
||||
- Added requirements checking with an error message telling the user to update their requirements
|
||||
- [#98](https://github.com/mza921/Plex-Auto-Collections/issues/98) - Added `cache` attribute to cache the IDs of movies/shows for quicker lookup and `cache_update_interval` to determine how often to update the cache
|
||||
- [#123](https://github.com/mza921/Plex-Auto-Collections/issues/123) - Added new filter `plex_collection`
|
||||
- [#125](https://github.com/mza921/Plex-Auto-Collections/issues/125) - Added error message for YAML Scan Failures
|
||||
|
||||
### Changed
|
||||
- Created a mapping for TMDb ID to Plex Rating Key once each run instead of every time tmdb, imdb, tvdb, or trakt list was run.
|
||||
- [#110](https://github.com/mza921/Plex-Auto-Collections/issues/110) - Added `add_to_radarr` as a collection level attribute override
|
||||
- [#121](https://github.com/mza921/Plex-Auto-Collections/issues/121) - Added paging to `tmdb_network` and `tmdb_company` for show libraries
|
||||
- Upgrade tmdbv3api dependency to 1.7.1
|
||||
|
||||
### Fixed
|
||||
- [#109](https://github.com/mza921/Plex-Auto-Collections/issues/109) - The Cache shouldn't be created unless it has to be
|
||||
- [#117](https://github.com/mza921/Plex-Auto-Collections/issues/117) - Typo
|
||||
- [#118](https://github.com/mza921/Plex-Auto-Collections/issues/118) - Check for to see if the tmdb attribute exists
|
||||
- [#120](https://github.com/mza921/Plex-Auto-Collections/issues/120) - Sometimes the collection wasn't found
|
||||
|
||||
## [2.6.0] - 2020-11-12 - [#113](https://github.com/mza921/Plex-Auto-Collections/pull/113)
|
||||
### Added
|
||||
- [#107](https://github.com/mza921/Plex-Auto-Collections/issues/107) - Added `plex_collection`
|
||||
|
@ -23,7 +43,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
- [#50](https://github.com/mza921/Plex-Auto-Collections/issues/50) - Trakt access_token refresh
|
||||
|
||||
|
||||
## [2.4.7] - 2020-11-09 - [#103](https://github.com/mza921/Plex-Auto-Collections/pull/103)
|
||||
### Fixed
|
||||
- [#92](https://github.com/mza921/Plex-Auto-Collections/issues/92) - fixed New Plex Movie Agent id lookup behind a proxy
|
||||
|
@ -37,8 +56,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
- [#93](https://github.com/mza921/Plex-Auto-Collections/issues/93) - actually fixed `max_age`
|
||||
|
||||
|
||||
|
||||
## [2.4.5] - 2020-11-05 - [#97](https://github.com/mza921/Plex-Auto-Collections/pull/97)
|
||||
### Changed
|
||||
- `max_age` no longer takes years
|
||||
|
|
302
README.md
302
README.md
|
@ -1,5 +1,5 @@
|
|||
# Plex Auto Collections
|
||||
##### Version 2.6.0
|
||||
##### Version 2.7.0
|
||||
Plex Auto Collections is a Python 3 script that works off a configuration file to create/update Plex collections. Collection management with this tool can be automated in a varying degree of customizability. Supports IMDB, TMDb, and Trakt lists as well as built in Plex Searches using actors, genres, year, studio and more.
|
||||
|
||||
![https://i.imgur.com/iHAYFIZ.png](https://i.imgur.com/iHAYFIZ.png)
|
||||
|
@ -18,6 +18,7 @@ Plex Auto Collections is a Python 3 script that works off a configuration file t
|
|||
- [TMDb Company (List Type)](#tmdb-company-list-type)
|
||||
- [TMDb Network (List Type)](#tmdb-network-list-type)
|
||||
- [TMDb Popular (List Type)](#tmdb-popular-list-type)
|
||||
- [TMDb Trending (List Type)](#tmdb-trending-list-type)
|
||||
- [TMDb Top Rated (List Type)](#tmdb-top-rated-list-type)
|
||||
- [TMDb Now Playing (List Type)](#tmdb-now-playing-list-type)
|
||||
- [TMDb Discover (List Type)](#tmdb-discover-list-type)
|
||||
|
@ -162,6 +163,7 @@ The only required attribute for each collection is the list type. There are many
|
|||
- [TMDb Company](#tmdb-company-list-type)
|
||||
- [TMDb Network](#tmdb-network-list-type)
|
||||
- [TMDb Popular](#tmdb-popular-list-type)
|
||||
- [TMDb Trending](#tmdb-trending-list-type)
|
||||
- [TMDb Top Rated](#tmdb-top-rated-list-type)
|
||||
- [TMDb Now Playing](#tmdb-now-playing-list-type)
|
||||
- [TMDb Discover](#tmdb-discover-list-type)
|
||||
|
@ -176,7 +178,13 @@ The only required attribute for each collection is the list type. There are many
|
|||
- [Tautulli List](#tautulli-list-list-type)
|
||||
|
||||
Note that most list types supports multiple lists, with the following exceptions:
|
||||
- TMDb Popular
|
||||
- TMDb Trending
|
||||
- TMDb Top Rated
|
||||
- TMDb Now Playing
|
||||
- TMDb Discover
|
||||
- Trakt Trending Lists
|
||||
- Trakt Watchlist
|
||||
- Tautulli Lists
|
||||
|
||||
#### Plex Search (List Type)
|
||||
|
@ -185,18 +193,19 @@ Note that most list types supports multiple lists, with the following exceptions
|
|||
|
||||
You can create a collection based on the Plex search feature using the `plex_search` attribute. The search will return any movie/show that matches at least one term from each search option. You can run multiple searches. The search options are listed below.
|
||||
|
||||
##### Search Options
|
||||
- `actor` (Gets every movie with the specified actor) (Movie libraries only)
|
||||
- `tmdb_actor` (Gets every movie with the specified actor as well as the added TMDb [metadata](#tmdb-people-list-type)) (Movie libraries only)
|
||||
- `country` (Gets every movie with the specified country) (Movie libraries only)
|
||||
- `decade` (Gets every movie from the specified year + the 9 that follow i.e. 1990 will get you 1990-1999) (Movie libraries only)
|
||||
- `director` (Gets every movie with the specified director) (Movie libraries only)
|
||||
- `tmdb_director` (Gets every movie with the specified director as well as the added TMDb [metadata](#tmdb-people-list-type)) (Movie libraries only)
|
||||
- `genre` (Gets every movie/show with the specified genre)
|
||||
- `studio` (Gets every movie/show with the specified studio)
|
||||
- `year` (Gets every movie/show with the specified year) (Put a `-` between two years for a range i.e. `year: 1990-1999` or end with `NOW` to go till current i.e. `year: 2000-NOW`)
|
||||
- `writer` (Gets every movie with the specified writer) (Movie libraries only)
|
||||
- `tmdb_writer` (Gets every movie with the specified writer as well as the added TMDb [metadata](#tmdb-people-list-type)) (Movie libraries only)
|
||||
| Search Option | Description | Movie<br>Libraries | Show<br>Libraries |
|
||||
| :-- | :-- | :--: | :--: |
|
||||
| `actor` | Gets every movie with the specified actor | :heavy_check_mark: | :x: |
|
||||
| `tmdb_actor` | Gets every movie with the specified actor as well as the added TMDb [metadata](#tmdb-people-list-type) | :heavy_check_mark: | :x: |
|
||||
| `country` | Gets every movie with the specified country | :heavy_check_mark: | :x: |
|
||||
| `decade` | Gets every movie from the specified year + the 9 that follow i.e. 1990 will get you 1990-1999 | :heavy_check_mark: | :x: |
|
||||
| `director` | Gets every movie with the specified director | :heavy_check_mark: | :x: |
|
||||
| `tmdb_director` | Gets every movie with the specified director as well as the added TMDb [metadata](#tmdb-people-list-type) | :heavy_check_mark: | :x: |
|
||||
| `genre` | Gets every movie/show with the specified genre | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `studio` | Gets every movie/show with the specified studio | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `year` | Gets every movie/show with the specified year (Put a `-` between two years for a range i.e. `year: 1990-1999` or end with `NOW` to go till current i.e. `year: 2000-NOW`) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `writer` | Gets every movie with the specified writer | :heavy_check_mark: | :x: |
|
||||
| `tmdb_writer` | Gets every movie with the specified writer as well as the added TMDb [metadata](#tmdb-people-list-type) | :heavy_check_mark: | :x: |
|
||||
|
||||
Here's some high-level ideas:
|
||||
|
||||
|
@ -435,11 +444,30 @@ You can build a collection using TMDb's most popular movies/shows by using `tmdb
|
|||
|
||||
```yaml
|
||||
collections:
|
||||
TMDb Trending:
|
||||
TMDb Popular:
|
||||
tmdb_popular: 30
|
||||
sync_mode: sync
|
||||
```
|
||||
|
||||
#### TMDb Trending (List Type)
|
||||
|
||||
###### Works with Movie and TV Show Libraries
|
||||
|
||||
You can build a collection using TMDb's daily or weekly trending movies/shows by using `tmdb_trending_daily` or `tmdb_trending_weekly`. Both attributes only support a single integer value. The `sync_mode: sync` option is recommended since the lists are continuously updated.
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
TMDb Daily Trending:
|
||||
tmdb_trending_daily: 30
|
||||
sync_mode: sync
|
||||
```
|
||||
```yaml
|
||||
collections:
|
||||
TMDb Weekly Trending:
|
||||
tmdb_trending_weekly: 30
|
||||
sync_mode: sync
|
||||
```
|
||||
|
||||
#### TMDb Top Rated (List Type)
|
||||
|
||||
###### Works with Movie and TV Show Libraries
|
||||
|
@ -472,63 +500,96 @@ collections:
|
|||
|
||||
You can use [TMDb's discover engine](https://www.themoviedb.org/documentation/api/discover) to create a collection based on the search for movies/shows using all different sorts of parameters shown below. The parameters are directly from [TMDb Movie Discover](https://developers.themoviedb.org/3/discover/movie-discover) and [TMDb TV Discover](https://developers.themoviedb.org/3/discover/tv-discover)
|
||||
|
||||
##### TMDb Discover Parameters For Movies
|
||||
- `limit` (Specify how many movies you want returned by the query. Value must be an integer greater then 0. default: 100)
|
||||
- `language` (Specify a language to query translatable fields with. pattern: `([a-z]{2})-([A-Z]{2})` default: en-US)
|
||||
- `region` (Specify a [ISO 3166-1 code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) to filter release dates. Must be uppercase. pattern: `^[A-Z]{2}$`)
|
||||
- `sort_by` (Choose from one of the many available sort options. Allowed Values: `popularity.asc`, `popularity.desc`, `release_date.asc`, `release_date.desc`, `revenue.asc`, `revenue.desc`, `primary_release_date.asc`, `primary_release_date.desc`, `original_title.asc`, `original_title.desc`, `vote_average.asc`, `vote_average.desc`, `vote_count.asc`, `vote_count.desc` default: `popularity.desc`)
|
||||
- `certification_country` (Used in conjunction with the certification parameter, use this to specify a country with a valid certification.)
|
||||
- `certification` (Filter results with a valid certification from the `certification_country` parameter.)
|
||||
- `certification.lte` (Filter and only include movies that have a certification that is less than or equal to the specified value.)
|
||||
- `certification.gte` (Filter and only include movies that have a certification that is greater than or equal to the specified value.)
|
||||
- `include_adult` (A filter and include or exclude adult movies. Must be `true` or `false`)
|
||||
- `primary_release_year` (A filter to limit the results to a specific primary release year. Year must be a 4 digit integer i.e. 1990.)
|
||||
- `primary_release_date.gte` (Filter and only include movies that have a primary release date that is greater or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `primary_release_date.lte` (Filter and only include movies that have a primary release date that is less than or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `release_date.gte` (Filter and only include movies that have a release date (looking at all release dates) that is greater or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `release_date.lte` (Filter and only include movies that have a release date (looking at all release dates) that is less than or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `year` (A filter to limit the results to a specific year (looking at all release dates). Year must be a 4 digit integer i.e. 1990.)
|
||||
- `vote_count.gte` (Filter and only include movies that have a vote count that is greater or equal to the specified value. Value must be an integer greater then 0.)
|
||||
- `vote_count.lte` (Filter and only include movies that have a vote count that is less than or equal to the specified value. Value must be an integer greater then 0.)
|
||||
- `vote_average.gte` (Filter and only include movies that have a rating that is greater or equal to the specified value. Value must be a number greater then 0.)
|
||||
- `vote_average.lte` (Filter and only include movies that have a rating that is less than or equal to the specified value. Value must be an number greater then 0.)
|
||||
- `with_cast` (A comma separated list of person ID's. Only include movies that have one of the ID's added as an actor.)
|
||||
- `with_crew` (A comma separated list of person ID's. Only include movies that have one of the ID's added as a crew member.)
|
||||
- `with_people` (A comma separated list of person ID's. Only include movies that have one of the ID's added as a either a actor or a crew member.)
|
||||
- `with_companies` (A comma separated list of production company ID's. Only include movies that have one of the ID's added as a production company.)
|
||||
- `with_genres` (Comma separated value of genre ids that you want to include in the results.)
|
||||
- `without_genres` (Comma separated value of genre ids that you want to exclude from the results.)
|
||||
- `with_keywords` (A comma separated list of keyword ID's. Only includes movies that have one of the ID's added as a keyword.)
|
||||
- `without_keywords` (Exclude items with certain keywords. You can comma and pipe seperate these values to create an 'AND' or 'OR' logic.)
|
||||
- `with_runtime.gte` (Filter and only include movies that have a runtime that is greater or equal to a value. Value must be an integer greater then 0.)
|
||||
- `with_runtime.lte` (Filter and only include movies that have a runtime that is less than or equal to a value. Value must be an integer greater then 0.)
|
||||
- `with_original_language` (Specify an ISO 639-1 string to filter results by their original language value.)
|
||||
| Type | Description |
|
||||
| :-- | :-- |
|
||||
| String | Any number of alphanumeric characters |
|
||||
| Integer | Any whole number greater then zero i.e. 2, 10, 50 |
|
||||
| Number | Any number greater then zero i.e. 2.5, 7.4, 9 |
|
||||
| Boolean | Must be `true` or `false` |
|
||||
| Date: `MM/DD/YYYY` | Date that fits the specified format |
|
||||
| Year: `YYYY` | Year must be a 4 digit integer i.e. 1990 |
|
||||
|
||||
##### TMDb Discover Parameters For Shows
|
||||
- `limit` (Specify how many movies you want returned by the query. Value must be an integer greater then 0. default: 100)
|
||||
- `language` (Specify a language to query translatable fields with. pattern: `([a-z]{2})-([A-Z]{2})` default: en-US)
|
||||
- `sort_by` (Choose from one of the many available sort options. Allowed Values: `vote_average.desc`, `vote_average.asc`, `first_air_date.desc`, `first_air_date.asc`, `popularity.desc`, `popularity.asc` default: `popularity.desc`)
|
||||
- `air_date.gte` (Filter and only include TV shows that have a air date (by looking at all episodes) that is greater or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `air_date.lte` (Filter and only include TV shows that have a air date (by looking at all episodes) that is less than or equal to the specified value. Date must be in the MM/DD/YYYY Format.)
|
||||
- `first_air_date.gte` (Filter and only include TV shows that have a original air date that is greater or equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. Date must be in the MM/DD/YYYY Format.)
|
||||
- `first_air_date.lte` (Filter and only include TV shows that have a original air date that is less than or equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. Date must be in the MM/DD/YYYY Format.)
|
||||
- `first_air_date_year` (Filter and only include TV shows that have a original air date year that equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. Year must be a 4 digit integer i.e. 1990.)
|
||||
- `include_null_first_air_dates` (Use this filter to include TV shows that don't have an air date while using any of the `first_air_date` filters. Must be `true` or `false`.)
|
||||
"- `timezone` (Used in conjunction with the `air_date.gte/lte` filter to calculate the proper UTC offset. default: America/New_York)
|
||||
- `vote_count.gte` (Filter and only include TV that have a vote count that is greater or equal to the specified value. Value must be an integer greater then 0.)
|
||||
- `vote_count.lte` (Filter and only include TV that have a vote count that is less than or equal to the specified value. Value must be an integer greater then 0.)
|
||||
- `vote_average.gte` (Filter and only include TV that have a rating that is greater or equal to the specified value. Value must be a number greater then 0.)
|
||||
- `vote_average.lte` (Filter and only include TV that have a rating that is less than or equal to the specified value. Value must be an number greater then 0.)
|
||||
- `with_networks` (Comma separated value of network ids that you want to include in the results.)
|
||||
- `with_companies` (A comma separated list of production company ID's. Only include movies that have one of the ID's added as a production company.)
|
||||
- `with_genres` (Comma separated value of genre ids that you want to include in the results.)
|
||||
- `without_genres` (Comma separated value of genre ids that you want to exclude from the results.)
|
||||
- `with_keywords` (A comma separated list of keyword ID's. Only includes TV shows that have one of the ID's added as a keyword.)
|
||||
- `without_keywords` (Exclude items with certain keywords. You can comma and pipe seperate these values to create an 'AND' or 'OR' logic.)
|
||||
- `with_runtime.gte` (Filter and only include TV shows with an episode runtime that is greater than or equal to a value.)
|
||||
- `with_runtime.lte` (Filter and only include TV shows with an episode runtime that is less than or equal to a value.)
|
||||
- `with_original_language` (Specify an ISO 639-1 string to filter results by their original language value.)
|
||||
- `screened_theatrically` (Filter results to include items that have been screened theatrically. Must be `true` or `false`.)
|
||||
### Discover Movies
|
||||
| Movie Parameters | Description | Type |
|
||||
| :-- | :-- | :--: |
|
||||
| `limit` | Specify how many movies you want returned by the query. (default: 100) | Integer |
|
||||
| `language` | Specify a language to query translatable fields with. (default: en-US) | `([a-z]{2})-([A-Z]{2})` |
|
||||
| `region` | Specify a [ISO 3166-1 code](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes) to filter release dates. Must be uppercase. | `^[A-Z]{2}$` |
|
||||
| `sort_by` | Choose from one of the many available sort options. (default: `popularity.desc`) | See [sort options](#sort-options) below |
|
||||
| `certification_country` | Used in conjunction with the certification parameter, use this to specify a country with a valid certification. | String |
|
||||
| `certification` | Filter results with a valid certification from the `certification_country` parameter. | String |
|
||||
| `certification.lte` | Filter and only include movies that have a certification that is less than or equal to the specified value. | String |
|
||||
| `certification.gte` | Filter and only include movies that have a certification that is greater than or equal to the specified value. | String |
|
||||
| `include_adult` | A filter and include or exclude adult movies. | Boolean |
|
||||
| `primary_release_year` | A filter to limit the results to a specific primary release year. | Year: YYYY |
|
||||
| `primary_release_date.gte` | Filter and only include movies that have a primary release date that is greater or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `primary_release_date.lte` | Filter and only include movies that have a primary release date that is less than or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `release_date.gte` | Filter and only include movies that have a release date (looking at all release dates) that is greater or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `release_date.lte` | Filter and only include movies that have a release date (looking at all release dates) that is less than or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `year` | A filter to limit the results to a specific year (looking at all release dates). | Year: YYYY |
|
||||
| `vote_count.gte` | Filter and only include movies that have a vote count that is greater or equal to the specified value. | Integer |
|
||||
| `vote_count.lte` | Filter and only include movies that have a vote count that is less than or equal to the specified value. | Integer |
|
||||
| `vote_average.gte` | Filter and only include movies that have a rating that is greater or equal to the specified value. | Number |
|
||||
| `vote_average.lte` | Filter and only include movies that have a rating that is less than or equal to the specified value. | Number |
|
||||
| `with_cast` | A comma separated list of person ID's. Only include movies that have one of the ID's added as an actor. | String |
|
||||
| `with_crew` | A comma separated list of person ID's. Only include movies that have one of the ID's added as a crew member. | String |
|
||||
| `with_people` | A comma separated list of person ID's. Only include movies that have one of the ID's added as a either a actor or a crew member. | String |
|
||||
| `with_companies` | A comma separated list of production company ID's. Only include movies that have one of the ID's added as a production company. | String |
|
||||
| `with_genres` | Comma separated value of genre ids that you want to include in the results. | String |
|
||||
| `without_genres` | Comma separated value of genre ids that you want to exclude from the results. | String |
|
||||
| `with_keywords` | A comma separated list of keyword ID's. Only includes movies that have one of the ID's added as a keyword. | String |
|
||||
| `without_keywords` | Exclude items with certain keywords. You can comma and pipe separate these values to create an 'AND' or 'OR' logic. | String |
|
||||
| `with_runtime.gte` | Filter and only include movies that have a runtime that is greater or equal to a value. | Integer |
|
||||
| `with_runtime.lte` | Filter and only include movies that have a runtime that is less than or equal to a value. | Integer |
|
||||
| `with_original_language` | Specify an ISO 639-1 string to filter results by their original language value. | String |
|
||||
|
||||
### Discover Shows
|
||||
| Show Parameters | Description | Type |
|
||||
| :-- | :-- | :--: |
|
||||
| `limit` | Specify how many movies you want returned by the query. (default: 100) | Integer |
|
||||
| `language` | Specify a language to query translatable fields with. (default: en-US) | `([a-z]{2})-([A-Z]{2})` |
|
||||
| `sort_by` | Choose from one of the many available sort options. (default: `popularity.desc`) | See [sort options](#sort-options) below |
|
||||
| `air_date.gte` | Filter and only include TV shows that have a air date (by looking at all episodes) that is greater or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `air_date.lte` | Filter and only include TV shows that have a air date (by looking at all episodes) that is less than or equal to the specified value. | Date: `MM/DD/YYYY` |
|
||||
| `first_air_date.gte` | Filter and only include TV shows that have a original air date that is greater or equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. | Date: `MM/DD/YYYY` |
|
||||
| `first_air_date.lte` | Filter and only include TV shows that have a original air date that is less than or equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. | Date: `MM/DD/YYYY` |
|
||||
| `first_air_date_year` | Filter and only include TV shows that have a original air date year that equal to the specified value. Can be used in conjunction with the `include_null_first_air_dates` filter if you want to include items with no air date. | Year: YYYY |
|
||||
| `include_null_first_air_dates` | Use this filter to include TV shows that don't have an air date while using any of the `first_air_date` filters. | Boolean |
|
||||
| `timezone` | Used in conjunction with the `air_date.gte/lte` filter to calculate the proper UTC offset. (default: America/New_York) | String |
|
||||
| `vote_count.gte` | Filter and only include TV that have a vote count that is greater or equal to the specified value. | Integer |
|
||||
| `vote_count.lte` | Filter and only include TV that have a vote count that is less than or equal to the specified value. | Integer |
|
||||
| `vote_average.gte` | Filter and only include TV that have a rating that is greater or equal to the specified value. | Number |
|
||||
| `vote_average.lte` | Filter and only include TV that have a rating that is less than or equal to the specified value. | Number |
|
||||
| `with_networks` | Comma separated value of network ids that you want to include in the results. | String |
|
||||
| `with_companies` | A comma separated list of production company ID's. Only include movies that have one of the ID's added as a production company. | String |
|
||||
| `with_genres` | Comma separated value of genre ids that you want to include in the results. | String |
|
||||
| `without_genres` | Comma separated value of genre ids that you want to exclude from the results. | String |
|
||||
| `with_keywords` | A comma separated list of keyword ID's. Only includes TV shows that have one of the ID's added as a keyword. | String |
|
||||
| `without_keywords` | Exclude items with certain keywords. You can comma and pipe separate these values to create an 'AND' or 'OR' logic. | String |
|
||||
| `with_runtime.gte` | Filter and only include TV shows with an episode runtime that is greater than or equal to a value. | Integer |
|
||||
| `with_runtime.lte` | Filter and only include TV shows with an episode runtime that is less than or equal to a value. | Integer |
|
||||
| `with_original_language` | Specify an ISO 639-1 string to filter results by their original language value. | String |
|
||||
| `screened_theatrically` | Filter results to include items that have been screened theatrically. | Boolean |
|
||||
|
||||
### Sort Options
|
||||
| Sort Option | Movie Sort | Show Sort |
|
||||
| :-- | :--: | :--: |
|
||||
| `popularity.asc` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `popularity.desc` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `original_title.asc` | :heavy_check_mark: | :x: |
|
||||
| `original_title.desc` | :heavy_check_mark: | :x: |
|
||||
| `revenue.asc` | :heavy_check_mark: | :x: |
|
||||
| `revenue.desc` | :heavy_check_mark: | :x: |
|
||||
| `release_date.asc` | :heavy_check_mark: | :x: |
|
||||
| `release_date.desc` | :heavy_check_mark: | :x: |
|
||||
| `primary_release_date.asc` | :heavy_check_mark: | :x: |
|
||||
| `primary_release_date.desc` | :heavy_check_mark: | :x: |
|
||||
| `first_air_date.asc` | :x: | :heavy_check_mark: |
|
||||
| `first_air_date.desc` | :x: | :heavy_check_mark: |
|
||||
| `vote_average.asc` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `vote_average.desc` | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `vote_count.asc` | :heavy_check_mark: | :x: |
|
||||
| `vote_count.desc` | :heavy_check_mark: | :x: |
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -756,7 +817,7 @@ This script can pull items from a Trakt user's Watchlist for [Movies](https://tr
|
|||
```yaml
|
||||
collections:
|
||||
Trakt Watchlist:
|
||||
trakt_watchlist:
|
||||
trakt_watchlist:
|
||||
- me
|
||||
- friendontrakt
|
||||
sync_mode: sync
|
||||
|
@ -768,10 +829,12 @@ collections:
|
|||
|
||||
Tautulli has watch analytics that can show the most watched or most popular Movies/Shows in your Library. This script can easily leverage that data into making and sync collection based on those lists using the `tautulli` attribute. Unlike other lists this one has subattribute options:
|
||||
|
||||
- `list_type`: watched (For Most Watched Lists) or popular (For Most Popular Lists) (Required)
|
||||
- `list_days`: Number of Days to look back of the list (Optional Defaults to 30)
|
||||
- `list_size`: Number of Movies/Shows to add to this list (Optional Defaults to 10)
|
||||
- `list_buffer`: Number of extra Movies/Shows to grab in case you have multiple show/movie Libraries (Optional Defaults to 10)
|
||||
| Attribute | Description | Required | Default |
|
||||
| :-- | :-- | :--: | :--: |
|
||||
| `list_type` | `watched` (For Most Watched Lists)<br>`popular` (For Most Popular Lists) | :heavy_check_mark: | :x: |
|
||||
| `list_days` | Number of Days to look back of the list | :x: | 30 |
|
||||
| `list_size` | Number of Movies/Shows to add to this list | :x: | 10 |
|
||||
| `list_buffer` | Number of extra Movies/Shows to grab in case you have multiple show/movie Libraries. | :x: | 10 |
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -803,29 +866,34 @@ Note that if you have multiple movie Libraries or multiple show Libraries Tautul
|
|||
###### Works with Movie and TV Show Libraries
|
||||
|
||||
The next optional attribute for any collection is the `filters` attribute. Collection filters allows for you to filter every movie/show added to the collection from every List Type. All collection filter options are listed below.
|
||||
In addition you can also use the `.not` at the end of any standard collection filter to do an inverse search matching everything that doesn't have the value specified. You can use `all: true` to start you filter from your entire library.
|
||||
In addition you can also use the `.not` at the end of any standard collection filter to do an inverse search matching everything that doesn't have the value specified. You can use `all: true` to start your filter from your entire library.
|
||||
|
||||
##### Standard Collection Filters Options
|
||||
- `actor` (Matches every movie/show with the specified actor)
|
||||
- `content_rating` (Matches every movie/show with the specified content rating)
|
||||
- `country` (Matches every movie with the specified country) (Movie libraries only)
|
||||
- `director` (Matches every movie with the specified director) (Movie libraries only)
|
||||
- `genre` (Matches every movie/show with the specified genre)
|
||||
- `studio` (Matches every movie/show with the specified studio)
|
||||
- `year` (Matches every movie/show with the specified year)
|
||||
- `writer` (Matches every movie with the specified writer) (Movie libraries only)
|
||||
- `video_resolution` (Matches every movie with the specified video resolution) (Movie libraries only)
|
||||
- `audio_language` (Matches every movie with the specified audio language) (Movie libraries only)
|
||||
- `subtitle_language` (Matches every movie with the specified subtitle language) (Movie libraries only)
|
||||
| Standard Filters | Description | Movie<br>Libraries | Show<br>Libraries |
|
||||
| :-- | :-- | :--: | :--: |
|
||||
| `actor` | Matches every movie/show with the specified actor | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `content_rating` | Matches every movie/show with the specified content rating | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `country` | Matches every movie with the specified country | :heavy_check_mark: | :x: |
|
||||
| `director` | Matches every movie with the specified director | :heavy_check_mark: | :x: |
|
||||
| `genre` | Matches every movie/show with the specified genre | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `studio` | Matches every movie/show with the specified studio | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `year` | Matches every movie/show with the specified year | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `writer` | Matches every movie with the specified writer | :heavy_check_mark: | :x: |
|
||||
| `video_resolution` | Matches every movie with the specified video resolution | :heavy_check_mark: | :x: |
|
||||
| `audio_language` | Matches every movie with the specified audio language | :heavy_check_mark: | :x: |
|
||||
| `subtitle_language` | Matches every movie with the specified subtitle language | :heavy_check_mark: | :x: |
|
||||
| `plex_collection` | Matches every movie/show with the specified plex collection | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
##### Special Collection Filters Options (These options can only take one value each)
|
||||
- `max_age` (Matches any movie/show whose Originally Available date is within the last specified number of days)
|
||||
- `year.gte` (Matches any movie/show whose year is greater then or equal to the specified year)
|
||||
- `year.lte` (Matches any movie/show whose year is less then or equal to the specified year)
|
||||
- `rating.gte` (Matches any movie/show whose rating is greater then or equal to the specified rating)
|
||||
- `rating.lte` (Matches any movie/show whose rating is less then or equal to the specified rating)
|
||||
- `originally_available.gte` (Matches any movie/show whose originally_available date is greater then or equal to the specified originally_available date) (Date must be in the MM/DD/YYYY Format)
|
||||
- `originally_available.lte` (Matches any movie/show whose originally_available date is less then or equal to the specified originally_available date) (Date must be in the MM/DD/YYYY Format)
|
||||
| Advanced Filters | Description | Movie<br>Libraries | Show<br>Libraries |
|
||||
| :-- | :-- | :--: | :--: |
|
||||
| `max_age` | Matches any movie/show whose Originally Available date is within the last specified number of days | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `year.gte` | Matches any movie/show whose year is greater then or equal to the specified year | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `year.lte` | Matches any movie/show whose year is less then or equal to the specified year | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `rating.gte` | Matches any movie/show whose rating is greater then or equal to the specified rating | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `rating.lte` | Matches any movie/show whose rating is less then or equal to the specified rating | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `originally_available.gte` | Matches any movie/show whose originally_available date is greater then or equal to the specified originally_available date (Date must be in the MM/DD/YYYY Format) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| `originally_available.lte` | Matches any movie/show whose originally_available date is less then or equal to the specified originally_available date (Date must be in the MM/DD/YYYY Format) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
|
||||
Note only standard filters can take multiple values
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -894,9 +962,10 @@ Note that multiple collection filters are supported but a movie must match at le
|
|||
### Sync Mode (Collection Attribute)
|
||||
You can specify how collections sync using `sync_mode`. Set it to `append` to only add movies/shows to the collection or set it to `sync` to add movies/shows to the collection and remove movies/shows from a collection.
|
||||
|
||||
##### Options
|
||||
- `append` (Only Add Items to the Collection)
|
||||
- `sync` (Add & Remove Items from the Collection)
|
||||
| Sync Options | Description |
|
||||
| :-- | :-- |
|
||||
| `append` | Only Add Items to the Collection |
|
||||
| `sync` | Add & Remove Items from the Collection |
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -978,11 +1047,12 @@ collections:
|
|||
|
||||
Plex allows for four different types of collection modes: library default, hide items in this collection, show this collection and its items, and hide collection (more details can be found in [Plex's Collection support article](https://support.plex.tv/articles/201273953-collections/#toc-2)). These options can be set with `default`, `hide_items`, `show_items`, and `hide`.
|
||||
|
||||
##### Options
|
||||
- `default` (Library default)
|
||||
- `hide` (Hide Collection)
|
||||
- `hide_items` (Hide Items in this Collection)
|
||||
- `show_items` (Show this Collection and its Items)
|
||||
| Collection Mode Options | Description |
|
||||
| :-- | :-- |
|
||||
| `default` | Library default |
|
||||
| `hide` | Hide Collection |
|
||||
| `hide_items` | Hide Items in this Collection |
|
||||
| `show_items` | Show this Collection and its Items |
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -998,9 +1068,10 @@ collections:
|
|||
|
||||
Lastly, Plex allows collections to be sorted by the media's release dates or alphabetically by title. These options can be set with `release` or `alpha`. Plex defaults all collections to `release`, but `alpha` can be helpful for rearranging collections. For example, with collections where the chronology does not follow the release dates, you could create custom sort titles for each media item and then sort the collection alphabetically.
|
||||
|
||||
##### Options
|
||||
- `release` (Order Collection by Release Dates)
|
||||
- `alpha` (Order Collection Alphabetically)
|
||||
| Collection Sort Options | Description |
|
||||
| :-- | :-- |
|
||||
| `release` | Order Collection by Release Dates |
|
||||
| `alpha` | Order Collection Alphabetically |
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
|
@ -1114,6 +1185,8 @@ plex: # Req
|
|||
token: ##### # Req - User's Plex authentication token
|
||||
url: http://192.168.1.1:32400 # Req - URL to access Plex
|
||||
sync_mode: append # Opt - Global Sync Mode
|
||||
cache: False # Opt - Create a cache of IDs
|
||||
cache_update_interval: 60 # Opt - How often to update each cache entry
|
||||
```
|
||||
|
||||
Note that Plex does not allow a `show` to be added to a `movie` library or vice versa.
|
||||
|
@ -1122,9 +1195,12 @@ This script can be run on a remote Plex server, but be sure that the `url` provi
|
|||
|
||||
You can set the global default [Sync Mode](#sync-mode-collection-attribute) here by using `sync_mode`. Set it to `append` to only add movies/shows to the collection or set it to `sync` to add movies/shows to the collection and remove movies/shows from a collection.
|
||||
|
||||
##### Options
|
||||
- `append` (Only Add Items to the Collection)
|
||||
- `sync` (Add & Remove Items from the Collection)
|
||||
| Sync Options | Description |
|
||||
| :-- | :-- |
|
||||
| `append` | Only Add Items to the Collection |
|
||||
| `sync` | Add & Remove Items from the Collection |
|
||||
|
||||
Setting the `cache` attribute to true will cause the program to create a cache file that allows for faster ID mapping on future runs. Every entry in the cache will update on a interval that can be specified setting `cache_update_interval` to an integer greater then 0 to represent how many days in between updates.
|
||||
|
||||
Lastly, if you need help finding your Plex authentication token, please see Plex's [support article](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/).
|
||||
|
||||
|
@ -1217,7 +1293,7 @@ radarr: # Opt
|
|||
token: ##### # Req - User's Radarr API key
|
||||
quality_profile_id: 4 # Req - See below
|
||||
root_folder_path: /mnt/movies # Req - See below
|
||||
add_movie: false # Opt - Add missing movies to Radarr
|
||||
add_to_radarr: false # Opt - Add missing movies to Radarr
|
||||
search_movie: false # Opt - Search while adding missing movies
|
||||
```
|
||||
|
||||
|
@ -1240,9 +1316,7 @@ If you were to add two more profiles, the `id` would be as follows:
|
|||
|
||||
In this example, to set any added movies to the `Ultra-HD` profile, set `quality_profile_id` to `5`. To set any added movies to `HD-1080p`, set `quality_profile_id` to `4`.
|
||||
|
||||
The `add_movie` key allows missing to movies to be added to Radarr. If this key is missing, the script will prompt the user to add missing movies or not. If you'd like to add movies but not had Radarr search, then set `search_movie` to `false`.
|
||||
|
||||
Note that Radarr support has not been tested with extensively Trakt lists and Sonarr support has not yet been implemented.
|
||||
The `add_to_radarr` key allows missing to movies to be added to Radarr. If this key is missing, the script will prompt the user to add missing movies or not. If you'd like to add movies but not had Radarr search, then set `search_movie` to `false`. If you want to override this attribute per collection you can add the `add_to_radarr` attribute under a collection and set it to true or false to override any global choice.
|
||||
|
||||
# Acknowledgements
|
||||
- [vladimir-tutin](https://github.com/vladimir-tutin) for writing substantially all of the code in this fork
|
||||
|
|
|
@ -4,6 +4,7 @@ import sys
|
|||
import yaml
|
||||
import ruamel.yaml
|
||||
import requests
|
||||
from yaml.scanner import ScannerError
|
||||
from urllib.parse import urlparse
|
||||
from tmdbv3api import Collection
|
||||
from plexapi.exceptions import Unauthorized
|
||||
|
@ -14,7 +15,6 @@ from plexapi.library import MovieSection
|
|||
from plexapi.library import ShowSection
|
||||
from trakt import Trakt
|
||||
import trakt_helpers
|
||||
import trakt
|
||||
|
||||
|
||||
def check_for_attribute(config, attribute, parent=None, test_list=None, options="", default=None, do_print=True, default_is_none=False, var_type="str", throw=False, save=True):
|
||||
|
@ -76,8 +76,11 @@ class Config:
|
|||
Config.headless = headless
|
||||
Config.config_path = config_path
|
||||
self.config_path = config_path
|
||||
with open(self.config_path, 'rt', encoding='utf-8') as yml:
|
||||
self.data = yaml.load(yml, Loader=yaml.FullLoader)
|
||||
try:
|
||||
with open(self.config_path, 'rt', encoding='utf-8') as yml:
|
||||
self.data = yaml.load(yml, Loader=yaml.FullLoader)
|
||||
except ScannerError as e:
|
||||
sys.exit("| Scan Error: {}".format(str(e).replace('\n', '\n|\t ')))
|
||||
if Config.valid == True:
|
||||
self.collections = check_for_attribute(self.data, "collections", default={}, do_print=False)
|
||||
self.plex = self.data['plex']
|
||||
|
@ -155,6 +158,16 @@ class Plex:
|
|||
except SystemExit as e:
|
||||
self.sync_mode = check_for_attribute(config, "sync_mode", parent="plex", default="append", test_list=["append", "sync"], do_print=False)
|
||||
message = message + "\n" + str(e) if len(message) > 0 else str(e)
|
||||
try:
|
||||
self.cache = check_for_attribute(config, "cache", parent="plex", options="| \ttrue (Create a cache to store ids)\n| \tfalse (Do not create a cache to store ids)", var_type="bool", default=False, throw=True)
|
||||
except SystemExit as e:
|
||||
self.cache = check_for_attribute(config, "cache", parent="plex", var_type="bool", default=False, do_print=False)
|
||||
message = message + "\n" + str(e) if len(message) > 0 else str(e)
|
||||
try:
|
||||
self.cache_interval = check_for_attribute(config, "cache_update_interval", parent="plex", var_type="int", default=60, throw=True)
|
||||
except SystemExit as e:
|
||||
self.cache_interval = check_for_attribute(config, "cache_update_interval", parent="plex", var_type="int", default=60, do_print=False)
|
||||
message = message + "\n" + str(e) if len(message) > 0 else str(e)
|
||||
self.timeout = 60
|
||||
if len(fatal_message) > 0:
|
||||
sys.exit(fatal_message + "\n" + message)
|
||||
|
@ -188,7 +201,7 @@ class Radarr:
|
|||
self.token = check_for_attribute(config, "token", parent="radarr")
|
||||
self.quality_profile_id = check_for_attribute(config, "quality_profile_id", parent="radarr", var_type="int")
|
||||
self.root_folder_path = check_for_attribute(config, "root_folder_path", parent="radarr")
|
||||
self.add_movie = check_for_attribute(config, "add_movie", parent="radarr", var_type="bool", default_is_none=True, do_print=False)
|
||||
self.add_to_radarr = check_for_attribute(config, "add_to_radarr", parent="radarr", var_type="bool", default_is_none=True, do_print=False)
|
||||
self.search_movie = check_for_attribute(config, "search_movie", parent="radarr", var_type="bool", default=False, do_print=False)
|
||||
elif Radarr.valid is None:
|
||||
if TMDB.valid:
|
||||
|
@ -216,9 +229,15 @@ class Radarr:
|
|||
except SystemExit as e:
|
||||
fatal_message = fatal_message + "\n" + str(e) if len(fatal_message) > 0 else str(e)
|
||||
try:
|
||||
self.add_movie = check_for_attribute(config, "add_movie", parent="radarr", options="| \ttrue (Add missing movies to Radarr)\n| \tfalse (Do not add missing movies to Radarr)", var_type="bool", default_is_none=True, throw=True)
|
||||
self.add_to_radarr = check_for_attribute(config, "add_to_radarr", parent="radarr", options="| \ttrue (Add missing movies to Radarr)\n| \tfalse (Do not add missing movies to Radarr)", var_type="bool", default_is_none=True, throw=True)
|
||||
except SystemExit as e:
|
||||
message = message + "\n" + str(e) if len(message) > 0 else str(e)
|
||||
if "add_movie" in config:
|
||||
try:
|
||||
self.add_to_radarr = check_for_attribute(config, "add_movie", parent="radarr", var_type="bool", throw=True, save=False)
|
||||
print("| Config Warning: replace add_movie with add_to_radarr")
|
||||
except SystemExit as e:
|
||||
pass
|
||||
try:
|
||||
self.search_movie = check_for_attribute(config, "search_movie", parent="radarr", options="| \ttrue (Have Radarr seach the added movies)\n| \tfalse (Do not have Radarr seach the added movies)", var_type="bool", default=False, throw=True)
|
||||
except SystemExit as e:
|
||||
|
@ -314,7 +333,7 @@ class Tautulli:
|
|||
except:
|
||||
print("| Config Error: Invalid url")
|
||||
Tautulli.valid = False
|
||||
print("| tautulli connection {}".format("scuccessful" if Tautulli.valid else "failed"))
|
||||
print("| tautulli Connection {}".format("Successful" if Tautulli.valid else "failed"))
|
||||
|
||||
|
||||
class TraktClient:
|
||||
|
@ -362,7 +381,7 @@ class TraktClient:
|
|||
else:
|
||||
self.refreshed_authorization = None
|
||||
if trakt_helpers.check_trakt(self.refreshed_authorization):
|
||||
# Save the refreshed authorization
|
||||
# Save the refreshed authorization
|
||||
trakt_helpers.save_authorization(Config(config_path).config_path, self.refreshed_authorization)
|
||||
TraktClient.valid = True
|
||||
else:
|
||||
|
|
|
@ -3,27 +3,23 @@ import requests
|
|||
import math
|
||||
import sys
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
from lxml import html
|
||||
from tmdbv3api import TMDb
|
||||
from tmdbv3api import Movie
|
||||
from tmdbv3api import List
|
||||
from tmdbv3api import TV
|
||||
from tmdbv3api import Discover
|
||||
from tmdbv3api import Collection
|
||||
from tmdbv3api import Company
|
||||
#from tmdbv3api import Network #TURNON:Trending
|
||||
from tmdbv3api import Person
|
||||
#from tmdbv3api import Trending #TURNON:Trending
|
||||
import config_tools
|
||||
import plex_tools
|
||||
import trakt
|
||||
|
||||
def adjust_space(old_length, display_title):
|
||||
space_length = old_length - len(display_title)
|
||||
if space_length > 0:
|
||||
display_title += " " * space_length
|
||||
return display_title
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
from lxml import html
|
||||
from tmdbv3api import TMDb
|
||||
from tmdbv3api import Movie
|
||||
from tmdbv3api import List
|
||||
from tmdbv3api import TV
|
||||
from tmdbv3api import Discover
|
||||
from tmdbv3api import Collection
|
||||
from tmdbv3api import Company
|
||||
from tmdbv3api import Network
|
||||
from tmdbv3api import Person
|
||||
from tmdbv3api import Trending
|
||||
except ImportError:
|
||||
print('|\n| Requirements Error: Please update requirements using "pip install -r requirements.txt"\n|')
|
||||
sys.exit(0)
|
||||
|
||||
def imdb_get_ids(plex, imdb_url):
|
||||
imdb_url = imdb_url.strip()
|
||||
|
@ -70,61 +66,56 @@ def imdb_get_ids(plex, imdb_url):
|
|||
print("| Config Error {} must begin with either:\n| https://www.imdb.com/list/ls (For Lists)\n| https://www.imdb.com/search/title/? (For Searches)")
|
||||
return None
|
||||
|
||||
def imdb_get_movies(config_path, plex, data):
|
||||
def tmdb_get_imdb(config_path, tmdb_id):
|
||||
movie = Movie()
|
||||
movie.api_key = config_tools.TMDB(config_path).apikey
|
||||
return movie.external_ids(tmdb_id)['imdb_id']
|
||||
|
||||
def tmdb_get_tvdb(config_path, tmdb_id):
|
||||
show = TV()
|
||||
show.api_key = config_tools.TMDB(config_path).apikey
|
||||
return show.external_ids(tmdb_id)['tvdb_id']
|
||||
|
||||
def imdb_get_tmdb(config_path, imdb_id):
|
||||
movie = Movie()
|
||||
movie.api_key = config_tools.TMDB(config_path).apikey
|
||||
search = movie.external(external_id=imdb_id, external_source="imdb_id")['movie_results']
|
||||
if len(search) == 1:
|
||||
try:
|
||||
return search[0]['id']
|
||||
except IndexError:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
def tvdb_get_tmdb(config_path, tvdb_id):
|
||||
movie = Movie()
|
||||
movie.api_key = config_tools.TMDB(config_path).apikey
|
||||
search = movie.external(external_id=tvdb_id, external_source="tvdb_id")['tv_results']
|
||||
if len(search) == 1:
|
||||
try:
|
||||
return search[0]['id']
|
||||
except IndexError:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
def imdb_get_movies(config_path, plex, plex_map, data):
|
||||
title_ids = data[1]
|
||||
print("| {} Movies found on IMDb".format(len(title_ids)))
|
||||
tmdb = TMDb()
|
||||
tmdb.api_key = config_tools.TMDB(config_path).apikey
|
||||
movie = Movie()
|
||||
imdb_map = {}
|
||||
matched_imdb_movies = []
|
||||
missing_imdb_movies = []
|
||||
current_length = 0
|
||||
current_count = 0
|
||||
plex_tools.create_cache(config_path)
|
||||
plex_movies = plex.Library.all()
|
||||
for m in plex_movies:
|
||||
current_count += 1
|
||||
print_display = "| Processing: {}/{} {}".format(current_count, len(plex_movies), m.title)
|
||||
print(adjust_space(current_length, print_display), end="\r")
|
||||
current_length = len(print_display)
|
||||
if 'plex://' in m.guid:
|
||||
item = m
|
||||
# Check cache for imdb_id
|
||||
imdb_id = plex_tools.query_cache(config_path, item.guid, 'imdb_id')
|
||||
if not imdb_id:
|
||||
imdb_id, tmdb_id = plex_tools.alt_id_lookup(plex, item)
|
||||
print(adjust_space(current_length, "| Cache | + | {} | {} | {} | {}".format(item.guid, imdb_id, tmdb_id, item.title)))
|
||||
plex_tools.update_cache(config_path, item.guid, imdb_id=imdb_id, tmdb_id=tmdb_id)
|
||||
elif 'themoviedb://' in m.guid:
|
||||
if not tmdb.api_key == "None":
|
||||
tmdb_id = m.guid.split('themoviedb://')[1].split('?')[0]
|
||||
tmdbapi = movie.details(tmdb_id)
|
||||
imdb_id = tmdbapi.imdb_id if hasattr(tmdbapi, 'imdb_id') else None
|
||||
else:
|
||||
imdb_id = None
|
||||
elif 'imdb://' in m.guid:
|
||||
imdb_id = m.guid.split('imdb://')[1].split('?')[0]
|
||||
else:
|
||||
imdb_id = None
|
||||
|
||||
if imdb_id and imdb_id in title_ids:
|
||||
imdb_map[imdb_id] = m
|
||||
else:
|
||||
imdb_map[m.ratingKey] = m
|
||||
|
||||
print(adjust_space(current_length, "| Processed {} Movies".format(len(plex_movies))))
|
||||
|
||||
t_movie = Movie()
|
||||
t_movie.api_key = config_tools.TMDB(config_path).apikey
|
||||
matched = []
|
||||
missing = []
|
||||
for imdb_id in title_ids:
|
||||
movie = imdb_map.pop(imdb_id, None)
|
||||
if movie:
|
||||
matched_imdb_movies.append(plex.Server.fetchItem(movie.ratingKey))
|
||||
tmdb_id = imdb_get_tmdb(config_path, imdb_id)
|
||||
if tmdb_id in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[tmdb_id]))
|
||||
else:
|
||||
missing_imdb_movies.append(imdb_id)
|
||||
missing.append(tmdb_id)
|
||||
return matched, missing
|
||||
|
||||
return matched_imdb_movies, missing_imdb_movies
|
||||
|
||||
def tmdb_get_movies(config_path, plex, data, method):
|
||||
def tmdb_get_movies(config_path, plex, plex_map, data, method):
|
||||
t_movs = []
|
||||
t_movie = Movie()
|
||||
t_movie.api_key = config_tools.TMDB(config_path).apikey # Set TMDb api key for Movie
|
||||
|
@ -133,19 +124,20 @@ def tmdb_get_movies(config_path, plex, data, method):
|
|||
|
||||
count = 0
|
||||
if method == "tmdb_discover":
|
||||
attrs = data.copy()
|
||||
discover = Discover()
|
||||
discover.api_key = t_movie.api_key
|
||||
discover.discover_movies(data)
|
||||
discover.discover_movies(attrs)
|
||||
total_pages = int(os.environ["total_pages"])
|
||||
total_results = int(os.environ["total_results"])
|
||||
limit = int(data.pop('limit'))
|
||||
amount = total_results if total_results < limit else limit
|
||||
print("| Processing {}: {} items".format(method, amount))
|
||||
for attr, value in data.items():
|
||||
limit = int(attrs.pop('limit'))
|
||||
amount = total_results if limit == 0 or total_results < limit else limit
|
||||
print("| Processing {}: {} movies".format(method, amount))
|
||||
for attr, value in attrs.items():
|
||||
print("| {}: {}".format(attr, value))
|
||||
for x in range(total_pages):
|
||||
data["page"] = x + 1
|
||||
tmdb_movies = discover.discover_movies(data)
|
||||
attrs["page"] = x + 1
|
||||
tmdb_movies = discover.discover_movies(attrs)
|
||||
for tmovie in tmdb_movies:
|
||||
count += 1
|
||||
t_movs.append(tmovie.id)
|
||||
|
@ -154,8 +146,8 @@ def tmdb_get_movies(config_path, plex, data, method):
|
|||
if count == amount:
|
||||
break
|
||||
elif method in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]:
|
||||
#trending = Trending() #TURNON:Trending
|
||||
#trending.api_key = t_movie.api_key #TURNON:Trending
|
||||
trending = Trending()
|
||||
trending.api_key = t_movie.api_key
|
||||
for x in range(int(data / 20) + 1):
|
||||
if method == "tmdb_popular":
|
||||
tmdb_movies = t_movie.popular(x + 1)
|
||||
|
@ -163,10 +155,10 @@ def tmdb_get_movies(config_path, plex, data, method):
|
|||
tmdb_movies = t_movie.top_rated(x + 1)
|
||||
elif method == "tmdb_now_playing":
|
||||
tmdb_movies = t_movie.now_playing(x + 1)
|
||||
#elif method == "tmdb_trending_daily": #TURNON:Trending
|
||||
# tmdb_movies = trending.movie_day(x + 1) #TURNON:Trending
|
||||
#elif method == "tmdb_trending_weekly": #TURNON:Trending
|
||||
# tmdb_movies = trending.movie_week(x + 1) #TURNON:Trending
|
||||
elif method == "tmdb_trending_daily":
|
||||
tmdb_movies = trending.movie_day(x + 1)
|
||||
elif method == "tmdb_trending_weekly":
|
||||
tmdb_movies = trending.movie_week(x + 1)
|
||||
for tmovie in tmdb_movies:
|
||||
count += 1
|
||||
t_movs.append(tmovie.id)
|
||||
|
@ -212,62 +204,13 @@ def tmdb_get_movies(config_path, plex, data, method):
|
|||
raise ValueError("| Config Error: TMDb ID: {} not found".format(tmdb_id))
|
||||
print("| Processing {}: ({}) {}".format(method, tmdb_id, tmdb_name))
|
||||
|
||||
|
||||
# Create dictionary of movies and their guid
|
||||
# GUIDs reference from which source Plex has pulled the metadata
|
||||
p_m_map = {}
|
||||
p_movies = plex.Library.all()
|
||||
for m in p_movies:
|
||||
guid = m.guid
|
||||
if "themoviedb://" in guid:
|
||||
guid = guid.split('themoviedb://')[1].split('?')[0]
|
||||
elif "imdb://" in guid:
|
||||
guid = guid.split('imdb://')[1].split('?')[0]
|
||||
elif "plex://" in guid:
|
||||
guid = guid.split('plex://')[1].split('?')[0]
|
||||
else:
|
||||
guid = "None"
|
||||
p_m_map[m] = guid
|
||||
|
||||
matched = []
|
||||
missing = []
|
||||
plex_tools.create_cache(config_path)
|
||||
# We want to search for a match first to limit TMDb API calls
|
||||
# Too many rapid calls can cause a momentary block
|
||||
# If needed in future maybe add a delay after x calls to let the limit reset
|
||||
for mid in t_movs: # For each TMBd ID in TMBd Collection
|
||||
match = False
|
||||
for m in p_m_map: # For each movie in Plex
|
||||
item = m
|
||||
agent_type = urlparse(m.guid).scheme.split('.')[-1]
|
||||
# Plex movie agent
|
||||
if agent_type == 'plex':
|
||||
# Check cache for tmdb_id
|
||||
tmdb_id = plex_tools.query_cache(config_path, item.guid, 'tmdb_id')
|
||||
imdb_id = plex_tools.query_cache(config_path, item.guid, 'imdb_id')
|
||||
if not tmdb_id:
|
||||
imdb_id, tmdb_id = plex_tools.alt_id_lookup(plex, item)
|
||||
print("| Cache | + | {} | {} | {} | {}".format(item.guid, imdb_id, tmdb_id, item.title))
|
||||
plex_tools.update_cache(config_path, item.guid, imdb_id=imdb_id, tmdb_id=tmdb_id)
|
||||
if int(tmdb_id) == int(mid):
|
||||
match = True
|
||||
break
|
||||
elif agent_type == 'themoviedb':
|
||||
if int(p_m_map[m]) == int(mid):
|
||||
match = True
|
||||
break
|
||||
elif agent_type == 'imdb':
|
||||
imdb_id = t_movie.details(mid).imdb_id
|
||||
for m in p_m_map:
|
||||
if "tt" in p_m_map[m]:
|
||||
if p_m_map[m] == imdb_id:
|
||||
match = True
|
||||
break
|
||||
if match:
|
||||
matched.append(m)
|
||||
for mid in t_movs:
|
||||
if mid in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[mid]))
|
||||
else:
|
||||
# Duplicate TMDb call?
|
||||
missing.append(t_movie.details(mid).imdb_id)
|
||||
missing.append(mid)
|
||||
|
||||
return matched, missing
|
||||
|
||||
|
@ -304,38 +247,43 @@ def get_tautulli(config_path, plex, data):
|
|||
|
||||
return matched, missing
|
||||
|
||||
def get_tvdb_id_from_tmdb_id(id):
|
||||
lookup = trakt.Trakt['search'].lookup(id, 'tmdb', 'show')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
return lookup.get_key('tvdb')
|
||||
else:
|
||||
return None
|
||||
|
||||
def tmdb_get_shows(config_path, plex, data, method):
|
||||
def tmdb_get_shows(config_path, plex, plex_map, data, method):
|
||||
config_tools.TraktClient(config_path)
|
||||
|
||||
t_tvs = []
|
||||
t_tv = TV()
|
||||
t_tv.api_key = config_tools.TMDB(config_path).apikey # Set TMDb api key for Movie
|
||||
t_tv.api_key = config_tools.TMDB(config_path).apikey
|
||||
if t_tv.api_key == "None":
|
||||
raise KeyError("Invalid TMDb API Key")
|
||||
discover = Discover()
|
||||
discover.api_key = t_tv.api_key
|
||||
|
||||
count = 0
|
||||
if method == "tmdb_discover":
|
||||
discover.discover_tv_shows(data)
|
||||
if method in ["tmdb_discover", "tmdb_company", "tmdb_network"]:
|
||||
if method in ["tmdb_company", "tmdb_network"]:
|
||||
tmdb = Company() if method == "tmdb_company" else Network()
|
||||
tmdb.api_key = t_tv.api_key
|
||||
tmdb_id = int(data)
|
||||
tmdb_name = str(tmdb.details(tmdb_id))
|
||||
discover_method = "with_companies" if method == "tmdb_company" else "with_networks"
|
||||
attrs = {discover_method: tmdb_id}
|
||||
limit = 0
|
||||
else:
|
||||
attrs = data.copy()
|
||||
limit = int(attrs.pop('limit'))
|
||||
discover = Discover()
|
||||
discover.api_key = t_tv.api_key
|
||||
discover.discover_tv_shows(attrs)
|
||||
total_pages = int(os.environ["total_pages"])
|
||||
total_results = int(os.environ["total_results"])
|
||||
limit = int(data.pop('limit'))
|
||||
amount = total_results if total_results < limit else limit
|
||||
print("| Processing {}: {} items".format(method, amount))
|
||||
for attr, value in data.items():
|
||||
print("| {}: {}".format(attr, value))
|
||||
amount = total_results if limit == 0 or total_results < limit else limit
|
||||
if method in ["tmdb_company", "tmdb_network"]:
|
||||
print("| Processing {}: {} ({} {} shows)".format(method, tmdb_id, amount, tmdb_name))
|
||||
else:
|
||||
print("| Processing {}: {} shows".format(method, amount))
|
||||
for attr, value in attrs.items():
|
||||
print("| {}: {}".format(attr, value))
|
||||
for x in range(total_pages):
|
||||
data["page"] = x + 1
|
||||
tmdb_shows = discover.discover_tv_shows(data)
|
||||
attrs["page"] = x + 1
|
||||
tmdb_shows = discover.discover_tv_shows(attrs)
|
||||
for tshow in tmdb_shows:
|
||||
count += 1
|
||||
t_tvs.append(tshow.id)
|
||||
|
@ -343,19 +291,18 @@ def tmdb_get_shows(config_path, plex, data, method):
|
|||
break
|
||||
if count == amount:
|
||||
break
|
||||
run_discover(data)
|
||||
elif method in ["tmdb_popular", "tmdb_top_rated", "tmdb_trending_daily", "tmdb_trending_weekly"]:
|
||||
#trending = Trending() #TURNON:Trending
|
||||
#trending.api_key = t_movie.api_key #TURNON:Trending
|
||||
trending = Trending()
|
||||
trending.api_key = t_movie.api_key
|
||||
for x in range(int(data / 20) + 1):
|
||||
if method == "tmdb_popular":
|
||||
tmdb_shows = t_tv.popular(x + 1)
|
||||
elif method == "tmdb_top_rated":
|
||||
tmdb_shows = t_tv.top_rated(x + 1)
|
||||
#elif method == "tmdb_trending_daily": #TURNON:Trending
|
||||
# tmdb_shows = trending.tv_day(x + 1) #TURNON:Trending
|
||||
#elif method == "tmdb_trending_weekly": #TURNON:Trending
|
||||
# tmdb_shows = trending.tv_week(x + 1) #TURNON:Trending
|
||||
elif method == "tmdb_trending_daily":
|
||||
tmdb_shows = trending.tv_day(x + 1)
|
||||
elif method == "tmdb_trending_weekly":
|
||||
tmdb_shows = trending.tv_week(x + 1)
|
||||
for tshow in tmdb_shows:
|
||||
count += 1
|
||||
t_tvs.append(tshow.id)
|
||||
|
@ -377,19 +324,6 @@ def tmdb_get_shows(config_path, plex, data, method):
|
|||
t_tvs.append(ttv.id)
|
||||
except:
|
||||
raise ValueError("| Config Error: TMDb List: {} not found".format(tmdb_id))
|
||||
elif method in ["tmdb_company", "tmdb_network"]:
|
||||
if method == "tmdb_company":
|
||||
tmdb = Company()
|
||||
tmdb.api_key = t_tv.api_key
|
||||
tmdb_name = str(tmdb.details(tmdb_id))
|
||||
else:
|
||||
#tmdb = Network() #TURNON:Trending
|
||||
#tmdb.api_key = t_tv.api_key #TURNON:Trending
|
||||
tmdb_name = ""#str(tmdb.details(tmdb_id)) #TURNON:Trending
|
||||
discover_method = "with_companies" if method == "tmdb_company" else "with_networks"
|
||||
tmdb_shows = discover.discover_tv_shows({discover_method: tmdb_id})
|
||||
for tshow in tmdb_shows:
|
||||
t_tvs.append(tshow.id)
|
||||
else:
|
||||
try:
|
||||
t_tv.details(tmdb_id).number_of_seasons
|
||||
|
@ -399,115 +333,81 @@ def tmdb_get_shows(config_path, plex, data, method):
|
|||
raise ValueError("| Config Error: TMDb ID: {} not found".format(tmdb_id))
|
||||
print("| Processing {}: ({}) {}".format(method, tmdb_id, tmdb_name))
|
||||
|
||||
p_tv_map = {}
|
||||
for item in plex.Library.all():
|
||||
guid = urlparse(item.guid)
|
||||
item_type = guid.scheme.split('.')[-1]
|
||||
if item_type == 'thetvdb':
|
||||
tvdb_id = guid.netloc
|
||||
elif item_type == 'themoviedb':
|
||||
tvdb_id = get_tvdb_id_from_tmdb_id(guid.netloc)
|
||||
else:
|
||||
tvdb_id = None
|
||||
p_tv_map[item] = tvdb_id
|
||||
|
||||
matched = []
|
||||
missing = []
|
||||
for mid in t_tvs:
|
||||
match = False
|
||||
tvdb_id = get_tvdb_id_from_tmdb_id(mid)
|
||||
tvdb_id = tmdb_get_tvdb(config_path, mid)
|
||||
if tvdb_id is None:
|
||||
print("| Trakt Error: tmbd_id: {} could not converted to tvdb_id try just using tvdb_id instead".format(mid))
|
||||
else:
|
||||
for t in p_tv_map:
|
||||
if p_tv_map[t] and "tt" not in p_tv_map[t] != "None":
|
||||
if p_tv_map[t] is not None and int(p_tv_map[t]) == int(tvdb_id):
|
||||
match = True
|
||||
break
|
||||
if match:
|
||||
matched.append(t)
|
||||
print("| TMDb Error: tmdb_id: {} ({}) has no associated tvdb_id try just using tvdb_id instead".format(mid, t_tv.details(mid).name))
|
||||
elif tvdb_id in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[tvdb_id]))
|
||||
else:
|
||||
missing.append(tvdb_id)
|
||||
|
||||
return matched, missing
|
||||
|
||||
def tvdb_get_shows(config_path, plex, data):
|
||||
config_tools.TraktClient(config_path)
|
||||
|
||||
id = int(data)
|
||||
|
||||
p_tv_map = {}
|
||||
for item in plex.Library.all():
|
||||
guid = urlparse(item.guid)
|
||||
item_type = guid.scheme.split('.')[-1]
|
||||
if item_type == 'thetvdb':
|
||||
tvdb_id = guid.netloc
|
||||
elif item_type == 'themoviedb' and TraktClient.valid:
|
||||
tvdb_id = get_tvdb_id_from_tmdb_id(guid.netloc)
|
||||
else:
|
||||
tvdb_id = None
|
||||
p_tv_map[item] = tvdb_id
|
||||
|
||||
def tvdb_get_shows(config_path, plex, plex_map, data):
|
||||
tvdb_id = int(data)
|
||||
matched = []
|
||||
missing = []
|
||||
match = False
|
||||
for t in p_tv_map:
|
||||
if p_tv_map[t] and "tt" not in p_tv_map[t] != "None":
|
||||
if p_tv_map[t] is not None and int(p_tv_map[t]) == int(id):
|
||||
match = True
|
||||
break
|
||||
if match:
|
||||
matched.append(t)
|
||||
if tvdb_id in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[tvdb_id]))
|
||||
else:
|
||||
missing.append(id)
|
||||
missing.append(tvdb_id)
|
||||
|
||||
return matched, missing
|
||||
|
||||
def tmdb_get_metadata(config_path, data, type):
|
||||
# Instantiate TMDB objects
|
||||
id = int(data)
|
||||
tmdb_id = int(data)
|
||||
|
||||
tmdb_url_prefix = "https://image.tmdb.org/t/p/original"
|
||||
api_key = config_tools.TMDB(config_path).apikey
|
||||
language = config_tools.TMDB(config_path).language
|
||||
is_movie = config_tools.Plex(config_path).library_type == "movie"
|
||||
|
||||
if type in ["overview", "poster_path", "backdrop_path"]:
|
||||
if type in ["overview", "poster", "backdrop"]:
|
||||
collection = Collection()
|
||||
collection.api_key = api_key
|
||||
collection.language = language
|
||||
try:
|
||||
if type == "overview":
|
||||
return collection.details(id).overview
|
||||
elif type == "poster_path":
|
||||
return tmdb_url_prefix + collection.details(id).poster_path
|
||||
elif type == "backdrop_path":
|
||||
return tmdb_url_prefix + collection.details(id).backdrop_path
|
||||
meta = collection.details(tmdb_id).overview
|
||||
elif type == "poster":
|
||||
meta = collection.details(tmdb_id).poster_path
|
||||
elif type == "backdrop":
|
||||
meta = collection.details(tmdb_id).backdrop_path
|
||||
except AttributeError:
|
||||
media = Movie() if is_movie else TV()
|
||||
media.api_key = api_key
|
||||
media.language = language
|
||||
try:
|
||||
if type == "overview":
|
||||
return media.details(id).overview
|
||||
elif type == "poster_path":
|
||||
return tmdb_url_prefix + media.details(id).poster_path
|
||||
elif type == "backdrop_path":
|
||||
return tmdb_url_prefix + media.details(id).backdrop_path
|
||||
meta = media.details(tmdb_id).overview
|
||||
elif type == "poster":
|
||||
meta = media.details(tmdb_id).poster_path
|
||||
elif type == "backdrop":
|
||||
meta = media.details(tmdb_id).backdrop_path
|
||||
except AttributeError:
|
||||
raise ValueError("| Config Error: TMBd {} ID: {} not found".format("Movie/Collection" if is_movie else "Show", id))
|
||||
elif type in ["biography", "profile_path", "name"]:
|
||||
raise ValueError("| TMDb Error: TMBd {} ID: {} not found".format("Movie/Collection" if is_movie else "Show", tmdb_id))
|
||||
elif type in ["biography", "profile", "name"]:
|
||||
person = Person()
|
||||
person.api_key = api_key
|
||||
person.language = language
|
||||
try:
|
||||
if type == "biography":
|
||||
return person.details(id).biography
|
||||
elif type == "profile_path":
|
||||
return tmdb_url_prefix + person.details(id).profile_path
|
||||
meta = person.details(tmdb_id).biography
|
||||
elif type == "profile":
|
||||
meta = person.details(tmdb_id).profile_path
|
||||
elif type == "name":
|
||||
return person.details(id).name
|
||||
meta = person.details(tmdb_id).name
|
||||
except AttributeError:
|
||||
raise ValueError("| Config Error: TMBd Actor ID: {} not found".format(id))
|
||||
raise ValueError("| TMDb Error: TMBd Actor ID: {} not found".format(tmdb_id))
|
||||
else:
|
||||
raise RuntimeError("type {} not yet supported in tmdb_get_metadata".format(type))
|
||||
|
||||
if meta is None:
|
||||
raise ValueError("| TMDb Error: TMDB ID {} has no {}".format(tmdb_id, type))
|
||||
elif type in ["profile", "poster", "backdrop"]:
|
||||
return "https://image.tmdb.org/t/p/original" + meta
|
||||
else:
|
||||
return meta
|
||||
|
|
|
@ -1,34 +1,39 @@
|
|||
import os
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import glob
|
||||
import datetime
|
||||
from plexapi.server import PlexServer
|
||||
from plexapi.video import Movie
|
||||
from plexapi.video import Show
|
||||
from plexapi.library import MovieSection
|
||||
from plexapi.library import ShowSection
|
||||
from plexapi.library import Collections
|
||||
from plex_tools import add_to_collection
|
||||
from plex_tools import delete_collection
|
||||
from plex_tools import get_actor_rkey
|
||||
from plex_tools import get_collection
|
||||
from plex_tools import get_movie
|
||||
from imdb_tools import tmdb_get_metadata
|
||||
from imdb_tools import imdb_get_ids
|
||||
from config_tools import Config
|
||||
from config_tools import Plex
|
||||
from config_tools import Radarr
|
||||
from config_tools import TMDB
|
||||
from config_tools import Tautulli
|
||||
from config_tools import TraktClient
|
||||
from config_tools import ImageServer
|
||||
from config_tools import modify_config
|
||||
from config_tools import check_for_attribute
|
||||
from radarr_tools import add_to_radarr
|
||||
from urllib.parse import urlparse
|
||||
try:
|
||||
import os
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import glob
|
||||
import datetime
|
||||
from plexapi.server import PlexServer
|
||||
from plexapi.video import Movie
|
||||
from plexapi.video import Show
|
||||
from plexapi.library import MovieSection
|
||||
from plexapi.library import ShowSection
|
||||
from plexapi.library import Collections
|
||||
from plex_tools import get_map
|
||||
from plex_tools import add_to_collection
|
||||
from plex_tools import delete_collection
|
||||
from plex_tools import get_actor_rkey
|
||||
from plex_tools import get_collection
|
||||
from plex_tools import get_item
|
||||
from imdb_tools import tmdb_get_metadata
|
||||
from imdb_tools import imdb_get_ids
|
||||
from config_tools import Config
|
||||
from config_tools import Plex
|
||||
from config_tools import Radarr
|
||||
from config_tools import TMDB
|
||||
from config_tools import Tautulli
|
||||
from config_tools import TraktClient
|
||||
from config_tools import ImageServer
|
||||
from config_tools import modify_config
|
||||
from config_tools import check_for_attribute
|
||||
from radarr_tools import add_to_radarr
|
||||
from urllib.parse import urlparse
|
||||
except ModuleNotFoundError:
|
||||
print('|\n| Requirements Error: Please install requirements using "pip install -r requirements.txt"\n|')
|
||||
sys.exit(0)
|
||||
|
||||
def regex_first_int(data, method, id_type="number", default=None):
|
||||
try:
|
||||
|
@ -104,12 +109,15 @@ def get_method_pair_year(method_to_parse, values_to_parse):
|
|||
def update_from_config(config_path, plex, headless=False, no_meta=False, no_images=False):
|
||||
config = Config(config_path)
|
||||
collections = config.collections
|
||||
if isinstance(plex.Library, MovieSection):
|
||||
libtype = "movie"
|
||||
elif isinstance(plex.Library, ShowSection):
|
||||
libtype = "show"
|
||||
if not headless:
|
||||
print("|\n|===================================================================================================|")
|
||||
if isinstance(plex.Library, MovieSection):
|
||||
radarr = Radarr(config_path) if Radarr.valid else None
|
||||
libtype = "movie"
|
||||
elif isinstance(plex.Library, ShowSection):
|
||||
radarr = None
|
||||
libtype = "show"
|
||||
plex_map = get_map(config_path, plex)
|
||||
alias = {
|
||||
"actors": "actor", "role": "actor", "roles": "actor",
|
||||
"content_ratings": "content_rating", "contentRating": "content_rating", "contentRatings": "content_rating",
|
||||
|
@ -130,6 +138,26 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
"subfilters": "filters",
|
||||
"collection_sort": "collection_order"
|
||||
}
|
||||
pretty_names = {
|
||||
"tmdb_collection": "TMDb Collection",
|
||||
"tmdb_id": "TMDb ID",
|
||||
"tmdb_company": "TMDb Company",
|
||||
"tmdb_network": "TMDb Network",
|
||||
"tmdb_discover": "TMDb Discover",
|
||||
"tmdb_popular": "TMDb Popular",
|
||||
"tmdb_top_rated": "TMDb Top Rated",
|
||||
"tmdb_now_playing": "TMDb Now Playing",
|
||||
"tmdb_trending_daily": "TMDb Trending Daily",
|
||||
"tmdb_trending_weekly": "TMDb Trending Weekly",
|
||||
"tmdb_list": "TMDb List",
|
||||
"tmdb_movie": "TMDb Movie",
|
||||
"tmdb_show": "TMDb Show",
|
||||
"tvdb_show": "TVDb Show",
|
||||
"imdb_list": "IMDb List",
|
||||
"trakt_list": "Trakt List",
|
||||
"trakt_trending": "Trakt Trending",
|
||||
"trakt_watchlist": "Trakt Watchlist"
|
||||
}
|
||||
all_lists = [
|
||||
"plex_search",
|
||||
"plex_collection",
|
||||
|
@ -144,8 +172,8 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
"tmdb_popular",
|
||||
"tmdb_top_rated",
|
||||
"tmdb_now_playing",
|
||||
#"tmdb_trending_daily", #TURNON:Trending
|
||||
#"tmdb_trending_weekly", #TURNON:Trending
|
||||
"tmdb_trending_daily",
|
||||
"tmdb_trending_weekly",
|
||||
"tmdb_list",
|
||||
"tmdb_movie",
|
||||
"tmdb_show",
|
||||
|
@ -169,8 +197,8 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
]
|
||||
show_only_lists = [
|
||||
"tmdb_show",
|
||||
"tvdb_show"
|
||||
"tmdb_network",
|
||||
"tvdb_show",
|
||||
"tmdb_network"
|
||||
]
|
||||
movie_only_lists = [
|
||||
"tmdb_collection",
|
||||
|
@ -205,7 +233,8 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
"originally_available.gte", "originally_available.lte",
|
||||
"video_resolution", "video_resolution.not",
|
||||
"audio_language", "audio_language.not",
|
||||
"subtitle_language", "subtitle_language.not"
|
||||
"subtitle_language", "subtitle_language.not",
|
||||
"plex_collection", "plex_collection.not"
|
||||
]
|
||||
movie_only_filters = [
|
||||
"country", "country.not",
|
||||
|
@ -221,7 +250,7 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
"collection_mode", "collection_order",
|
||||
"poster", "tmdb_poster", "tmdb_profile", "file_poster",
|
||||
"background", "file_background",
|
||||
"name_mapping"
|
||||
"name_mapping", "add_to_radarr"
|
||||
]
|
||||
discover_movie = [
|
||||
"language", "with_original_language", "region", "sort_by",
|
||||
|
@ -308,7 +337,7 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
if ("tmdb" in m or "imdb" in m) and not TMDB.valid:
|
||||
print("| Config Error: {} skipped. tmdb incorrectly configured".format(m))
|
||||
map = {}
|
||||
elif ("trakt" in m or (("tmdb" in m or "tvdb" in m) and plex.library_type == "show")) and not TraktClient.valid:
|
||||
elif "trakt" in m and not TraktClient.valid:
|
||||
print("| Config Error: {} skipped. trakt incorrectly configured".format(m))
|
||||
map = {}
|
||||
elif m == "tautulli" and not Tautulli.valid:
|
||||
|
@ -349,12 +378,12 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
posters_found.append(["url", check_value, check_name])
|
||||
elif check_name == "tmdb_poster":
|
||||
try:
|
||||
posters_found.append(["url", tmdb_get_metadata(config_path, check_value, "poster_path"), check_name])
|
||||
posters_found.append(["url", tmdb_get_metadata(config_path, check_value, "poster"), check_name])
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
elif check_name == "tmdb_profile":
|
||||
try:
|
||||
posters_found.append(["url", tmdb_get_metadata(config_path, check_value, "profile_path"), check_name])
|
||||
posters_found.append(["url", tmdb_get_metadata(config_path, check_value, "profile"), check_name])
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
elif check_name == "file_poster":
|
||||
|
@ -366,7 +395,7 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
backgrounds_found.append(["url", check_value, check_name])
|
||||
elif check_name == "tmdb_background":
|
||||
try:
|
||||
backgrounds_found.append(["url", tmdb_get_metadata(config_path, check_value, "backdrop_path"), check_name])
|
||||
backgrounds_found.append(["url", tmdb_get_metadata(config_path, check_value, "backdrop"), check_name])
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
elif check_name == "file_background":
|
||||
|
@ -374,9 +403,18 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
backgrounds_found.append(["file", os.path.abspath(check_value), check_name])
|
||||
else:
|
||||
print("| Config Error: Background Path Does Not Exist: {}".format(os.path.abspath(check_value)))
|
||||
elif check_name == "add_to_radarr":
|
||||
if check_value == True or check_value == False:
|
||||
details[check_name] = check_value
|
||||
else:
|
||||
print("| Config Error: add_to_radarr must be either true or false")
|
||||
else:
|
||||
details[check_name] = check_value
|
||||
if method_name == "details":
|
||||
if method_name in show_only_lists and libtype == "movie":
|
||||
print("| Config Error: {} attribute only works for show libraries".format(method_name))
|
||||
elif (method_name in movie_only_filters or method_name in movie_only_lists) and libtype == "show":
|
||||
print("| Config Error: {} attribute only works for movie libraries".format(method_name))
|
||||
elif method_name == "details":
|
||||
print("| Config Error: Please remove the details attribute all its old sub-attributes should be one level higher")
|
||||
for detail_m in collections[c][m]:
|
||||
if detail_m in alias:
|
||||
|
@ -405,7 +443,7 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
if person_id is None:
|
||||
if "summary" not in details:
|
||||
details["summary"] = tmdb_get_metadata(config_path, id, "biography")
|
||||
details["poster"] = ["url", tmdb_get_metadata(config_path, id, "profile_path"), method_name]
|
||||
details["poster"] = ["url", tmdb_get_metadata(config_path, id, "profile"), method_name]
|
||||
person_id = id
|
||||
person_method = method_name
|
||||
if method_name == "tmdb_actor":
|
||||
|
@ -421,9 +459,9 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
final_collections = []
|
||||
for new_collection in collection_list:
|
||||
try:
|
||||
final_collections.append(get_collection(plex, new_collection, headless))
|
||||
final_collections.append(get_collection(plex, str(new_collection), headless))
|
||||
except ValueError as e:
|
||||
print("| Config Error: {} {}".format(method_name, new_collection))
|
||||
print("| Config Error: {} {} Not Found".format(method_name, new_collection))
|
||||
if len(final_collections) > 0:
|
||||
methods.append(("plex_collection", final_collections))
|
||||
elif method_name == "tmdb_collection":
|
||||
|
@ -434,9 +472,18 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
id = get_method_pair_tmdb(method_name, collections[c][m], "TMDb ID")
|
||||
if tmdb_id is None:
|
||||
if "summary" not in details:
|
||||
details["summary"] = tmdb_get_metadata(config_path, id[1][0], "overview")
|
||||
details["poster"] = ["url", tmdb_get_metadata(config_path, id[1][0], "poster_path"), method_name]
|
||||
details["poster"] = ["url", tmdb_get_metadata(config_path, id[1][0], "backdrop_path"), method_name]
|
||||
try:
|
||||
details["summary"] = tmdb_get_metadata(config_path, id[1][0], "overview")
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
try:
|
||||
details["poster"] = ["url", tmdb_get_metadata(config_path, id[1][0], "poster"), method_name]
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
try:
|
||||
details["background"] = ["url", tmdb_get_metadata(config_path, id[1][0], "backdrop"), method_name]
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
tmdb_id = id[1][0]
|
||||
methods.append(id)
|
||||
elif method_name in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]:
|
||||
|
@ -602,6 +649,12 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
first_filter = False
|
||||
print("| Collection Filter {}: {}".format(f[0], f[1]))
|
||||
|
||||
do_radarr = False
|
||||
if radarr:
|
||||
do_radarr = radarr.add_to_radarr
|
||||
if "add_to_radarr" in details:
|
||||
do_radarr = details["add_to_radarr"]
|
||||
|
||||
# Loops though and actually processes the methods
|
||||
for m, values in methods:
|
||||
for v in values:
|
||||
|
@ -612,39 +665,26 @@ def update_from_config(config_path, plex, headless=False, no_meta=False, no_imag
|
|||
elif m not in ["plex_search", "tmdb_list", "tmdb_id", "tmdb_movie", "tmdb_collection", "tmdb_company", "tmdb_network", "tmdb_discover", "tmdb_show"]:
|
||||
print("| \n| Processing {}: {}".format(m, v))
|
||||
try:
|
||||
missing, map = add_to_collection(config_path, plex, m, v, c, map, filters)
|
||||
missing, map = add_to_collection(config_path, plex, m, v, c, plex_map, map, filters)
|
||||
except (KeyError, ValueError, SystemExit) as e:
|
||||
print(e)
|
||||
missing = False
|
||||
if missing:
|
||||
if libtype == "movie":
|
||||
method_name = "IMDb" if "imdb" in m else "Trakt" if "trakt" in m else "TMDb"
|
||||
if m in ["trakt_list", "trakt_watchlist", "tmdb_list"]:
|
||||
print("| {} missing movie{} from {} List: {}".format(len(missing), "s" if len(missing) > 1 else "", method_name, v))
|
||||
elif m == "imdb_list":
|
||||
print("| {} missing movie{} from {} List: {}".format(len(missing), "s" if len(missing) > 1 else "", method_name, v[0]))
|
||||
elif m == "tmdb_collection":
|
||||
print("| {} missing movie{} from {} Collection: {}".format(len(missing), "s" if len(missing) > 1 else "", method_name, v))
|
||||
elif m == "trakt_trending":
|
||||
print("| {} missing movie{} from {} List: Trending (top {})".format(len(missing), "s" if len(missing) > 1 else "", method_name, v))
|
||||
else:
|
||||
print("| {} ID: {} missing".format(method_name, v))
|
||||
if Radarr.valid:
|
||||
radarr = Radarr(config_path)
|
||||
if radarr.add_movie:
|
||||
print("| Adding missing movies to Radarr")
|
||||
add_to_radarr(config_path, missing)
|
||||
elif not headless and radarr.add_movie is None and input("| Add missing movies to Radarr? (y/n): ").upper() == "Y":
|
||||
add_to_radarr(config_path, missing)
|
||||
elif libtype == "show":
|
||||
method_name = "Trakt" if "trakt" in m else "TVDb" if "tvdb" in m else "TMDb"
|
||||
if m in ["trakt_list", "trakt_watchlist", "tmdb_list"]:
|
||||
print("| {} missing show{} from {} List: {}".format(len(missing), "s" if len(missing) > 1 else "", method_name, v))
|
||||
elif m == "trakt_trending":
|
||||
print("| {} missing show{} from {} List: Trending (top {})".format(len(missing), "s" if len(missing) > 1 else "", method_name, v))
|
||||
else:
|
||||
print("| {} ID: {} missing".format(method_name, v))
|
||||
def missing_print(display_value):
|
||||
print("| {} missing {}{} from {}: {}".format(len(missing), libtype, "s" if len(missing) > 1 else "", pretty_names[m], display_value))
|
||||
|
||||
if m in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly", "trakt_trending"]:
|
||||
missing_print("Top {}".format(v))
|
||||
elif m == "imdb_list":
|
||||
missing_print(v[0])
|
||||
else:
|
||||
missing_print(v)
|
||||
|
||||
if do_radarr:
|
||||
print("| Adding missing movies to Radarr")
|
||||
add_to_radarr(config_path, missing)
|
||||
elif do_radarr is None and not headless and input("| Add missing movies to Radarr? (y/n): ").upper() == "Y":
|
||||
add_to_radarr(config_path, missing)
|
||||
# if not skip_sonarr:
|
||||
# if input("Add missing shows to Sonarr? (y/n): ").upper() == "Y":
|
||||
# add_to_radarr(missing_shows)
|
||||
|
@ -799,12 +839,12 @@ def append_collection(config_path, config_update=None):
|
|||
method = "movie"
|
||||
value = input("| Enter Movie (Name or Rating Key): ")
|
||||
if value is int:
|
||||
plex_movie = get_movie(plex, int(value))
|
||||
plex_movie = get_item(plex, int(value))
|
||||
print('| +++ Adding %s to collection %s' % (
|
||||
plex_movie.title, selected_collection.title))
|
||||
plex_movie.addCollection(selected_collection.title)
|
||||
else:
|
||||
results = get_movie(plex, value)
|
||||
results = get_item(plex, value)
|
||||
if len(results) > 1:
|
||||
while True:
|
||||
i = 1
|
||||
|
@ -962,7 +1002,7 @@ print("| | _/| |/ -_)\ \ / / _ \| || || _|/ _ \ | (__ / _ \| || |/ -_)/ _|
|
|||
print("| |_| |_|\___|/_\_\ /_/ \_\\\\_,_| \__|\___/ \___|\___/|_||_|\___|\__| \__||_|\___/|_||_|/__/ |")
|
||||
print("| |")
|
||||
print("|===================================================================================================|")
|
||||
print("| Version 2.6.0")
|
||||
print("| Version 2.7.0")
|
||||
print("| Locating config...")
|
||||
config_path = None
|
||||
app_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
|
|
@ -14,46 +14,32 @@ from bs4 import BeautifulSoup
|
|||
from urllib.request import Request
|
||||
from urllib.request import urlopen
|
||||
from urllib.parse import urlparse
|
||||
from tmdbv3api import TMDb
|
||||
from tmdbv3api import Movie as TMDb_Movie
|
||||
import os
|
||||
import sqlite3
|
||||
import random
|
||||
|
||||
|
||||
def get_movie(plex, data):
|
||||
# If an int is passed as data, assume it is a movie's rating key
|
||||
if isinstance(data, int):
|
||||
try:
|
||||
return plex.Server.fetchItem(data)
|
||||
except PlexExceptions.BadRequest:
|
||||
print("| Nothing found")
|
||||
return None
|
||||
elif isinstance(data, Movie):
|
||||
return data
|
||||
else:
|
||||
movie_list = plex.Library.search(title=data)
|
||||
if movie_list:
|
||||
return movie_list
|
||||
else:
|
||||
print("| Movie: {} not found".format(data))
|
||||
return None
|
||||
def adjust_space(old_length, display_title):
|
||||
display_title = str(display_title)
|
||||
space_length = old_length - len(display_title)
|
||||
if space_length > 0:
|
||||
display_title += " " * space_length
|
||||
return display_title
|
||||
|
||||
def get_item(plex, data):
|
||||
# If an int is passed as data, assume it is a movie's rating key
|
||||
if isinstance(data, int):
|
||||
if isinstance(data, int) or isinstance(data, Movie) or isinstance(data, Show):
|
||||
try:
|
||||
return plex.Server.fetchItem(data)
|
||||
return plex.Server.fetchItem(data.ratingKey if isinstance(data, Movie) or isinstance(data, Show) else data)
|
||||
except PlexExceptions.BadRequest:
|
||||
return "Nothing found"
|
||||
elif isinstance(data, Movie):
|
||||
return data
|
||||
elif isinstance(data, Show):
|
||||
return data
|
||||
else:
|
||||
print(data)
|
||||
item_list = plex.Library.search(title=data)
|
||||
if item_list:
|
||||
return item_list
|
||||
else:
|
||||
return "Item: " + data + " not found"
|
||||
return "Item: {} not found".format(data)
|
||||
|
||||
def get_actor_rkey(plex, data):
|
||||
"""Takes in actors name as str and returns as Plex's corresponding rating key ID"""
|
||||
|
@ -83,6 +69,94 @@ def get_actor_rkey(plex, data):
|
|||
except UnboundLocalError:
|
||||
raise ValueError("| Config Error: Actor: {} not found".format(search))
|
||||
|
||||
def get_map(config_path, plex):
|
||||
plex_map = {}
|
||||
current_length = 0
|
||||
current_count = 0
|
||||
if TMDB.valid:
|
||||
tmdb = TMDb()
|
||||
tmdb.api_key = TMDB(config_path).apikey
|
||||
tmovie = TMDb_Movie()
|
||||
print("|")
|
||||
if plex.cache:
|
||||
create_cache(config_path)
|
||||
print("| Mapping Plex {}".format("Movies" if plex.library_type == "movie" else "Shows"))
|
||||
plex_items = plex.Library.all()
|
||||
try:
|
||||
for item in plex_items:
|
||||
current_count += 1
|
||||
print_display = "| Processing: {}/{} {}".format(current_count, len(plex_items), item.title)
|
||||
print(adjust_space(current_length, print_display), end="\r")
|
||||
current_length = len(print_display)
|
||||
update = None
|
||||
key_id = None
|
||||
error_message = "Unable to map {} ID".format("TMDb" if plex.library_type == "movie" else "TVDb")
|
||||
if plex.cache == True:
|
||||
key_id, update = query_cache(config_path, plex.cache_interval, item.guid)
|
||||
if key_id is None or update:
|
||||
guid = urlparse(item.guid)
|
||||
item_type = guid.scheme.split('.')[-1]
|
||||
check_id = guid.netloc
|
||||
if item_type == 'plex' and plex.library_type == "movie":
|
||||
key_id = new_movie_agent_get_tmdb(plex, item)
|
||||
elif item_type == 'imdb' and plex.library_type == "movie":
|
||||
key_id = None
|
||||
if TMDB.valid and key_id is None:
|
||||
key_id = imdb_tools.imdb_get_tmdb(config_path, check_id)
|
||||
if TraktClient.valid and key_id is None:
|
||||
key_id = trakt_tools.trakt_imdb_to_tmdb(config_path, check_id)
|
||||
if key_id is None:
|
||||
if TMDB.valid and TraktClient.valid:
|
||||
error_message = "Unable to convert IMDb ID: {} to TMDb ID using TMDb or Trakt".format(check_id)
|
||||
elif TMDB.valid:
|
||||
error_message = "Unable to convert IMDb ID: {} to TMDb ID using TMDb".format(check_id)
|
||||
elif TraktClient.valid:
|
||||
error_message = "Unable to convert IMDb ID: {} to TMDb ID using Trakt".format(check_id)
|
||||
else:
|
||||
error_message = "Configure TMDb or Trakt to covert IMDb ID: {} to TMDb ID".format(check_id)
|
||||
elif item_type == 'themoviedb' and plex.library_type == "movie":
|
||||
tmdbapi = tmovie.details(check_id)
|
||||
if hasattr(tmdbapi, 'id'):
|
||||
key_id = tmdbapi.id
|
||||
else:
|
||||
key_id = None
|
||||
error_message = "TMDb ID: {} Invalid".format(check_id)
|
||||
elif item_type == 'thetvdb' and plex.library_type == "show":
|
||||
key_id = check_id
|
||||
elif item_type == 'themoviedb' and plex.library_type == "show":
|
||||
key_id = None
|
||||
if TMDB.valid and key_id is None:
|
||||
key_id = imdb_tools.tmdb_get_tvdb(config_path, check_id)
|
||||
if TraktClient.valid and key_id is None:
|
||||
key_id = trakt_tools.trakt_tmdb_to_tvdb(config_path, check_id)
|
||||
if key_id is None:
|
||||
if TMDB.valid and TraktClient.valid:
|
||||
error_message = "Unable to convert TMDb ID: {} to TVDb ID using TMDb or Trakt".format(check_id)
|
||||
elif TMDB.valid:
|
||||
error_message = "Unable to convert TMDb ID: {} to TVDb ID using TMDb".format(check_id)
|
||||
elif TraktClient.valid:
|
||||
error_message = "Unable to convert TMDb ID: {} to TVDb ID using Trakt".format(check_id)
|
||||
else:
|
||||
error_message = "Configure TMDb or Trakt to covert TMDb ID: {} to TVDb ID".format(check_id)
|
||||
elif item_type == "local":
|
||||
key_id = None
|
||||
error_message = "No match in Plex"
|
||||
else:
|
||||
key_id = None
|
||||
error_message = "Agent {} not supported".format(item_type)
|
||||
if plex.cache == True and key_id:
|
||||
print(adjust_space(current_length, "| Cache | {} | {:<46} | {:<6} | {}".format("^" if update == True else "+", item.guid, key_id, item.title)))
|
||||
update_cache(config_path, item.guid, key_id, True if update == True else False, plex.cache_interval)
|
||||
if key_id:
|
||||
plex_map[key_id] = item.ratingKey
|
||||
else:
|
||||
print(adjust_space(current_length, "| {} {:<46} | {} for {}".format("Cache | ! |" if plex.cache == True else "Mapping Error:", item.guid, error_message, item.title)))
|
||||
print(adjust_space(current_length, "| Processed {} {}".format(len(plex_items), "Movies" if plex.library_type == "movie" else "Shows")))
|
||||
except:
|
||||
print()
|
||||
raise
|
||||
return plex_map
|
||||
|
||||
# subtype can be 'movie', 'show', or None (movie/tv combined)
|
||||
def get_collection(plex, data, exact=None, subtype=None):
|
||||
collection_list = plex.Library.search(title=data, libtype="collection")
|
||||
|
@ -105,17 +179,24 @@ def get_collection(plex, data, exact=None, subtype=None):
|
|||
print("| Invalid entry")
|
||||
except (IndexError, ValueError) as E:
|
||||
print("| Invalid entry")
|
||||
elif len(collection_list) == 1 and (exact is None or (exact and collection_list[0].title == data)):
|
||||
elif len(collection_list) == 1 and (exact is None or collection_list[0].title == data):
|
||||
return collection_list[0]
|
||||
else:
|
||||
raise ValueError("Collection {} not found".format(data))
|
||||
raise ValueError("Collection {} Not Found".format(data))
|
||||
|
||||
def add_to_collection(config_path, plex, method, value, c, map, filters=None):
|
||||
movies = []
|
||||
shows = []
|
||||
def add_to_collection(config_path, plex, method, value, c, plex_map=None, map=None, filters=None):
|
||||
if plex_map is None and ("imdb" in method or "tvdb" in method or "tmdb" in method or "trakt" in method):
|
||||
plex_map = get_map(config_path, plex)
|
||||
if map is None:
|
||||
map = {}
|
||||
items = []
|
||||
missing = []
|
||||
def search_plex():
|
||||
|
||||
if method == "all":
|
||||
items = plex.Library.all()
|
||||
elif method == "plex_collection":
|
||||
items = value.children
|
||||
elif method == "plex_search":
|
||||
search_terms = {}
|
||||
output = ""
|
||||
for attr_pair in value:
|
||||
|
@ -134,48 +215,32 @@ def add_to_collection(config_path, plex, method, value, c, map, filters=None):
|
|||
ors = ors + (" OR " if len(ors) > 0 else attr_pair[0] + "(") + str(param)
|
||||
output = output + ("\n|\t\t AND " if len(output) > 0 else "| Processing Plex Search: ") + ors + ")"
|
||||
print(output)
|
||||
return plex.Library.search(**search_terms)
|
||||
|
||||
if ("trakt" in method or (("tmdb" in method or "tvdb" in method) and plex.library_type == "show")) and not TraktClient.valid:
|
||||
raise KeyError("| trakt connection required for {}",format(method))
|
||||
elif ("imdb" in method or "tmdb" in method) and not TMDB.valid:
|
||||
raise KeyError("| tmdb connection required for {}",format(method))
|
||||
elif method == "tautulli" and not Tautulli.valid:
|
||||
raise KeyError("| tautulli connection required for {}",format(method))
|
||||
elif plex.library_type == "movie":
|
||||
if method == "plex_collection":
|
||||
movies = value.children
|
||||
elif method == "imdb_list":
|
||||
movies, missing = imdb_tools.imdb_get_movies(config_path, plex, value)
|
||||
elif "tmdb" in method:
|
||||
movies, missing = imdb_tools.tmdb_get_movies(config_path, plex, value, method)
|
||||
elif "trakt" in method:
|
||||
movies, missing = trakt_tools.trakt_get_movies(config_path, plex, value, method)
|
||||
elif method == "tautulli":
|
||||
movies, missing = imdb_tools.get_tautulli(config_path, plex, value)
|
||||
elif method == "all":
|
||||
movies = plex.Library.all()
|
||||
elif method == "plex_search":
|
||||
movies = search_plex()
|
||||
items = plex.Library.search(**search_terms)
|
||||
elif method == "tvdb_show" and plex.library_type == "show":
|
||||
items, missing = imdb_tools.tvdb_get_shows(config_path, plex, plex_map, value)
|
||||
elif "imdb" in method or "tmdb" in method:
|
||||
if not TMDB.valid:
|
||||
raise KeyError("| tmdb connection required for {}",format(method))
|
||||
elif method == "imdb_list" and plex.library_type == "movie":
|
||||
items, missing = imdb_tools.imdb_get_movies(config_path, plex, plex_map, value)
|
||||
elif "tmdb" in method and plex.library_type == "movie":
|
||||
items, missing = imdb_tools.tmdb_get_movies(config_path, plex, plex_map, value, method)
|
||||
elif "tmdb" in method and plex.library_type == "show":
|
||||
items, missing = imdb_tools.tmdb_get_shows(config_path, plex, plex_map, value, method)
|
||||
elif "trakt" in method:
|
||||
if not TraktClient.valid:
|
||||
raise KeyError("| trakt connection required for {}",format(method))
|
||||
elif plex.library_type == "movie":
|
||||
items, missing = trakt_tools.trakt_get_movies(config_path, plex, plex_map, value, method)
|
||||
elif plex.library_type == "show":
|
||||
items, missing = trakt_tools.trakt_get_shows(config_path, plex, plex_map, value, method)
|
||||
elif method == "tautulli":
|
||||
if not Tautulli.valid:
|
||||
raise KeyError("| tautulli connection required for {}",format(method))
|
||||
else:
|
||||
print("| Config Error: {} method not supported".format(method))
|
||||
elif plex.library_type == "show":
|
||||
if method == "plex_collection":
|
||||
shows = value.children
|
||||
elif "tmdb" in method:
|
||||
shows, missing = imdb_tools.tmdb_get_shows(config_path, plex, value, method)
|
||||
elif method == "tvdb_show":
|
||||
shows, missing = imdb_tools.tvdb_get_shows(config_path, plex, value)
|
||||
elif "trakt" in method:
|
||||
shows, missing = trakt_tools.trakt_get_shows(config_path, plex, value, method)
|
||||
elif method == "tautulli":
|
||||
shows, missing = imdb_tools.get_tautulli(config_path, plex, value)
|
||||
elif method == "all":
|
||||
shows = plex.Library.all()
|
||||
elif method == "plex_search":
|
||||
shows = search_plex()
|
||||
else:
|
||||
print("| Config Error: {} method not supported".format(method))
|
||||
items, missing = imdb_tools.get_tautulli(config_path, plex, value)
|
||||
else:
|
||||
print("| Config Error: {} method not supported".format(method))
|
||||
|
||||
filter_alias = {
|
||||
"actor": "actors",
|
||||
|
@ -192,161 +257,83 @@ def add_to_collection(config_path, plex, method, value, c, map, filters=None):
|
|||
"video_resolution": "video_resolution",
|
||||
"audio_language": "audio_language",
|
||||
"subtitle_language": "subtitle_language",
|
||||
"plex_collection": "collections",
|
||||
}
|
||||
|
||||
if movies:
|
||||
if items:
|
||||
# Check if already in collection
|
||||
cols = plex.Library.search(title=c, libtype="collection")
|
||||
try:
|
||||
fs = cols[0].children
|
||||
except IndexError:
|
||||
fs = []
|
||||
movie_count = 0
|
||||
movie_max = len(movies)
|
||||
max_str_len = len(str(movie_max))
|
||||
item_count = 0
|
||||
item_max = len(items)
|
||||
max_str_len = len(str(item_max))
|
||||
current_length = 0
|
||||
for rk in movies:
|
||||
current_m = get_movie(plex, rk)
|
||||
current_m.reload()
|
||||
movie_count += 1
|
||||
count_str_len = len(str(movie_count))
|
||||
display_count = (" " * (max_str_len - count_str_len)) + str(movie_count)
|
||||
for rk in items:
|
||||
current_item = get_item(plex, rk)
|
||||
item_count += 1
|
||||
match = True
|
||||
if filters:
|
||||
display_count = (" " * (max_str_len - len(str(item_count)))) + str(item_count)
|
||||
print_display = "| Filtering {}/{} {}".format(display_count, item_max, current_item.title)
|
||||
print(adjust_space(current_length, print_display), end = "\r")
|
||||
current_length = len(print_display)
|
||||
for f in filters:
|
||||
print_display = "| Filtering {}/{} {}".format(display_count, movie_max, current_m.title)
|
||||
print(imdb_tools.adjust_space(current_length, print_display), end = "\r")
|
||||
current_length = len(print_display)
|
||||
modifier = f[0][-4:]
|
||||
method = filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else filter_alias[f[0]]
|
||||
if method == "max_age":
|
||||
threshold_date = datetime.now() - timedelta(days=f[1])
|
||||
attr = getattr(current_m, "originallyAvailableAt")
|
||||
attr = getattr(current_item, "originallyAvailableAt")
|
||||
if attr is None or attr < threshold_date:
|
||||
match = False
|
||||
break
|
||||
elif modifier in [".gte", ".lte"]:
|
||||
if method == "originallyAvailableAt":
|
||||
threshold_date = datetime.strptime(f[1], "%m/%d/%y")
|
||||
attr = getattr(current_m, "originallyAvailableAt")
|
||||
attr = getattr(current_item, "originallyAvailableAt")
|
||||
if (modifier == ".lte" and attr > threshold_date) or (modifier == ".gte" and attr < threshold_date):
|
||||
match = False
|
||||
break
|
||||
elif method in ["year", "rating"]:
|
||||
attr = getattr(current_m, method)
|
||||
attr = getattr(current_item, method)
|
||||
if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]):
|
||||
match = False
|
||||
break
|
||||
else:
|
||||
terms = f[1] if isinstance(f[1], list) else str(f[1]).split(", ")
|
||||
if method in ["video_resolution", "audio_language", "subtitle_language"]:
|
||||
for media in current_m.media:
|
||||
for media in current_item.media:
|
||||
if method == "video_resolution":
|
||||
mv_attrs = [media.videoResolution]
|
||||
attrs = [media.videoResolution]
|
||||
for part in media.parts:
|
||||
if method == "audio_language":
|
||||
mv_attrs = ([audio_stream.language for audio_stream in part.audioStreams()])
|
||||
attrs = ([audio_stream.language for audio_stream in part.audioStreams()])
|
||||
if method == "subtitle_language":
|
||||
mv_attrs = ([subtitle_stream.language for subtitle_stream in part.subtitleStreams()])
|
||||
attrs = ([subtitle_stream.language for subtitle_stream in part.subtitleStreams()])
|
||||
elif method in ["contentRating", "studio", "year", "rating", "originallyAvailableAt"]: # Otherwise, it's a string. Make it a list.
|
||||
mv_attrs = [str(getattr(current_m, method))]
|
||||
elif method in ["actors", "countries", "directors", "genres", "writers"]:
|
||||
mv_attrs = [getattr(x, 'tag') for x in getattr(current_m, method)]
|
||||
attrs = [str(getattr(current_item, method))]
|
||||
elif method in ["actors", "countries", "directors", "genres", "writers", "collections"]:
|
||||
attrs = [getattr(x, 'tag') for x in getattr(current_item, method)]
|
||||
|
||||
# Get the intersection of the user's terms and movie's terms
|
||||
# Get the intersection of the user's terms and item's terms
|
||||
# If it's empty and modifier is not .not, it's not a match
|
||||
# If it's not empty and modifier is .not, it's not a match
|
||||
if (not list(set(terms) & set(mv_attrs)) and modifier != ".not") or (list(set(terms) & set(mv_attrs)) and modifier == ".not"):
|
||||
if (not list(set(terms) & set(attrs)) and modifier != ".not") or (list(set(terms) & set(attrs)) and modifier == ".not"):
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
if current_m in fs:
|
||||
map[current_m.ratingKey] = None
|
||||
if current_item in fs:
|
||||
map[current_item.ratingKey] = None
|
||||
else:
|
||||
current_m.addCollection(c)
|
||||
print(imdb_tools.adjust_space(current_length, "| {} Collection | {} | {}".format(c, "=" if current_m in fs else "+", current_m.title)))
|
||||
print(imdb_tools.adjust_space(current_length, "| Processed {} Movies".format(movie_max)))
|
||||
elif plex.library_type == "movie":
|
||||
print("| No movies found")
|
||||
|
||||
if shows:
|
||||
# Check if already in collection
|
||||
cols = plex.Library.search(title=c, libtype="collection")
|
||||
try:
|
||||
fs = cols[0].children
|
||||
except IndexError:
|
||||
fs = []
|
||||
show_count = 0
|
||||
show_max = len(shows)
|
||||
current_length = 0
|
||||
for rk in shows:
|
||||
current_s = get_item(plex, rk)
|
||||
current_s.reload()
|
||||
show_count += 1
|
||||
match = True
|
||||
if filters:
|
||||
for f in filters:
|
||||
print_display = "| Filtering {}/{} {}".format(show_count, show_max, current_s.title)
|
||||
print(imdb_tools.adjust_space(current_length, print_display), end = "\r")
|
||||
current_length = len(print_display)
|
||||
modifier = f[0][-4:]
|
||||
method = filter_alias[f[0][:-4]] if modifier in [".not", ".lte", ".gte"] else filter_alias[f[0]]
|
||||
if method == "max_age":
|
||||
threshold_date = datetime.now() - timedelta(days=f[1])
|
||||
attr = getattr(current_s, "originallyAvailableAt")
|
||||
if attr is None or attr < threshold_date:
|
||||
match = False
|
||||
break
|
||||
elif modifier in [".gte", ".lte"]:
|
||||
if method == "originallyAvailableAt":
|
||||
threshold_date = datetime.strptime(f[1], "%m/%d/%y")
|
||||
attr = getattr(current_s, "originallyAvailableAt")
|
||||
if (modifier == ".lte" and attr > threshold_date) or (modifier == ".gte" and attr < threshold_date):
|
||||
match = False
|
||||
break
|
||||
elif method in ["year", "rating"]:
|
||||
attr = getattr(current_s, method)
|
||||
if (modifier == ".lte" and attr > f[1]) or (modifier == ".gte" and attr < f[1]):
|
||||
match = False
|
||||
break
|
||||
else:
|
||||
terms = f[1] if isinstance(f[1], list) else str(f[1]).split(", ")
|
||||
# if method in ["video_resolution", "audio_language", "subtitle_language"]:
|
||||
# for media in current_s.media:
|
||||
# if method == "video_resolution":
|
||||
# show_attrs = [media.videoResolution]
|
||||
# for part in media.parts:
|
||||
# if method == "audio_language":
|
||||
# show_attrs = ([audio_stream.language for audio_stream in part.audioStreams()])
|
||||
# if method == "subtitle_language":
|
||||
# show_attrs = ([subtitle_stream.language for subtitle_stream in part.subtitleStreams()])
|
||||
if method in ["contentRating", "studio", "year", "rating", "originallyAvailableAt"]:
|
||||
mv_attrs = [str(getattr(current_s, method))]
|
||||
elif method in ["actors", "genres"]:
|
||||
mv_attrs = [getattr(x, 'tag') for x in getattr(current_s, method)]
|
||||
|
||||
# Get the intersection of the user's terms and show's terms
|
||||
# If it's empty and modifier is not .not, it's not a match
|
||||
# If it's not empty and modifier is .not, it's not a match
|
||||
if (not list(set(terms) & set(show_attrs)) and modifier != ".not") or (list(set(terms) & set(show_attrs)) and modifier == ".not"):
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
if current_s in fs:
|
||||
map[current_s.ratingKey] = None
|
||||
else:
|
||||
current_s.addCollection(c)
|
||||
print(imdb_tools.adjust_space(current_length, "| {} Collection | {} | {}".format(c, "=" if current_s in fs else "+", current_s.title)))
|
||||
print(imdb_tools.adjust_space(current_length, "| Processed {} Shows".format(show_max)))
|
||||
elif plex.library_type == "show":
|
||||
print("| No shows found")
|
||||
|
||||
try:
|
||||
missing
|
||||
except UnboundLocalError:
|
||||
return
|
||||
current_item.addCollection(c)
|
||||
print(adjust_space(current_length, "| {} Collection | {} | {}".format(c, "=" if current_item in fs else "+", current_item.title)))
|
||||
print(adjust_space(current_length, "| Processed {} {}".format(item_max, "Movies" if plex.library_type == "movie" else "Shows")))
|
||||
else:
|
||||
return missing, map
|
||||
print("| No {} Found".format("Movies" if plex.library_type == "movie" else "Shows"))
|
||||
|
||||
return missing, map
|
||||
|
||||
def delete_collection(data):
|
||||
confirm = input("| {} selected. Confirm deletion (y/n):".format(data.title))
|
||||
|
@ -354,55 +341,55 @@ def delete_collection(data):
|
|||
data.delete()
|
||||
print("| Collection deleted")
|
||||
|
||||
def alt_id_lookup(plex, value):
|
||||
req = Request('{}{}'.format(plex.url, value.key))
|
||||
def new_movie_agent_get_tmdb(plex, movie):
|
||||
req = Request('{}{}'.format(plex.url, movie.key))
|
||||
req.add_header('X-Plex-Token', plex.token)
|
||||
req.add_header('User-Agent', 'Mozilla/5.0')
|
||||
with urlopen(req) as response:
|
||||
contents = response.read()
|
||||
bs = BeautifulSoup(contents, 'lxml')
|
||||
imdb_id = None
|
||||
tmdb_id = None
|
||||
for guid_tag in bs.find_all('guid'):
|
||||
agent = urlparse(guid_tag['id']).scheme
|
||||
guid = urlparse(guid_tag['id']).netloc
|
||||
if agent == 'imdb':
|
||||
imdb_id = guid
|
||||
elif agent == 'tmdb':
|
||||
if agent == 'tmdb':
|
||||
tmdb_id = guid
|
||||
return imdb_id, tmdb_id
|
||||
break
|
||||
return tmdb_id
|
||||
|
||||
def create_cache(config_path):
|
||||
cache = os.path.join(os.path.dirname(config_path), 'cache.db')
|
||||
connection = sqlite3.connect(cache)
|
||||
cache = "{}.cache".format(os.path.splitext(config_path)[0])
|
||||
with sqlite3.connect(cache) as connection:
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(''' SELECT count(name) FROM sqlite_master WHERE type='table' AND name='guids' ''')
|
||||
if cursor.fetchone()[0] != 1:
|
||||
print("| Initializing cache database.".format(cache))
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS guids (plex_guid TEXT PRIMARY KEY, imdb_id TEXT, tmdb_id TEXT)')
|
||||
print("| Initializing cache database at {}".format(cache))
|
||||
cursor.execute('CREATE TABLE IF NOT EXISTS guids (plex_guid TEXT PRIMARY KEY, id TEXT, updated TEXT)')
|
||||
else:
|
||||
print("| Using cache database at {}".format(cache))
|
||||
|
||||
def query_cache(config_path, key, column):
|
||||
cache = os.path.join(os.path.dirname(config_path), 'cache.db')
|
||||
def query_cache(config_path, cache_interval, key):
|
||||
cache = "{}.cache".format(os.path.splitext(config_path)[0])
|
||||
id_to_return = None
|
||||
update = None
|
||||
with sqlite3.connect(cache) as connection:
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("SELECT * FROM guids WHERE plex_guid = ?", (key, ))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row[column]
|
||||
if row and row["id"]:
|
||||
datetime_object = datetime.strptime(row["updated"], "%Y-%m-%d")
|
||||
time_between_insertion = datetime.now() - datetime_object
|
||||
id_to_return = int(row["id"])
|
||||
update = True if time_between_insertion.days > cache_interval else False
|
||||
return id_to_return, update
|
||||
|
||||
def update_cache(config_path, plex_guid, **kwargs):
|
||||
cache = os.path.join(os.path.dirname(config_path), 'cache.db')
|
||||
def update_cache(config_path, plex_guid, input_id, update, cache_interval):
|
||||
updated_date = datetime.now() if update == True else (datetime.now() - timedelta(days=random.randint(1, cache_interval)))
|
||||
cache = "{}.cache".format(os.path.splitext(config_path)[0])
|
||||
with sqlite3.connect(cache) as connection:
|
||||
connection.row_factory = sqlite3.Row
|
||||
cursor = connection.cursor()
|
||||
if 'imdb_id' in kwargs:
|
||||
imdb_id = kwargs['imdb_id']
|
||||
cursor.execute('INSERT OR IGNORE INTO guids(plex_guid, imdb_id) VALUES(?, ?)', (plex_guid, imdb_id, ))
|
||||
cursor.execute('UPDATE guids SET imdb_id = ? WHERE plex_guid = ?', (imdb_id, plex_guid))
|
||||
if 'tmdb_id' in kwargs:
|
||||
tmdb_id = kwargs['tmdb_id']
|
||||
cursor.execute('INSERT OR IGNORE INTO guids(plex_guid, tmdb_id) VALUES(?, ?)', (plex_guid, tmdb_id, ))
|
||||
cursor.execute('UPDATE guids SET tmdb_id = ? WHERE plex_guid = ?', (tmdb_id, plex_guid))
|
||||
cursor.execute('INSERT OR IGNORE INTO guids(plex_guid) VALUES(?)', (plex_guid, ))
|
||||
cursor.execute('UPDATE guids SET id = ?, updated = ? WHERE plex_guid = ?', (input_id, updated_date.strftime("%Y-%m-%d"), plex_guid))
|
||||
|
|
|
@ -12,28 +12,19 @@ def add_to_radarr(config_path, missing):
|
|||
tmdb.language = config_tmdb.language
|
||||
|
||||
movie = Movie()
|
||||
for m in missing:
|
||||
# Get TMDb ID data from IMDb ID
|
||||
search = movie.external(external_id=str(m), external_source="imdb_id")['movie_results']
|
||||
if len(search) == 1:
|
||||
tmdb_details = search[0]
|
||||
else:
|
||||
print("| --- Unable to match TMDb ID for IMDb ID {}, skipping".format(m))
|
||||
continue
|
||||
|
||||
# Validate TMDb information (very few TMDb entries don't yet have basic information)
|
||||
for tmdb_id in missing:
|
||||
try:
|
||||
tmdb_title = tmdb_details['title']
|
||||
tmdb_id = tmdb_details['id']
|
||||
except IndexError:
|
||||
tmovie = movie.details(tmdb_id)
|
||||
tmdb_title = tmovie.title
|
||||
except AttributeError:
|
||||
print("| --- Unable to fetch necessary TMDb information for IMDb ID {}, skipping".format(m))
|
||||
continue
|
||||
|
||||
# Validate TMDb year (several TMDb entries don't yet have release dates)
|
||||
try:
|
||||
# This might be overly punitive
|
||||
tmdb_year = tmdb_details['release_date'].split("-")[0]
|
||||
except KeyError:
|
||||
tmdb_year = tmovie.release_date.split("-")[0]
|
||||
except AttributeError:
|
||||
print("| --- {} does not have a release date on TMDb yet, skipping".format(tmdb_title))
|
||||
continue
|
||||
|
||||
|
@ -41,7 +32,7 @@ def add_to_radarr(config_path, missing):
|
|||
print("| --- {} does not have a release date on TMDb yet, skipping".format(tmdb_title))
|
||||
continue
|
||||
|
||||
tmdb_poster = "https://image.tmdb.org/t/p/original{}".format(tmdb_details['poster_path'])
|
||||
tmdb_poster = "https://image.tmdb.org/t/p/original{}".format(tmovie.poster_path)
|
||||
|
||||
titleslug = "{} {}".format(tmdb_title, tmdb_year)
|
||||
titleslug = re.sub(r'([^\s\w]|_)+', '', titleslug)
|
||||
|
|
|
@ -1,11 +1,37 @@
|
|||
import config_tools
|
||||
from urllib.parse import urlparse
|
||||
import plex_tools
|
||||
import trakt
|
||||
import os
|
||||
|
||||
|
||||
def trakt_get_movies(config_path, plex, data, method):
|
||||
def trakt_tmdb_to_imdb(config_path, tmdb_id):
|
||||
config_tools.TraktClient(config_path)
|
||||
lookup = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
return lookup.get_key('imdb')
|
||||
else:
|
||||
return None
|
||||
|
||||
def trakt_imdb_to_tmdb(config_path, imdb_id):
|
||||
config_tools.TraktClient(config_path)
|
||||
lookup = trakt.Trakt['search'].lookup(imdb_id, 'imdb', 'movie')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
return lookup.get_key('tmdb')
|
||||
else:
|
||||
return None
|
||||
|
||||
def trakt_tmdb_to_tvdb(config_path, tmdb_id):
|
||||
config_tools.TraktClient(config_path)
|
||||
lookup = trakt.Trakt['search'].lookup(id, 'tmdb', 'show')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
return lookup.get_key('tvdb')
|
||||
else:
|
||||
return None
|
||||
|
||||
def trakt_get_movies(config_path, plex, plex_map, data, method):
|
||||
config_tools.TraktClient(config_path)
|
||||
if method == "trakt_trending":
|
||||
max_items = int(data)
|
||||
|
@ -22,54 +48,19 @@ def trakt_get_movies(config_path, plex, data, method):
|
|||
trakt_url = trakt_url[:-1]
|
||||
trakt_list_path = urlparse(trakt_url).path
|
||||
trakt_list_items = trakt.Trakt[trakt_list_path].items()
|
||||
title_ids = [m.pk[1] for m in trakt_list_items if isinstance(m, trakt.objects.movie.Movie)]
|
||||
title_ids = [int(m.get_key('tmdb')) for m in trakt_list_items if isinstance(m, trakt.objects.movie.Movie)]
|
||||
|
||||
imdb_map = {}
|
||||
plex_tools.create_cache(config_path)
|
||||
if title_ids:
|
||||
for item in plex.Library.all():
|
||||
item_type = urlparse(item.guid).scheme.split('.')[-1]
|
||||
if item_type == 'plex':
|
||||
# Check cache for imdb_id
|
||||
imdb_id = plex_tools.query_cache(config_path, item.guid, 'imdb_id')
|
||||
if not imdb_id:
|
||||
imdb_id, tmdb_id = plex_tools.alt_id_lookup(plex, item)
|
||||
print("| Cache | + | {} | {} | {} | {}".format(item.guid, imdb_id, tmdb_id, item.title))
|
||||
plex_tools.update_cache(config_path, item.guid, imdb_id=imdb_id, tmdb_id=tmdb_id)
|
||||
elif item_type == 'imdb':
|
||||
imdb_id = urlparse(item.guid).netloc
|
||||
elif item_type == 'themoviedb':
|
||||
tmdb_id = urlparse(item.guid).netloc
|
||||
# lookup can sometimes return a list
|
||||
lookup = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'movie')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
imdb_id = lookup.get_key('imdb')
|
||||
else:
|
||||
imdb_id = None
|
||||
else:
|
||||
imdb_id = None
|
||||
print("| {} Movies found on Trakt".format(len(title_ids)))
|
||||
matched = []
|
||||
missing = []
|
||||
for tmdb_id in title_ids:
|
||||
if tmdb_id in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[tmdb_id]))
|
||||
else:
|
||||
missing.append(tmdb_id)
|
||||
return matched, missing
|
||||
|
||||
if imdb_id and imdb_id in title_ids:
|
||||
imdb_map[imdb_id] = item
|
||||
else:
|
||||
imdb_map[item.ratingKey] = item
|
||||
|
||||
matched_imdb_movies = []
|
||||
missing_imdb_movies = []
|
||||
for imdb_id in title_ids:
|
||||
movie = imdb_map.pop(imdb_id, None)
|
||||
if movie:
|
||||
matched_imdb_movies.append(plex.Server.fetchItem(movie.ratingKey))
|
||||
else:
|
||||
missing_imdb_movies.append(imdb_id)
|
||||
|
||||
return matched_imdb_movies, missing_imdb_movies
|
||||
else:
|
||||
# No movies
|
||||
return None, None
|
||||
|
||||
def trakt_get_shows(config_path, plex, data, method):
|
||||
def trakt_get_shows(config_path, plex, plex_map, data, method):
|
||||
config_tools.TraktClient(config_path)
|
||||
if method == "trakt_trending":
|
||||
max_items = int(data)
|
||||
|
@ -87,7 +78,6 @@ def trakt_get_shows(config_path, plex, data, method):
|
|||
trakt_list_path = urlparse(trakt_url).path
|
||||
trakt_list_items = trakt.Trakt[trakt_list_path].items()
|
||||
|
||||
tvdb_map = {}
|
||||
title_ids = []
|
||||
for m in trakt_list_items:
|
||||
if isinstance(m, trakt.objects.show.Show):
|
||||
|
@ -100,40 +90,11 @@ def trakt_get_shows(config_path, plex, data, method):
|
|||
if m.show.pk[1] not in title_ids:
|
||||
title_ids.append(m.show.pk[1])
|
||||
|
||||
if title_ids:
|
||||
for item in plex.Library.all():
|
||||
guid = urlparse(item.guid)
|
||||
item_type = guid.scheme.split('.')[-1]
|
||||
# print('item_type', item, item_type)
|
||||
if item_type == 'thetvdb':
|
||||
tvdb_id = guid.netloc
|
||||
elif item_type == 'themoviedb':
|
||||
tmdb_id = guid.netloc
|
||||
lookup = trakt.Trakt['search'].lookup(tmdb_id, 'tmdb', 'show')
|
||||
if lookup:
|
||||
lookup = lookup[0] if isinstance(lookup, list) else lookup
|
||||
tvdb_id = lookup.get_key('tvdb')
|
||||
else:
|
||||
tvdb_id = None
|
||||
else:
|
||||
tvdb_id = None
|
||||
|
||||
if tvdb_id and tvdb_id in title_ids:
|
||||
tvdb_map[tvdb_id] = item
|
||||
else:
|
||||
tvdb_map[item.ratingKey] = item
|
||||
|
||||
matched_tvdb_shows = []
|
||||
missing_tvdb_shows = []
|
||||
|
||||
for tvdb_id in title_ids:
|
||||
show = tvdb_map.pop(tvdb_id, None)
|
||||
if show:
|
||||
matched_tvdb_shows.append(plex.Server.fetchItem(show.ratingKey))
|
||||
else:
|
||||
missing_tvdb_shows.append(tvdb_id)
|
||||
|
||||
return matched_tvdb_shows, missing_tvdb_shows
|
||||
else:
|
||||
# No shows
|
||||
return None, None
|
||||
matched = []
|
||||
missing = []
|
||||
for tvdb_id in title_ids:
|
||||
if tvdb_id in plex_map:
|
||||
matched.append(plex.Server.fetchItem(plex_map[tvdb_id]))
|
||||
else:
|
||||
missing.append(tvdb_id)
|
||||
return matched, missing
|
||||
|
|
|
@ -64,12 +64,13 @@ plex:
|
|||
token: ###################
|
||||
url: http://192.168.1.5:32400
|
||||
sync_mode: append
|
||||
cache: false
|
||||
radarr:
|
||||
url: http://192.168.1.5:7878/radarr/
|
||||
token: ###########################
|
||||
quality_profile_id: 4
|
||||
root_folder_path: /mnt/user/PlexMedia/movies
|
||||
add_movie: false
|
||||
add_to_radarr: false
|
||||
search_movie: false
|
||||
tmdb:
|
||||
apikey: ############################
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
PyYAML==5.3.1
|
||||
# Less common, pinned
|
||||
PlexAPI==4.2.0
|
||||
tmdbv3api==1.6.2
|
||||
tmdbv3api==1.7.1
|
||||
trakt.py==4.2.0
|
||||
# More common, flexible
|
||||
bs4
|
||||
|
|
Loading…
Reference in a new issue