anipy-cli 3.1.10__tar.gz → 3.2.0__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.
Potentially problematic release.
This version of anipy-cli might be problematic. Click here for more details.
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/PKG-INFO +2 -2
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/pyproject.toml +2 -2
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/__init__.py +1 -1
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/download_cli.py +5 -44
- anipy_cli-3.2.0/src/anipy_cli/download_component.py +166 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/menus/mal_menu.py +23 -43
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/menus/menu.py +4 -1
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/menus/seasonal_menu.py +21 -43
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/util.py +10 -9
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/README.md +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/arg_parser.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/__init__.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/base_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/binge_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/default_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/history_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/mal_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/clis/seasonal_cli.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/colors.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/config.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/discord.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/mal_proxy.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/menus/__init__.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/menus/base_menu.py +0 -0
- {anipy_cli-3.1.10 → anipy_cli-3.2.0}/src/anipy_cli/prompts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anipy-cli
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Watch and Download anime from the comfort of your Terminal
|
|
5
5
|
Home-page: https://sdaqo.github.io/anipy-cli
|
|
6
6
|
License: GPL-3.0
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Dist: anipy-api (>=3.
|
|
17
|
+
Requires-Dist: anipy-api (>=3.2.0,<4.0.0)
|
|
18
18
|
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
|
19
19
|
Requires-Dist: inquirerpy (>=0.3.4,<0.4.0)
|
|
20
20
|
Requires-Dist: pypresence (>=4.3.0,<5.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anipy-cli"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "Watch and Download anime from the comfort of your Terminal"
|
|
5
5
|
authors = ["sdaqo <sdaqo.dev@protonmail.com>"]
|
|
6
6
|
license = "GPL-3.0"
|
|
@@ -20,7 +20,7 @@ yaspin = "^3.0.2"
|
|
|
20
20
|
inquirerpy = "^0.3.4"
|
|
21
21
|
appdirs = "^1.4.4"
|
|
22
22
|
pypresence = "^4.3.0"
|
|
23
|
-
anipy-api = "^3.
|
|
23
|
+
anipy-api = "^3.2.0"
|
|
24
24
|
|
|
25
25
|
[tool.poetry.scripts]
|
|
26
26
|
anipy-cli = "anipy_cli.cli:run_cli"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__appname__ = "anipy-cli"
|
|
2
|
-
__version__ = "3.
|
|
2
|
+
__version__ = "3.2.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Optional, List
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from anipy_cli.download_component import DownloadComponent
|
|
4
4
|
|
|
5
5
|
from anipy_cli.clis.base_cli import CliBase
|
|
6
6
|
from anipy_cli.colors import colors, cprint
|
|
@@ -12,10 +12,6 @@ from anipy_cli.prompts import (
|
|
|
12
12
|
lang_prompt,
|
|
13
13
|
parse_auto_search,
|
|
14
14
|
)
|
|
15
|
-
from anipy_cli.util import (
|
|
16
|
-
DotSpinner,
|
|
17
|
-
get_download_path,
|
|
18
|
-
)
|
|
19
15
|
|
|
20
16
|
if TYPE_CHECKING:
|
|
21
17
|
from anipy_api.anime import Anime
|
|
@@ -73,45 +69,10 @@ class DownloadCli(CliBase):
|
|
|
73
69
|
assert self.anime is not None
|
|
74
70
|
assert self.lang is not None
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
s.set_text(f"Progress: {percentage:.1f}%")
|
|
81
|
-
|
|
82
|
-
def info_display(message: str):
|
|
83
|
-
s.write(f"> {message}")
|
|
84
|
-
|
|
85
|
-
downloader = Downloader(progress_indicator, info_display)
|
|
86
|
-
|
|
87
|
-
for e in self.episodes:
|
|
88
|
-
s.set_text(
|
|
89
|
-
"Extracting streams for ",
|
|
90
|
-
colors.BLUE,
|
|
91
|
-
f"{self.anime.name} ({self.lang})",
|
|
92
|
-
colors.END,
|
|
93
|
-
" Episode ",
|
|
94
|
-
e,
|
|
95
|
-
"...",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
stream = self.anime.get_video(
|
|
99
|
-
e, self.lang, preferred_quality=self.options.quality
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
info_display(
|
|
103
|
-
f"Downloading Episode {stream.episode} of {self.anime.name} ({self.lang})"
|
|
104
|
-
)
|
|
105
|
-
s.set_text("Downloading...")
|
|
106
|
-
|
|
107
|
-
downloader.download(
|
|
108
|
-
stream,
|
|
109
|
-
get_download_path(
|
|
110
|
-
self.anime, stream, parent_directory=self.dl_path
|
|
111
|
-
),
|
|
112
|
-
container=config.remux_to,
|
|
113
|
-
ffmpeg=self.options.ffmpeg or config.ffmpeg_hls,
|
|
114
|
-
)
|
|
72
|
+
errors = DownloadComponent(self.options, self.dl_path).download_anime(
|
|
73
|
+
[(self.anime, self.lang, self.episodes)], only_skip_ep_on_err=True
|
|
74
|
+
)
|
|
75
|
+
DownloadComponent.serve_download_errors(errors, only_skip_ep_on_err=True)
|
|
115
76
|
|
|
116
77
|
def show(self):
|
|
117
78
|
pass
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import List, Protocol, Tuple
|
|
3
|
+
from anipy_cli.arg_parser import CliArgs
|
|
4
|
+
from anipy_cli.colors import color, colors
|
|
5
|
+
from anipy_cli.config import Config
|
|
6
|
+
from anipy_cli.util import DotSpinner, get_download_path
|
|
7
|
+
|
|
8
|
+
from anipy_api.anime import Anime
|
|
9
|
+
from anipy_api.download import Downloader
|
|
10
|
+
from anipy_api.provider.base import Episode, LanguageTypeEnum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SuccessfulEpDownload(Protocol):
|
|
14
|
+
"""
|
|
15
|
+
Callback for when an episode successfully downloads
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __call__(self, anime: Anime, ep: Episode, lang: LanguageTypeEnum):
|
|
19
|
+
"""
|
|
20
|
+
Args:
|
|
21
|
+
anime: The relevant anime
|
|
22
|
+
ep: An int/float for the episode
|
|
23
|
+
lang: The language that downloaded
|
|
24
|
+
"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DownloadComponent:
|
|
29
|
+
"""
|
|
30
|
+
A component used to download anime for
|
|
31
|
+
the ani-py CLI.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, cliArgs: CliArgs, dl_path: Path) -> None:
|
|
35
|
+
self.options = cliArgs
|
|
36
|
+
self.dl_path = dl_path
|
|
37
|
+
|
|
38
|
+
def download_anime(
|
|
39
|
+
self,
|
|
40
|
+
picked: List[Tuple[Anime, LanguageTypeEnum, List[Episode]]],
|
|
41
|
+
after_success_ep: SuccessfulEpDownload = lambda anime, ep, lang: None,
|
|
42
|
+
only_skip_ep_on_err: bool = False,
|
|
43
|
+
) -> List[Tuple[Anime, Episode]]:
|
|
44
|
+
"""
|
|
45
|
+
Attributes:
|
|
46
|
+
picked: The chosen animes to download
|
|
47
|
+
after_success_ep: The code to run when an anime successful downloads
|
|
48
|
+
only_skip_ep_on_err: If we should skip the specific episode on an error. If false, we skip the entire anime.
|
|
49
|
+
"""
|
|
50
|
+
with DotSpinner("Starting download...") as s:
|
|
51
|
+
|
|
52
|
+
def progress_indicator(percentage: float):
|
|
53
|
+
s.set_text(f"Progress: {percentage:.1f}%")
|
|
54
|
+
|
|
55
|
+
def info_display(message: str):
|
|
56
|
+
s.write(f"> {message}")
|
|
57
|
+
|
|
58
|
+
def error_display(message: str):
|
|
59
|
+
s.write(color(colors.RED, "! ", message))
|
|
60
|
+
|
|
61
|
+
downloader = Downloader(progress_indicator, info_display, error_display)
|
|
62
|
+
|
|
63
|
+
failed: List[Tuple[Anime, Episode]] = []
|
|
64
|
+
|
|
65
|
+
for anime, lang, eps in picked:
|
|
66
|
+
failed = self.download_episodes(
|
|
67
|
+
s,
|
|
68
|
+
downloader,
|
|
69
|
+
anime,
|
|
70
|
+
lang,
|
|
71
|
+
eps,
|
|
72
|
+
after_success_ep,
|
|
73
|
+
only_skip_ep_on_err,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return failed
|
|
77
|
+
|
|
78
|
+
def download_episodes(
|
|
79
|
+
self,
|
|
80
|
+
spinner: DotSpinner,
|
|
81
|
+
downloader: Downloader,
|
|
82
|
+
anime: Anime,
|
|
83
|
+
lang: LanguageTypeEnum,
|
|
84
|
+
eps: List[Episode],
|
|
85
|
+
after_success_ep: SuccessfulEpDownload = lambda anime, ep, lang: None,
|
|
86
|
+
only_skip_ep_on_err: bool = False,
|
|
87
|
+
) -> List[Tuple[Anime, Episode]]:
|
|
88
|
+
fails = []
|
|
89
|
+
for ep in eps:
|
|
90
|
+
try:
|
|
91
|
+
self.download_ep(spinner, downloader, anime, lang, ep)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
if only_skip_ep_on_err:
|
|
94
|
+
error_msg = f"! Issues downloading episode {ep} of {anime.name}. Skipping..."
|
|
95
|
+
else:
|
|
96
|
+
error_msg = f"! Issues occurred while downloading the series ${anime.name}. Skipping..."
|
|
97
|
+
spinner.write(
|
|
98
|
+
color(
|
|
99
|
+
colors.RED,
|
|
100
|
+
f"! Error: {e}\n",
|
|
101
|
+
error_msg,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
fails.append((anime, ep))
|
|
105
|
+
if only_skip_ep_on_err:
|
|
106
|
+
continue
|
|
107
|
+
return fails
|
|
108
|
+
|
|
109
|
+
after_success_ep(anime, ep, lang)
|
|
110
|
+
return fails
|
|
111
|
+
|
|
112
|
+
def download_ep(
|
|
113
|
+
self,
|
|
114
|
+
spinner: DotSpinner,
|
|
115
|
+
downloader: Downloader,
|
|
116
|
+
anime: Anime,
|
|
117
|
+
lang: LanguageTypeEnum,
|
|
118
|
+
ep: Episode,
|
|
119
|
+
):
|
|
120
|
+
config = Config()
|
|
121
|
+
|
|
122
|
+
spinner.set_text(
|
|
123
|
+
"Extracting streams for ",
|
|
124
|
+
colors.BLUE,
|
|
125
|
+
f"{anime.name} ({lang})",
|
|
126
|
+
colors.END,
|
|
127
|
+
" Episode ",
|
|
128
|
+
ep,
|
|
129
|
+
"...",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
stream = anime.get_video(ep, lang, preferred_quality=self.options.quality)
|
|
133
|
+
|
|
134
|
+
spinner.write(
|
|
135
|
+
f"> Downloading Episode {stream.episode} of {anime.name} ({lang})"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
spinner.set_text("Downloading...")
|
|
139
|
+
|
|
140
|
+
downloader.download(
|
|
141
|
+
stream,
|
|
142
|
+
get_download_path(anime, stream, parent_directory=self.dl_path),
|
|
143
|
+
container=config.remux_to,
|
|
144
|
+
ffmpeg=self.options.ffmpeg or config.ffmpeg_hls,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def serve_download_errors(
|
|
149
|
+
failures: List[Tuple[Anime, Episode]],
|
|
150
|
+
only_skip_ep_on_err: bool = False,
|
|
151
|
+
):
|
|
152
|
+
if not failures:
|
|
153
|
+
return
|
|
154
|
+
text = ", ".join([f"\n\tEpisode {i[1]} of {i[0].name}" for i in failures])
|
|
155
|
+
extra_info = (
|
|
156
|
+
" (and the remaining episodes in that series)"
|
|
157
|
+
if not only_skip_ep_on_err
|
|
158
|
+
else ""
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
print(
|
|
162
|
+
color(
|
|
163
|
+
colors.RED,
|
|
164
|
+
f"! Unable to download the following episodes{extra_info}: {text}",
|
|
165
|
+
)
|
|
166
|
+
)
|
|
@@ -2,8 +2,9 @@ import sys
|
|
|
2
2
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
3
3
|
from typing import Dict, List, Tuple
|
|
4
4
|
|
|
5
|
+
from anipy_cli.download_component import DownloadComponent
|
|
6
|
+
|
|
5
7
|
from anipy_api.anime import Anime
|
|
6
|
-
from anipy_api.download import Downloader
|
|
7
8
|
from anipy_api.mal import MALAnime, MALMyListStatusEnum, MyAnimeList
|
|
8
9
|
from anipy_api.provider import LanguageTypeEnum
|
|
9
10
|
from anipy_api.provider.base import Episode
|
|
@@ -23,7 +24,6 @@ from anipy_cli.util import (
|
|
|
23
24
|
error,
|
|
24
25
|
find_closest,
|
|
25
26
|
get_configured_player,
|
|
26
|
-
get_download_path,
|
|
27
27
|
migrate_locallist,
|
|
28
28
|
)
|
|
29
29
|
from anipy_cli.prompts import search_show_prompt
|
|
@@ -249,7 +249,6 @@ class MALMenu(MenuBase):
|
|
|
249
249
|
|
|
250
250
|
def download(self, all: bool = False):
|
|
251
251
|
picked = self._choose_latest(all=all)
|
|
252
|
-
config = Config()
|
|
253
252
|
total_eps = sum([len(e) for a, m, lang, e in picked])
|
|
254
253
|
if total_eps == 0:
|
|
255
254
|
print("Nothing to download, returning...")
|
|
@@ -257,49 +256,26 @@ class MALMenu(MenuBase):
|
|
|
257
256
|
else:
|
|
258
257
|
print(f"Downloading a total of {total_eps} episode(s)")
|
|
259
258
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def info_display(message: str):
|
|
266
|
-
s.write(f"> {message}")
|
|
267
|
-
|
|
268
|
-
downloader = Downloader(progress_indicator, info_display)
|
|
269
|
-
|
|
270
|
-
for anime, mal_anime, lang, eps in picked:
|
|
271
|
-
for ep in eps:
|
|
272
|
-
s.set_text(
|
|
273
|
-
"Extracting streams for ",
|
|
274
|
-
colors.BLUE,
|
|
275
|
-
f"{anime.name} ({lang})",
|
|
276
|
-
colors.END,
|
|
277
|
-
" Episode ",
|
|
278
|
-
ep,
|
|
279
|
-
"...",
|
|
280
|
-
)
|
|
259
|
+
convert: Dict[Anime, MALAnime] = {d[0]: d[1] for d in picked}
|
|
260
|
+
new_picked = [
|
|
261
|
+
(anime_info[0], anime_info[2], anime_info[3]) for anime_info in picked
|
|
262
|
+
]
|
|
281
263
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
264
|
+
def on_successful_download(anime: Anime, ep: Episode, lang: LanguageTypeEnum):
|
|
265
|
+
if all:
|
|
266
|
+
return
|
|
267
|
+
self.mal_proxy.update_show(
|
|
268
|
+
convert[anime],
|
|
269
|
+
status=MALMyListStatusEnum.WATCHING,
|
|
270
|
+
episode=int(ep),
|
|
271
|
+
)
|
|
285
272
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
273
|
+
errors = DownloadComponent(self.options, self.dl_path).download_anime(
|
|
274
|
+
new_picked, on_successful_download
|
|
275
|
+
)
|
|
276
|
+
DownloadComponent.serve_download_errors(errors)
|
|
290
277
|
|
|
291
|
-
|
|
292
|
-
stream,
|
|
293
|
-
get_download_path(anime, stream, parent_directory=self.dl_path),
|
|
294
|
-
container=config.remux_to,
|
|
295
|
-
ffmpeg=self.options.ffmpeg or config.ffmpeg_hls,
|
|
296
|
-
)
|
|
297
|
-
if not all:
|
|
298
|
-
self.mal_proxy.update_show(
|
|
299
|
-
mal_anime,
|
|
300
|
-
status=MALMyListStatusEnum.WATCHING,
|
|
301
|
-
episode=int(ep),
|
|
302
|
-
)
|
|
278
|
+
self.print_options(clear_screen=len(errors) == 0)
|
|
303
279
|
|
|
304
280
|
def binge_latest(self):
|
|
305
281
|
picked = self._choose_latest()
|
|
@@ -417,6 +393,10 @@ class MALMenu(MenuBase):
|
|
|
417
393
|
if e.my_list_status.num_episodes_watched == e.num_episodes:
|
|
418
394
|
mylist.pop(i)
|
|
419
395
|
|
|
396
|
+
if not mylist:
|
|
397
|
+
error("MAL is empty", False)
|
|
398
|
+
return []
|
|
399
|
+
|
|
420
400
|
if not (all or self.options.auto_update):
|
|
421
401
|
choices = inquirer.fuzzy( # type: ignore
|
|
422
402
|
message="Select shows to catch up to:",
|
|
@@ -211,7 +211,10 @@ class Menu(MenuBase):
|
|
|
211
211
|
def info_display(message: str):
|
|
212
212
|
s.write(f"> {message}")
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
def error_display(message: str):
|
|
215
|
+
s.write(f"{colors.RED}! {message}{colors.END}")
|
|
216
|
+
|
|
217
|
+
downloader = Downloader(progress_indicator, info_display, error_display)
|
|
215
218
|
|
|
216
219
|
s.set_text(
|
|
217
220
|
"Extracting streams for ",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from typing import TYPE_CHECKING, List, Tuple
|
|
3
3
|
|
|
4
|
+
from anipy_cli.download_component import DownloadComponent
|
|
5
|
+
|
|
4
6
|
from anipy_api.anime import Anime
|
|
5
|
-
from anipy_api.download import Downloader
|
|
6
7
|
from anipy_api.provider import LanguageTypeEnum
|
|
7
8
|
from anipy_api.provider.base import Episode
|
|
8
9
|
from anipy_api.locallist import LocalList, LocalListEntry
|
|
@@ -17,7 +18,6 @@ from anipy_cli.util import (
|
|
|
17
18
|
DotSpinner,
|
|
18
19
|
error,
|
|
19
20
|
get_configured_player,
|
|
20
|
-
get_download_path,
|
|
21
21
|
migrate_locallist,
|
|
22
22
|
)
|
|
23
23
|
from anipy_cli.prompts import pick_episode_prompt, search_show_prompt, lang_prompt
|
|
@@ -75,6 +75,9 @@ class SeasonalMenu(MenuBase):
|
|
|
75
75
|
if self.options.auto_update:
|
|
76
76
|
return [ch.value for ch in choices]
|
|
77
77
|
|
|
78
|
+
if not choices:
|
|
79
|
+
return []
|
|
80
|
+
|
|
78
81
|
choices = inquirer.fuzzy( # type: ignore
|
|
79
82
|
message="Select Seasonals to catch up to:",
|
|
80
83
|
choices=choices,
|
|
@@ -86,6 +89,7 @@ class SeasonalMenu(MenuBase):
|
|
|
86
89
|
{"long_instruction": "fg:#5FAFFF bg:#222"}, style_override=False
|
|
87
90
|
),
|
|
88
91
|
).execute()
|
|
92
|
+
|
|
89
93
|
return choices or []
|
|
90
94
|
|
|
91
95
|
def add_anime(self):
|
|
@@ -180,65 +184,39 @@ class SeasonalMenu(MenuBase):
|
|
|
180
184
|
print(f"> {new_lang} is for {e.name} not available")
|
|
181
185
|
|
|
182
186
|
def list_animes(self):
|
|
183
|
-
|
|
187
|
+
all_seasonals = self.seasonal_list.get_all()
|
|
188
|
+
if not all_seasonals:
|
|
189
|
+
error("No seasonals configured.")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
for i in all_seasonals:
|
|
184
193
|
print(i)
|
|
185
194
|
|
|
186
195
|
def download_latest(self):
|
|
187
196
|
picked = self._choose_latest()
|
|
188
|
-
config = Config()
|
|
189
197
|
total_eps = sum([len(e) for _a, _d, e in picked])
|
|
190
198
|
if total_eps == 0:
|
|
191
|
-
print("
|
|
199
|
+
print("All up to date on downloads.")
|
|
192
200
|
return
|
|
193
201
|
else:
|
|
194
202
|
print(f"Downloading a total of {total_eps} episode(s)")
|
|
195
|
-
with DotSpinner("Starting Download...") as s:
|
|
196
|
-
|
|
197
|
-
def progress_indicator(percentage: float):
|
|
198
|
-
s.set_text(f"Progress: {percentage:.1f}%")
|
|
199
|
-
|
|
200
|
-
def info_display(message: str):
|
|
201
|
-
s.write(f"> {message}")
|
|
202
|
-
|
|
203
|
-
downloader = Downloader(progress_indicator, info_display)
|
|
204
|
-
|
|
205
|
-
for anime, lang, eps in picked:
|
|
206
|
-
for ep in eps:
|
|
207
|
-
s.set_text(
|
|
208
|
-
"Extracting streams for ",
|
|
209
|
-
colors.BLUE,
|
|
210
|
-
f"{anime.name} ({lang})",
|
|
211
|
-
colors.END,
|
|
212
|
-
" Episode ",
|
|
213
|
-
ep,
|
|
214
|
-
"...",
|
|
215
|
-
)
|
|
216
203
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
204
|
+
def on_successful_download(anime: Anime, ep: Episode, lang: LanguageTypeEnum):
|
|
205
|
+
self.seasonal_list.update(anime, episode=ep, language=lang)
|
|
220
206
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
s.set_text("Downloading...")
|
|
225
|
-
|
|
226
|
-
downloader.download(
|
|
227
|
-
stream,
|
|
228
|
-
get_download_path(anime, stream, parent_directory=self.dl_path),
|
|
229
|
-
container=config.remux_to,
|
|
230
|
-
ffmpeg=self.options.ffmpeg or config.ffmpeg_hls,
|
|
231
|
-
)
|
|
232
|
-
self.seasonal_list.update(anime, episode=ep, language=lang)
|
|
207
|
+
failed_series = DownloadComponent(self.options, self.dl_path).download_anime(
|
|
208
|
+
picked, on_successful_download
|
|
209
|
+
)
|
|
233
210
|
|
|
234
211
|
if not self.options.auto_update:
|
|
235
|
-
|
|
212
|
+
# Clear screen only if there were no issues
|
|
213
|
+
self.print_options(clear_screen=len(failed_series) == 0)
|
|
236
214
|
|
|
237
215
|
def binge_latest(self):
|
|
238
216
|
picked = self._choose_latest()
|
|
239
217
|
total_eps = sum([len(e) for _a, _d, e in picked])
|
|
240
218
|
if total_eps == 0:
|
|
241
|
-
print("
|
|
219
|
+
print("Up to date on binge.")
|
|
242
220
|
return
|
|
243
221
|
else:
|
|
244
222
|
print(f"Playing a total of {total_eps} episode(s)")
|
|
@@ -59,16 +59,17 @@ def error(error: str, fatal: bool = False) -> Union[NoReturn, None]:
|
|
|
59
59
|
sys.stderr.write(
|
|
60
60
|
color(colors.RED, "anipy-cli: error: ", colors.END, f"{error}\n")
|
|
61
61
|
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
sys.stderr.write(
|
|
65
|
+
color(
|
|
66
|
+
colors.RED,
|
|
67
|
+
"anipy-cli: fatal error: ",
|
|
68
|
+
colors.END,
|
|
69
|
+
f"{error}, exiting\n",
|
|
70
70
|
)
|
|
71
|
-
|
|
71
|
+
)
|
|
72
|
+
sys.exit(1)
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
def get_prefered_providers(mode: str) -> Iterator["BaseProvider"]:
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|