mirror of
https://github.com/meisnate12/Plex-Meta-Manager
synced 2024-11-10 06:54:21 +00:00
[106] small updates
This commit is contained in:
parent
9d94f00ac7
commit
5369dc4fd8
18 changed files with 116 additions and 64 deletions
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
|
@ -16,6 +16,4 @@ Please delete options that are not relevant.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] My code follows the style guidelines of this project
|
- [ ] My code was submitted to the nightly branch of the repository.
|
||||||
- [ ] I have performed a self-review of my own code
|
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
[![Discord](https://img.shields.io/discord/822460010649878528?label=Discord&style=plastic)](https://discord.gg/NfH6mGFuAB)
|
[![Discord](https://img.shields.io/discord/822460010649878528?label=Discord&style=plastic)](https://discord.gg/NfH6mGFuAB)
|
||||||
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/meisnate12/plex-meta-manager?style=plastic)](https://hub.docker.com/r/meisnate12/plex-meta-manager)
|
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/meisnate12/plex-meta-manager?style=plastic)](https://hub.docker.com/r/meisnate12/plex-meta-manager)
|
||||||
[![Read the Docs](https://img.shields.io/readthedocs/plex-meta-manager?style=plastic)](https://metamanager.wiki)
|
[![Wiki](https://img.shields.io/readthedocs/plex-meta-manager?style=plastic)](https://metamanager.wiki)
|
||||||
[![Sponsor or Donate](https://img.shields.io/badge/-Sponsor_or_Donate-blueviolet?style=plastic)](https://github.com/sponsors/meisnate12)
|
[![Sponsor or Donate](https://img.shields.io/badge/-Sponsor_or_Donate-blueviolet?style=plastic)](https://github.com/sponsors/meisnate12)
|
||||||
|
|
||||||
Plex Meta Manager is an open source Python 3 project that has been designed to ease the creation and maintenance of metadata, collections, and playlists within a Plex Media Server. The script is designed to be run continuously and be able to update information based on sources outside your plex environment. Plex Meta Manager supports Movie/TV/Music libraries and Playlists.
|
Plex Meta Manager is an open source Python 3 project that has been designed to ease the creation and maintenance of metadata, collections, and playlists within a Plex Media Server. The script is designed to be run continuously and be able to update information based on sources outside your plex environment. Plex Meta Manager supports Movie/TV/Music libraries and Playlists.
|
||||||
|
@ -21,7 +21,7 @@ Plex Meta Manager is an open source Python 3 project that has been designed to e
|
||||||
|
|
||||||
3. After that you can start updating Metadata and building automatic Collections by creating a [Metadata File](https://metamanager.wiki/en/latest/metadata/metadata.html) for each Library you want to interact with.
|
3. After that you can start updating Metadata and building automatic Collections by creating a [Metadata File](https://metamanager.wiki/en/latest/metadata/metadata.html) for each Library you want to interact with.
|
||||||
|
|
||||||
4. After that, explore the Wiki to see all the different Collection Builders that can be used to create collections.
|
4. After that, explore the [Wiki](https://metamanager.wiki/) to see all the different Collection Builders that can be used to create collections.
|
||||||
|
|
||||||
## Walkthroughs
|
## Walkthroughs
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ If you find steps 1-3 above daunting, there are some walkthroughs available that
|
||||||
|
|
||||||
The [develop](https://github.com/meisnate12/Plex-Meta-Manager/tree/develop) branch has the most updated **documented** fixes and enhancements to Plex Meta Manager. This version is tested and documented to some degree, but it is still an active development branch, so there may be rough edges.
|
The [develop](https://github.com/meisnate12/Plex-Meta-Manager/tree/develop) branch has the most updated **documented** fixes and enhancements to Plex Meta Manager. This version is tested and documented to some degree, but it is still an active development branch, so there may be rough edges.
|
||||||
|
|
||||||
If switching to the develop build, it is recommended to also use the [develop](https://metamanager.wiki/en/develop/) branch of the wiki, which documents any changes made from the Master build.
|
If switching to the develop build, it is recommended to also use the [develop branch of the wiki](https://metamanager.wiki/en/develop/), which documents any changes made from the Master build.
|
||||||
|
|
||||||
### Nightly
|
### Nightly
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ Before posting on GitHub about an enhancement, error, or configuration question
|
||||||
If you are unable to use the [Plex Meta Manager Discord Server](https://discord.gg/NfH6mGFuAB), please follow this guidance:
|
If you are unable to use the [Plex Meta Manager Discord Server](https://discord.gg/NfH6mGFuAB), please follow this guidance:
|
||||||
* If you have an idea for how to enhance Plex Meta Manager please open a new [Feature Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+enhancement&2.feature_request.yml&title=%5BFeature%5D%3A+).
|
* If you have an idea for how to enhance Plex Meta Manager please open a new [Feature Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+enhancement&2.feature_request.yml&title=%5BFeature%5D%3A+).
|
||||||
* If you're getting an Error please update to the latest version and then open a [Bug Report](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+bug&template=1.bug_report.yml&title=%5BBug%5D%3A++) if the error persists.
|
* If you're getting an Error please update to the latest version and then open a [Bug Report](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+bug&template=1.bug_report.yml&title=%5BBug%5D%3A++) if the error persists.
|
||||||
* If you see a mistake/typo with the Plex Meta Manager Wiki or have an idea of how we can improve it please open a [Wiki Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+documentation&template=3.docs_request.yml&title=%5BDocs%5D%3A+)
|
* If you see a mistake/typo with the [Plex Meta Manager Wiki](https://metamanager.wiki/) or have an idea of how we can improve it please open a [Wiki Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+documentation&template=3.docs_request.yml&title=%5BDocs%5D%3A+)
|
||||||
* If you have a metadata configuration query please post in the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions).
|
* If you have a metadata configuration query please post in the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.16.5-develop105
|
1.16.5-develop106
|
||||||
|
|
|
@ -36,6 +36,7 @@ libraries:
|
||||||
- git: meisnate12/ShowCharts
|
- git: meisnate12/ShowCharts
|
||||||
- git: meisnate12/Networks
|
- git: meisnate12/Networks
|
||||||
overlay_path:
|
overlay_path:
|
||||||
|
- remove_overlays: false
|
||||||
- file: config/Overlays.yml
|
- file: config/Overlays.yml
|
||||||
TV Shows On Second Plex:
|
TV Shows On Second Plex:
|
||||||
library_name: TV Shows
|
library_name: TV Shows
|
||||||
|
@ -121,6 +122,15 @@ plex:
|
||||||
|
|
||||||
The `metadata_path` attribute is used to define [Metadata Files](../metadata/metadata) by specifying the path type and path of the files that will be executed against the parent library. See [Path Types](paths) for how to define them.
|
The `metadata_path` attribute is used to define [Metadata Files](../metadata/metadata) by specifying the path type and path of the files that will be executed against the parent library. See [Path Types](paths) for how to define them.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
libraries:
|
||||||
|
TV Shows:
|
||||||
|
metadata_path:
|
||||||
|
- file: config/TV Shows.yml
|
||||||
|
- git: meisnate12/ShowCharts
|
||||||
|
- git: meisnate12/Networks
|
||||||
|
```
|
||||||
|
|
||||||
By default, when `metadata_path` is missing the script will look within the root PMM directory for a metadata file called `<MAPPING_NAME>.yml`. In this example, Plex Meta Manager will look for a file named `TV Shows.yml`.
|
By default, when `metadata_path` is missing the script will look within the root PMM directory for a metadata file called `<MAPPING_NAME>.yml`. In this example, Plex Meta Manager will look for a file named `TV Shows.yml`.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
@ -6,6 +6,8 @@ Whilst it is possible to have `python plex-meta-manager.py` running in an open w
|
||||||
|
|
||||||
Instead, it is recommended to set an automated scheduling service so that Plex Meta Manager can run in the background when scheduled to without any visible impact to the user (other than the Plex libraries and playlists updating).
|
Instead, it is recommended to set an automated scheduling service so that Plex Meta Manager can run in the background when scheduled to without any visible impact to the user (other than the Plex libraries and playlists updating).
|
||||||
|
|
||||||
|
**To control how individual parts of PMM are scheduled see the [Schedule detail](../../metadata/details/schedule)**
|
||||||
|
|
||||||
## Docker Run
|
## Docker Run
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
[![Discord](https://img.shields.io/discord/822460010649878528?label=Discord&style=plastic)](https://discord.gg/NfH6mGFuAB)
|
[![Discord](https://img.shields.io/discord/822460010649878528?label=Discord&style=plastic)](https://discord.gg/NfH6mGFuAB)
|
||||||
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/meisnate12/plex-meta-manager?style=plastic)](https://hub.docker.com/r/meisnate12/plex-meta-manager)
|
[![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/meisnate12/plex-meta-manager?style=plastic)](https://hub.docker.com/r/meisnate12/plex-meta-manager)
|
||||||
[![Read the Docs](https://img.shields.io/readthedocs/plex-meta-manager?style=plastic)](https://metamanager.wiki)
|
[![Wiki](https://img.shields.io/readthedocs/plex-meta-manager?style=plastic)](https://metamanager.wiki)
|
||||||
[![Sponsor or Donate](https://img.shields.io/badge/-Sponsor_or_Donate-blueviolet?style=plastic)](https://github.com/sponsors/meisnate12)
|
[![Sponsor or Donate](https://img.shields.io/badge/-Sponsor_or_Donate-blueviolet?style=plastic)](https://github.com/sponsors/meisnate12)
|
||||||
|
|
||||||
Plex Meta Manager is an open source Python 3 project that has been designed to ease the creation and maintenance of metadata, collections, and playlists within a Plex Media Server. The script is designed to be run continuously and be able to update information based on sources outside your plex environment. Plex Meta Manager supports Movie/TV/Music libraries and Playlists.
|
Plex Meta Manager is an open source Python 3 project that has been designed to ease the creation and maintenance of metadata, collections, and playlists within a Plex Media Server. The script is designed to be run continuously and be able to update information based on sources outside your plex environment. Plex Meta Manager supports Movie/TV/Music libraries and Playlists.
|
||||||
|
@ -21,7 +21,7 @@ Plex Meta Manager is an open source Python 3 project that has been designed to e
|
||||||
|
|
||||||
3. After that you can start updating Metadata and building automatic Collections by creating a [Metadata File](metadata/metadata) for each Library you want to interact with.
|
3. After that you can start updating Metadata and building automatic Collections by creating a [Metadata File](metadata/metadata) for each Library you want to interact with.
|
||||||
|
|
||||||
4. After that, explore the Wiki to see all the different Collection Builders that can be used to create collections.
|
4. After that, explore the [Wiki](https://metamanager.wiki/) to see all the different Collection Builders that can be used to create collections.
|
||||||
|
|
||||||
## Walkthroughs
|
## Walkthroughs
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ git checkout master
|
||||||
<br/>
|
<br/>
|
||||||
````
|
````
|
||||||
|
|
||||||
If switching to the develop build, it is recommended to also use the [develop](https://metamanager.wiki/en/develop/) branch of the wiki, which documents any changes made from the Master build.
|
If switching to the develop build, it is recommended to also use the [develop branch of the wiki](https://metamanager.wiki/en/develop/), which documents any changes made from the Master build.
|
||||||
|
|
||||||
### Nightly
|
### Nightly
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ Before posting on GitHub about an enhancement, error, or configuration question
|
||||||
If you are unable to use the [Plex Meta Manager Discord Server](https://discord.gg/NfH6mGFuAB), please follow this guidance:
|
If you are unable to use the [Plex Meta Manager Discord Server](https://discord.gg/NfH6mGFuAB), please follow this guidance:
|
||||||
* If you have an idea for how to enhance Plex Meta Manager please open a new [Feature Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+enhancement&template=feature_request.md&title=Feature+Request%3A+).
|
* If you have an idea for how to enhance Plex Meta Manager please open a new [Feature Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+enhancement&template=feature_request.md&title=Feature+Request%3A+).
|
||||||
* If you're getting an Error please update to the latest version and then open a [Bug Report](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+bug&template=bug_report.md&title=Bug%3A+) if the error persists.
|
* If you're getting an Error please update to the latest version and then open a [Bug Report](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+bug&template=bug_report.md&title=Bug%3A+) if the error persists.
|
||||||
* If you see a mistake/typo with the Plex Meta Manager Wiki or have an idea of how we can improve it please open a [Wiki Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+documentation&template=3.docs_request.yml&title=%5BDocs%5D%3A+)
|
* If you see a mistake/typo with the [Plex Meta Manager Wiki](https://metamanager.wiki/) or have an idea of how we can improve it please open a [Wiki Request](https://github.com/meisnate12/Plex-Meta-Manager/issues/new?assignees=meisnate12&labels=status%3Anot-yet-viewed%2C+documentation&template=3.docs_request.yml&title=%5BDocs%5D%3A+)
|
||||||
* If you have a metadata configuration query please post in the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions).
|
* If you have a metadata configuration query please post in the [Discussions](https://github.com/meisnate12/Plex-Meta-Manager/discussions).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
|
@ -122,7 +122,7 @@ dynamic_collections:
|
||||||
|
|
||||||
## Metadata Attributes
|
## Metadata Attributes
|
||||||
|
|
||||||
Plex Meta Manager can automatically update items in Plex based on what's defined within the `metadata` attribute.
|
Plex Meta Manager can automatically update items in Plex [Movie](metadata/movie), [Show](metadata/movie), and [Music](metadata/movie) Libraries based on what's defined within the `metadata` attribute.
|
||||||
|
|
||||||
Each metadata requires its own section within the `metadata` attribute. Each item is defined by the mapping name which must be the same as the item name in the library unless an `alt_title` is specified.
|
Each metadata requires its own section within the `metadata` attribute. Each item is defined by the mapping name which must be the same as the item name in the library unless an `alt_title` is specified.
|
||||||
|
|
||||||
|
|
BIN
fonts/Comfortaa-Bold.ttf
Normal file
BIN
fonts/Comfortaa-Bold.ttf
Normal file
Binary file not shown.
BIN
fonts/Comfortaa-Light.ttf
Normal file
BIN
fonts/Comfortaa-Light.ttf
Normal file
Binary file not shown.
BIN
fonts/Comfortaa-Medium.ttf
Normal file
BIN
fonts/Comfortaa-Medium.ttf
Normal file
Binary file not shown.
BIN
fonts/Comfortaa-Regular.ttf
Normal file
BIN
fonts/Comfortaa-Regular.ttf
Normal file
Binary file not shown.
BIN
fonts/Comfortaa-SemiBold.ttf
Normal file
BIN
fonts/Comfortaa-SemiBold.ttf
Normal file
Binary file not shown.
|
@ -127,6 +127,8 @@ class ConfigFile:
|
||||||
if "settings" in self.data["libraries"][library] and self.data["libraries"][library]["settings"]:
|
if "settings" in self.data["libraries"][library] and self.data["libraries"][library]["settings"]:
|
||||||
if "collection_minimum" in self.data["libraries"][library]["settings"]:
|
if "collection_minimum" in self.data["libraries"][library]["settings"]:
|
||||||
self.data["libraries"][library]["settings"]["minimum_items"] = self.data["libraries"][library]["settings"].pop("collection_minimum")
|
self.data["libraries"][library]["settings"]["minimum_items"] = self.data["libraries"][library]["settings"].pop("collection_minimum")
|
||||||
|
if "save_missing" in self.data["libraries"][library]["settings"]:
|
||||||
|
self.data["libraries"][library]["settings"]["save_report"] = self.data["libraries"][library]["settings"].pop("save_missing")
|
||||||
if "radarr" in self.data["libraries"][library] and self.data["libraries"][library]["radarr"]:
|
if "radarr" in self.data["libraries"][library] and self.data["libraries"][library]["radarr"]:
|
||||||
if "add" in self.data["libraries"][library]["radarr"]:
|
if "add" in self.data["libraries"][library]["radarr"]:
|
||||||
self.data["libraries"][library]["radarr"]["add_missing"] = self.data["libraries"][library]["radarr"].pop("add")
|
self.data["libraries"][library]["radarr"]["add_missing"] = self.data["libraries"][library]["radarr"].pop("add")
|
||||||
|
@ -156,6 +158,8 @@ class ConfigFile:
|
||||||
temp["minimum_items"] = temp.pop("collection_minimum")
|
temp["minimum_items"] = temp.pop("collection_minimum")
|
||||||
if "playlist_sync_to_user" in temp:
|
if "playlist_sync_to_user" in temp:
|
||||||
temp["playlist_sync_to_users"] = temp.pop("playlist_sync_to_user")
|
temp["playlist_sync_to_users"] = temp.pop("playlist_sync_to_user")
|
||||||
|
if "save_missing" in temp:
|
||||||
|
temp["save_report"] = temp.pop("save_missing")
|
||||||
self.data["settings"] = temp
|
self.data["settings"] = temp
|
||||||
if "webhooks" in self.data:
|
if "webhooks" in self.data:
|
||||||
temp = self.data.pop("webhooks")
|
temp = self.data.pop("webhooks")
|
||||||
|
|
|
@ -11,27 +11,43 @@ class Letterboxd:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def _parse_list(self, list_url, language):
|
def _parse_page(self, list_url, language):
|
||||||
if self.config.trace_mode:
|
list_url = list_url.replace("https://letterboxd.com/films", "https://letterboxd.com/films/ajax")
|
||||||
logger.debug(f"URL: {list_url}")
|
|
||||||
response = self.config.get_html(list_url, headers=util.header(language))
|
response = self.config.get_html(list_url, headers=util.header(language))
|
||||||
letterboxd_ids = response.xpath("//li[contains(@class, 'poster-container') or contains(@class, 'film-detail')]/div/@data-film-id")
|
letterboxd_ids = response.xpath(
|
||||||
|
"//li[contains(@class, 'poster-container') or contains(@class, 'film-detail')]/div/@data-film-id")
|
||||||
items = []
|
items = []
|
||||||
for letterboxd_id in letterboxd_ids:
|
for letterboxd_id in letterboxd_ids:
|
||||||
slugs = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/@data-film-slug")
|
slugs = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/@data-film-slug")
|
||||||
notes = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']/div/p/text()") if list_url.endswith(("/detail", "/detail/")) else None
|
comments = response.xpath(
|
||||||
ratings = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']//span[contains(@class, 'rating')]/@class") if list_url.endswith(("/detail", "/detail/")) else None
|
f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']/div/p/text()")
|
||||||
years = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']/h2/small/a/text()") if list_url.endswith(("/detail", "/detail/")) else None
|
ratings = response.xpath(
|
||||||
|
f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']//span[contains(@class, 'rating')]/@class")
|
||||||
|
years = response.xpath(
|
||||||
|
f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']/h2/small/a/text()")
|
||||||
rating = None
|
rating = None
|
||||||
if ratings:
|
if ratings:
|
||||||
match = re.search("rated-(\\d+)", ratings[0])
|
match = re.search("rated-(\\d+)", ratings[0])
|
||||||
if match:
|
if match:
|
||||||
rating = int(match.group(1))
|
rating = int(match.group(1))
|
||||||
items.append((letterboxd_id, slugs[0], int(years[0]) if years else None, notes[0] if notes else None, rating))
|
items.append((letterboxd_id, slugs[0],
|
||||||
|
int(years[0]) if years else None,
|
||||||
|
comments[0] if comments else None,
|
||||||
|
rating
|
||||||
|
))
|
||||||
next_url = response.xpath("//a[@class='next']/@href")
|
next_url = response.xpath("//a[@class='next']/@href")
|
||||||
if len(next_url) > 0:
|
return items, next_url
|
||||||
|
|
||||||
|
def _parse_list(self, list_url, limit, language):
|
||||||
|
if self.config.trace_mode:
|
||||||
|
logger.debug(f"URL: {list_url}")
|
||||||
|
items, next_url = self._parse_page(list_url, language)
|
||||||
|
while len(next_url) > 0:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
items.extend(self._parse_list(f"{base_url}{next_url[0]}", language))
|
new_items, next_url = self._parse_page(f"{base_url}{next_url[0]}", language)
|
||||||
|
items.extend(new_items)
|
||||||
|
if limit and len(items) >= limit:
|
||||||
|
return items[:limit]
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def _tmdb(self, letterboxd_url, language):
|
def _tmdb(self, letterboxd_url, language):
|
||||||
|
@ -60,13 +76,14 @@ class Letterboxd:
|
||||||
dict_methods = {dm.lower(): dm for dm in letterboxd_dict}
|
dict_methods = {dm.lower(): dm for dm in letterboxd_dict}
|
||||||
final = {
|
final = {
|
||||||
"url": util.parse(err_type, "url", letterboxd_dict, methods=dict_methods, parent="letterboxd_list").strip(),
|
"url": util.parse(err_type, "url", letterboxd_dict, methods=dict_methods, parent="letterboxd_list").strip(),
|
||||||
|
"limit": util.parse(err_type, "limit", letterboxd_dict, methods=dict_methods, datatype="int", parent="letterboxd_list", default=0) if "limit" in dict_methods else 0,
|
||||||
"note": util.parse(err_type, "note", letterboxd_dict, methods=dict_methods, parent="letterboxd_list") if "note" in dict_methods else None,
|
"note": util.parse(err_type, "note", letterboxd_dict, methods=dict_methods, parent="letterboxd_list") if "note" in dict_methods else None,
|
||||||
"rating": util.parse(err_type, "rating", letterboxd_dict, methods=dict_methods, datatype="int", parent="letterboxd_list", maximum=100, range_split="-") if "rating" in dict_methods else None,
|
"rating": util.parse(err_type, "rating", letterboxd_dict, methods=dict_methods, datatype="int", parent="letterboxd_list", maximum=100, range_split="-") if "rating" in dict_methods else None,
|
||||||
"year": util.parse(err_type, "year", letterboxd_dict, methods=dict_methods, datatype="int", parent="letterboxd_list", minimum=1000, maximum=3000, range_split="-") if "year" in dict_methods else None
|
"year": util.parse(err_type, "year", letterboxd_dict, methods=dict_methods, datatype="int", parent="letterboxd_list", minimum=1000, maximum=3000, range_split="-") if "year" in dict_methods else None
|
||||||
}
|
}
|
||||||
if not final["url"].startswith(base_url):
|
if not final["url"].startswith(base_url):
|
||||||
raise Failed(f"{err_type} Error: {final['url']} must begin with: {base_url}")
|
raise Failed(f"{err_type} Error: {final['url']} must begin with: {base_url}")
|
||||||
elif not self._parse_list(final["url"], language):
|
elif not self._parse_page(final["url"], language)[0]:
|
||||||
raise Failed(f"{err_type} Error: {final['url']} failed to parse")
|
raise Failed(f"{err_type} Error: {final['url']} failed to parse")
|
||||||
valid_lists.append(final)
|
valid_lists.append(final)
|
||||||
return valid_lists
|
return valid_lists
|
||||||
|
@ -74,7 +91,7 @@ class Letterboxd:
|
||||||
def get_tmdb_ids(self, method, data, language):
|
def get_tmdb_ids(self, method, data, language):
|
||||||
if method == "letterboxd_list":
|
if method == "letterboxd_list":
|
||||||
logger.info(f"Processing Letterboxd List: {data}")
|
logger.info(f"Processing Letterboxd List: {data}")
|
||||||
items = self._parse_list(data["url"], language)
|
items = self._parse_list(data["url"], data["limit"], language)
|
||||||
total_items = len(items)
|
total_items = len(items)
|
||||||
if total_items > 0:
|
if total_items > 0:
|
||||||
ids = []
|
ids = []
|
||||||
|
|
|
@ -515,6 +515,13 @@ class MetadataFile(DataFile):
|
||||||
methods["key_name_override"] = methods.pop("pre_format_override")
|
methods["key_name_override"] = methods.pop("pre_format_override")
|
||||||
title_override = util.parse("Config", "title_override", dynamic, parent=map_name, methods=methods, datatype="strdict") if "title_override" in methods else {}
|
title_override = util.parse("Config", "title_override", dynamic, parent=map_name, methods=methods, datatype="strdict") if "title_override" in methods else {}
|
||||||
key_name_override = util.parse("Config", "key_name_override", dynamic, parent=map_name, methods=methods, datatype="strdict") if "key_name_override" in methods else {}
|
key_name_override = util.parse("Config", "key_name_override", dynamic, parent=map_name, methods=methods, datatype="strdict") if "key_name_override" in methods else {}
|
||||||
|
test_override = []
|
||||||
|
for k, v in key_name_override.items():
|
||||||
|
if v in test_override:
|
||||||
|
logger.warning(f"Config Error: {v} can only be used once skipping {k}: {v}")
|
||||||
|
key_name_override.pop(k)
|
||||||
|
else:
|
||||||
|
test_override.append(v)
|
||||||
test = util.parse("Config", "test", dynamic, parent=map_name, methods=methods, default=False, datatype="bool") if "test" in methods else False
|
test = util.parse("Config", "test", dynamic, parent=map_name, methods=methods, default=False, datatype="bool") if "test" in methods else False
|
||||||
sync = util.parse("Config", "sync", dynamic, parent=map_name, methods=methods, default=False, datatype="bool") if "sync" in methods else False
|
sync = util.parse("Config", "sync", dynamic, parent=map_name, methods=methods, default=False, datatype="bool") if "sync" in methods else False
|
||||||
if "<<library_type>>" in title_format:
|
if "<<library_type>>" in title_format:
|
||||||
|
|
|
@ -169,8 +169,6 @@ class Overlays:
|
||||||
|
|
||||||
new_poster = Image.open(poster.location if poster else has_original) \
|
new_poster = Image.open(poster.location if poster else has_original) \
|
||||||
.convert("RGB").resize((image_width, image_height), Image.ANTIALIAS)
|
.convert("RGB").resize((image_width, image_height), Image.ANTIALIAS)
|
||||||
overlay_image = Image.new('RGBA', new_poster.size, (255, 255, 255, 0))
|
|
||||||
drawing = ImageDraw.Draw(overlay_image)
|
|
||||||
if blur_num > 0:
|
if blur_num > 0:
|
||||||
new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num))
|
new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num))
|
||||||
for over_name in normal_overlays:
|
for over_name in normal_overlays:
|
||||||
|
@ -178,43 +176,24 @@ class Overlays:
|
||||||
if not overlay.has_coordinates():
|
if not overlay.has_coordinates():
|
||||||
new_poster = new_poster.resize(overlay.image.size, Image.ANTIALIAS)
|
new_poster = new_poster.resize(overlay.image.size, Image.ANTIALIAS)
|
||||||
new_poster.paste(overlay.image, overlay.get_coordinates(image_width, image_height), overlay.image)
|
new_poster.paste(overlay.image, overlay.get_coordinates(image_width, image_height), overlay.image)
|
||||||
if text_names:
|
|
||||||
for over_name in text_names:
|
for over_name in text_names:
|
||||||
overlay = properties[over_name]
|
overlay = properties[over_name]
|
||||||
text = over_name[5:-1]
|
text = over_name[5:-1]
|
||||||
if text in [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "%"]]:
|
if text in [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "%"]]:
|
||||||
per = text.endswith("%")
|
per = text.endswith("%")
|
||||||
rating_type = text[:-1] if per else text
|
rating_type = text[:-1] if per else text
|
||||||
|
|
||||||
actual = plex.attribute_translation[rating_type]
|
actual = plex.attribute_translation[rating_type]
|
||||||
if not hasattr(item, actual) or getattr(item, actual) is None:
|
if not hasattr(item, actual) or getattr(item, actual) is None:
|
||||||
logger.error(f"Overlay Error: No {rating_type} found")
|
logger.warning(f"Overlay Warning: No {rating_type} found")
|
||||||
continue
|
continue
|
||||||
text = getattr(item, actual)
|
text = getattr(item, actual)
|
||||||
if self.config.Cache:
|
if self.config.Cache:
|
||||||
self.config.Cache.update_overlay_ratings(item.ratingKey, rating_type, text)
|
self.config.Cache.update_overlay_ratings(item.ratingKey, rating_type, text)
|
||||||
if per:
|
if per:
|
||||||
text = f"{int(text * 10)}%"
|
text = f"{int(text * 10)}%"
|
||||||
x_cord, y_cord = overlay.get_coordinates(image_width, image_height, text=str(text))
|
overlay_image = overlay.get_text_overlay(text, image_width, image_height)
|
||||||
_, _, width, height = overlay.get_text_size(str(text))
|
|
||||||
if overlay.back_color:
|
|
||||||
cords = (
|
|
||||||
x_cord - overlay.back_padding,
|
|
||||||
y_cord - overlay.back_padding,
|
|
||||||
x_cord + (overlay.back_width if overlay.back_width else width) + overlay.back_padding,
|
|
||||||
y_cord + (overlay.back_height if overlay.back_height else height) + overlay.back_padding
|
|
||||||
)
|
|
||||||
if overlay.back_width:
|
|
||||||
x_cord = int(x_cord + (overlay.back_width - width) / 2)
|
|
||||||
y_cord = int(y_cord + (overlay.back_height - height) / 2)
|
|
||||||
|
|
||||||
if overlay.back_radius:
|
|
||||||
drawing.rounded_rectangle(cords, fill=overlay.back_color, outline=overlay.back_line_color,
|
|
||||||
width=overlay.back_line_width, radius=overlay.back_radius)
|
|
||||||
else:
|
else:
|
||||||
drawing.rectangle(cords, fill=overlay.back_color, outline=overlay.back_line_color,
|
overlay_image = overlay.landscape if isinstance(item, Episode) else overlay.image
|
||||||
width=overlay.back_line_width)
|
|
||||||
drawing.text((x_cord, y_cord), str(text), font=overlay.font, fill=overlay.font_color, anchor='lt')
|
|
||||||
new_poster.paste(overlay_image, (0, 0), overlay_image)
|
new_poster.paste(overlay_image, (0, 0), overlay_image)
|
||||||
temp = os.path.join(self.library.overlay_folder, f"temp.png")
|
temp = os.path.join(self.library.overlay_folder, f"temp.png")
|
||||||
new_poster.save(temp, "PNG")
|
new_poster.save(temp, "PNG")
|
||||||
|
|
|
@ -151,6 +151,7 @@ method_alias = {
|
||||||
"labels": "label",
|
"labels": "label",
|
||||||
"collection_minimum": "minimum_items",
|
"collection_minimum": "minimum_items",
|
||||||
"playlist_minimum": "minimum_items",
|
"playlist_minimum": "minimum_items",
|
||||||
|
"save_missing": "save_report",
|
||||||
"rating": "critic_rating",
|
"rating": "critic_rating",
|
||||||
"show_user_rating": "user_rating",
|
"show_user_rating": "user_rating",
|
||||||
"video_resolution": "resolution",
|
"video_resolution": "resolution",
|
||||||
|
|
|
@ -835,6 +835,7 @@ class Overlay:
|
||||||
self.keys = []
|
self.keys = []
|
||||||
self.updated = False
|
self.updated = False
|
||||||
self.image = None
|
self.image = None
|
||||||
|
self.landscape = None
|
||||||
self.group = None
|
self.group = None
|
||||||
self.weight = None
|
self.weight = None
|
||||||
self.path = None
|
self.path = None
|
||||||
|
@ -993,6 +994,16 @@ class Overlay:
|
||||||
self.back_height = parse("Overlay", "back_height", self.data["back_height"], datatype="int", parent="overlay")
|
self.back_height = parse("Overlay", "back_height", self.data["back_height"], datatype="int", parent="overlay")
|
||||||
if (self.back_width and not self.back_height) or (self.back_height and not self.back_width):
|
if (self.back_width and not self.back_height) or (self.back_height and not self.back_width):
|
||||||
raise Failed(f"Overlay Error: overlay attributes back_width and back_height must be used together")
|
raise Failed(f"Overlay Error: overlay attributes back_width and back_height must be used together")
|
||||||
|
text = self.name[5:-1]
|
||||||
|
if text not in [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "%"]]:
|
||||||
|
self.image = self.get_text_overlay(text, 1000, 1500)
|
||||||
|
self.landscape = self.get_text_overlay(text, 1920, 1080)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if "|" in self.name:
|
if "|" in self.name:
|
||||||
raise Failed(f"Overlay Error: Overlay Name: {self.name} cannot contain '|'")
|
raise Failed(f"Overlay Error: Overlay Name: {self.name} cannot contain '|'")
|
||||||
|
@ -1013,6 +1024,29 @@ class Overlay:
|
||||||
except OSError:
|
except OSError:
|
||||||
raise Failed(f"Overlay Error: overlay image {self.path} failed to load")
|
raise Failed(f"Overlay Error: overlay image {self.path} failed to load")
|
||||||
|
|
||||||
|
def get_text_overlay(self, text, image_width, image_height):
|
||||||
|
overlay_image = Image.new("RGBA", (image_width, image_height), (255, 255, 255, 0))
|
||||||
|
drawing = ImageDraw.Draw(overlay_image)
|
||||||
|
x_cord, y_cord = self.get_coordinates(image_width, image_height, text=str(text))
|
||||||
|
_, _, width, height = self.get_text_size(str(text))
|
||||||
|
if self.back_color:
|
||||||
|
cords = (
|
||||||
|
x_cord - self.back_padding,
|
||||||
|
y_cord - self.back_padding,
|
||||||
|
x_cord + (self.back_width if self.back_width else width) + self.back_padding,
|
||||||
|
y_cord + (self.back_height if self.back_height else height) + self.back_padding
|
||||||
|
)
|
||||||
|
if self.back_width:
|
||||||
|
x_cord = x_cord + (self.back_width - width) // 2
|
||||||
|
y_cord = y_cord + (self.back_height - height) // 2
|
||||||
|
|
||||||
|
if self.back_radius:
|
||||||
|
drawing.rounded_rectangle(cords, fill=self.back_color, outline=self.back_line_color, width=self.back_line_width, radius=self.back_radius)
|
||||||
|
else:
|
||||||
|
drawing.rectangle(cords, fill=self.back_color, outline=self.back_line_color, width=self.back_line_width)
|
||||||
|
drawing.text((x_cord, y_cord), str(text), font=self.font, fill=self.font_color, anchor="lt")
|
||||||
|
return overlay_image
|
||||||
|
|
||||||
def get_overlay_compare(self):
|
def get_overlay_compare(self):
|
||||||
output = self.name
|
output = self.name
|
||||||
if self.group:
|
if self.group:
|
||||||
|
|
Loading…
Reference in a new issue