diff --git a/VERSION b/VERSION index c47e4592..f2600895 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.16.2-develop45 +1.16.2-develop46 diff --git a/docs/metadata/builders/letterboxd.md b/docs/metadata/builders/letterboxd.md index 898dbbf2..fd3bc439 100644 --- a/docs/metadata/builders/letterboxd.md +++ b/docs/metadata/builders/letterboxd.md @@ -35,3 +35,21 @@ collections: collection_order: custom sync_mode: sync ``` + +You can add 3 different filters directly to this builder. + +| Filter Attribute | Description | +|:-----------------|:---------------------------------------------------------------------------------------------------| +| `rating` | **Description:** Search for the specified rating range
**Values:** range of int i.e. `80-100` | +| `year` | **Description:** Search for the specified year range
**Values:** range of int i.e. `1990-1999` | +| `note` | **Description:** Search for the specified value in the note
**Values:** Any String | + +```yaml +collections: + Vulture’s 101 Best Movie Endings From the 90s: + letterboxd_list_details: + url: https://letterboxd.com/brianformo/list/vultures-101-best-movie-endings/ + year: 1990-1999 + collection_order: custom + sync_mode: sync +``` \ No newline at end of file diff --git a/modules/builder.py b/modules/builder.py index 57a02400..ccc147ee 100644 --- a/modules/builder.py +++ b/modules/builder.py @@ -1084,11 +1084,11 @@ class CollectionBuilder: def _letterboxd(self, method_name, method_data): if method_name.startswith("letterboxd_list"): - letterboxd_lists = self.config.Letterboxd.validate_letterboxd_lists(method_data, self.language) + letterboxd_lists = self.config.Letterboxd.validate_letterboxd_lists(self.Type, method_data, self.language) for letterboxd_list in letterboxd_lists: self.builders.append(("letterboxd_list", letterboxd_list)) if method_name.endswith("_details"): - self.summaries[method_name] = self.config.Letterboxd.get_list_description(letterboxd_lists[0], self.language) + self.summaries[method_name] = self.config.Letterboxd.get_list_description(letterboxd_lists[0]["url"], self.language) def _mal(self, method_name, method_data): if method_name == "mal_id": @@ -2575,7 +2575,7 @@ class CollectionBuilder: items = self.library.get_filter_items(search_data[2]) previous = None for i, item in enumerate(items, 0): - if len(self.items) <= i or item.ratingKey != self.items[i]: + if len(self.items) <= i or item.ratingKey != self.items[i].ratingKey: text = f"after {util.item_title(previous)}" if previous else "to the beginning" logger.info(f"Moving {util.item_title(item)} {text}") self.library.moveItem(self.obj, item, previous) diff --git a/modules/letterboxd.py b/modules/letterboxd.py index 04b05557..a2f60916 100644 --- a/modules/letterboxd.py +++ b/modules/letterboxd.py @@ -1,4 +1,4 @@ -import time +import re, time from modules import util from modules.util import Failed @@ -20,6 +20,16 @@ class Letterboxd: for letterboxd_id in letterboxd_ids: slugs = response.xpath(f"//div[@data-film-id='{letterboxd_id}']/@data-film-slug") items.append((letterboxd_id, slugs[0])) + 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()") + 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)) next_url = response.xpath("//a[@class='next']/@href") if len(next_url) > 0: time.sleep(2) @@ -44,27 +54,50 @@ class Letterboxd: descriptions = response.xpath("//meta[@property='og:description']/@content") return descriptions[0] if len(descriptions) > 0 and len(descriptions[0]) > 0 else None - def validate_letterboxd_lists(self, letterboxd_lists, language): + def validate_letterboxd_lists(self, err_type, letterboxd_lists, language): valid_lists = [] - for letterboxd_list in util.get_list(letterboxd_lists, split=False): - list_url = letterboxd_list.strip() - if not list_url.startswith(base_url): - raise Failed(f"Letterboxd Error: {list_url} must begin with: {base_url}") - elif len(self._parse_list(list_url, language)) > 0: - valid_lists.append(list_url) - else: - raise Failed(f"Letterboxd Error: {list_url} failed to parse") + for letterboxd_dict in util.get_list(letterboxd_lists, split=False): + if not isinstance(letterboxd_dict, dict): + letterboxd_dict = {"url": letterboxd_dict} + 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(), + "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): + raise Failed(f"{err_type} Error: {final['url']} failed to parse") + valid_lists.append(final) return valid_lists def get_tmdb_ids(self, method, data, language): if method == "letterboxd_list": logger.info(f"Processing Letterboxd List: {data}") - items = self._parse_list(data, language) + items = self._parse_list(data["url"], language) total_items = len(items) if total_items > 0: ids = [] + filtered_ids = [] for i, item in enumerate(items, 1): - letterboxd_id, slug = item + letterboxd_id, slug, year, note, rating = item + filtered = False + if data["year"]: + start_year, end_year = data["year"].split("-") + if not year or int(end_year) < year or year < int(start_year): + filtered = True + if data["rating"]: + start_rating, end_rating = data["rating"].split("-") + if not rating or int(end_rating) < rating or rating < int(start_rating): + filtered = True + if data["note"]: + if not note or data["note"] not in note: + filtered = True + if filtered: + filtered_ids.append(slug) + continue logger.ghost(f"Finding TMDb ID {i}/{total_items}") tmdb_id = None expired = None @@ -80,6 +113,8 @@ class Letterboxd: self.config.Cache.update_letterboxd_map(expired, letterboxd_id, tmdb_id) ids.append((tmdb_id, "tmdb")) logger.info(f"Processed {total_items} TMDb IDs") + if filtered_ids: + logger.info(f"Filtered: {filtered_ids}") return ids else: raise Failed(f"Letterboxd Error: No List Items found in {data}") diff --git a/modules/meta.py b/modules/meta.py index 29c4f0c7..1fe47b46 100644 --- a/modules/meta.py +++ b/modules/meta.py @@ -521,8 +521,11 @@ class MetadataFile(DataFile): "name": template_name, "value": other_keys, auto_type: other_keys, - "key_name": str(map_name), "key": str(map_name) + "key_name": other_name, "key": "other" } + for k, v in template_variables.items(): + if "other" in v: + template_call[k] = v["other"] col = {"template": template_call, "label": str(map_name)} if test: col["test"] = True diff --git a/modules/plex.py b/modules/plex.py index be67bb22..3725cbd3 100644 --- a/modules/plex.py +++ b/modules/plex.py @@ -860,7 +860,7 @@ class Plex(Library): self.query_data(getattr(obj, f"remove{attr_call}"), _remove) display += f"-{', -'.join(_remove)}" final = f"{obj.title[:25]:<25} | {attr_display} | {display}" if display else display - if do_print: + if do_print and final: logger.info(final) return final