anime-sama-api 0.2.2.post2__tar.gz → 0.2.2.post3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/PKG-INFO +6 -2
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/README.md +4 -1
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/TODO.md +15 -13
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/__init__.py +3 -3
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/catalogue.py +3 -4
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/__main__.py +24 -14
- anime_sama_api-0.2.2.post3/anime_sama_api/cli/config.py +119 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/downloader.py +42 -46
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/episode_extra_info.py +29 -5
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/internal_player.py +4 -4
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/play_menu.py +1 -1
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/utils.py +2 -2
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/episode.py +3 -3
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/langs.py +1 -2
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/season.py +5 -5
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/top_level.py +6 -7
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/utils.py +8 -2
- anime_sama_api-0.2.2.post3/debug_vidmoly.py +42 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/pyproject.toml +3 -2
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/uv.lock +33 -22
- anime_sama_api-0.2.2.post2/anime_sama_api/cli/config.py +0 -110
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/.github/workflows/ci.yaml +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/.github/workflows/release.yaml +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/.gitignore +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/.python-version +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/LICENSE +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/config.toml +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/error_handeling.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/exemples/new_episodes_rss.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/exemples/run_cli_(for_debugging).py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/__init__.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/data/catalogue_data.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/data/episode_data.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/data/season_data.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_catalogue.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_cli_utils.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_download.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_seasons.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_top_level.py +0 -0
- {anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anime-sama_api
|
|
3
|
-
Version: 0.2.2.
|
|
3
|
+
Version: 0.2.2.post3
|
|
4
4
|
Summary: Api for anime-sama
|
|
5
5
|
Project-URL: Repository, https://github.com/Sky-NiniKo/anime-sama_api/
|
|
6
6
|
Author-email: NiniKo <pcninik@gmail.com>
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: System :: Archiving
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: httpx>=0.28.1
|
|
23
23
|
Provides-Extra: cli
|
|
24
|
+
Requires-Dist: platformdirs>=4.5.1; extra == 'cli'
|
|
24
25
|
Requires-Dist: rich>=14.2.0; extra == 'cli'
|
|
25
26
|
Requires-Dist: tomli>=2.2.1; (python_full_version < '3.11') and extra == 'cli'
|
|
26
27
|
Requires-Dist: yt-dlp>=2025.8.22; extra == 'cli'
|
|
@@ -47,7 +48,10 @@ anime-sama
|
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
## Configuration
|
|
50
|
-
You can customize the config at
|
|
51
|
+
You can customize the config at :
|
|
52
|
+
- Linux: `~/.config/anime-sama_cli/config.toml`
|
|
53
|
+
- Windows: `%USER%/AppData/Local/anime-sama_cli/config.toml`
|
|
54
|
+
- macOS: `~/Library/Application Support/anime-sama_api/config.toml`
|
|
51
55
|
|
|
52
56
|
# For developers
|
|
53
57
|
## Requirements
|
|
@@ -19,7 +19,10 @@ anime-sama
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Configuration
|
|
22
|
-
You can customize the config at
|
|
22
|
+
You can customize the config at :
|
|
23
|
+
- Linux: `~/.config/anime-sama_cli/config.toml`
|
|
24
|
+
- Windows: `%USER%/AppData/Local/anime-sama_cli/config.toml`
|
|
25
|
+
- macOS: `~/Library/Application Support/anime-sama_api/config.toml`
|
|
23
26
|
|
|
24
27
|
# For developers
|
|
25
28
|
## Requirements
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
- [
|
|
2
|
-
- [
|
|
3
|
-
- [
|
|
4
|
-
- [
|
|
5
|
-
- [
|
|
6
|
-
- [
|
|
7
|
-
- [
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
1
|
+
- [x] Search
|
|
2
|
+
- [x] Download
|
|
3
|
+
- [x] Play
|
|
4
|
+
- [x] Name Episode
|
|
5
|
+
- [x] All language
|
|
6
|
+
- [x] Order Episode
|
|
7
|
+
- [x] Separate backend
|
|
8
|
+
- [x] Fallback on other players
|
|
9
|
+
- [x] Retry players
|
|
10
|
+
- [x] Config
|
|
11
11
|
- [ ] Tests (ie: search that doesn't exist)
|
|
12
|
-
- [
|
|
12
|
+
- [x] Make a package
|
|
13
13
|
- [ ] Implement all NotImplemented
|
|
14
|
-
- [
|
|
14
|
+
- [x] Auto detect download folder and config folder, see https://stackoverflow.com/questions/35851281/python-finding-the-users-downloads-folder and appdirs (pypi)
|
|
15
15
|
- [ ] Add new {} for episode_path
|
|
16
|
-
- [
|
|
16
|
+
- [x] Select a range of seasons to download
|
|
17
17
|
- [ ] Take args in cli
|
|
18
18
|
- [ ] Cache players link for offline use
|
|
19
19
|
- [ ] Nix?
|
|
@@ -26,3 +26,5 @@
|
|
|
26
26
|
- [ ] MAL sync
|
|
27
27
|
- [ ] Play from download
|
|
28
28
|
- [ ] Discord presence
|
|
29
|
+
|
|
30
|
+
- [ ] Seven deadly sins S1 EP 26 "Episode 26 interrupted (ERROR: unable to download video data: HTTP Error 416: Requested Range Not Satisfiable). Retrying in 256s."
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from .top_level import AnimeSama, find_site_url
|
|
2
1
|
from .catalogue import Catalogue
|
|
3
|
-
from .season import Season
|
|
4
2
|
from .episode import Episode, Languages, Players
|
|
5
|
-
from .langs import Lang, LangId,
|
|
3
|
+
from .langs import Lang, LangId, flags, id2lang, lang2ids
|
|
4
|
+
from .season import Season
|
|
5
|
+
from .top_level import AnimeSama, find_site_url
|
|
6
6
|
|
|
7
7
|
try:
|
|
8
8
|
from .cli.__main__ import main
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
from collections.abc import Sequence
|
|
2
1
|
import re
|
|
2
|
+
from collections.abc import Sequence
|
|
3
3
|
from typing import Any, Literal, cast
|
|
4
4
|
|
|
5
5
|
from httpx import AsyncClient
|
|
6
6
|
|
|
7
|
-
from .
|
|
7
|
+
from .langs import Lang, flags
|
|
8
8
|
from .season import Season
|
|
9
|
-
from .
|
|
10
|
-
|
|
9
|
+
from .utils import remove_some_js_comments
|
|
11
10
|
|
|
12
11
|
# Oversight from anime-sama that we should handle
|
|
13
12
|
# 'Animes' instead of 'Anime' seen in Cyberpunk: Edgerunners and Valkyrie Apocalypse
|
|
@@ -7,12 +7,11 @@ from rich import get_console
|
|
|
7
7
|
from rich.logging import RichHandler
|
|
8
8
|
from rich.status import Status
|
|
9
9
|
|
|
10
|
-
from . import downloader, internal_player
|
|
11
|
-
from .config import config
|
|
12
|
-
from .episode_extra_info import convert_with_extra_info
|
|
13
|
-
from .utils import safe_input, select_one, select_range
|
|
14
|
-
|
|
15
|
-
from ..top_level import AnimeSama, find_site_url
|
|
10
|
+
from anime_sama_api.cli import downloader, internal_player
|
|
11
|
+
from anime_sama_api.cli.config import config
|
|
12
|
+
from anime_sama_api.cli.episode_extra_info import convert_with_extra_info
|
|
13
|
+
from anime_sama_api.cli.utils import safe_input, select_one, select_range
|
|
14
|
+
from anime_sama_api.top_level import AnimeSama, find_site_url
|
|
16
15
|
|
|
17
16
|
console = get_console()
|
|
18
17
|
console._highlight = False
|
|
@@ -37,16 +36,27 @@ async def async_main() -> None:
|
|
|
37
36
|
catalogue = select_one(catalogues)
|
|
38
37
|
|
|
39
38
|
with spinner(f"Getting season list for [blue]{catalogue.name}"):
|
|
40
|
-
|
|
41
|
-
season = select_one(seasons)
|
|
39
|
+
seasons_list = await catalogue.seasons()
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
episodes = await season.episodes()
|
|
41
|
+
selected_seasons = select_range(seasons_list, msg="Choose season(s)")
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
if len(selected_seasons) > 1:
|
|
44
|
+
# Multi-season automatic download
|
|
45
|
+
selected_episodes = []
|
|
46
|
+
for s in selected_seasons:
|
|
47
|
+
with spinner(f"Getting episode list for [blue]{s.name}"):
|
|
48
|
+
episodes = await s.episodes()
|
|
49
|
+
selected_episodes.extend(episodes)
|
|
50
|
+
else:
|
|
51
|
+
# Single season behavior (manual episode selection)
|
|
52
|
+
season = selected_seasons[0]
|
|
53
|
+
with spinner(f"Getting episode list for [blue]{season.name}"):
|
|
54
|
+
episodes = await season.episodes()
|
|
55
|
+
|
|
56
|
+
console.print(f"\n[cyan bold underline]{season.serie_name} - {season.name}")
|
|
57
|
+
selected_episodes = select_range(
|
|
58
|
+
episodes, msg="Choose episode(s)", print_choices=True
|
|
59
|
+
)
|
|
50
60
|
|
|
51
61
|
if config.download:
|
|
52
62
|
downloader.multi_download(
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# TODO: Allway hard to read but I can't find a better way to do it
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# import platformdirs for config_dir and download_dir
|
|
8
|
+
from platformdirs import user_config_dir, user_downloads_dir
|
|
9
|
+
|
|
10
|
+
from anime_sama_api.langs import Lang, lang2ids
|
|
11
|
+
|
|
12
|
+
if sys.version_info >= (3, 11):
|
|
13
|
+
import tomllib
|
|
14
|
+
else:
|
|
15
|
+
import tomli as tomllib
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class PlayersConfig:
|
|
22
|
+
prefers: list[str] = field(default_factory=list)
|
|
23
|
+
bans: list[str] = field(default_factory=list)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class Config:
|
|
28
|
+
prefer_languages: list[Lang]
|
|
29
|
+
download_path: Path
|
|
30
|
+
episode_path: str
|
|
31
|
+
download: bool
|
|
32
|
+
show_players: bool
|
|
33
|
+
max_retry_time: int
|
|
34
|
+
format: str
|
|
35
|
+
format_sort: str
|
|
36
|
+
internal_player_command: list[str]
|
|
37
|
+
url: str
|
|
38
|
+
provider_url: str
|
|
39
|
+
players_config: PlayersConfig
|
|
40
|
+
concurrent_downloads: dict[str, int]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_config() -> Config:
|
|
44
|
+
# 1. Load default config from the package
|
|
45
|
+
default_config_file = Path(__file__).parent / "config.toml"
|
|
46
|
+
if not default_config_file.exists():
|
|
47
|
+
raise FileNotFoundError(
|
|
48
|
+
f"The default config.toml could not be found at {default_config_file}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with open(default_config_file, "rb") as f:
|
|
52
|
+
config_dict = tomllib.load(f)
|
|
53
|
+
|
|
54
|
+
# 2. Determine user config path
|
|
55
|
+
# Order of priority:
|
|
56
|
+
# - current directory (./config.toml)
|
|
57
|
+
# - system config directory (e.g. ~/AppData/Local/anime-sama_api/config.toml)
|
|
58
|
+
local_config = Path("config.toml")
|
|
59
|
+
system_config_dir = Path(user_config_dir("anime-sama_api", appauthor=False))
|
|
60
|
+
system_config_file = system_config_dir / "config.toml"
|
|
61
|
+
|
|
62
|
+
user_config_data = {}
|
|
63
|
+
if local_config.exists():
|
|
64
|
+
with open(local_config, "rb") as f:
|
|
65
|
+
user_config_data = tomllib.load(f)
|
|
66
|
+
elif system_config_file.exists():
|
|
67
|
+
with open(system_config_file, "rb") as f:
|
|
68
|
+
user_config_data = tomllib.load(f)
|
|
69
|
+
else:
|
|
70
|
+
# Recreate default config if not found anywhere
|
|
71
|
+
from shutil import copy
|
|
72
|
+
|
|
73
|
+
system_config_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
copy(default_config_file, system_config_file)
|
|
75
|
+
logger.info("Default config created at %s", system_config_file)
|
|
76
|
+
|
|
77
|
+
# 3. Merge configs (user values override defaults)
|
|
78
|
+
config_dict.update(user_config_data)
|
|
79
|
+
|
|
80
|
+
# 4. Backward compatibility and data cleaning
|
|
81
|
+
# Languages conversion
|
|
82
|
+
new_langs: list[Lang] = []
|
|
83
|
+
for lang in config_dict.get("prefer_languages", []):
|
|
84
|
+
if lang == "VO":
|
|
85
|
+
lang = "VOSTFR"
|
|
86
|
+
if lang in lang2ids:
|
|
87
|
+
new_langs.append(lang)
|
|
88
|
+
else:
|
|
89
|
+
logger.warning("'%s' is not a valid language, ignoring it.", lang)
|
|
90
|
+
config_dict["prefer_languages"] = new_langs
|
|
91
|
+
|
|
92
|
+
# Path detection and expansion
|
|
93
|
+
raw_path = config_dict.get("download_path")
|
|
94
|
+
if not raw_path:
|
|
95
|
+
# Default to system Downloads / Anime-Sama if not specified
|
|
96
|
+
config_dict["download_path"] = Path(user_downloads_dir()) / "Anime-Sama"
|
|
97
|
+
else:
|
|
98
|
+
config_dict["download_path"] = Path(raw_path).expanduser()
|
|
99
|
+
|
|
100
|
+
# Internal player command string to list
|
|
101
|
+
player_cmd = config_dict.get("internal_player_command")
|
|
102
|
+
if isinstance(player_cmd, str):
|
|
103
|
+
config_dict["internal_player_command"] = player_cmd.split()
|
|
104
|
+
elif player_cmd is None:
|
|
105
|
+
config_dict["internal_player_command"] = []
|
|
106
|
+
|
|
107
|
+
# Players config mapping
|
|
108
|
+
players_data = config_dict.pop("players_hostname", {})
|
|
109
|
+
if "players" in config_dict: # Old key removal
|
|
110
|
+
del config_dict["players"]
|
|
111
|
+
config_dict["players_config"] = PlayersConfig(**players_data)
|
|
112
|
+
|
|
113
|
+
# Ensure all required keys are present for Config dataclass
|
|
114
|
+
# (Checking against keys in Config.__annotations__ if necessary)
|
|
115
|
+
return Config(**config_dict)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Exported config instance
|
|
119
|
+
config = load_config()
|
|
@@ -1,35 +1,33 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import random
|
|
2
3
|
import time
|
|
3
|
-
import logging
|
|
4
4
|
from concurrent.futures import ThreadPoolExecutor
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import cast
|
|
7
7
|
from urllib.parse import urlparse
|
|
8
8
|
|
|
9
|
-
import httpx
|
|
10
|
-
from yt_dlp import YoutubeDL
|
|
11
|
-
from yt_dlp.utils import DownloadError
|
|
12
9
|
from rich import get_console
|
|
13
|
-
from rich.live import Live
|
|
14
10
|
from rich.console import Group
|
|
15
|
-
from rich.
|
|
11
|
+
from rich.live import Live
|
|
16
12
|
from rich.progress import (
|
|
17
13
|
BarColumn,
|
|
14
|
+
MofNCompleteColumn,
|
|
18
15
|
Progress,
|
|
16
|
+
ProgressColumn,
|
|
17
|
+
TaskID,
|
|
19
18
|
TextColumn,
|
|
20
19
|
TimeRemainingColumn,
|
|
21
|
-
TransferSpeedColumn,
|
|
22
|
-
MofNCompleteColumn,
|
|
23
|
-
TaskID,
|
|
24
20
|
TotalFileSizeColumn,
|
|
25
|
-
|
|
21
|
+
TransferSpeedColumn,
|
|
26
22
|
)
|
|
23
|
+
from rich.table import Column
|
|
24
|
+
from yt_dlp import YoutubeDL
|
|
25
|
+
from yt_dlp.utils import DownloadError
|
|
27
26
|
|
|
28
|
-
from .
|
|
29
|
-
from .
|
|
30
|
-
from
|
|
31
|
-
from .
|
|
32
|
-
|
|
27
|
+
from anime_sama_api.langs import Lang
|
|
28
|
+
from anime_sama_api.cli.config import PlayersConfig, config
|
|
29
|
+
from anime_sama_api.cli.episode_extra_info import EpisodeWithExtraInfo
|
|
30
|
+
from anime_sama_api.cli.error_handeling import YDL_log_filter, reaction_to
|
|
33
31
|
|
|
34
32
|
logger = logging.getLogger(__name__)
|
|
35
33
|
logger.addFilter(YDL_log_filter)
|
|
@@ -69,13 +67,18 @@ def download(
|
|
|
69
67
|
episode: EpisodeWithExtraInfo,
|
|
70
68
|
path: Path,
|
|
71
69
|
episode_path: str = "{episode}",
|
|
72
|
-
prefer_languages: list[Lang] =
|
|
73
|
-
players_config: PlayersConfig =
|
|
70
|
+
prefer_languages: list[Lang] | None = None,
|
|
71
|
+
players_config: PlayersConfig | None = None,
|
|
74
72
|
concurrent_fragment_downloads: int = 3,
|
|
75
73
|
max_retry_time: int = 1024,
|
|
76
|
-
|
|
74
|
+
video_format: str = "",
|
|
77
75
|
format_sort: str = "",
|
|
78
76
|
) -> None:
|
|
77
|
+
if prefer_languages is None:
|
|
78
|
+
prefer_languages = ["VOSTFR"]
|
|
79
|
+
if players_config is None:
|
|
80
|
+
players_config = PlayersConfig([], [])
|
|
81
|
+
|
|
79
82
|
if not any(episode.warpped.languages.values()):
|
|
80
83
|
logger.error("No player available")
|
|
81
84
|
return
|
|
@@ -83,7 +86,7 @@ def download(
|
|
|
83
86
|
me = download_progress.add_task(
|
|
84
87
|
"download", episode_name=episode.warpped.name, site="", total=None
|
|
85
88
|
)
|
|
86
|
-
task = download_progress.tasks
|
|
89
|
+
task = next(t for t in download_progress.tasks if t.id == me)
|
|
87
90
|
|
|
88
91
|
full_path = (
|
|
89
92
|
path
|
|
@@ -108,7 +111,7 @@ def download(
|
|
|
108
111
|
"concurrent_fragment_downloads": concurrent_fragment_downloads,
|
|
109
112
|
"progress_hooks": [hook],
|
|
110
113
|
"logger": logger,
|
|
111
|
-
"format":
|
|
114
|
+
"format": video_format,
|
|
112
115
|
"format_sort": format_sort.split(","),
|
|
113
116
|
}
|
|
114
117
|
|
|
@@ -120,17 +123,6 @@ def download(
|
|
|
120
123
|
download_progress.update(me, site=urlparse(player).hostname)
|
|
121
124
|
|
|
122
125
|
while True:
|
|
123
|
-
# Check if the video is not accessible through vidmoly
|
|
124
|
-
try:
|
|
125
|
-
if (
|
|
126
|
-
player.startswith("https://vidmoly.")
|
|
127
|
-
and "Please wait" # Note "Please wait" appear in all the player page
|
|
128
|
-
not in httpx.get(player, headers={"User-Agent": ""}).text
|
|
129
|
-
):
|
|
130
|
-
break
|
|
131
|
-
except httpx.ConnectError:
|
|
132
|
-
break
|
|
133
|
-
|
|
134
126
|
try:
|
|
135
127
|
with YoutubeDL(option) as ydl: # type: ignore
|
|
136
128
|
error_code = cast(int, ydl.download([player]))
|
|
@@ -139,20 +131,14 @@ def download(
|
|
|
139
131
|
sucess = True
|
|
140
132
|
else:
|
|
141
133
|
logger.fatal(
|
|
142
|
-
|
|
134
|
+
"The download encountered an error code %s. Please report this to the developer with URL: %s",
|
|
135
|
+
error_code,
|
|
136
|
+
player,
|
|
143
137
|
)
|
|
144
138
|
|
|
145
139
|
break
|
|
146
140
|
|
|
147
141
|
except DownloadError as exception:
|
|
148
|
-
# yt-dlp thinks vidmoly is unsupported but it just need wait
|
|
149
|
-
if (
|
|
150
|
-
player.startswith("https://vidmoly.")
|
|
151
|
-
and exception.msg is not None
|
|
152
|
-
and "Unsupported URL: https://vidmoly.net/" in exception.msg
|
|
153
|
-
):
|
|
154
|
-
exception.msg = "Waiting for vidmoly"
|
|
155
|
-
|
|
156
142
|
match reaction_to(exception.msg):
|
|
157
143
|
case "continue":
|
|
158
144
|
break
|
|
@@ -162,7 +148,10 @@ def download(
|
|
|
162
148
|
break
|
|
163
149
|
|
|
164
150
|
logger.warning(
|
|
165
|
-
|
|
151
|
+
"%s interrupted (%s). Retrying in %ss.",
|
|
152
|
+
episode.warpped.name,
|
|
153
|
+
exception.msg,
|
|
154
|
+
retry_time,
|
|
166
155
|
)
|
|
167
156
|
# random is used to spread the resume time and so mitigate deadlock when multiple downloads resume at the same time
|
|
168
157
|
time.sleep(retry_time * random.uniform(0.8, 1.2))
|
|
@@ -190,13 +179,20 @@ def multi_download(
|
|
|
190
179
|
episodes: list[EpisodeWithExtraInfo],
|
|
191
180
|
path: Path,
|
|
192
181
|
episode_path: str = "{episode}",
|
|
193
|
-
concurrent_downloads: dict[str, int] =
|
|
194
|
-
prefer_languages: list[Lang] =
|
|
195
|
-
players_config: PlayersConfig =
|
|
182
|
+
concurrent_downloads: dict[str, int] | None = None,
|
|
183
|
+
prefer_languages: list[Lang] | None = None,
|
|
184
|
+
players_config: PlayersConfig | None = None,
|
|
196
185
|
max_retry_time: int = 1024,
|
|
197
|
-
|
|
186
|
+
video_format: str = "",
|
|
198
187
|
format_sort: str = "",
|
|
199
188
|
) -> None:
|
|
189
|
+
if concurrent_downloads is None:
|
|
190
|
+
concurrent_downloads = {}
|
|
191
|
+
if prefer_languages is None:
|
|
192
|
+
prefer_languages = ["VOSTFR"]
|
|
193
|
+
if players_config is None:
|
|
194
|
+
players_config = PlayersConfig([], [])
|
|
195
|
+
|
|
200
196
|
"""
|
|
201
197
|
Not sure if you can use this function multiple times
|
|
202
198
|
"""
|
|
@@ -215,6 +211,6 @@ def multi_download(
|
|
|
215
211
|
players_config,
|
|
216
212
|
concurrent_downloads.get("fragment", 1),
|
|
217
213
|
max_retry_time,
|
|
218
|
-
|
|
214
|
+
video_format,
|
|
219
215
|
format_sort,
|
|
220
216
|
)
|
{anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/episode_extra_info.py
RENAMED
|
@@ -7,9 +7,9 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from .
|
|
10
|
+
from anime_sama_api.catalogue import Catalogue
|
|
11
|
+
from anime_sama_api.cli.utils import normalize
|
|
12
|
+
from anime_sama_api.episode import Episode
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclass(frozen=True)
|
|
@@ -68,12 +68,20 @@ def _get_mal_listing(serie: Catalogue) -> None | Any:
|
|
|
68
68
|
for name in [serie.name] + list(serie.alternative_names):
|
|
69
69
|
i = 0
|
|
70
70
|
while True:
|
|
71
|
-
|
|
71
|
+
try:
|
|
72
|
+
response = httpx.get(
|
|
73
|
+
f"https://api.jikan.moe/v4/anime?q={name}&limit=5", timeout=10
|
|
74
|
+
)
|
|
75
|
+
except httpx.HTTPError:
|
|
76
|
+
return None
|
|
77
|
+
|
|
72
78
|
i += 1
|
|
73
79
|
if response.status_code != 429 or i > 9:
|
|
74
80
|
break
|
|
75
81
|
|
|
76
|
-
response.
|
|
82
|
+
if response.status_code != 200:
|
|
83
|
+
return None
|
|
84
|
+
|
|
77
85
|
animes = response.json().get("data", [])
|
|
78
86
|
|
|
79
87
|
for anime in animes:
|
|
@@ -110,3 +118,19 @@ def _get_mal_listing(serie: Catalogue) -> None | Any:
|
|
|
110
118
|
return anime
|
|
111
119
|
|
|
112
120
|
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
import asyncio
|
|
125
|
+
from anime_sama_api import find_site_url, AnimeSama
|
|
126
|
+
|
|
127
|
+
async def main():
|
|
128
|
+
url = await find_site_url()
|
|
129
|
+
|
|
130
|
+
if url is None:
|
|
131
|
+
raise
|
|
132
|
+
|
|
133
|
+
async for catalogue in AnimeSama(url).catalogues_iter():
|
|
134
|
+
print(catalogue.name, get_serie_release_date(catalogue))
|
|
135
|
+
|
|
136
|
+
asyncio.run(main())
|
{anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/internal_player.py
RENAMED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import sys
|
|
3
2
|
import subprocess
|
|
3
|
+
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from rich import print
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from .
|
|
10
|
-
from
|
|
8
|
+
from anime_sama_api.cli.config import config
|
|
9
|
+
from anime_sama_api.episode import Episode
|
|
10
|
+
from anime_sama_api.langs import Lang
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def open_silent_process(command: list[str]) -> subprocess.Popen[bytes]:
|
|
@@ -67,8 +67,8 @@ def select_range(
|
|
|
67
67
|
return [choices[0]]
|
|
68
68
|
|
|
69
69
|
def transform(string: str) -> list[T]:
|
|
70
|
-
# Return all choices if the user enter '*' symbol
|
|
71
|
-
if string
|
|
70
|
+
# Return all choices if the user enter '*' symbol or 'all'
|
|
71
|
+
if string in ("*", "all"):
|
|
72
72
|
return choices
|
|
73
73
|
# Else, detect the string and transform it into a list of choices
|
|
74
74
|
ints_set: set[int] = set()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from collections.abc import Generator, Sequence
|
|
2
|
-
import re
|
|
3
1
|
import logging
|
|
2
|
+
import re
|
|
3
|
+
from collections.abc import Generator, Sequence
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import Any
|
|
6
6
|
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
|
-
from .langs import
|
|
8
|
+
from .langs import Lang, LangId, flags, id2lang, lang2ids
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
Lang = Literal["VASTFR", "VCN", "VF", "VJSTFR", "VKR", "VQC", "VOSTFR"]
|
|
5
4
|
LangId = Literal["va", "vcn", "vf", "vf1", "vf2", "vj", "vkr", "vqc", "vostfr"]
|
|
6
5
|
FlagId = Literal["cn", "qc", "en", "pal", "kr", "fr", "jp"]
|
|
@@ -44,8 +43,8 @@ for language, language_ids in lang2ids.items():
|
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
if __name__ == "__main__":
|
|
47
|
-
import re
|
|
48
46
|
import asyncio
|
|
47
|
+
import re
|
|
49
48
|
from pprint import pprint
|
|
50
49
|
|
|
51
50
|
import httpx
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
1
3
|
from ast import literal_eval
|
|
2
4
|
from dataclasses import dataclass, replace
|
|
3
5
|
from functools import reduce
|
|
4
|
-
import re
|
|
5
|
-
import asyncio
|
|
6
6
|
from typing import Any, cast, get_args
|
|
7
7
|
|
|
8
8
|
from httpx import AsyncClient
|
|
9
9
|
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
12
|
-
from .utils import remove_some_js_comments,
|
|
10
|
+
from .episode import Episode, Languages, Players
|
|
11
|
+
from .langs import LangId, flagid2lang, lang2ids
|
|
12
|
+
from .utils import remove_some_js_comments, split_and_strip, zip_varlen
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@dataclass
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from collections.abc import AsyncIterator, Generator
|
|
3
|
-
from html import unescape
|
|
4
|
-
from dataclasses import dataclass
|
|
5
2
|
import logging
|
|
6
3
|
import re
|
|
4
|
+
from collections.abc import AsyncIterator, Generator
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from html import unescape
|
|
7
7
|
from typing import Any, cast
|
|
8
8
|
|
|
9
9
|
from httpx import AsyncClient
|
|
10
10
|
|
|
11
|
+
from .catalogue import Catalogue, Category
|
|
11
12
|
from .episode import Episode
|
|
12
|
-
from .season import Season
|
|
13
13
|
from .langs import Lang, flags
|
|
14
|
+
from .season import Season
|
|
14
15
|
from .utils import filter_literal, is_Literal
|
|
15
|
-
from .catalogue import Catalogue, Category
|
|
16
|
-
|
|
17
16
|
|
|
18
17
|
logger = logging.getLogger(__name__)
|
|
19
18
|
|
|
@@ -28,7 +27,7 @@ async def find_site_url(
|
|
|
28
27
|
if response.is_error:
|
|
29
28
|
return None
|
|
30
29
|
|
|
31
|
-
match = re.search(r"href=\"(.+?)\">Accéder à
|
|
30
|
+
match = re.search(r"href=\"(.+?)\">Accéder à anime-sama", response.text)
|
|
32
31
|
|
|
33
32
|
if match:
|
|
34
33
|
return match.group(1) + "/"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from typing import Any, TypeVar, cast, get_args
|
|
3
|
-
from itertools import zip_longest
|
|
4
2
|
from collections.abc import Callable, Generator, Iterable, Sequence
|
|
3
|
+
from itertools import zip_longest
|
|
4
|
+
from typing import Any, TypeVar, cast, get_args
|
|
5
5
|
|
|
6
6
|
T = TypeVar("T")
|
|
7
7
|
|
|
@@ -43,6 +43,9 @@ def is_Literal(
|
|
|
43
43
|
callback_when_false(value)
|
|
44
44
|
return False
|
|
45
45
|
|
|
46
|
+
# def is_Literal(value: Any, Lit: Any) -> bool:
|
|
47
|
+
# return value in get_args(Lit)
|
|
48
|
+
|
|
46
49
|
|
|
47
50
|
def filter_literal(
|
|
48
51
|
iterable: Iterable[Any],
|
|
@@ -50,3 +53,6 @@ def filter_literal(
|
|
|
50
53
|
callback_when_false: Callable[[Any], None] = lambda _: None,
|
|
51
54
|
) -> Generator[T]:
|
|
52
55
|
return (value for value in iterable if is_Literal(value, Lit, callback_when_false))
|
|
56
|
+
|
|
57
|
+
# def filter_literal(iterable: Iterable[Any], Lit: T) -> Generator[T, None, None]:
|
|
58
|
+
# return (value for value in iterable if is_Literal(value, Lit))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Script de débogage pour comprendre ce que VidMoly retourne
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
if len(sys.argv) < 2:
|
|
9
|
+
print("Usage: python debug_vidmoly.py <vidmoly_url>")
|
|
10
|
+
sys.exit(1)
|
|
11
|
+
|
|
12
|
+
url = sys.argv[1]
|
|
13
|
+
|
|
14
|
+
print(f"Fetching: {url}")
|
|
15
|
+
print("=" * 80)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
response = httpx.get(
|
|
19
|
+
url, headers={"User-Agent": ""}, timeout=10.0, follow_redirects=True
|
|
20
|
+
)
|
|
21
|
+
print(f"Status Code: {response.status_code}")
|
|
22
|
+
print(f"Final URL: {response.url}")
|
|
23
|
+
print("=" * 80)
|
|
24
|
+
print("Response Text (first 2000 chars):")
|
|
25
|
+
print(response.text[:2000])
|
|
26
|
+
print("=" * 80)
|
|
27
|
+
|
|
28
|
+
# Check specific criteria
|
|
29
|
+
print("\nChecking criteria:")
|
|
30
|
+
print(f" 'Please wait' found: {'Please wait' in response.text}")
|
|
31
|
+
print(
|
|
32
|
+
f" 'please wait' found (lowercase): {'please wait' in response.text.lower()}"
|
|
33
|
+
)
|
|
34
|
+
print(f" Response length: {len(response.text)} chars")
|
|
35
|
+
|
|
36
|
+
# Save full response to file for inspection
|
|
37
|
+
with open("vidmoly_response.html", "w", encoding="utf-8") as f:
|
|
38
|
+
f.write(response.text)
|
|
39
|
+
print("\nFull response saved to: vidmoly_response.html")
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"Error: {type(e).__name__}: {e}")
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "anime-sama_api"
|
|
7
|
-
version = "0.2.2-
|
|
7
|
+
version = "0.2.2-3"
|
|
8
8
|
description = "Api for anime-sama"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "NiniKo", email = "pcninik@gmail.com"}
|
|
@@ -39,6 +39,7 @@ Repository = "https://github.com/Sky-NiniKo/anime-sama_api/"
|
|
|
39
39
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
41
|
cli = [
|
|
42
|
+
"platformdirs>=4.5.1",
|
|
42
43
|
"rich>=14.2.0",
|
|
43
44
|
"tomli>=2.2.1 ; python_full_version < '3.11'",
|
|
44
45
|
"yt-dlp>=2025.8.22",
|
|
@@ -46,7 +47,7 @@ cli = [
|
|
|
46
47
|
|
|
47
48
|
[dependency-groups]
|
|
48
49
|
dev = [
|
|
49
|
-
"ruff>=0.14.
|
|
50
|
+
"ruff>=0.14.13",
|
|
50
51
|
"mypy>=1.19.1",
|
|
51
52
|
"pytest>=9.0.2",
|
|
52
53
|
"pytest-asyncio>=1.3.0",
|
|
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "anime-sama-api"
|
|
7
|
-
version = "0.2.2.
|
|
7
|
+
version = "0.2.2.post3"
|
|
8
8
|
source = { editable = "." }
|
|
9
9
|
dependencies = [
|
|
10
10
|
{ name = "httpx" },
|
|
@@ -12,6 +12,7 @@ dependencies = [
|
|
|
12
12
|
|
|
13
13
|
[package.optional-dependencies]
|
|
14
14
|
cli = [
|
|
15
|
+
{ name = "platformdirs" },
|
|
15
16
|
{ name = "rich" },
|
|
16
17
|
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
17
18
|
{ name = "yt-dlp" },
|
|
@@ -30,6 +31,7 @@ dev = [
|
|
|
30
31
|
[package.metadata]
|
|
31
32
|
requires-dist = [
|
|
32
33
|
{ name = "httpx", specifier = ">=0.28.1" },
|
|
34
|
+
{ name = "platformdirs", marker = "extra == 'cli'", specifier = ">=4.5.1" },
|
|
33
35
|
{ name = "rich", marker = "extra == 'cli'", specifier = ">=14.2.0" },
|
|
34
36
|
{ name = "tomli", marker = "python_full_version < '3.11' and extra == 'cli'", specifier = ">=2.2.1" },
|
|
35
37
|
{ name = "yt-dlp", marker = "extra == 'cli'", specifier = ">=2025.8.22" },
|
|
@@ -42,7 +44,7 @@ dev = [
|
|
|
42
44
|
{ name = "pytest", specifier = ">=9.0.2" },
|
|
43
45
|
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
|
44
46
|
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
|
45
|
-
{ name = "ruff", specifier = ">=0.14.
|
|
47
|
+
{ name = "ruff", specifier = ">=0.14.13" },
|
|
46
48
|
{ name = "yt-dlp-types", specifier = ">=0.0.18" },
|
|
47
49
|
]
|
|
48
50
|
|
|
@@ -423,6 +425,15 @@ wheels = [
|
|
|
423
425
|
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
|
424
426
|
]
|
|
425
427
|
|
|
428
|
+
[[package]]
|
|
429
|
+
name = "platformdirs"
|
|
430
|
+
version = "4.5.1"
|
|
431
|
+
source = { registry = "https://pypi.org/simple" }
|
|
432
|
+
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
|
433
|
+
wheels = [
|
|
434
|
+
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
|
435
|
+
]
|
|
436
|
+
|
|
426
437
|
[[package]]
|
|
427
438
|
name = "pluggy"
|
|
428
439
|
version = "1.5.0"
|
|
@@ -502,28 +513,28 @@ wheels = [
|
|
|
502
513
|
|
|
503
514
|
[[package]]
|
|
504
515
|
name = "ruff"
|
|
505
|
-
version = "0.14.
|
|
516
|
+
version = "0.14.13"
|
|
506
517
|
source = { registry = "https://pypi.org/simple" }
|
|
507
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
518
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" }
|
|
508
519
|
wheels = [
|
|
509
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
510
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
511
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
512
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
513
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
514
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
515
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
516
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
517
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
518
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
519
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
520
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
521
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
522
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
523
|
-
{ url = "https://files.pythonhosted.org/packages/3b/
|
|
524
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
525
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
526
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
520
|
+
{ url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" },
|
|
521
|
+
{ url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" },
|
|
522
|
+
{ url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" },
|
|
523
|
+
{ url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" },
|
|
524
|
+
{ url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" },
|
|
525
|
+
{ url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" },
|
|
526
|
+
{ url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" },
|
|
527
|
+
{ url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" },
|
|
528
|
+
{ url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" },
|
|
529
|
+
{ url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" },
|
|
530
|
+
{ url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" },
|
|
531
|
+
{ url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" },
|
|
532
|
+
{ url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" },
|
|
533
|
+
{ url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" },
|
|
534
|
+
{ url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" },
|
|
535
|
+
{ url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" },
|
|
536
|
+
{ url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" },
|
|
537
|
+
{ url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" },
|
|
527
538
|
]
|
|
528
539
|
|
|
529
540
|
[[package]]
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# TODO: Code hard to read / violent error, need refactor
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
|
|
9
|
-
from ..langs import Lang, lang2ids
|
|
10
|
-
|
|
11
|
-
if sys.version_info >= (3, 11):
|
|
12
|
-
import tomllib
|
|
13
|
-
else:
|
|
14
|
-
import tomli as tomllib
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@dataclass(frozen=True)
|
|
21
|
-
class PlayersConfig:
|
|
22
|
-
prefers: list[str]
|
|
23
|
-
bans: list[str]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass(frozen=True)
|
|
27
|
-
class Config:
|
|
28
|
-
prefer_languages: list[Lang]
|
|
29
|
-
download_path: Path
|
|
30
|
-
episode_path: str
|
|
31
|
-
download: bool
|
|
32
|
-
show_players: bool
|
|
33
|
-
max_retry_time: int
|
|
34
|
-
format: str
|
|
35
|
-
format_sort: str
|
|
36
|
-
internal_player_command: list[str]
|
|
37
|
-
url: str
|
|
38
|
-
provider_url: str
|
|
39
|
-
players_config: PlayersConfig
|
|
40
|
-
concurrent_downloads: dict[str, int]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Load default config
|
|
44
|
-
exemple_config = Path(__file__).parent / "config.toml"
|
|
45
|
-
if not exemple_config.exists():
|
|
46
|
-
raise FileNotFoundError(
|
|
47
|
-
"There is an issues with the installation of the package.\nThe exemple config cannot be found."
|
|
48
|
-
)
|
|
49
|
-
with open(exemple_config, "rb") as file:
|
|
50
|
-
default_config = tomllib.load(file)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Load user config
|
|
54
|
-
possible_path_str = ["."]
|
|
55
|
-
possible_path_str.append(
|
|
56
|
-
"~/AppData/Local/anime-sama_api" if os.name == "nt" else "~/.config/anime-sama_cli"
|
|
57
|
-
)
|
|
58
|
-
possible_path = [Path(path).expanduser() for path in possible_path_str]
|
|
59
|
-
del possible_path_str
|
|
60
|
-
|
|
61
|
-
user_config = {}
|
|
62
|
-
for path in possible_path:
|
|
63
|
-
config_file = path / "config.toml"
|
|
64
|
-
if config_file.is_file():
|
|
65
|
-
with open(config_file, "rb") as config_file_reader:
|
|
66
|
-
user_config = tomllib.load(config_file_reader)
|
|
67
|
-
break
|
|
68
|
-
else:
|
|
69
|
-
from shutil import copy
|
|
70
|
-
|
|
71
|
-
possible_path[1].mkdir(parents=True, exist_ok=True)
|
|
72
|
-
copy(exemple_config, possible_path[1])
|
|
73
|
-
logger.info("Default config created at %s", possible_path[1])
|
|
74
|
-
del possible_path
|
|
75
|
-
|
|
76
|
-
# Update the default values by values set by the user
|
|
77
|
-
config_dict = default_config | user_config
|
|
78
|
-
|
|
79
|
-
# Check if value respect the type
|
|
80
|
-
for index, lang in enumerate(config_dict["prefer_languages"]):
|
|
81
|
-
# Backward compatibility
|
|
82
|
-
if lang == "VO":
|
|
83
|
-
config_dict["prefer_languages"][index] = "VOSTFR"
|
|
84
|
-
lang = "VOSTFR"
|
|
85
|
-
|
|
86
|
-
assert lang in lang2ids, (
|
|
87
|
-
f"{lang} is not a valid languages for prefer_languages\nOnly the following are acceptable: {list(lang2ids.keys())}"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# Convert type
|
|
91
|
-
config_dict["download_path"] = (
|
|
92
|
-
Path(config_dict["download_path"])
|
|
93
|
-
if config_dict.get("download_path") is not None
|
|
94
|
-
else ""
|
|
95
|
-
)
|
|
96
|
-
config_dict["internal_player_command"] = (
|
|
97
|
-
config_dict["internal_player_command"].split()
|
|
98
|
-
if config_dict.get("internal_player_command") is not None
|
|
99
|
-
else ""
|
|
100
|
-
)
|
|
101
|
-
config_dict["players_config"] = (
|
|
102
|
-
PlayersConfig(**config_dict["players_hostname"])
|
|
103
|
-
if config_dict.get("players_hostname") is not None
|
|
104
|
-
else PlayersConfig([], [])
|
|
105
|
-
)
|
|
106
|
-
del config_dict["players_hostname"]
|
|
107
|
-
if config_dict.get("players"): # Backward compatibility
|
|
108
|
-
del config_dict["players"]
|
|
109
|
-
config = Config(**config_dict)
|
|
110
|
-
del config_dict
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/anime_sama_api/cli/error_handeling.py
RENAMED
|
File without changes
|
|
File without changes
|
{anime_sama_api-0.2.2.post2 → anime_sama_api-0.2.2.post3}/exemples/run_cli_(for_debugging).py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|