From 9a78dc2540bc950da366b92819140a6f7f0df9b2 Mon Sep 17 00:00:00 2001 From: sHa Date: Sun, 28 Dec 2025 09:02:42 +0000 Subject: [PATCH] Add TMDBExtractor class for movie information extraction - Implement TMDBExtractor to fetch movie details from The Movie Database (TMDB) API. - Include caching mechanism for API responses to reduce redundant requests. - Extract movie database ID from filenames using regex patterns. - Add methods to extract TMDB ID, title, original title, release year, and TMDB URL. - Create secrets.py to store TMDB API key and access token. - Add a sample MKV file for testing purposes. --- dist/renamer-0.4.1-py3-none-any.whl | Bin 0 -> 37770 bytes pyproject.toml | 3 +- renamer/extractors/default_extractor.py | 11 +- renamer/extractors/extractor.py | 4 + renamer/extractors/tmdb_extractor.py | 238 ++++++++++++++++++++++++ renamer/formatters/formatter.py | 1 + renamer/formatters/media_formatter.py | 49 ++++- renamer/formatters/text_formatter.py | 17 +- renamer/secrets.py | 2 + uv.lock | 119 +++++++++++- 10 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 dist/renamer-0.4.1-py3-none-any.whl create mode 100644 renamer/extractors/tmdb_extractor.py create mode 100644 renamer/secrets.py diff --git a/dist/renamer-0.4.1-py3-none-any.whl b/dist/renamer-0.4.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..101ed194cf4b0a1555593db80854673465e58f16 GIT binary patch literal 37770 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 literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index 96a7396..4178b8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "renamer" -version = "0.3.3" +version = "0.4.1" description = "Terminal-based media file renamer and metadata viewer" readme = "README.md" requires-python = ">=3.11" @@ -11,6 +11,7 @@ dependencies = [ "pymediainfo>=6.0.0", "pytest>=7.0.0", "langcodes>=3.5.1", + "requests>=2.31.0", ] [project.scripts] diff --git a/renamer/extractors/default_extractor.py b/renamer/extractors/default_extractor.py index dddc252..e9c7223 100644 --- a/renamer/extractors/default_extractor.py +++ b/renamer/extractors/default_extractor.py @@ -53,4 +53,13 @@ class DefaultExtractor: return [] def extract_subtitle_tracks(self): - return [] \ No newline at end of file + return [] + + def extract_tmdb_url(self): + return None + + def extract_tmdb_id(self): + return None + + def extract_original_title(self): + return None \ No newline at end of file diff --git a/renamer/extractors/extractor.py b/renamer/extractors/extractor.py index 91a0d03..b7afec8 100644 --- a/renamer/extractors/extractor.py +++ b/renamer/extractors/extractor.py @@ -3,6 +3,7 @@ from .filename_extractor import FilenameExtractor from .metadata_extractor import MetadataExtractor from .mediainfo_extractor import MediaInfoExtractor from .fileinfo_extractor import FileInfoExtractor +from .tmdb_extractor import TMDBExtractor from .default_extractor import DefaultExtractor @@ -14,6 +15,7 @@ class MediaExtractor: self.metadata_extractor = MetadataExtractor(file_path) self.mediainfo_extractor = MediaInfoExtractor(file_path) self.fileinfo_extractor = FileInfoExtractor(file_path) + self.tmdb_extractor = TMDBExtractor(file_path) self.default_extractor = DefaultExtractor() # Extractor mapping @@ -22,6 +24,7 @@ class MediaExtractor: "Filename": self.filename_extractor, "MediaInfo": self.mediainfo_extractor, "FileInfo": self.fileinfo_extractor, + "TMDB": self.tmdb_extractor, "Default": self.default_extractor, } @@ -74,6 +77,7 @@ class MediaExtractor: }, "movie_db": { "sources": [ + ("TMDB", "extract_movie_db"), ("Filename", "extract_movie_db"), ("Default", "extract_movie_db"), ], diff --git a/renamer/extractors/tmdb_extractor.py b/renamer/extractors/tmdb_extractor.py new file mode 100644 index 0000000..1392ab9 --- /dev/null +++ b/renamer/extractors/tmdb_extractor.py @@ -0,0 +1,238 @@ +import json +import os +import time +import hashlib +import requests +from pathlib import Path +from typing import Dict, Optional, Tuple, Any +from ..secrets import TMDB_API_KEY, TMDB_ACCESS_TOKEN + + +class TMDBExtractor: + """Class to extract TMDB movie information""" + + CACHE_DIR = Path.home() / ".cache" / "renamer" / "tmdb" + CACHE_DURATION = 5 * 24 * 60 * 60 # 5 days in seconds + + def __init__(self, file_path: Path): + self.file_path = file_path + self._movie_db_info = None + + def _get_cache_file_path(self, cache_key: str) -> Path: + """Get the cache file path for a given cache key""" + # Create a hash of the cache key for the filename + key_hash = hashlib.md5(cache_key.encode('utf-8')).hexdigest() + return self.CACHE_DIR / f"{key_hash}.json" + + def _is_cache_valid(self, cache_key: str) -> bool: + """Check if cache entry is still valid""" + cache_file = self._get_cache_file_path(cache_key) + if not cache_file.exists(): + return False + + try: + # Check file modification time + stat = cache_file.stat() + return time.time() - stat.st_mtime < self.CACHE_DURATION + except OSError: + return False + + def _get_cached_data(self, cache_key: str) -> Optional[Dict[str, Any]]: + """Get data from cache if valid""" + if not self._is_cache_valid(cache_key): + return None + + cache_file = self._get_cache_file_path(cache_key) + try: + with open(cache_file, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, OSError): + return None + + def _set_cached_data(self, cache_key: str, data: Dict[str, Any]): + """Store data in cache""" + try: + self.CACHE_DIR.mkdir(parents=True, exist_ok=True) + cache_file = self._get_cache_file_path(cache_key) + with open(cache_file, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + except OSError: + pass # Silently fail if we can't save cache + + def _make_tmdb_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]: + """Make a request to TMDB API""" + base_url = "https://api.themoviedb.org/3" + url = f"{base_url}{endpoint}" + + headers = { + "Authorization": f"Bearer {TMDB_ACCESS_TOKEN}", + "accept": "application/json" + } + + if params is None: + params = {} + params['api_key'] = TMDB_API_KEY + + try: + response = requests.get(url, headers=headers, params=params, timeout=10) + response.raise_for_status() + return response.json() + except (requests.RequestException, ValueError): + return None + + def _search_movie_by_title_year(self, title: str, year: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Search for movie by title and optionally year""" + cache_key = f"search_{title}_{year or 'no_year'}" + + # Check cache first + cached = self._get_cached_data(cache_key) + if cached is not None: + return cached + + params = {'query': title} + if year: + params['year'] = year + + result = self._make_tmdb_request('/search/movie', params) + if result and result.get('results'): + movies = result['results'] + + # If year provided, try exact match first + if year: + exact_matches = [m for m in movies if str(m.get('release_date', ''))[:4] == year] + if exact_matches: + movie = exact_matches[0] + else: + # Try ±1 year + year_int = int(year) + close_matches = [m for m in movies if abs(int(str(m.get('release_date', ''))[:4]) - year_int) <= 1] + if close_matches: + movie = close_matches[0] + else: + movie = movies[0] # Fallback to first result + else: + movie = movies[0] # No year filter, take first result + + # Cache the result + self._set_cached_data(cache_key, movie) + return movie + + return None + + def _get_movie_details(self, movie_id: int) -> Optional[Dict[str, Any]]: + """Get detailed movie information by ID""" + cache_key = f"movie_{movie_id}" + + # Check cache first + cached = self._get_cached_data(cache_key) + if cached is not None: + return cached + + result = self._make_tmdb_request(f'/movie/{movie_id}') + if result: + # Cache the result + self._set_cached_data(cache_key, result) + return result + + return None + + def _extract_movie_db_from_filename(self) -> Optional[Tuple[str, str]]: + """Extract movie database ID from filename (similar to FilenameExtractor.extract_movie_db)""" + import re + from ..constants import MOVIE_DB_DICT + + file_name = self.file_path.name + + # Look for patterns at the end of filename in brackets or braces + # Patterns: [tmdbid-123] {imdb-tt123} [imdbid-tt123] etc. + + # Match patterns like [tmdbid-123456] or {imdb-tt1234567} + pattern = r'[\[\{]([a-zA-Z]+(?:id)?)[-\s]*([a-zA-Z0-9]+)[\]\}]' + matches = re.findall(pattern, file_name) + + if matches: + # Take the last match (closest to end of filename) + db_type, db_id = matches[-1] + + # Normalize database type + db_type_lower = db_type.lower() + for db_key, db_info in MOVIE_DB_DICT.items(): + if any(db_type_lower.startswith(pattern.rstrip('-')) for pattern in db_info['patterns']): + return (db_key, db_id) + + return None + + def _get_movie_info(self) -> Optional[Dict[str, Any]]: + """Get movie information from TMDB""" + if self._movie_db_info is not None: + return self._movie_db_info + + # First, check if we have a TMDB ID in the filename + movie_db = self._extract_movie_db_from_filename() + if movie_db and movie_db[0] == 'tmdb': + try: + movie_id = int(movie_db[1]) + movie_data = self._get_movie_details(movie_id) + if movie_data: + self._movie_db_info = movie_data + return movie_data + except ValueError: + pass # Invalid ID format + + # If no TMDB ID or failed to get details, try searching by title/year + # We need title and year from filename extraction + from .filename_extractor import FilenameExtractor + filename_extractor = FilenameExtractor(self.file_path) + title = filename_extractor.extract_title() + year = filename_extractor.extract_year() + + if title: + movie_data = self._search_movie_by_title_year(title, year) + if movie_data: + self._movie_db_info = movie_data + return movie_data + + self._movie_db_info = None + return None + + def extract_tmdb_id(self) -> Optional[str]: + """Extract TMDB ID""" + movie_info = self._get_movie_info() + if movie_info: + return str(movie_info.get('id')) + return None + + def extract_title(self) -> Optional[str]: + """Extract TMDB title""" + movie_info = self._get_movie_info() + if movie_info: + return movie_info.get('title') + return None + + def extract_original_title(self) -> Optional[str]: + """Extract TMDB original title""" + movie_info = self._get_movie_info() + if movie_info: + return movie_info.get('original_title') + return None + + def extract_year(self) -> Optional[str]: + """Extract TMDB release year""" + movie_info = self._get_movie_info() + if movie_info and movie_info.get('release_date'): + return movie_info['release_date'][:4] + return None + + def extract_tmdb_url(self) -> Optional[str]: + """Extract TMDB movie URL""" + movie_id = self.extract_tmdb_id() + if movie_id: + return f"https://www.themoviedb.org/movie/{movie_id}" + return None + + def extract_movie_db(self) -> Optional[Tuple[str, str]]: + """Extract TMDB database info as (name, id) tuple""" + movie_id = self.extract_tmdb_id() + if movie_id: + return ("tmdb", movie_id) + return None diff --git a/renamer/formatters/formatter.py b/renamer/formatters/formatter.py index af76133..9cece64 100644 --- a/renamer/formatters/formatter.py +++ b/renamer/formatters/formatter.py @@ -64,6 +64,7 @@ class FormatterApplier: TextFormatter.blue, TextFormatter.grey, TextFormatter.dim, + TextFormatter.format_url, ] @staticmethod diff --git a/renamer/formatters/media_formatter.py b/renamer/formatters/media_formatter.py index d0262a2..a6c3844 100644 --- a/renamer/formatters/media_formatter.py +++ b/renamer/formatters/media_formatter.py @@ -22,6 +22,7 @@ class MediaFormatter: sections = [ self.file_info(), self.selected_data(), + self.tmdb_data(), self.tracks_info(), self.filename_extracted_data(), self.metadata_extracted_data(), @@ -76,6 +77,52 @@ class MediaFormatter: ] return FormatterApplier.format_data_items(data) + def tmdb_data(self) -> list[str]: + """Return formatted TMDB data""" + data = [ + { + "label": "TMDB Data", + "label_formatters": [TextFormatter.bold, TextFormatter.uppercase], + }, + { + "label": "ID", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("tmdb_id", "TMDB") or "", + "value_formatters": [TextFormatter.yellow], + }, + { + "label": "Title", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("title", "TMDB") or "", + "value_formatters": [TextFormatter.yellow], + }, + { + "label": "Original Title", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("original_title", "TMDB") or "", + "value_formatters": [TextFormatter.yellow], + }, + { + "label": "Year", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("year", "TMDB") or "", + "value_formatters": [TextFormatter.yellow,], + }, + { + "label": "Database Info", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("movie_db", "TMDB") or "", + "value_formatters": [SpecialInfoFormatter.format_database_info, TextFormatter.yellow], + }, + { + "label": "URL", + "label_formatters": [TextFormatter.bold, TextFormatter.blue], + "value": self.extractor.get("tmdb_url", "TMDB") or "", + "value_formatters": [TextFormatter.format_url], + } + ] + return FormatterApplier.format_data_items(data) + def tracks_info(self) -> list[str]: """Return formatted tracks information""" data = [ @@ -350,7 +397,7 @@ class MediaFormatter: "value_formatters": [TextFormatter.yellow], }, { - "label": "DBid", + "label": "Database Info", "label_formatters": [TextFormatter.bold, TextFormatter.blue], "value": self.extractor.get("movie_db") or "", "value_formatters": [SpecialInfoFormatter.format_database_info, TextFormatter.yellow], diff --git a/renamer/formatters/text_formatter.py b/renamer/formatters/text_formatter.py index 7e17ae3..7b4109c 100644 --- a/renamer/formatters/text_formatter.py +++ b/renamer/formatters/text_formatter.py @@ -100,4 +100,19 @@ class TextFormatter: @staticmethod def dim(text: str) -> str: - return f"[dim]{text}[/dim]" \ No newline at end of file + return f"[dim]{text}[/dim]" + + @staticmethod + def link(url: str, text: str | None = None) -> str: + """Create a clickable link. If text is None, uses the URL as text.""" + if text is None: + text = url + return f"[link={url}]{text}[/link]" + + @staticmethod + def format_url(url: str) -> str: + """Format a URL as a clickable link using OSC 8 if it's a valid URL, otherwise return as-is.""" + if url and url != "" and url.startswith("http"): + # Use OSC 8 hyperlink escape sequence for clickable links + return f"\x1b]8;;{url}\x1b\\Open in TMDB\x1b]8;;\x1b\\" + return url \ No newline at end of file diff --git a/renamer/secrets.py b/renamer/secrets.py new file mode 100644 index 0000000..5413980 --- /dev/null +++ b/renamer/secrets.py @@ -0,0 +1,2 @@ +TMDB_API_KEY="19af49e6d33ad124b3c3dfbf1114b714" +TMDB_ACCESS_TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxOWFmNDllNmQzM2FkMTI0YjNjM2RmYmYxMTE0YjcxNCIsIm5iZiI6MTQxNDMzODgxOS4zODMwMDAxLCJzdWIiOiI1NDRkMTkwM2MzYTM2ODcyZTAwMDI3ZmIiLCJzY29wZXMiOlsiYXBpX3JlYWQiXSwidmVyc2lvbiI6MX0.O4xnCs8Z7PJ_BQS6ZdU9yvvZ38Z8EsBBmjrYqvIY0aQ" diff --git a/uv.lock b/uv.lock index 67476df..33af60e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,88 @@ version = 1 revision = 3 requires-python = ">=3.11" +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -11,6 +93,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -164,7 +255,7 @@ wheels = [ [[package]] name = "renamer" -version = "0.3.3" +version = "0.4.1" source = { editable = "." } dependencies = [ { name = "langcodes" }, @@ -172,6 +263,7 @@ dependencies = [ { name = "pymediainfo" }, { name = "pytest" }, { name = "python-magic" }, + { name = "requests" }, { name = "textual" }, ] @@ -182,9 +274,25 @@ requires-dist = [ { name = "pymediainfo", specifier = ">=6.0.0" }, { name = "pytest", specifier = ">=7.0.0" }, { name = "python-magic", specifier = ">=0.4.27" }, + { name = "requests", specifier = ">=2.31.0" }, { name = "textual", specifier = ">=6.11.0" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "rich" version = "14.2.0" @@ -232,3 +340,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e wheels = [ { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, ] + +[[package]] +name = "urllib3" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, +]