diff --git a/README.md b/README.md
index e6d8597..fc35dfb 100644
--- a/README.md
+++ b/README.md
@@ -22,16 +22,26 @@
-

My Rice
-
+
+ **Anilist results menu:**

+**Episodes menu preview:**
+
+
+**Without preview images enabled:**
+
+
+**Desktop notifications + episodes menu without image preview:**
+
+
+
@@ -55,7 +65,6 @@
-
- [**FastAnime**](#fastanime)
@@ -90,6 +99,7 @@
- [Receiving Support](#receiving-support)
- [Supporting the Project](#supporting-the-project)
+
## Installation

@@ -102,6 +112,14 @@ The app can run wherever python can run. So all you need to have is python insta
On android you can use [termux](https://github.com/termux/termux-app).
If you have any difficulty consult for help on the [discord channel](https://discord.gg/HBEmAwvbHV)
+### Installation on nixos
+
+
+
+```bash
+nix profile install github:Benex254/fastanime
+```
+
### Installation using your favourite package manager
Currently the app is only published on [pypi](https://pypi.org/project/fastanime/).
@@ -114,7 +132,7 @@ With the following extras available:
#### Using uv
-Recommended method of installation is using [uv](https://docs.astral.sh/uv/).
+Recommended method of installation is using [uv](https://docs.astral.sh/uv/).
```bash
# generally:
@@ -380,6 +398,33 @@ For more details visit the anilist docs or just get the completions which will i
Like seriously **[get the completions](https://github.com/FastAnime/FastAnime#completions-subcommand)** and the experience will be a 💯 💯 better.
+**Fastanime anilist download:**
+Supports all the options for search except its used for downloading.
+it also supports all options for `fastanime download`
+Example:
+
+```bash
+# get anime with the tag of isekai
+fastanime anilist download -T isekai
+
+# get anime of 2024 and sort by popularity
+# that has already finished airing or is releasing
+# and is not in your anime lists
+fastanime anilist download -y 2024 -s POPULARITY_DESC --status RELEASING --status FINISHED --not-on-list
+
+# get anime of 2024 season WINTER
+fastanime anilist download -y 2024 --season WINTER
+
+# get anime genre action and tag isekai,magic
+ fastanime anilist download -g Action -T Isekai -T Magic
+
+# get anime of 2024 thats finished airing
+fastanime anilist download -y 2024 -S FINISHED
+
+# get the most favourite anime movies
+fastanime anilist download -f MOVIE -s FAVOURITES_DESC
+```
+
The following are commands you can only run if you are signed in to your AniList account:
- `fastanime anilist watching`
@@ -618,7 +663,7 @@ fastanime config --view
> [!Note]
>
-> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` 😉.
+> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` 😉.
#### cache subcommand
@@ -1448,14 +1493,16 @@ For inquiries, join our [Discord Server](https://discord.gg/HBEmAwvbHV).
## Supporting the Project
+
More pr's less issues 🙃
Those who contribute at least five times will be able to make changes to the repo without my review.
Show your support by starring the GitHub repository or [buying me a coffee](https://ko-fi.com/benex254).
## Disclaimer
+
> [!IMPORTANT]
>
> This project currently scrapes allanime, hianime, nyaa, yugen and animepahe.
-> The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
+> The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
> [DISCLAIMER](https://github.com/Benex254/FastAnime/blob/master/DISCLAIMER.md)
diff --git a/fastanime/AnimeProvider.py b/fastanime/AnimeProvider.py
index 51f669b..5fb7064 100644
--- a/fastanime/AnimeProvider.py
+++ b/fastanime/AnimeProvider.py
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
-# TODO: improve performance of this class and add cool features like auto retry
+# TODO: add cool features like auto retry
class AnimeProvider:
"""Class that manages all anime sources adding some extra functionality to them.
Attributes:
diff --git a/fastanime/__init__.py b/fastanime/__init__.py
index 7193d36..c8bbc66 100644
--- a/fastanime/__init__.py
+++ b/fastanime/__init__.py
@@ -6,7 +6,7 @@ if sys.version_info < (3, 10):
) # noqa: F541
-__version__ = "v2.8.1"
+__version__ = "v2.8.3"
APP_NAME = "FastAnime"
AUTHOR = "Benex254"
diff --git a/fastanime/cli/app_updater.py b/fastanime/cli/app_updater.py
index 907d6ad..0bb7eed 100644
--- a/fastanime/cli/app_updater.py
+++ b/fastanime/cli/app_updater.py
@@ -4,6 +4,7 @@ import shlex
import shutil
import subprocess
import sys
+import os
import requests
from rich import print
@@ -88,7 +89,14 @@ def update_app(force=False):
tag_name = release_json["tag_name"]
print("[cyan]Updating app to version %s[/]" % tag_name)
- if is_git_repo(AUTHOR, APP_NAME):
+ if os.path.exists("/nix/store") and os.path.exists("/run/current-system"):
+ NIX = shutil.which("nix")
+ if not NIX:
+ print("[red]Cannot find nix, it looks like your system is broken.[/]")
+ return False, release_json
+
+ process = subprocess.run([NIX, "profile", "upgrade", APP_NAME.lower()])
+ elif is_git_repo(AUTHOR, APP_NAME):
GIT_EXECUTABLE = shutil.which("git")
args = [
GIT_EXECUTABLE,
diff --git a/fastanime/cli/commands/anilist/__init__.py b/fastanime/cli/commands/anilist/__init__.py
index 3e8a4d3..6ce7fc5 100644
--- a/fastanime/cli/commands/anilist/__init__.py
+++ b/fastanime/cli/commands/anilist/__init__.py
@@ -21,6 +21,8 @@ commands = {
"planning": "planning.planning",
"notifier": "notifier.notifier",
"stats": "stats.stats",
+ "download": "download.download",
+ "downloads": "downloads.downloads",
}
diff --git a/fastanime/cli/commands/anilist/data.py b/fastanime/cli/commands/anilist/data.py
new file mode 100644
index 0000000..e799d30
--- /dev/null
+++ b/fastanime/cli/commands/anilist/data.py
@@ -0,0 +1,476 @@
+sorts_available = [
+ "ID",
+ "ID_DESC",
+ "TITLE_ROMAJI",
+ "TITLE_ROMAJI_DESC",
+ "TITLE_ENGLISH",
+ "TITLE_ENGLISH_DESC",
+ "TITLE_NATIVE",
+ "TITLE_NATIVE_DESC",
+ "TYPE",
+ "TYPE_DESC",
+ "FORMAT",
+ "FORMAT_DESC",
+ "START_DATE",
+ "START_DATE_DESC",
+ "END_DATE",
+ "END_DATE_DESC",
+ "SCORE",
+ "SCORE_DESC",
+ "POPULARITY",
+ "POPULARITY_DESC",
+ "TRENDING",
+ "TRENDING_DESC",
+ "EPISODES",
+ "EPISODES_DESC",
+ "DURATION",
+ "DURATION_DESC",
+ "STATUS",
+ "STATUS_DESC",
+ "CHAPTERS",
+ "CHAPTERS_DESC",
+ "VOLUMES",
+ "VOLUMES_DESC",
+ "UPDATED_AT",
+ "UPDATED_AT_DESC",
+ "SEARCH_MATCH",
+ "FAVOURITES",
+ "FAVOURITES_DESC",
+]
+
+media_statuses_available = [
+ "FINISHED",
+ "RELEASING",
+ "NOT_YET_RELEASED",
+ "CANCELLED",
+ "HIATUS",
+]
+seasons_available = ["WINTER", "SPRING", "SUMMER", "FALL"]
+genres_available = [
+ "Action",
+ "Adventure",
+ "Comedy",
+ "Drama",
+ "Ecchi",
+ "Fantasy",
+ "Horror",
+ "Mahou Shoujo",
+ "Mecha",
+ "Music",
+ "Mystery",
+ "Psychological",
+ "Romance",
+ "Sci-Fi",
+ "Slice of Life",
+ "Sports",
+ "Supernatural",
+ "Thriller",
+ "Hentai",
+]
+media_formats_available = [
+ "TV",
+ "TV_SHORT",
+ "MOVIE",
+ "SPECIAL",
+ "OVA",
+ "MUSIC",
+ "NOVEL",
+ "ONE_SHOT",
+]
+years_available = [
+ "1900",
+ "1910",
+ "1920",
+ "1930",
+ "1940",
+ "1950",
+ "1960",
+ "1970",
+ "1980",
+ "1990",
+ "2000",
+ "2004",
+ "2005",
+ "2006",
+ "2007",
+ "2008",
+ "2009",
+ "2010",
+ "2011",
+ "2012",
+ "2013",
+ "2014",
+ "2015",
+ "2016",
+ "2017",
+ "2018",
+ "2019",
+ "2020",
+ "2021",
+ "2022",
+ "2023",
+ "2024",
+]
+
+tags_available = {
+ "Cast": ["Polyamorous"],
+ "Cast Main Cast": [
+ "Anti-Hero",
+ "Elderly Protagonist",
+ "Ensemble Cast",
+ "Estranged Family",
+ "Female Protagonist",
+ "Male Protagonist",
+ "Primarily Adult Cast",
+ "Primarily Animal Cast",
+ "Primarily Child Cast",
+ "Primarily Female Cast",
+ "Primarily Male Cast",
+ "Primarily Teen Cast",
+ ],
+ "Cast Traits": [
+ "Age Regression",
+ "Agender",
+ "Aliens",
+ "Amnesia",
+ "Angels",
+ "Anthropomorphism",
+ "Aromantic",
+ "Arranged Marriage",
+ "Artificial Intelligence",
+ "Asexual",
+ "Butler",
+ "Centaur",
+ "Chimera",
+ "Chuunibyou",
+ "Clone",
+ "Cosplay",
+ "Cowboys",
+ "Crossdressing",
+ "Cyborg",
+ "Delinquents",
+ "Demons",
+ "Detective",
+ "Dinosaurs",
+ "Disability",
+ "Dissociative Identities",
+ "Dragons",
+ "Dullahan",
+ "Elf",
+ "Fairy",
+ "Femboy",
+ "Ghost",
+ "Goblin",
+ "Gods",
+ "Gyaru",
+ "Hikikomori",
+ "Homeless",
+ "Idol",
+ "Kemonomimi",
+ "Kuudere",
+ "Maids",
+ "Mermaid",
+ "Monster Boy",
+ "Monster Girl",
+ "Nekomimi",
+ "Ninja",
+ "Nudity",
+ "Nun",
+ "Office Lady",
+ "Oiran",
+ "Ojou-sama",
+ "Orphan",
+ "Pirates",
+ "Robots",
+ "Samurai",
+ "Shrine Maiden",
+ "Skeleton",
+ "Succubus",
+ "Tanned Skin",
+ "Teacher",
+ "Tomboy",
+ "Transgender",
+ "Tsundere",
+ "Twins",
+ "Vampire",
+ "Veterinarian",
+ "Vikings",
+ "Villainess",
+ "VTuber",
+ "Werewolf",
+ "Witch",
+ "Yandere",
+ "Zombie",
+ ],
+ "Demographic": ["Josei", "Kids", "Seinen", "Shoujo", "Shounen"],
+ "Setting": ["Matriarchy"],
+ "Setting Scene": [
+ "Bar",
+ "Boarding School",
+ "Circus",
+ "Coastal",
+ "College",
+ "Desert",
+ "Dungeon",
+ "Foreign",
+ "Inn",
+ "Konbini",
+ "Natural Disaster",
+ "Office",
+ "Outdoor",
+ "Prison",
+ "Restaurant",
+ "Rural",
+ "School",
+ "School Club",
+ "Snowscape",
+ "Urban",
+ "Work",
+ ],
+ "Setting Time": [
+ "Achronological Order",
+ "Anachronism",
+ "Ancient China",
+ "Dystopian",
+ "Historical",
+ "Time Skip",
+ ],
+ "Setting Universe": [
+ "Afterlife",
+ "Alternate Universe",
+ "Augmented Reality",
+ "Omegaverse",
+ "Post-Apocalyptic",
+ "Space",
+ "Urban Fantasy",
+ "Virtual World",
+ ],
+ "Technical": [
+ "4-koma",
+ "Achromatic",
+ "Advertisement",
+ "Anthology",
+ "CGI",
+ "Episodic",
+ "Flash",
+ "Full CGI",
+ "Full Color",
+ "No Dialogue",
+ "Non-fiction",
+ "POV",
+ "Puppetry",
+ "Rotoscoping",
+ "Stop Motion",
+ ],
+ "Theme Action": [
+ "Archery",
+ "Battle Royale",
+ "Espionage",
+ "Fugitive",
+ "Guns",
+ "Martial Arts",
+ "Spearplay",
+ "Swordplay",
+ ],
+ "Theme Arts": [
+ "Acting",
+ "Calligraphy",
+ "Classic Literature",
+ "Drawing",
+ "Fashion",
+ "Food",
+ "Makeup",
+ "Photography",
+ "Rakugo",
+ "Writing",
+ ],
+ "Theme Arts-Music": [
+ "Band",
+ "Classical Music",
+ "Dancing",
+ "Hip-hop Music",
+ "Jazz Music",
+ "Metal Music",
+ "Musical Theater",
+ "Rock Music",
+ ],
+ "Theme Comedy": ["Parody", "Satire", "Slapstick", "Surreal Comedy"],
+ "Theme Drama": [
+ "Bullying",
+ "Class Struggle",
+ "Coming of Age",
+ "Conspiracy",
+ "Eco-Horror",
+ "Fake Relationship",
+ "Kingdom Management",
+ "Rehabilitation",
+ "Revenge",
+ "Suicide",
+ "Tragedy",
+ ],
+ "Theme Fantasy": [
+ "Alchemy",
+ "Body Swapping",
+ "Cultivation",
+ "Fairy Tale",
+ "Henshin",
+ "Isekai",
+ "Kaiju",
+ "Magic",
+ "Mythology",
+ "Necromancy",
+ "Shapeshifting",
+ "Steampunk",
+ "Super Power",
+ "Superhero",
+ "Wuxia",
+ "Youkai",
+ ],
+ "Theme Game": ["Board Game", "E-Sports", "Video Games"],
+ "Theme Game-Card & Board Game": [
+ "Card Battle",
+ "Go",
+ "Karuta",
+ "Mahjong",
+ "Poker",
+ "Shogi",
+ ],
+ "Theme Game-Sport": [
+ "Acrobatics",
+ "Airsoft",
+ "American Football",
+ "Athletics",
+ "Badminton",
+ "Baseball",
+ "Basketball",
+ "Bowling",
+ "Boxing",
+ "Cheerleading",
+ "Cycling",
+ "Fencing",
+ "Fishing",
+ "Fitness",
+ "Football",
+ "Golf",
+ "Handball",
+ "Ice Skating",
+ "Judo",
+ "Lacrosse",
+ "Parkour",
+ "Rugby",
+ "Scuba Diving",
+ "Skateboarding",
+ "Sumo",
+ "Surfing",
+ "Swimming",
+ "Table Tennis",
+ "Tennis",
+ "Volleyball",
+ "Wrestling",
+ ],
+ "Theme Other": [
+ "Adoption",
+ "Animals",
+ "Astronomy",
+ "Autobiographical",
+ "Biographical",
+ "Body Horror",
+ "Cannibalism",
+ "Chibi",
+ "Cosmic Horror",
+ "Crime",
+ "Crossover",
+ "Death Game",
+ "Denpa",
+ "Drugs",
+ "Economics",
+ "Educational",
+ "Environmental",
+ "Ero Guro",
+ "Filmmaking",
+ "Found Family",
+ "Gambling",
+ "Gender Bending",
+ "Gore",
+ "Language Barrier",
+ "LGBTQ+ Themes",
+ "Lost Civilization",
+ "Marriage",
+ "Medicine",
+ "Memory Manipulation",
+ "Meta",
+ "Mountaineering",
+ "Noir",
+ "Otaku Culture",
+ "Pandemic",
+ "Philosophy",
+ "Politics",
+ "Proxy Battle",
+ "Psychosexual",
+ "Reincarnation",
+ "Religion",
+ "Royal Affairs",
+ "Slavery",
+ "Software Development",
+ "Survival",
+ "Terrorism",
+ "Torture",
+ "Travel",
+ "War",
+ ],
+ "Theme Other-Organisations": [
+ "Assassins",
+ "Criminal Organization",
+ "Cult",
+ "Firefighters",
+ "Gangs",
+ "Mafia",
+ "Military",
+ "Police",
+ "Triads",
+ "Yakuza",
+ ],
+ "Theme Other-Vehicle": [
+ "Aviation",
+ "Cars",
+ "Mopeds",
+ "Motorcycles",
+ "Ships",
+ "Tanks",
+ "Trains",
+ ],
+ "Theme Romance": [
+ "Age Gap",
+ "Bisexual",
+ "Boys' Love",
+ "Female Harem",
+ "Heterosexual",
+ "Love Triangle",
+ "Male Harem",
+ "Matchmaking",
+ "Mixed Gender Harem",
+ "Teens' Love",
+ "Unrequited Love",
+ "Yuri",
+ ],
+ "Theme Sci Fi": [
+ "Cyberpunk",
+ "Space Opera",
+ "Time Loop",
+ "Time Manipulation",
+ "Tokusatsu",
+ ],
+ "Theme Sci Fi-Mecha": ["Real Robot", "Super Robot"],
+ "Theme Slice of Life": [
+ "Agriculture",
+ "Cute Boys Doing Cute Things",
+ "Cute Girls Doing Cute Things",
+ "Family Life",
+ "Horticulture",
+ "Iyashikei",
+ "Parenthood",
+ ],
+}
+tags_available_list = []
+for tag_category, tags_in_category in tags_available.items():
+ tags_available_list.extend(tags_in_category)
diff --git a/fastanime/cli/commands/anilist/download.py b/fastanime/cli/commands/anilist/download.py
new file mode 100644
index 0000000..afcaa59
--- /dev/null
+++ b/fastanime/cli/commands/anilist/download.py
@@ -0,0 +1,382 @@
+import click
+
+
+from ...completion_functions import anime_titles_shell_complete
+from .data import (
+ tags_available_list,
+ sorts_available,
+ media_statuses_available,
+ seasons_available,
+ genres_available,
+ media_formats_available,
+ years_available,
+)
+
+
+@click.command(
+ help="download anime using anilists api to get the titles",
+ short_help="download anime with anilist intergration",
+)
+@click.option("--title", "-t", shell_complete=anime_titles_shell_complete)
+@click.option(
+ "--season",
+ help="The season the media was released",
+ type=click.Choice(seasons_available),
+)
+@click.option(
+ "--status",
+ "-S",
+ help="The media status of the anime",
+ multiple=True,
+ type=click.Choice(media_statuses_available),
+)
+@click.option(
+ "--sort",
+ "-s",
+ help="What to sort the search results on",
+ type=click.Choice(sorts_available),
+)
+@click.option(
+ "--genres",
+ "-g",
+ multiple=True,
+ help="the genres to filter by",
+ type=click.Choice(genres_available),
+)
+@click.option(
+ "--tags",
+ "-T",
+ multiple=True,
+ help="the tags to filter by",
+ type=click.Choice(tags_available_list),
+)
+@click.option(
+ "--media-format",
+ "-f",
+ multiple=True,
+ help="Media format",
+ type=click.Choice(media_formats_available),
+)
+@click.option(
+ "--year",
+ "-y",
+ type=click.Choice(years_available),
+ help="the year the media was released",
+)
+@click.option(
+ "--on-list/--not-on-list",
+ "-L/-no-L",
+ help="Whether the anime should be in your list or not",
+ type=bool,
+)
+@click.option(
+ "--episode-range",
+ "-r",
+ help="A range of episodes to download (start-end)",
+)
+@click.option(
+ "--force-unknown-ext",
+ "-F",
+ help="This option forces yt-dlp to download extensions its not aware of",
+ is_flag=True,
+)
+@click.option(
+ "--silent/--no-silent",
+ "-q/-V",
+ type=bool,
+ help="Download silently (during download)",
+ default=True,
+)
+@click.option("--verbose", "-v", is_flag=True, help="Download verbosely (everywhere)")
+@click.option(
+ "--merge", "-m", is_flag=True, help="Merge the subfile with video using ffmpeg"
+)
+@click.option(
+ "--clean",
+ "-c",
+ is_flag=True,
+ help="After merging delete the original files",
+)
+@click.option(
+ "--wait-time",
+ "-w",
+ type=int,
+ help="The amount of time to wait after downloading is complete before the screen is completely cleared",
+ default=60,
+)
+@click.option(
+ "--prompt/--no-prompt",
+ help="Whether to prompt for anything instead just do the best thing",
+ default=True,
+)
+@click.option(
+ "--max-results", "-M", type=int, help="The maximum number of results to show"
+)
+@click.pass_obj
+def download(
+ config,
+ title,
+ season,
+ status,
+ sort,
+ genres,
+ tags,
+ media_format,
+ year,
+ on_list,
+ episode_range,
+ force_unknown_ext,
+ silent,
+ verbose,
+ merge,
+ clean,
+ wait_time,
+ prompt,
+ max_results,
+):
+ from ....anilist import AniList
+ from rich import print
+
+ success, anilist_search_results = AniList.search(
+ query=title,
+ sort=sort,
+ status_in=list(status),
+ genre_in=list(genres),
+ season=season,
+ tag_in=list(tags),
+ seasonYear=year,
+ format_in=list(media_format),
+ on_list=on_list,
+ max_results=max_results,
+ )
+ if success:
+ import time
+
+ from rich.progress import Progress
+ from thefuzz import fuzz
+
+ from ....AnimeProvider import AnimeProvider
+ from ....libs.anime_provider.types import Anime
+ from ....libs.fzf import fzf
+ from ....Utility.data import anime_normalizer
+ from ....Utility.downloader.downloader import downloader
+ from ...utils.tools import exit_app
+ from ...utils.utils import (
+ filter_by_quality,
+ fuzzy_inquirer,
+ move_preferred_subtitle_lang_to_top,
+ )
+
+ anime_provider = AnimeProvider(config.provider)
+ anilist_anime_info = None
+
+ translation_type = config.translation_type
+ download_dir = config.downloads_dir
+ anime_titles = [
+ (
+ anime["title"][config.preferred_language]
+ or anime["title"]["english"]
+ or anime["title"]["romaji"]
+ )
+ for anime in anilist_search_results["data"]["Page"]["media"]
+ ]
+ print(f"[green bold]Queued:[/] {anime_titles}")
+ for i, anime_title in enumerate(anime_titles):
+ print(f"[green bold]Now Downloading: [/] {anime_title}")
+ # ---- search for anime ----
+ with Progress() as progress:
+ progress.add_task("Fetching Search Results...", total=None)
+ search_results = anime_provider.search_for_anime(
+ anime_title, translation_type=translation_type
+ )
+ if not search_results:
+ print(
+ "No search results found from provider for {}".format(anime_title)
+ )
+ continue
+ search_results = search_results["results"]
+ if not search_results:
+ print("Nothing muches your search term")
+ continue
+ search_results_ = {
+ search_result["title"]: search_result
+ for search_result in search_results
+ }
+
+ if config.auto_select:
+ selected_anime_title = max(
+ search_results_.keys(),
+ key=lambda title: fuzz.ratio(
+ anime_normalizer.get(title, title), anime_title
+ ),
+ )
+ print("[cyan]Auto selecting:[/] ", selected_anime_title)
+ else:
+ choices = list(search_results_.keys())
+ if config.use_fzf:
+ selected_anime_title = fzf.run(
+ choices, "Please Select title", "FastAnime"
+ )
+ else:
+ selected_anime_title = fuzzy_inquirer(
+ choices,
+ "Please Select title",
+ )
+
+ # ---- fetch anime ----
+ with Progress() as progress:
+ progress.add_task("Fetching Anime...", total=None)
+ anime: Anime | None = anime_provider.get_anime(
+ search_results_[selected_anime_title]["id"]
+ )
+ if not anime:
+ print("Failed to fetch anime {}".format(selected_anime_title))
+ continue
+
+ episodes = sorted(
+ anime["availableEpisodesDetail"][config.translation_type], key=float
+ )
+ # where the magic happens
+ if episode_range:
+ if ":" in episode_range:
+ ep_range_tuple = episode_range.split(":")
+ if len(ep_range_tuple) == 2 and all(ep_range_tuple):
+ episodes_start, episodes_end = ep_range_tuple
+ episodes_range = episodes[
+ int(episodes_start) : int(episodes_end)
+ ]
+ elif len(ep_range_tuple) == 3 and all(ep_range_tuple):
+ episodes_start, episodes_end, step = ep_range_tuple
+ episodes_range = episodes[
+ int(episodes_start) : int(episodes_end) : int(step)
+ ]
+ else:
+ episodes_start, episodes_end = ep_range_tuple
+ if episodes_start.strip():
+ episodes_range = episodes[int(episodes_start) :]
+ elif episodes_end.strip():
+ episodes_range = episodes[: int(episodes_end)]
+ else:
+ episodes_range = episodes
+ else:
+ episodes_range = episodes[int(episode_range) :]
+ print(f"[green bold]Downloading: [/] {episodes_range}")
+
+ else:
+ episodes_range = sorted(episodes, key=float)
+
+ if config.normalize_titles:
+ anilist_anime_info = anilist_search_results["data"]["Page"]["media"][i]
+
+ # lets download em
+ for episode in episodes_range:
+ try:
+ episode = str(episode)
+ if episode not in episodes:
+ print(
+ f"[cyan]Warning[/]: Episode {episode} not found, skipping"
+ )
+ continue
+ with Progress() as progress:
+ progress.add_task("Fetching Episode Streams...", total=None)
+ streams = anime_provider.get_episode_streams(
+ anime["id"], episode, config.translation_type
+ )
+ if not streams:
+ print("No streams skipping")
+ continue
+ # ---- fetch servers ----
+ if config.server == "top":
+ with Progress() as progress:
+ progress.add_task("Fetching top server...", total=None)
+ server_name = next(streams, None)
+ if not server_name:
+ print("Sth went wrong when fetching the server")
+ continue
+ stream_link = filter_by_quality(
+ config.quality, server_name["links"]
+ )
+ if not stream_link:
+ print("[yellow bold]WARNING:[/] No streams found")
+ time.sleep(1)
+ print("Continuing...")
+ continue
+ link = stream_link["link"]
+ provider_headers = server_name["headers"]
+ episode_title = server_name["episode_title"]
+ subtitles = server_name["subtitles"]
+ else:
+ with Progress() as progress:
+ progress.add_task("Fetching servers", total=None)
+ # prompt for server selection
+ servers = {server["server"]: server for server in streams}
+ servers_names = list(servers.keys())
+ if config.server in servers_names:
+ server_name = config.server
+ else:
+ if config.use_fzf:
+ server_name = fzf.run(servers_names, "Select an link")
+ else:
+ server_name = fuzzy_inquirer(
+ servers_names,
+ "Select link",
+ )
+ stream_link = filter_by_quality(
+ config.quality, servers[server_name]["links"]
+ )
+ if not stream_link:
+ print("[yellow bold]WARNING:[/] No streams found")
+ time.sleep(1)
+ print("Continuing...")
+ continue
+ link = stream_link["link"]
+ provider_headers = servers[server_name]["headers"]
+
+ subtitles = servers[server_name]["subtitles"]
+ episode_title = servers[server_name]["episode_title"]
+
+ if anilist_anime_info:
+ selected_anime_title = (
+ anilist_anime_info["title"][config.preferred_language]
+ or anilist_anime_info["title"]["romaji"]
+ or anilist_anime_info["title"]["english"]
+ )
+ import re
+
+ for episode_detail in anilist_anime_info["streamingEpisodes"]:
+ if re.match(
+ f".*Episode {episode} .*", episode_detail["title"]
+ ):
+ episode_title = episode_detail["title"]
+ break
+ print(f"[purple]Now Downloading:[/] {episode_title}")
+ subtitles = move_preferred_subtitle_lang_to_top(
+ subtitles, config.sub_lang
+ )
+ downloader._download_file(
+ link,
+ selected_anime_title,
+ episode_title,
+ download_dir,
+ silent,
+ vid_format=config.format,
+ force_unknown_ext=force_unknown_ext,
+ verbose=verbose,
+ headers=provider_headers,
+ sub=subtitles[0]["url"] if subtitles else "",
+ merge=merge,
+ clean=clean,
+ prompt=prompt,
+ )
+ except Exception as e:
+ print(e)
+ time.sleep(1)
+ print("Continuing...")
+ print("Done Downloading")
+ time.sleep(wait_time)
+ exit_app()
+ else:
+ from sys import exit
+
+ print("Failed to search for anime", anilist_search_results)
+ exit(1)
diff --git a/fastanime/cli/commands/anilist/downloads.py b/fastanime/cli/commands/anilist/downloads.py
new file mode 100644
index 0000000..e02d04f
--- /dev/null
+++ b/fastanime/cli/commands/anilist/downloads.py
@@ -0,0 +1,358 @@
+import logging
+from typing import TYPE_CHECKING
+
+import click
+
+from ...completion_functions import downloaded_anime_titles
+
+logger = logging.getLogger(__name__)
+if TYPE_CHECKING:
+ from ..config import Config
+
+
+@click.command(
+ help="View and watch your downloads using mpv",
+ short_help="Watch downloads",
+ epilog="""
+\b
+\b\bExamples:
+ fastanime downloads
+\b
+ # view individual episodes
+ fastanime downloads --view-episodes
+ # --- or ---
+ fastanime downloads -v
+\b
+ # to set seek time when using ffmpegthumbnailer for local previews
+ # -1 means random and is the default
+ fastanime downloads --time-to-seek
+ # --- or ---
+ fastanime downloads -t
+\b
+ # to watch a specific title
+ # be sure to get the completions for the best experience
+ fastanime downloads --title
+\b
+ # to get the path to the downloads folder set
+ fastanime downloads --path
+ # useful when you want to use the value for other programs
+""",
+)
+@click.option("--path", "-p", help="print the downloads folder and exit", is_flag=True)
+@click.option(
+ "--title",
+ "-T",
+ shell_complete=downloaded_anime_titles,
+ help="watch a specific title",
+)
+@click.option("--view-episodes", "-v", help="View individual episodes", is_flag=True)
+@click.option(
+ "--ffmpegthumbnailer-seek-time",
+ "--time-to-seek",
+ "-t",
+ type=click.IntRange(-1, 100),
+ help="ffmpegthumbnailer seek time",
+)
+@click.pass_obj
+def downloads(
+ config: "Config", path: bool, title, view_episodes, ffmpegthumbnailer_seek_time
+):
+ import os
+
+ from ....cli.utils.mpv import run_mpv
+ from ....libs.fzf import fzf
+ from ....libs.rofi import Rofi
+ from ....Utility.utils import sort_by_episode_number
+ from ...utils.tools import exit_app
+ from ...utils.utils import fuzzy_inquirer
+
+ if not ffmpegthumbnailer_seek_time:
+ ffmpegthumbnailer_seek_time = config.ffmpegthumbnailer_seek_time
+ USER_VIDEOS_DIR = config.downloads_dir
+ if path:
+ print(USER_VIDEOS_DIR)
+ return
+ if not os.path.exists(USER_VIDEOS_DIR):
+ print("Downloads directory specified does not exist")
+ return
+ anime_downloads = sorted(
+ os.listdir(USER_VIDEOS_DIR),
+ )
+ anime_downloads.append("Exit")
+
+ def create_thumbnails(video_path, anime_title, downloads_thumbnail_cache_dir):
+ import os
+ import shutil
+ import subprocess
+
+ FFMPEG_THUMBNAILER = shutil.which("ffmpegthumbnailer")
+ if not FFMPEG_THUMBNAILER:
+ return
+
+ out = os.path.join(downloads_thumbnail_cache_dir, anime_title)
+ if ffmpegthumbnailer_seek_time == -1:
+ import random
+
+ seektime = str(random.randrange(0, 100))
+ else:
+ seektime = str(ffmpegthumbnailer_seek_time)
+ _ = subprocess.run(
+ [
+ FFMPEG_THUMBNAILER,
+ "-i",
+ video_path,
+ "-o",
+ out,
+ "-s",
+ "0",
+ "-t",
+ seektime,
+ ],
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ )
+
+ def get_previews_anime(workers=None, bg=True):
+ import concurrent.futures
+ import random
+ import shutil
+ from pathlib import Path
+
+ if not shutil.which("ffmpegthumbnailer"):
+ print("ffmpegthumbnailer not found")
+ logger.error("ffmpegthumbnailer not found")
+ return
+
+ from ....constants import APP_CACHE_DIR
+ from ...utils.scripts import fzf_preview
+
+ downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
+ Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
+
+ def _worker():
+ # use concurrency to download the images as fast as possible
+ with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
+ # load the jobs
+ future_to_url = {}
+ for anime_title in anime_downloads:
+ anime_path = os.path.join(USER_VIDEOS_DIR, anime_title)
+ if not os.path.isdir(anime_path):
+ continue
+ playlist = [
+ anime
+ for anime in sorted(
+ os.listdir(anime_path),
+ )
+ if "mp4" in anime
+ ]
+ if playlist:
+ # actual link to download image from
+ video_path = os.path.join(anime_path, random.choice(playlist))
+ future_to_url[
+ executor.submit(
+ create_thumbnails,
+ video_path,
+ anime_title,
+ downloads_thumbnail_cache_dir,
+ )
+ ] = anime_title
+
+ # execute the jobs
+ for future in concurrent.futures.as_completed(future_to_url):
+ url = future_to_url[future]
+ try:
+ future.result()
+ except Exception as e:
+ logger.error("%r generated an exception: %s" % (url, e))
+
+ if bg:
+ from threading import Thread
+
+ worker = Thread(target=_worker)
+ worker.daemon = True
+ worker.start()
+ else:
+ _worker()
+ os.environ["SHELL"] = shutil.which("bash") or "bash"
+ preview = """
+ %s
+ if [ -s %s/{} ]; then
+ if ! fzf-preview %s/{} 2>/dev/null; then
+ echo Loading...
+ fi
+ else echo Loading...
+ fi
+ """ % (
+ fzf_preview,
+ downloads_thumbnail_cache_dir,
+ downloads_thumbnail_cache_dir,
+ )
+ return preview
+
+ def get_previews_episodes(anime_playlist_path, workers=None, bg=True):
+ import shutil
+ from pathlib import Path
+
+ from ....constants import APP_CACHE_DIR
+ from ...utils.scripts import fzf_preview
+
+ if not shutil.which("ffmpegthumbnailer"):
+ print("ffmpegthumbnailer not found")
+ logger.error("ffmpegthumbnailer not found")
+ return
+
+ downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
+ Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
+
+ def _worker():
+ import concurrent.futures
+
+ # use concurrency to download the images as fast as possible
+ # anime_playlist_path = os.path.join(USER_VIDEOS_DIR, anime_playlist_path)
+ if not os.path.isdir(anime_playlist_path):
+ return
+ anime_episodes = sorted(
+ os.listdir(anime_playlist_path), key=sort_by_episode_number
+ )
+ with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
+ # load the jobs
+ future_to_url = {}
+ for episode_title in anime_episodes:
+ episode_path = os.path.join(anime_playlist_path, episode_title)
+
+ # actual link to download image from
+ future_to_url[
+ executor.submit(
+ create_thumbnails,
+ episode_path,
+ episode_title,
+ downloads_thumbnail_cache_dir,
+ )
+ ] = episode_title
+
+ # execute the jobs
+ for future in concurrent.futures.as_completed(future_to_url):
+ url = future_to_url[future]
+ try:
+ future.result()
+ except Exception as e:
+ logger.error("%r generated an exception: %s" % (url, e))
+
+ if bg:
+ from threading import Thread
+
+ worker = Thread(target=_worker)
+ worker.daemon = True
+ worker.start()
+ else:
+ _worker()
+ os.environ["SHELL"] = shutil.which("bash") or "bash"
+ preview = """
+ %s
+ if [ -s %s/{} ]; then
+ if ! fzf-preview %s/{} 2>/dev/null; then
+ echo Loading...
+ fi
+ else echo Loading...
+ fi
+ """ % (
+ fzf_preview,
+ downloads_thumbnail_cache_dir,
+ downloads_thumbnail_cache_dir,
+ )
+ return preview
+
+ def stream_episode(
+ anime_playlist_path,
+ ):
+ if view_episodes:
+ if not os.path.isdir(anime_playlist_path):
+ print(anime_playlist_path, "is not dir")
+ exit_app(1)
+ return
+ episodes = sorted(
+ os.listdir(anime_playlist_path), key=sort_by_episode_number
+ )
+ downloaded_episodes = [*episodes, "Back"]
+
+ if config.use_fzf:
+ if not config.preview:
+ episode_title = fzf.run(
+ downloaded_episodes,
+ "Enter Episode ",
+ )
+ else:
+ preview = get_previews_episodes(anime_playlist_path)
+ episode_title = fzf.run(
+ downloaded_episodes,
+ "Enter Episode ",
+ preview=preview,
+ )
+ elif config.use_rofi:
+ episode_title = Rofi.run(downloaded_episodes, "Enter Episode")
+ else:
+ episode_title = fuzzy_inquirer(
+ downloaded_episodes,
+ "Enter Playlist Name",
+ )
+ if episode_title == "Back":
+ stream_anime()
+ return
+ episode_path = os.path.join(anime_playlist_path, episode_title)
+ if config.sync_play:
+ from ...utils.syncplay import SyncPlayer
+
+ SyncPlayer(episode_path)
+ else:
+ run_mpv(
+ episode_path,
+ player=config.player,
+ )
+ stream_episode(anime_playlist_path)
+
+ def stream_anime(title=None):
+ if title:
+ from thefuzz import fuzz
+
+ playlist_name = max(anime_downloads, key=lambda t: fuzz.ratio(title, t))
+ elif config.use_fzf:
+ if not config.preview:
+ playlist_name = fzf.run(
+ anime_downloads,
+ "Enter Playlist Name",
+ )
+ else:
+ preview = get_previews_anime()
+ playlist_name = fzf.run(
+ anime_downloads,
+ "Enter Playlist Name",
+ preview=preview,
+ )
+ elif config.use_rofi:
+ playlist_name = Rofi.run(anime_downloads, "Enter Playlist Name")
+ else:
+ playlist_name = fuzzy_inquirer(
+ anime_downloads,
+ "Enter Playlist Name",
+ )
+ if playlist_name == "Exit":
+ exit_app()
+ return
+ playlist = os.path.join(USER_VIDEOS_DIR, playlist_name)
+ if view_episodes:
+ stream_episode(
+ playlist,
+ )
+ else:
+ if config.sync_play:
+ from ...utils.syncplay import SyncPlayer
+
+ SyncPlayer(playlist)
+ else:
+ run_mpv(
+ playlist,
+ player=config.player,
+ )
+ stream_anime()
+
+ stream_anime(title)
diff --git a/fastanime/cli/commands/anilist/search.py b/fastanime/cli/commands/anilist/search.py
index 7adb4fb..207876d 100644
--- a/fastanime/cli/commands/anilist/search.py
+++ b/fastanime/cli/commands/anilist/search.py
@@ -1,369 +1,15 @@
import click
from ...completion_functions import anime_titles_shell_complete
-
-tags_available = {
- "Cast": ["Polyamorous"],
- "Cast Main Cast": [
- "Anti-Hero",
- "Elderly Protagonist",
- "Ensemble Cast",
- "Estranged Family",
- "Female Protagonist",
- "Male Protagonist",
- "Primarily Adult Cast",
- "Primarily Animal Cast",
- "Primarily Child Cast",
- "Primarily Female Cast",
- "Primarily Male Cast",
- "Primarily Teen Cast",
- ],
- "Cast Traits": [
- "Age Regression",
- "Agender",
- "Aliens",
- "Amnesia",
- "Angels",
- "Anthropomorphism",
- "Aromantic",
- "Arranged Marriage",
- "Artificial Intelligence",
- "Asexual",
- "Butler",
- "Centaur",
- "Chimera",
- "Chuunibyou",
- "Clone",
- "Cosplay",
- "Cowboys",
- "Crossdressing",
- "Cyborg",
- "Delinquents",
- "Demons",
- "Detective",
- "Dinosaurs",
- "Disability",
- "Dissociative Identities",
- "Dragons",
- "Dullahan",
- "Elf",
- "Fairy",
- "Femboy",
- "Ghost",
- "Goblin",
- "Gods",
- "Gyaru",
- "Hikikomori",
- "Homeless",
- "Idol",
- "Kemonomimi",
- "Kuudere",
- "Maids",
- "Mermaid",
- "Monster Boy",
- "Monster Girl",
- "Nekomimi",
- "Ninja",
- "Nudity",
- "Nun",
- "Office Lady",
- "Oiran",
- "Ojou-sama",
- "Orphan",
- "Pirates",
- "Robots",
- "Samurai",
- "Shrine Maiden",
- "Skeleton",
- "Succubus",
- "Tanned Skin",
- "Teacher",
- "Tomboy",
- "Transgender",
- "Tsundere",
- "Twins",
- "Vampire",
- "Veterinarian",
- "Vikings",
- "Villainess",
- "VTuber",
- "Werewolf",
- "Witch",
- "Yandere",
- "Zombie",
- ],
- "Demographic": ["Josei", "Kids", "Seinen", "Shoujo", "Shounen"],
- "Setting": ["Matriarchy"],
- "Setting Scene": [
- "Bar",
- "Boarding School",
- "Circus",
- "Coastal",
- "College",
- "Desert",
- "Dungeon",
- "Foreign",
- "Inn",
- "Konbini",
- "Natural Disaster",
- "Office",
- "Outdoor",
- "Prison",
- "Restaurant",
- "Rural",
- "School",
- "School Club",
- "Snowscape",
- "Urban",
- "Work",
- ],
- "Setting Time": [
- "Achronological Order",
- "Anachronism",
- "Ancient China",
- "Dystopian",
- "Historical",
- "Time Skip",
- ],
- "Setting Universe": [
- "Afterlife",
- "Alternate Universe",
- "Augmented Reality",
- "Omegaverse",
- "Post-Apocalyptic",
- "Space",
- "Urban Fantasy",
- "Virtual World",
- ],
- "Technical": [
- "4-koma",
- "Achromatic",
- "Advertisement",
- "Anthology",
- "CGI",
- "Episodic",
- "Flash",
- "Full CGI",
- "Full Color",
- "No Dialogue",
- "Non-fiction",
- "POV",
- "Puppetry",
- "Rotoscoping",
- "Stop Motion",
- ],
- "Theme Action": [
- "Archery",
- "Battle Royale",
- "Espionage",
- "Fugitive",
- "Guns",
- "Martial Arts",
- "Spearplay",
- "Swordplay",
- ],
- "Theme Arts": [
- "Acting",
- "Calligraphy",
- "Classic Literature",
- "Drawing",
- "Fashion",
- "Food",
- "Makeup",
- "Photography",
- "Rakugo",
- "Writing",
- ],
- "Theme Arts-Music": [
- "Band",
- "Classical Music",
- "Dancing",
- "Hip-hop Music",
- "Jazz Music",
- "Metal Music",
- "Musical Theater",
- "Rock Music",
- ],
- "Theme Comedy": ["Parody", "Satire", "Slapstick", "Surreal Comedy"],
- "Theme Drama": [
- "Bullying",
- "Class Struggle",
- "Coming of Age",
- "Conspiracy",
- "Eco-Horror",
- "Fake Relationship",
- "Kingdom Management",
- "Rehabilitation",
- "Revenge",
- "Suicide",
- "Tragedy",
- ],
- "Theme Fantasy": [
- "Alchemy",
- "Body Swapping",
- "Cultivation",
- "Fairy Tale",
- "Henshin",
- "Isekai",
- "Kaiju",
- "Magic",
- "Mythology",
- "Necromancy",
- "Shapeshifting",
- "Steampunk",
- "Super Power",
- "Superhero",
- "Wuxia",
- "Youkai",
- ],
- "Theme Game": ["Board Game", "E-Sports", "Video Games"],
- "Theme Game-Card & Board Game": [
- "Card Battle",
- "Go",
- "Karuta",
- "Mahjong",
- "Poker",
- "Shogi",
- ],
- "Theme Game-Sport": [
- "Acrobatics",
- "Airsoft",
- "American Football",
- "Athletics",
- "Badminton",
- "Baseball",
- "Basketball",
- "Bowling",
- "Boxing",
- "Cheerleading",
- "Cycling",
- "Fencing",
- "Fishing",
- "Fitness",
- "Football",
- "Golf",
- "Handball",
- "Ice Skating",
- "Judo",
- "Lacrosse",
- "Parkour",
- "Rugby",
- "Scuba Diving",
- "Skateboarding",
- "Sumo",
- "Surfing",
- "Swimming",
- "Table Tennis",
- "Tennis",
- "Volleyball",
- "Wrestling",
- ],
- "Theme Other": [
- "Adoption",
- "Animals",
- "Astronomy",
- "Autobiographical",
- "Biographical",
- "Body Horror",
- "Cannibalism",
- "Chibi",
- "Cosmic Horror",
- "Crime",
- "Crossover",
- "Death Game",
- "Denpa",
- "Drugs",
- "Economics",
- "Educational",
- "Environmental",
- "Ero Guro",
- "Filmmaking",
- "Found Family",
- "Gambling",
- "Gender Bending",
- "Gore",
- "Language Barrier",
- "LGBTQ+ Themes",
- "Lost Civilization",
- "Marriage",
- "Medicine",
- "Memory Manipulation",
- "Meta",
- "Mountaineering",
- "Noir",
- "Otaku Culture",
- "Pandemic",
- "Philosophy",
- "Politics",
- "Proxy Battle",
- "Psychosexual",
- "Reincarnation",
- "Religion",
- "Royal Affairs",
- "Slavery",
- "Software Development",
- "Survival",
- "Terrorism",
- "Torture",
- "Travel",
- "War",
- ],
- "Theme Other-Organisations": [
- "Assassins",
- "Criminal Organization",
- "Cult",
- "Firefighters",
- "Gangs",
- "Mafia",
- "Military",
- "Police",
- "Triads",
- "Yakuza",
- ],
- "Theme Other-Vehicle": [
- "Aviation",
- "Cars",
- "Mopeds",
- "Motorcycles",
- "Ships",
- "Tanks",
- "Trains",
- ],
- "Theme Romance": [
- "Age Gap",
- "Bisexual",
- "Boys' Love",
- "Female Harem",
- "Heterosexual",
- "Love Triangle",
- "Male Harem",
- "Matchmaking",
- "Mixed Gender Harem",
- "Teens' Love",
- "Unrequited Love",
- "Yuri",
- ],
- "Theme Sci Fi": [
- "Cyberpunk",
- "Space Opera",
- "Time Loop",
- "Time Manipulation",
- "Tokusatsu",
- ],
- "Theme Sci Fi-Mecha": ["Real Robot", "Super Robot"],
- "Theme Slice of Life": [
- "Agriculture",
- "Cute Boys Doing Cute Things",
- "Cute Girls Doing Cute Things",
- "Family Life",
- "Horticulture",
- "Iyashikei",
- "Parenthood",
- ],
-}
-tags_available_list = []
-for tag_category, tags_in_category in tags_available.items():
- tags_available_list.extend(tags_in_category)
+from .data import (
+ tags_available_list,
+ sorts_available,
+ media_statuses_available,
+ seasons_available,
+ genres_available,
+ media_formats_available,
+ years_available,
+)
@click.command(
@@ -380,91 +26,27 @@ for tag_category, tags_in_category in tags_available.items():
@click.option(
"--season",
help="The season the media was released",
- type=click.Choice(["WINTER", "SPRING", "SUMMER", "FALL"]),
+ type=click.Choice(seasons_available),
)
@click.option(
"--status",
"-S",
help="The media status of the anime",
multiple=True,
- type=click.Choice(
- ["FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"]
- ),
+ type=click.Choice(media_statuses_available),
)
@click.option(
"--sort",
"-s",
help="What to sort the search results on",
- type=click.Choice(
- [
- "ID",
- "ID_DESC",
- "TITLE_ROMAJI",
- "TITLE_ROMAJI_DESC",
- "TITLE_ENGLISH",
- "TITLE_ENGLISH_DESC",
- "TITLE_NATIVE",
- "TITLE_NATIVE_DESC",
- "TYPE",
- "TYPE_DESC",
- "FORMAT",
- "FORMAT_DESC",
- "START_DATE",
- "START_DATE_DESC",
- "END_DATE",
- "END_DATE_DESC",
- "SCORE",
- "SCORE_DESC",
- "POPULARITY",
- "POPULARITY_DESC",
- "TRENDING",
- "TRENDING_DESC",
- "EPISODES",
- "EPISODES_DESC",
- "DURATION",
- "DURATION_DESC",
- "STATUS",
- "STATUS_DESC",
- "CHAPTERS",
- "CHAPTERS_DESC",
- "VOLUMES",
- "VOLUMES_DESC",
- "UPDATED_AT",
- "UPDATED_AT_DESC",
- "SEARCH_MATCH",
- "FAVOURITES",
- "FAVOURITES_DESC",
- ]
- ),
+ type=click.Choice(sorts_available),
)
@click.option(
"--genres",
"-g",
multiple=True,
help="the genres to filter by",
- type=click.Choice(
- [
- "Action",
- "Adventure",
- "Comedy",
- "Drama",
- "Ecchi",
- "Fantasy",
- "Horror",
- "Mahou Shoujo",
- "Mecha",
- "Music",
- "Mystery",
- "Psychological",
- "Romance",
- "Sci-Fi",
- "Slice of Life",
- "Sports",
- "Supernatural",
- "Thriller",
- "Hentai",
- ]
- ),
+ type=click.Choice(genres_available),
)
@click.option(
"--tags",
@@ -478,49 +60,12 @@ for tag_category, tags_in_category in tags_available.items():
"-f",
multiple=True,
help="Media format",
- type=click.Choice(
- ["TV", "TV_SHORT", "MOVIE", "SPECIAL", "OVA", "MUSIC", "NOVEL", "ONE_SHOT"]
- ),
+ type=click.Choice(media_formats_available),
)
@click.option(
"--year",
"-y",
- type=click.Choice(
- [
- "1900",
- "1910",
- "1920",
- "1930",
- "1940",
- "1950",
- "1960",
- "1970",
- "1980",
- "1990",
- "2000",
- "2004",
- "2005",
- "2006",
- "2007",
- "2008",
- "2009",
- "2010",
- "2011",
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- "2018",
- "2019",
- "2020",
- "2021",
- "2022",
- "2023",
- "2024",
- ]
- ),
+ type=click.Choice(years_available),
help="the year the media was released",
)
@click.option(
diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py
index 892ad47..ef56893 100644
--- a/fastanime/cli/config.py
+++ b/fastanime/cli/config.py
@@ -257,21 +257,7 @@ class Config(object):
# ╚═╝░░░░░╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░░░░╚═╝╚══════╝ ░╚════╝░░╚════╝░╚═╝░░╚══╝╚═╝░░░░░╚═╝░╚═════╝░
#
[general]
-# well recently somebody made a post @unixporn subreddit
-# the rice contained fastanime in preview mode
-# Did not even realise you rice with it
-# or i would have cross-posted
-# 'A new way to rice'
-# and immediately one of the mods removed it
-# am not sure why though
-# cause the preview is actually pretty good looking.
-# just like you would use fastfetch or cava
-# as means to protest against this injustice i have addded
-# the following
-# for you all ricers out there
-# and be sure to post a rice with fastanime on it
-# if you also think this is unfair
-
+# Can you rice it?
# for the preview pane
preview_separator_color = {self.preview_separator_color}
@@ -284,7 +270,6 @@ header_ascii_art = {new_line.join([tab+line for line in self.header_ascii_art.sp
header_color = {self.header_color}
# to be passed to fzf
-# may break it down further for now just pass the options as is
# be sure to indent
fzf_opts = {new_line.join([tab+line for line in self.fzf_opts.split(new_line)])}
@@ -336,8 +321,8 @@ downloads_dir = {self.downloads_dir}
# whether to show a preview window when using fzf or rofi [True/False]
# the preview requires you have a commandline image viewer as documented in the README
-# this is only when usinf fzf
-# if you dont care about image previews it doesnt matter
+# this is only when using fzf or rofi
+# if you dont care about image and text previews it doesnt matter
# though its awesome
# try it and you will see
preview = {self.preview}
@@ -352,8 +337,10 @@ image_previews = {self.image_previews}
# the time to seek when using ffmpegthumbnailer [-1 to 100]
# -1 means random and is the default
-# ffmpegthumbnailer is used to generate previews and you can select at what time in the video to extract an image
+# ffmpegthumbnailer is used to generate previews
+# and you can select at what time in the video to extract an image
# random makes things quite exciting cause you never no at what time it will extract the image from
+# used by the ```fastanime downloads``` command
ffmpegthumbnailer_seek_time = {self.ffmpegthumbnailer_seek_time}
# whether to use fzf as the interface for the anilist command and others. [True/False]
@@ -438,9 +425,12 @@ continue_from_history = {self.continue_from_history}
# which history to use [local/remote]
# local history means it will just use the watch history stored locally in your device
-# the file that stores it is called watch_history.json and is stored next to your config file
-# remote means it ignores the last episode stored locally and instead uses the one in your anilist anime list
-# this config option is useful if you want to overwrite your local history or import history covered from another device or platform
+# the file that stores it is called watch_history.json
+# and is stored next to your config file
+# remote means it ignores the last episode stored locally
+# and instead uses the one in your anilist anime list
+# this config option is useful if you want to overwrite your local history
+# or import history covered from another device or platform
# since remote history will take precendence over whats available locally
preferred_history = {self.preferred_history}
@@ -468,16 +458,13 @@ auto_next = {self.auto_next}
# this is because the providers sometime use non-standard names
# that are there own preference rather than the official names
# But 99% of the time will be accurate
-# if this happens just turn of auto_select in the menus or from the commandline and manually select the correct anime title
-# and then please open an issue
-# highlighting the normalized title
-# and the title given by the provider for the anime you wished to watch
-# or even better edit this file
-# and open a pull request
-# prefrably, so you can give me a small break
-# of doing everything 😄
-# and its always nice to see people contributing
-# to projects they love and use
+# if this happens just turn off auto_select in the menus or from the commandline
+# and manually select the correct anime title
+# edit this file
+# and to the dictionary of the provider
+# the provider title (key) and their corresponding anilist names (value)
+# and then please open a pr
+# issues on the same will be ignored and then closed 😆
auto_select = {self.auto_select}
# whether to skip the opening and ending theme songs [True/False]
@@ -497,7 +484,7 @@ episode_complete_at = {self.episode_complete_at}
# whether to use python-mpv [True/False]
# to enable superior control over the player
# adding more options to it
-# Enable this one and you will be wonder
+# Enabling this option and you will ask yourself
# why you did not discover fastanime sooner 🙃
# Since you basically don't have to close the player window
# to go to the next or previous episode, switch servers,
@@ -510,13 +497,13 @@ episode_complete_at = {self.episode_complete_at}
# personally it took me quite sometime to figure it out
# this is because of how windows handles shared libraries
# so just ask when you find yourself stuck
-# or just switch to arch linux
+# or just switch to nixos 😄
use_python_mpv = {self.use_python_mpv}
# whether to use popen to get the timestamps for continue_from_history
-# implemented because popen does not work for some reason in nixos
-# if you are on nixos and you have a solution to this problem please share
+# implemented because popen does not work for some reason in nixos and apparently on mac as well
+# if you are on nixos or mac and you have a solution to this problem please share
# i will be glad to hear it 😄
# So for now ignore this option
# and anyways the new method of getting timestamps is better
@@ -543,17 +530,14 @@ format = {self.format}
# since you will miss out on some features if you use the others
player = {self.player}
-# NOTE:
-# if you have any trouble setting up your config
-# please don't be afraid to ask in our discord
-# plus if there are any errors, improvements or suggestions please tell us in the discord
-# or help us by contributing
-# we appreciate all the help we can get
-# since we may not always have the time to immediately implement the changes
#
# HOPE YOU ENJOY FASTANIME AND BE SURE TO STAR THE PROJECT ON GITHUB
# https://github.com/Benex254/FastAnime
#
+# Also join the discord server
+# where the anime tech community lives :)
+# https://discord.gg/C4rhMA4mmK
+#
"""
return current_config_state
diff --git a/fastanime/cli/interfaces/utils.py b/fastanime/cli/interfaces/utils.py
index 4f28e8e..dc412b6 100644
--- a/fastanime/cli/interfaces/utils.py
+++ b/fastanime/cli/interfaces/utils.py
@@ -103,7 +103,6 @@ def write_search_results(
titles: sanitized anime titles
workers:number of threads to use defaults to as many as possible
"""
- # NOTE: Will probably make this a configuraable option
# use concurency to download and write as fast as possible
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
future_to_task = {}
@@ -285,7 +284,7 @@ def get_fzf_episode_preview(
anilist_results: the anilist results from an anilist action
"""
- HEADER_COLOR = 215, 0, 95
+ # HEADER_COLOR = 215, 0, 95
import re
def _worker():
@@ -293,18 +292,16 @@ def get_fzf_episode_preview(
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
+
for episode in episodes:
episode_title = ""
image_url = ""
for episode_detail in anilist_result["streamingEpisodes"]:
- if re.match(f"Episode {episode} ", episode_detail["title"]):
+ if re.match(f".*Episode {episode} .*", episode_detail["title"]):
episode_title = episode_detail["title"]
image_url = episode_detail["thumbnail"]
if episode_title and image_url:
- # actual link to download image from
- if not image_url:
- continue
future_to_url[
executor.submit(save_image_from_url, image_url, episode)
] = image_url
@@ -315,13 +312,25 @@ def get_fzf_episode_preview(
echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}"
((ll++))
done
- echo "{get_true_fg('Anime Title:',*HEADER_COLOR)} {(anilist_result['title']['romaji'] or anilist_result['title']['english']).replace('"',SINGLE_QUOTE)}"
- echo "{get_true_fg('Episode Title:',*HEADER_COLOR)} {str(episode_title).replace('"',SINGLE_QUOTE)}"
+ echo "{get_true_fg('Anime Title(eng):',*HEADER_COLOR)} {('' or anilist_result['title']['english']).replace('"',SINGLE_QUOTE)}"
+ echo "{get_true_fg('Anime Title(jp):',*HEADER_COLOR)} {(anilist_result['title']['romaji'] or '').replace('"',SINGLE_QUOTE)}"
+
+ ll=2
+ while [ $ll -le $FZF_PREVIEW_COLUMNS ];do
+ echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}"
+ ((ll++))
+ done
+ echo "{str(episode_title).replace('"',SINGLE_QUOTE)}"
+ ll=2
+ while [ $ll -le $FZF_PREVIEW_COLUMNS ];do
+ echo -n -e "{get_true_fg("─",*SEPARATOR_COLOR,bold=False)}"
+ ((ll++))
+ done
"""
)
future_to_url[
- executor.submit(save_info_from_str, template, episode)
- ] = episode_title
+ executor.submit(save_info_from_str, template, str(episode))
+ ] = str(episode)
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
@@ -371,14 +380,15 @@ def get_fzf_episode_preview(
)
else:
preview = """
+ title={}
%s
show_image_previews="%s"
if [ $show_image_previews = "true" ];then
- if [ -s %s/{} ]; then fzf-preview %s/{}
+ if [ -s %s/${title}.png ]; then fzf-preview %s/${title}.png
else echo Loading...
fi
fi
- if [ -s %s/{} ]; then source %s/{}
+ if [ -f %s/${title} ]; then source %s/${title}
else echo Loading...
fi
""" % (
diff --git a/fastanime/libs/anilist/api.py b/fastanime/libs/anilist/api.py
index 1b4f5e9..541f436 100644
--- a/fastanime/libs/anilist/api.py
+++ b/fastanime/libs/anilist/api.py
@@ -306,6 +306,7 @@ class AniListApi:
def search(
self,
+ max_results=50,
query: str | None = None,
sort: str | None = None,
genre_in: list[str] | None = None,
diff --git a/fastanime/libs/anilist/queries_graphql.py b/fastanime/libs/anilist/queries_graphql.py
index 68d65fe..6131055 100644
--- a/fastanime/libs/anilist/queries_graphql.py
+++ b/fastanime/libs/anilist/queries_graphql.py
@@ -281,6 +281,7 @@ query ($userId: Int, $status: MediaListStatus, $type: MediaType) {
optional_variables = "\
+$max_results:Int,\
$page:Int,\
$sort:[MediaSort],\
$id_in:[Int],\
@@ -310,7 +311,7 @@ $on_list:Boolean\
search_query = (
"""
query($query:String,%s){
- Page(perPage: 50, page: $page) {
+ Page(perPage: $max_results, page: $page) {
pageInfo {
total
currentPage
diff --git a/flake.nix b/flake.nix
index 76f47b5..3165f79 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,7 +14,7 @@
pythonPackages = python.pkgs;
fastanimeEnv = pythonPackages.buildPythonApplication {
pname = "fastanime";
- version = "2.8.0";
+ version = "2.8.3";
src = ./.;
diff --git a/make_release b/make_release
index 7de6b37..13d839d 100755
--- a/make_release
+++ b/make_release
@@ -5,7 +5,12 @@ VERSION=$1
[ "$VERSION" = "current" ] && fastanime --version && exit 0
sed -i "s/^version.*/version = \"$VERSION\"/" "$CLI_DIR/pyproject.toml" &&
sed -i "s/__version__.*/__version__ = \"v$VERSION\"/" "$CLI_DIR/fastanime/__init__.py" &&
- git stage "$CLI_DIR/pyproject.toml" "$CLI_DIR/fastanime/__init__.py" &&
+ sed -i "s/version = .*/version = \"$VERSION\";/" "$CLI_DIR/flake.nix" &&
+ git stage "$CLI_DIR/pyproject.toml" "$CLI_DIR/fastanime/__init__.py" "$CLI_DIR/flake.nix" &&
git commit -m "chore: bump version (v$VERSION)" &&
+ nix flake lock &&
+ uv lock &&
+ git stage "$CLI_DIR/flake.lock" "$CLI_DIR/uv.lock" &&
+ git commit -m "chore: update lock files" &&
git push &&
gh release create "v$VERSION"
diff --git a/pyproject.toml b/pyproject.toml
index ad02445..43a5387 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "fastanime"
-version = "2.8.1"
+version = "2.8.3"
description = "A browser anime site experience from the terminal"
license = "UNLICENSE"
readme = "README.md"
@@ -11,7 +11,7 @@ dependencies = [
"requests>=2.32.3",
"rich>=13.9.2",
"thefuzz>=0.22.1",
- "yt-dlp>=2024.10.7",
+ "yt-dlp[default]>=2024.10.7",
]
[project.scripts]
diff --git a/uv.lock b/uv.lock
index 74ec73b..37dd8f1 100644
--- a/uv.lock
+++ b/uv.lock
@@ -38,6 +38,98 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
]
+[[package]]
+name = "brotli"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6d/3a/dbf4fb970c1019a57b5e492e1e0eae745d32e59ba4d6161ab5422b08eefe/Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752", size = 873045 },
+ { url = "https://files.pythonhosted.org/packages/dd/11/afc14026ea7f44bd6eb9316d800d439d092c8d508752055ce8d03086079a/Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9", size = 446218 },
+ { url = "https://files.pythonhosted.org/packages/36/83/7545a6e7729db43cb36c4287ae388d6885c85a86dd251768a47015dfde32/Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3", size = 2903872 },
+ { url = "https://files.pythonhosted.org/packages/32/23/35331c4d9391fcc0f29fd9bec2c76e4b4eeab769afbc4b11dd2e1098fb13/Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d", size = 2941254 },
+ { url = "https://files.pythonhosted.org/packages/3b/24/1671acb450c902edb64bd765d73603797c6c7280a9ada85a195f6b78c6e5/Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e", size = 2857293 },
+ { url = "https://files.pythonhosted.org/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da", size = 3002385 },
+ { url = "https://files.pythonhosted.org/packages/b8/cb/8aaa83f7a4caa131757668c0fb0c4b6384b09ffa77f2fba9570d87ab587d/Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80", size = 2911104 },
+ { url = "https://files.pythonhosted.org/packages/bc/c4/65456561d89d3c49f46b7fbeb8fe6e449f13bdc8ea7791832c5d476b2faf/Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d", size = 2809981 },
+ { url = "https://files.pythonhosted.org/packages/05/1b/cf49528437bae28abce5f6e059f0d0be6fecdcc1d3e33e7c54b3ca498425/Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0", size = 2935297 },
+ { url = "https://files.pythonhosted.org/packages/81/ff/190d4af610680bf0c5a09eb5d1eac6e99c7c8e216440f9c7cfd42b7adab5/Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e", size = 2930735 },
+ { url = "https://files.pythonhosted.org/packages/80/7d/f1abbc0c98f6e09abd3cad63ec34af17abc4c44f308a7a539010f79aae7a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c", size = 2933107 },
+ { url = "https://files.pythonhosted.org/packages/34/ce/5a5020ba48f2b5a4ad1c0522d095ad5847a0be508e7d7569c8630ce25062/Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1", size = 2845400 },
+ { url = "https://files.pythonhosted.org/packages/44/89/fa2c4355ab1eecf3994e5a0a7f5492c6ff81dfcb5f9ba7859bd534bb5c1a/Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2", size = 3031985 },
+ { url = "https://files.pythonhosted.org/packages/af/a4/79196b4a1674143d19dca400866b1a4d1a089040df7b93b88ebae81f3447/Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec", size = 2927099 },
+ { url = "https://files.pythonhosted.org/packages/e9/54/1c0278556a097f9651e657b873ab08f01b9a9ae4cac128ceb66427d7cd20/Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2", size = 333172 },
+ { url = "https://files.pythonhosted.org/packages/f7/65/b785722e941193fd8b571afd9edbec2a9b838ddec4375d8af33a50b8dab9/Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128", size = 357255 },
+ { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068 },
+ { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244 },
+ { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500 },
+ { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950 },
+ { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527 },
+ { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489 },
+ { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080 },
+ { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051 },
+ { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172 },
+ { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023 },
+ { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871 },
+ { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784 },
+ { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905 },
+ { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467 },
+ { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169 },
+ { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253 },
+ { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693 },
+ { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489 },
+ { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081 },
+ { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244 },
+ { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505 },
+ { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152 },
+ { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252 },
+ { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955 },
+ { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304 },
+ { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452 },
+ { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751 },
+ { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757 },
+ { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146 },
+ { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055 },
+ { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102 },
+ { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029 },
+ { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276 },
+ { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255 },
+ { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681 },
+ { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475 },
+ { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173 },
+ { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803 },
+ { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946 },
+ { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707 },
+ { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231 },
+ { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157 },
+ { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122 },
+ { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206 },
+ { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804 },
+ { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517 },
+]
+
+[[package]]
+name = "brotlicffi"
+version = "1.1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/9d/70caa61192f570fcf0352766331b735afa931b4c6bc9a348a0925cc13288/brotlicffi-1.1.0.0.tar.gz", hash = "sha256:b77827a689905143f87915310b93b273ab17888fd43ef350d4832c4a71083c13", size = 465192 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a2/11/7b96009d3dcc2c931e828ce1e157f03824a69fb728d06bfd7b2fc6f93718/brotlicffi-1.1.0.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9b7ae6bd1a3f0df532b6d67ff674099a96d22bc0948955cb338488c31bfb8851", size = 453786 },
+ { url = "https://files.pythonhosted.org/packages/d6/e6/a8f46f4a4ee7856fbd6ac0c6fb0dc65ed181ba46cd77875b8d9bbe494d9e/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19ffc919fa4fc6ace69286e0a23b3789b4219058313cf9b45625016bf7ff996b", size = 2911165 },
+ { url = "https://files.pythonhosted.org/packages/be/20/201559dff14e83ba345a5ec03335607e47467b6633c210607e693aefac40/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feb210d932ffe7798ee62e6145d3a757eb6233aa9a4e7db78dd3690d7755814", size = 2927895 },
+ { url = "https://files.pythonhosted.org/packages/cd/15/695b1409264143be3c933f708a3f81d53c4a1e1ebbc06f46331decbf6563/brotlicffi-1.1.0.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84763dbdef5dd5c24b75597a77e1b30c66604725707565188ba54bab4f114820", size = 2851834 },
+ { url = "https://files.pythonhosted.org/packages/b4/40/b961a702463b6005baf952794c2e9e0099bde657d0d7e007f923883b907f/brotlicffi-1.1.0.0-cp37-abi3-win32.whl", hash = "sha256:1b12b50e07c3911e1efa3a8971543e7648100713d4e0971b13631cce22c587eb", size = 341731 },
+ { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783 },
+ { url = "https://files.pythonhosted.org/packages/e5/3b/bd4f3d2bcf2306ae66b0346f5b42af1962480b200096ffc7abc3bd130eca/brotlicffi-1.1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2e4aeb0bd2540cb91b069dbdd54d458da8c4334ceaf2d25df2f4af576d6766ca", size = 397397 },
+ { url = "https://files.pythonhosted.org/packages/54/10/1fd57864449360852c535c2381ee7120ba8f390aa3869df967c44ca7eba1/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b7b0033b0d37bb33009fb2fef73310e432e76f688af76c156b3594389d81391", size = 379698 },
+ { url = "https://files.pythonhosted.org/packages/e5/95/15aa422aa6450e6556e54a5fd1650ff59f470aed77ac739aa90ab63dc611/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54a07bb2374a1eba8ebb52b6fafffa2afd3c4df85ddd38fcc0511f2bb387c2a8", size = 378635 },
+ { url = "https://files.pythonhosted.org/packages/6c/a7/f254e13b2cb43337d6d99a4ec10394c134e41bfda8a2eff15b75627f4a3d/brotlicffi-1.1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7901a7dc4b88f1c1475de59ae9be59799db1007b7d059817948d8e4f12e24e35", size = 385719 },
+ { url = "https://files.pythonhosted.org/packages/72/a9/0971251c4427c14b2a827dba3d910d4d3330dabf23d4278bf6d06a978847/brotlicffi-1.1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce01c7316aebc7fce59da734286148b1d1b9455f89cf2c8a4dfce7d41db55c2d", size = 361760 },
+]
+
[[package]]
name = "certifi"
version = "2024.8.30"
@@ -47,6 +139,63 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
]
+[[package]]
+name = "cffi"
+version = "1.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 },
+ { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 },
+ { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 },
+ { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 },
+ { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 },
+ { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 },
+ { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 },
+ { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 },
+ { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 },
+ { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 },
+ { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 },
+ { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 },
+ { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 },
+ { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 },
+ { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 },
+ { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 },
+ { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 },
+ { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 },
+ { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 },
+ { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 },
+ { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 },
+ { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 },
+ { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 },
+ { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 },
+ { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 },
+ { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 },
+ { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 },
+ { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 },
+ { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 },
+ { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 },
+ { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 },
+ { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 },
+ { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 },
+ { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 },
+ { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 },
+ { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 },
+ { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 },
+ { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 },
+ { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 },
+ { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 },
+ { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 },
+ { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 },
+ { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 },
+ { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 },
+ { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 },
+ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 },
+]
+
[[package]]
name = "charset-normalizer"
version = "3.4.0"
@@ -170,7 +319,7 @@ wheels = [
[[package]]
name = "fastanime"
-version = "2.8.0"
+version = "2.8.3"
source = { editable = "." }
dependencies = [
{ name = "click" },
@@ -178,7 +327,7 @@ dependencies = [
{ name = "requests" },
{ name = "rich" },
{ name = "thefuzz" },
- { name = "yt-dlp" },
+ { name = "yt-dlp", extra = ["default"] },
]
[package.optional-dependencies]
@@ -218,7 +367,7 @@ requires-dist = [
{ name = "requests", specifier = ">=2.32.3" },
{ name = "rich", specifier = ">=13.9.2" },
{ name = "thefuzz", specifier = ">=0.22.1" },
- { name = "yt-dlp", specifier = ">=2024.10.7" },
+ { name = "yt-dlp", extras = ["default"], specifier = ">=2024.10.7" },
]
[package.metadata.requires-dev]
@@ -488,6 +637,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/aa/3f/d835556e34804cd0078507ed0f8a550f15d2861b875656193dd3451b720b/mpv-1.0.7-py3-none-any.whl", hash = "sha256:520fb134c18185b69c7fce4aa3514f14371028022d92eb193818e9fefb1e9fe8", size = 45257 },
]
+[[package]]
+name = "mutagen"
+version = "1.47.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/e6/64bc71b74eef4b68e61eb921dcf72dabd9e4ec4af1e11891bbd312ccbb77/mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99", size = 1274186 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/7a/620f945b96be1f6ee357d211d5bf74ab1b7fe72a9f1525aafbfe3aee6875/mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719", size = 194391 },
+]
+
[[package]]
name = "nodeenv"
version = "1.9.1"
@@ -554,6 +712,39 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/6a/fd08d94654f7e67c52ca30523a178b3f8ccc4237fce4be90d39c938a831a/prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e", size = 386595 },
]
+[[package]]
+name = "pycparser"
+version = "2.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
+]
+
+[[package]]
+name = "pycryptodomex"
+version = "3.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/dc/e66551683ade663b5f07d7b3bc46434bf703491dbd22ee12d1f979ca828f/pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c", size = 4818543 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/5e/99f217d9881eead69607a2248dd7bbdf610837d7f5ad53f45a6cb71bbbfb/pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e", size = 2499490 },
+ { url = "https://files.pythonhosted.org/packages/ce/8f/4d0e2a859a6470289d64e39b419f01d2494dfa2e4995342d50f6c2834237/pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516", size = 1638037 },
+ { url = "https://files.pythonhosted.org/packages/0c/9e/6e748c1fa814c956d356f93cf7192b19487ca56fc9e2a0bcde2bbc057601/pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3", size = 2172279 },
+ { url = "https://files.pythonhosted.org/packages/46/3f/f5bef92b11750af9e3516d4e69736eeeff20a2818d34611508bef5a7b381/pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2", size = 2258130 },
+ { url = "https://files.pythonhosted.org/packages/de/4d/f0c65afd64ce435fd0547187ce6f99dfb37cdde16b05b57bca9f5c06966e/pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b", size = 2297719 },
+ { url = "https://files.pythonhosted.org/packages/1c/6a/2a1a101b0345ee70376ba93df8de6c8c01aac8341fda02970800873456a7/pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce", size = 2164079 },
+ { url = "https://files.pythonhosted.org/packages/3d/00/90a15f16c234815b660303c2d7266b41b401ea2605f3a90373e9d425e39f/pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a", size = 2333060 },
+ { url = "https://files.pythonhosted.org/packages/61/74/49f5d20c514ccc631b940cc9dfec45dcce418dc84a98463a2e2ebec33904/pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e", size = 2257982 },
+ { url = "https://files.pythonhosted.org/packages/92/4b/d33ef74e2cc0025a259936661bb53432c5bbbadc561c5f2e023bcd73ce4c/pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e", size = 1779052 },
+ { url = "https://files.pythonhosted.org/packages/5b/be/7c991840af1184009fc86267160948350d1bf875f153c97bb471ad944e40/pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0", size = 1816307 },
+ { url = "https://files.pythonhosted.org/packages/af/ac/24125ad36778914a36f08d61ba5338cb9159382c638d9761ee19c8de822c/pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8", size = 1694999 },
+ { url = "https://files.pythonhosted.org/packages/93/73/be7a54a5903508070e5508925ba94493a1f326cfeecfff750e3eb250ea28/pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c", size = 1769437 },
+ { url = "https://files.pythonhosted.org/packages/e5/9f/39a6187f3986841fa6a9f35c6fdca5030ef73ff708b45a993813a51d7d10/pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31", size = 1619607 },
+ { url = "https://files.pythonhosted.org/packages/f8/70/60bb08e9e9841b18d4669fb69d84b64ce900aacd7eb0ebebd4c7b9bdecd3/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3", size = 1653571 },
+ { url = "https://files.pythonhosted.org/packages/c9/6f/191b73509291c5ff0dddec9cc54797b1d73303c12b2e4017b24678e57099/pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37", size = 1691548 },
+ { url = "https://files.pythonhosted.org/packages/2d/c7/a0d3356f3074ac548afefa515ff46f3bea011deca607faf1c09b26dd5330/pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e", size = 1792099 },
+]
+
[[package]]
name = "pydantic"
version = "2.9.2"
@@ -1205,3 +1396,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/52/50/0014e9099a9dc3dec
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/e4/e45c5067a79780954b905db4a42aa83d7aaefd91e32b18ab91c77600e668/yt_dlp-2024.11.4-py3-none-any.whl", hash = "sha256:589d51ed9f154624a45c1f0ceb3d68d0d1e2031460e8dbc62139be631c20b388", size = 3165645 },
]
+
+[package.optional-dependencies]
+default = [
+ { name = "brotli", marker = "implementation_name == 'cpython'" },
+ { name = "brotlicffi", marker = "implementation_name != 'cpython'" },
+ { name = "certifi" },
+ { name = "mutagen" },
+ { name = "pycryptodomex" },
+ { name = "requests" },
+ { name = "urllib3" },
+ { name = "websockets" },
+]