diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b066b454..80706f02 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,6 +16,4 @@ Please delete options that are not relevant. ## Checklist -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My code was submitted to the nightly branch of the repository. diff --git a/README.md b/README.md index 6f2cc759..3a65bd77 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![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) -[![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) 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. -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 @@ -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. -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 @@ -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 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 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). ## Contributing diff --git a/VERSION b/VERSION index 1ba24376..952616de 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.5-develop105 +1.16.5-develop106 diff --git a/docs/config/libraries.md b/docs/config/libraries.md index eaaf6b11..b1baf0c1 100644 --- a/docs/config/libraries.md +++ b/docs/config/libraries.md @@ -36,6 +36,7 @@ libraries: - git: meisnate12/ShowCharts - git: meisnate12/Networks overlay_path: + - remove_overlays: false - file: config/Overlays.yml TV Shows On Second Plex: 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. +```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 `.yml`. In this example, Plex Meta Manager will look for a file named `TV Shows.yml`. ```yaml diff --git a/docs/home/guides/scheduling.md b/docs/home/guides/scheduling.md index 2a9e96ad..2960f809 100644 --- a/docs/home/guides/scheduling.md +++ b/docs/home/guides/scheduling.md @@ -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). +**To control how individual parts of PMM are scheduled see the [Schedule detail](../../metadata/details/schedule)** + ## Docker Run
diff --git a/docs/index.md b/docs/index.md index adc631f3..d7883e55 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ [![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) -[![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) 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. -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 @@ -66,7 +66,7 @@ git checkout master
```` -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 @@ -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 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 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). ## Contributing diff --git a/docs/metadata/metadata.md b/docs/metadata/metadata.md index 8f17f20a..e98c0701 100644 --- a/docs/metadata/metadata.md +++ b/docs/metadata/metadata.md @@ -122,7 +122,7 @@ dynamic_collections: ## 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. diff --git a/fonts/Comfortaa-Bold.ttf b/fonts/Comfortaa-Bold.ttf new file mode 100644 index 00000000..62389b61 Binary files /dev/null and b/fonts/Comfortaa-Bold.ttf differ diff --git a/fonts/Comfortaa-Light.ttf b/fonts/Comfortaa-Light.ttf new file mode 100644 index 00000000..cfaba16d Binary files /dev/null and b/fonts/Comfortaa-Light.ttf differ diff --git a/fonts/Comfortaa-Medium.ttf b/fonts/Comfortaa-Medium.ttf new file mode 100644 index 00000000..cdde4f3f Binary files /dev/null and b/fonts/Comfortaa-Medium.ttf differ diff --git a/fonts/Comfortaa-Regular.ttf b/fonts/Comfortaa-Regular.ttf new file mode 100644 index 00000000..28627361 Binary files /dev/null and b/fonts/Comfortaa-Regular.ttf differ diff --git a/fonts/Comfortaa-SemiBold.ttf b/fonts/Comfortaa-SemiBold.ttf new file mode 100644 index 00000000..913d7995 Binary files /dev/null and b/fonts/Comfortaa-SemiBold.ttf differ diff --git a/modules/config.py b/modules/config.py index 91342233..56d8c09b 100644 --- a/modules/config.py +++ b/modules/config.py @@ -127,6 +127,8 @@ class ConfigFile: if "settings" in self.data["libraries"][library] and 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") + 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 "add" in self.data["libraries"][library]["radarr"]: 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") if "playlist_sync_to_user" in temp: 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 if "webhooks" in self.data: temp = self.data.pop("webhooks") diff --git a/modules/letterboxd.py b/modules/letterboxd.py index 2f355d03..f4bed556 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -11,27 +11,43 @@ class Letterboxd: def __init__(self, config): self.config = config - def _parse_list(self, list_url, language): - if self.config.trace_mode: - logger.debug(f"URL: {list_url}") + def _parse_page(self, list_url, language): + list_url = list_url.replace("https://letterboxd.com/films", "https://letterboxd.com/films/ajax") 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 = [] for letterboxd_id in letterboxd_ids: 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 - 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 - 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 + comments = response.xpath( + f"//div[@data-film-id='{letterboxd_id}']/parent::li/div[@class='film-detail-content']/div/p/text()") + 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 if ratings: match = re.search("rated-(\\d+)", ratings[0]) if match: 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") - 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) - 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 def _tmdb(self, letterboxd_url, language): @@ -60,13 +76,14 @@ class Letterboxd: dict_methods = {dm.lower(): dm for dm in letterboxd_dict} final = { "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, "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 } if not final["url"].startswith(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") valid_lists.append(final) return valid_lists @@ -74,7 +91,7 @@ class Letterboxd: def get_tmdb_ids(self, method, data, language): if method == "letterboxd_list": 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) if total_items > 0: ids = [] diff --git a/modules/meta.py b/modules/meta.py index cdfa93c0..8f1892ee 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -515,6 +515,13 @@ class MetadataFile(DataFile): 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 {} 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 sync = util.parse("Config", "sync", dynamic, parent=map_name, methods=methods, default=False, datatype="bool") if "sync" in methods else False if "<>" in title_format: diff --git a/modules/overlays.py b/modules/overlays.py index 74184268..458210dc 100644 --- a/modules/overlays.py +++ b/modules/overlays.py @@ -169,8 +169,6 @@ class Overlays: new_poster = Image.open(poster.location if poster else has_original) \ .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: new_poster = new_poster.filter(ImageFilter.GaussianBlur(blur_num)) for over_name in normal_overlays: @@ -178,43 +176,24 @@ class Overlays: if not overlay.has_coordinates(): new_poster = new_poster.resize(overlay.image.size, Image.ANTIALIAS) new_poster.paste(overlay.image, overlay.get_coordinates(image_width, image_height), overlay.image) - if text_names: - for over_name in text_names: - overlay = properties[over_name] - text = over_name[5:-1] - if text in [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "%"]]: - per = text.endswith("%") - rating_type = text[:-1] if per else text - - actual = plex.attribute_translation[rating_type] - if not hasattr(item, actual) or getattr(item, actual) is None: - logger.error(f"Overlay Error: No {rating_type} found") - continue - text = getattr(item, actual) - if self.config.Cache: - self.config.Cache.update_overlay_ratings(item.ratingKey, rating_type, text) - if per: - text = f"{int(text * 10)}%" - x_cord, y_cord = overlay.get_coordinates(image_width, image_height, text=str(text)) - _, _, 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: - drawing.rectangle(cords, fill=overlay.back_color, outline=overlay.back_line_color, - width=overlay.back_line_width) - drawing.text((x_cord, y_cord), str(text), font=overlay.font, fill=overlay.font_color, anchor='lt') + for over_name in text_names: + overlay = properties[over_name] + text = over_name[5:-1] + if text in [f"{a}{s}" for a in ["audience_rating", "critic_rating", "user_rating"] for s in ["", "%"]]: + per = text.endswith("%") + rating_type = text[:-1] if per else text + actual = plex.attribute_translation[rating_type] + if not hasattr(item, actual) or getattr(item, actual) is None: + logger.warning(f"Overlay Warning: No {rating_type} found") + continue + text = getattr(item, actual) + if self.config.Cache: + self.config.Cache.update_overlay_ratings(item.ratingKey, rating_type, text) + if per: + text = f"{int(text * 10)}%" + overlay_image = overlay.get_text_overlay(text, image_width, image_height) + else: + overlay_image = overlay.landscape if isinstance(item, Episode) else overlay.image new_poster.paste(overlay_image, (0, 0), overlay_image) temp = os.path.join(self.library.overlay_folder, f"temp.png") new_poster.save(temp, "PNG") diff --git a/modules/plex.py b/modules/plex.py index 9d604a26..a9dfacca 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -151,6 +151,7 @@ method_alias = { "labels": "label", "collection_minimum": "minimum_items", "playlist_minimum": "minimum_items", + "save_missing": "save_report", "rating": "critic_rating", "show_user_rating": "user_rating", "video_resolution": "resolution", diff --git a/modules/util.py b/modules/util.py index e58484f3..13885332 100644 --- a/modules/util.py +++ b/modules/util.py @@ -835,6 +835,7 @@ class Overlay: self.keys = [] self.updated = False self.image = None + self.landscape = None self.group = None self.weight = 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") 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") + 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: if "|" in self.name: raise Failed(f"Overlay Error: Overlay Name: {self.name} cannot contain '|'") @@ -1013,6 +1024,29 @@ class Overlay: except OSError: 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): output = self.name if self.group: