anipy-cli 2.7.31__tar.gz → 3.0.0.dev0__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.0.0.dev0/PKG-INFO +67 -0
- anipy_cli-3.0.0.dev0/README.md +39 -0
- anipy_cli-3.0.0.dev0/pyproject.toml +33 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/__init__.py +2 -0
- {anipy_cli-2.7.31 → anipy_cli-3.0.0.dev0/src}/anipy_cli/arg_parser.py +30 -20
- anipy_cli-3.0.0.dev0/src/anipy_cli/cli.py +66 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/__init__.py +15 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/base_cli.py +32 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/binge_cli.py +83 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/default_cli.py +104 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/download_cli.py +111 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/history_cli.py +93 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/clis/mal_cli.py +71 -0
- {anipy_cli-2.7.31/anipy_cli/cli → anipy_cli-3.0.0.dev0/src/anipy_cli}/clis/seasonal_cli.py +9 -6
- {anipy_cli-2.7.31 → anipy_cli-3.0.0.dev0/src}/anipy_cli/colors.py +4 -4
- anipy_cli-3.0.0.dev0/src/anipy_cli/config.py +412 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/discord.py +34 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/mal_proxy.py +216 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/menus/__init__.py +5 -0
- {anipy_cli-2.7.31/anipy_cli/cli → anipy_cli-3.0.0.dev0/src/anipy_cli}/menus/base_menu.py +8 -12
- anipy_cli-3.0.0.dev0/src/anipy_cli/menus/mal_menu.py +660 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/menus/menu.py +194 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/menus/seasonal_menu.py +263 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/prompts.py +231 -0
- anipy_cli-3.0.0.dev0/src/anipy_cli/util.py +262 -0
- anipy_cli-2.7.31/LICENSE +0 -674
- anipy_cli-2.7.31/PKG-INFO +0 -162
- anipy_cli-2.7.31/README.md +0 -137
- anipy_cli-2.7.31/anipy_cli/__init__.py +0 -20
- anipy_cli-2.7.31/anipy_cli/cli/__init__.py +0 -1
- anipy_cli-2.7.31/anipy_cli/cli/cli.py +0 -37
- anipy_cli-2.7.31/anipy_cli/cli/clis/__init__.py +0 -6
- anipy_cli-2.7.31/anipy_cli/cli/clis/base_cli.py +0 -43
- anipy_cli-2.7.31/anipy_cli/cli/clis/binge_cli.py +0 -54
- anipy_cli-2.7.31/anipy_cli/cli/clis/default_cli.py +0 -46
- anipy_cli-2.7.31/anipy_cli/cli/clis/download_cli.py +0 -92
- anipy_cli-2.7.31/anipy_cli/cli/clis/history_cli.py +0 -64
- anipy_cli-2.7.31/anipy_cli/cli/clis/mal_cli.py +0 -27
- anipy_cli-2.7.31/anipy_cli/cli/menus/__init__.py +0 -3
- anipy_cli-2.7.31/anipy_cli/cli/menus/mal_menu.py +0 -411
- anipy_cli-2.7.31/anipy_cli/cli/menus/menu.py +0 -108
- anipy_cli-2.7.31/anipy_cli/cli/menus/seasonal_menu.py +0 -177
- anipy_cli-2.7.31/anipy_cli/cli/util.py +0 -125
- anipy_cli-2.7.31/anipy_cli/config.py +0 -191
- anipy_cli-2.7.31/anipy_cli/download.py +0 -467
- anipy_cli-2.7.31/anipy_cli/history.py +0 -83
- anipy_cli-2.7.31/anipy_cli/mal.py +0 -651
- anipy_cli-2.7.31/anipy_cli/misc.py +0 -227
- anipy_cli-2.7.31/anipy_cli/player/__init__.py +0 -1
- anipy_cli-2.7.31/anipy_cli/player/player.py +0 -35
- anipy_cli-2.7.31/anipy_cli/player/players/__init__.py +0 -3
- anipy_cli-2.7.31/anipy_cli/player/players/base.py +0 -107
- anipy_cli-2.7.31/anipy_cli/player/players/mpv.py +0 -19
- anipy_cli-2.7.31/anipy_cli/player/players/mpv_control.py +0 -37
- anipy_cli-2.7.31/anipy_cli/player/players/syncplay.py +0 -19
- anipy_cli-2.7.31/anipy_cli/player/players/vlc.py +0 -18
- anipy_cli-2.7.31/anipy_cli/query.py +0 -100
- anipy_cli-2.7.31/anipy_cli/run_anipy_cli.py +0 -14
- anipy_cli-2.7.31/anipy_cli/seasonal.py +0 -112
- anipy_cli-2.7.31/anipy_cli/url_handler.py +0 -470
- anipy_cli-2.7.31/anipy_cli/version.py +0 -1
- anipy_cli-2.7.31/anipy_cli.egg-info/PKG-INFO +0 -162
- anipy_cli-2.7.31/anipy_cli.egg-info/SOURCES.txt +0 -47
- anipy_cli-2.7.31/anipy_cli.egg-info/dependency_links.txt +0 -1
- anipy_cli-2.7.31/anipy_cli.egg-info/entry_points.txt +0 -2
- anipy_cli-2.7.31/anipy_cli.egg-info/requires.txt +0 -13
- anipy_cli-2.7.31/anipy_cli.egg-info/top_level.txt +0 -1
- anipy_cli-2.7.31/pyproject.toml +0 -6
- anipy_cli-2.7.31/setup.cfg +0 -4
- anipy_cli-2.7.31/setup.py +0 -44
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: anipy-cli
|
|
3
|
+
Version: 3.0.0.dev0
|
|
4
|
+
Summary: Watch and Download anime from the comfort of your Terminal
|
|
5
|
+
Home-page: https://sdaqo.github.io/anipy-cli
|
|
6
|
+
License: GPL-3.0
|
|
7
|
+
Keywords: anime,cli
|
|
8
|
+
Author: sdaqo
|
|
9
|
+
Author-email: sdaqo.dev@protonmail.com
|
|
10
|
+
Requires-Python: >=3.9,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Dist: anipy-api (==3.*)
|
|
18
|
+
Requires-Dist: appdirs (>=1.4.4,<2.0.0)
|
|
19
|
+
Requires-Dist: inquirerpy (>=0.3.4,<0.4.0)
|
|
20
|
+
Requires-Dist: pypresence (>=4.3.0,<5.0.0)
|
|
21
|
+
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
22
|
+
Requires-Dist: yaspin (>=3.0.2,<4.0.0)
|
|
23
|
+
Project-URL: Bug Tracker, https://github.com/sdaqo/anipy-cli/issues
|
|
24
|
+
Project-URL: Documentation, https://sdaqo.github.io/anipy-cli/getting-started-cli
|
|
25
|
+
Project-URL: Repository, https://github.com/sdaqo/anipy-cli
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ANIPY-CLI
|
|
29
|
+
**Anime from the comfort of your Terminal**
|
|
30
|
+
|
|
31
|
+
<p align="center"><img src="https://github.com/sdaqo/anipy-cli/assets/63876564/1dafa5fb-4273-4dc1-a7ab-2664dd668fc9" /> </p>
|
|
32
|
+
|
|
33
|
+
https://user-images.githubusercontent.com/63876564/162056019-ed0e7a60-78f6-4a2c-bc73-9be5dc2a4f07.mp4
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## What even is this?
|
|
37
|
+
A Little tool written in python to watch and download anime from the terminal (the better way to watch anime)
|
|
38
|
+
This project's main aim is to create a enjoyable anime watching and downloading experience, directly in the terminal - your favorite place.
|
|
39
|
+
|
|
40
|
+
Since the version 3 rewrite this project is split into api and frontend this makes it easy to integrate this into your project!
|
|
41
|
+
|
|
42
|
+
## You are just here for the client?
|
|
43
|
+
As one wise man once said:
|
|
44
|
+
> I DONT GIVE A FUCK ABOUT THE FUCKING CODE! i just want to download this stupid fucking application and use it.
|
|
45
|
+
>
|
|
46
|
+
> WHY IS THERE CODE??? MAKE A FUCKING .EXE FILE AND GIVE IT TO ME. these dumbfucks think that everyone is a developer and understands code. well i am not and i don't understand it. I only know to download and install applications. SO WHY THE FUCK IS THERE CODE? make an EXE file and give it to me. STUPID FUCKING SMELLY NERDS
|
|
47
|
+
|
|
48
|
+
<sub>Please do not take this seriously this is some stupid copypasta</sub>
|
|
49
|
+
|
|
50
|
+
We do not have a .exe but we have pipx: `pipx install anipy-cli`
|
|
51
|
+
|
|
52
|
+
Check out [Getting Started - CLI](https://sdaqo.github.io/anipy-cli/getting-started-cli) for better instructions and advice!
|
|
53
|
+
|
|
54
|
+
## You want to use the api for your project?
|
|
55
|
+
Feel free to - please check out [Getting Started - API](https://sdaqo.github.io/anipy-cli/getting-started-api) for instructions
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## :heart: Credits!
|
|
59
|
+
|
|
60
|
+
#### Heavily inspired by https://github.com/pystardust/ani-cli/
|
|
61
|
+
|
|
62
|
+
#### All contributors for contributing
|
|
63
|
+
|
|
64
|
+
<a href="https://github.com/sdaqo/anipy-cli/graphs/contributors">
|
|
65
|
+
<img src="https://contrib.rocks/image?repo=sdaqo/anipy-cli" alt="anipy-cli contributors" title="anipy-cli contributors" width="800"/>
|
|
66
|
+
</a>
|
|
67
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ANIPY-CLI
|
|
2
|
+
**Anime from the comfort of your Terminal**
|
|
3
|
+
|
|
4
|
+
<p align="center"><img src="https://github.com/sdaqo/anipy-cli/assets/63876564/1dafa5fb-4273-4dc1-a7ab-2664dd668fc9" /> </p>
|
|
5
|
+
|
|
6
|
+
https://user-images.githubusercontent.com/63876564/162056019-ed0e7a60-78f6-4a2c-bc73-9be5dc2a4f07.mp4
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## What even is this?
|
|
10
|
+
A Little tool written in python to watch and download anime from the terminal (the better way to watch anime)
|
|
11
|
+
This project's main aim is to create a enjoyable anime watching and downloading experience, directly in the terminal - your favorite place.
|
|
12
|
+
|
|
13
|
+
Since the version 3 rewrite this project is split into api and frontend this makes it easy to integrate this into your project!
|
|
14
|
+
|
|
15
|
+
## You are just here for the client?
|
|
16
|
+
As one wise man once said:
|
|
17
|
+
> I DONT GIVE A FUCK ABOUT THE FUCKING CODE! i just want to download this stupid fucking application and use it.
|
|
18
|
+
>
|
|
19
|
+
> WHY IS THERE CODE??? MAKE A FUCKING .EXE FILE AND GIVE IT TO ME. these dumbfucks think that everyone is a developer and understands code. well i am not and i don't understand it. I only know to download and install applications. SO WHY THE FUCK IS THERE CODE? make an EXE file and give it to me. STUPID FUCKING SMELLY NERDS
|
|
20
|
+
|
|
21
|
+
<sub>Please do not take this seriously this is some stupid copypasta</sub>
|
|
22
|
+
|
|
23
|
+
We do not have a .exe but we have pipx: `pipx install anipy-cli`
|
|
24
|
+
|
|
25
|
+
Check out [Getting Started - CLI](https://sdaqo.github.io/anipy-cli/getting-started-cli) for better instructions and advice!
|
|
26
|
+
|
|
27
|
+
## You want to use the api for your project?
|
|
28
|
+
Feel free to - please check out [Getting Started - API](https://sdaqo.github.io/anipy-cli/getting-started-api) for instructions
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## :heart: Credits!
|
|
32
|
+
|
|
33
|
+
#### Heavily inspired by https://github.com/pystardust/ani-cli/
|
|
34
|
+
|
|
35
|
+
#### All contributors for contributing
|
|
36
|
+
|
|
37
|
+
<a href="https://github.com/sdaqo/anipy-cli/graphs/contributors">
|
|
38
|
+
<img src="https://contrib.rocks/image?repo=sdaqo/anipy-cli" alt="anipy-cli contributors" title="anipy-cli contributors" width="800"/>
|
|
39
|
+
</a>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "anipy-cli"
|
|
3
|
+
version = "3.0.0.dev0"
|
|
4
|
+
description = "Watch and Download anime from the comfort of your Terminal"
|
|
5
|
+
authors = ["sdaqo <sdaqo.dev@protonmail.com>"]
|
|
6
|
+
license = "GPL-3.0"
|
|
7
|
+
repository = "https://github.com/sdaqo/anipy-cli"
|
|
8
|
+
homepage = "https://sdaqo.github.io/anipy-cli"
|
|
9
|
+
documentation = "https://sdaqo.github.io/anipy-cli/getting-started-cli"
|
|
10
|
+
keywords = ["anime", "cli"]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
packages = [
|
|
13
|
+
{include = "anipy_cli", from = "src"}
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.poetry.dependencies]
|
|
17
|
+
python = "^3.9"
|
|
18
|
+
pyyaml = "^6.0.1"
|
|
19
|
+
yaspin = "^3.0.2"
|
|
20
|
+
inquirerpy = "^0.3.4"
|
|
21
|
+
appdirs = "^1.4.4"
|
|
22
|
+
anipy-api = "^3.0.0.dev0"
|
|
23
|
+
pypresence = "^4.3.0"
|
|
24
|
+
|
|
25
|
+
[tool.poetry.scripts]
|
|
26
|
+
anipy-cli = "anipy_cli.cli:run_cli"
|
|
27
|
+
|
|
28
|
+
[tool.poetry.urls]
|
|
29
|
+
"Bug Tracker" = "https://github.com/sdaqo/anipy-cli/issues"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["poetry-core"]
|
|
33
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
from dataclasses import dataclass
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
from anipy_cli import __version__
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@dataclass(frozen=True)
|
|
@@ -15,17 +16,18 @@ class CliArgs:
|
|
|
15
16
|
delete: bool
|
|
16
17
|
quality: Optional[Union[str, int]]
|
|
17
18
|
ffmpeg: bool
|
|
18
|
-
no_season_search: bool
|
|
19
19
|
auto_update: bool
|
|
20
|
+
mal_sync_seasonals: bool
|
|
20
21
|
optional_player: Optional[str]
|
|
22
|
+
search: Optional[str]
|
|
21
23
|
location: Optional[Path]
|
|
22
24
|
mal_password: Optional[str]
|
|
23
25
|
config: bool
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def parse_args() -> CliArgs:
|
|
28
|
+
def parse_args(override_args: Optional[list[str]] = None) -> CliArgs:
|
|
27
29
|
parser = argparse.ArgumentParser(
|
|
28
|
-
description="Play Animes from
|
|
30
|
+
description="Play Animes from online anime providers locally or download them, and much more.",
|
|
29
31
|
add_help=False,
|
|
30
32
|
)
|
|
31
33
|
|
|
@@ -96,12 +98,22 @@ def parse_args() -> CliArgs:
|
|
|
96
98
|
help="Delete your History.",
|
|
97
99
|
)
|
|
98
100
|
|
|
101
|
+
options_group.add_argument(
|
|
102
|
+
"-s",
|
|
103
|
+
"--search",
|
|
104
|
+
required=False,
|
|
105
|
+
dest="search",
|
|
106
|
+
action="store",
|
|
107
|
+
help="Provide a search term to the Download or Binge mode in this format: {query}:{episode range}:{dub/sub}. Examples: 'frieren:1-10:sub' or 'frieren:1:sub' or 'frieren:1-3 7-12:dub'",
|
|
108
|
+
)
|
|
109
|
+
|
|
99
110
|
options_group.add_argument(
|
|
100
111
|
"-q",
|
|
101
112
|
"--quality",
|
|
102
113
|
action="store",
|
|
103
114
|
required=False,
|
|
104
|
-
default="
|
|
115
|
+
default="best",
|
|
116
|
+
type=lambda v: int(v) if v.isdigit() else v,
|
|
105
117
|
help="Change the quality of the video, accepts: best, worst or 360, 480, 720 etc. Default: best",
|
|
106
118
|
)
|
|
107
119
|
|
|
@@ -114,30 +126,20 @@ def parse_args() -> CliArgs:
|
|
|
114
126
|
help="Use ffmpeg to download m3u8 playlists, may be more stable but is way slower than internal downloader",
|
|
115
127
|
)
|
|
116
128
|
|
|
117
|
-
options_group.add_argument(
|
|
118
|
-
"-o",
|
|
119
|
-
"--no-seas-search",
|
|
120
|
-
required=False,
|
|
121
|
-
dest="no_season_search",
|
|
122
|
-
action="store_true",
|
|
123
|
-
help="Turn off search in season. "
|
|
124
|
-
"Disables prompting if GoGoAnime is to be searched for anime in specific season.",
|
|
125
|
-
)
|
|
126
|
-
|
|
127
129
|
options_group.add_argument(
|
|
128
130
|
"-a",
|
|
129
131
|
"--auto-update",
|
|
130
132
|
required=False,
|
|
131
133
|
dest="auto_update",
|
|
132
134
|
action="store_true",
|
|
133
|
-
help="Automatically update and download all Anime in seasonals
|
|
135
|
+
help="Automatically update and download all Anime in seasonals or mal mode from start EP to newest.",
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
options_group.add_argument(
|
|
137
139
|
"-p",
|
|
138
140
|
"--optional-player",
|
|
139
141
|
required=False,
|
|
140
|
-
choices=["mpv", "vlc", "syncplay", "mpvnet"],
|
|
142
|
+
choices=["mpv", "vlc", "syncplay", "mpvnet", "mpv-controlled"],
|
|
141
143
|
help="Override the player set in the config.",
|
|
142
144
|
)
|
|
143
145
|
|
|
@@ -160,6 +162,14 @@ def parse_args() -> CliArgs:
|
|
|
160
162
|
help="Provide password for MAL login (overrides password set in config)",
|
|
161
163
|
)
|
|
162
164
|
|
|
165
|
+
options_group.add_argument(
|
|
166
|
+
"--mal-sync-to-seasonals",
|
|
167
|
+
required=False,
|
|
168
|
+
dest="mal_sync_seasonals",
|
|
169
|
+
action="store_true",
|
|
170
|
+
help="Automatically sync myanimelist to seasonals (only works with `-M`)",
|
|
171
|
+
)
|
|
172
|
+
|
|
163
173
|
info_group.add_argument(
|
|
164
174
|
"-h", "--help", action="help", help="show this help message and exit"
|
|
165
175
|
)
|
|
@@ -174,4 +184,4 @@ def parse_args() -> CliArgs:
|
|
|
174
184
|
help="Print path to the config file.",
|
|
175
185
|
)
|
|
176
186
|
|
|
177
|
-
return CliArgs(**vars(parser.parse_args()))
|
|
187
|
+
return CliArgs(**vars(parser.parse_args(args=override_args)))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pypresence.exceptions import DiscordNotFound
|
|
4
|
+
|
|
5
|
+
from anipy_cli.arg_parser import parse_args
|
|
6
|
+
from anipy_cli.clis import *
|
|
7
|
+
from anipy_cli.colors import colors, cprint
|
|
8
|
+
from anipy_cli.util import error, DotSpinner
|
|
9
|
+
from anipy_cli.config import Config
|
|
10
|
+
from anipy_cli.discord import DiscordPresence
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_cli(override_args: Optional[list[str]] = None):
|
|
14
|
+
args = parse_args(override_args)
|
|
15
|
+
config = Config()
|
|
16
|
+
# This updates the config, adding new values doc changes and the like.
|
|
17
|
+
config._create_config()
|
|
18
|
+
|
|
19
|
+
if config.dc_presence:
|
|
20
|
+
with DotSpinner("Intializing Discord Presence...") as s:
|
|
21
|
+
try:
|
|
22
|
+
DiscordPresence()
|
|
23
|
+
s.set_text(colors.GREEN, "Initialized Discord Presence")
|
|
24
|
+
s.ok("✔")
|
|
25
|
+
except DiscordNotFound:
|
|
26
|
+
s.set_text(
|
|
27
|
+
colors.RED,
|
|
28
|
+
"Discord is not opened, can't initialize Discord Presence",
|
|
29
|
+
)
|
|
30
|
+
s.fail("✘")
|
|
31
|
+
except ConnectionError:
|
|
32
|
+
s.set_text(
|
|
33
|
+
colors.RED,
|
|
34
|
+
"Can't Connect to discord, can't initialize Discord Presence",
|
|
35
|
+
)
|
|
36
|
+
s.fail("✘")
|
|
37
|
+
|
|
38
|
+
if args.config:
|
|
39
|
+
print(config._config_file)
|
|
40
|
+
return
|
|
41
|
+
elif args.delete:
|
|
42
|
+
try:
|
|
43
|
+
config._history_file_path.unlink()
|
|
44
|
+
cprint(colors.RED, "Done")
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
error("no history file found")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
clis_dict = {
|
|
50
|
+
args.download: DownloadCli,
|
|
51
|
+
args.binge: BingeCli,
|
|
52
|
+
args.seasonal: SeasonalCli,
|
|
53
|
+
args.history: HistoryCli,
|
|
54
|
+
args.mal: MalCli,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
cli_class = clis_dict.get(True, DefaultCli)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
cli_class(options=args).run()
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
error("interrupted", fatal=True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
run_cli()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from anipy_cli.clis.default_cli import DefaultCli
|
|
2
|
+
from anipy_cli.clis.history_cli import HistoryCli
|
|
3
|
+
from anipy_cli.clis.mal_cli import MalCli
|
|
4
|
+
from anipy_cli.clis.seasonal_cli import SeasonalCli
|
|
5
|
+
from anipy_cli.clis.binge_cli import BingeCli
|
|
6
|
+
from anipy_cli.clis.download_cli import DownloadCli
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DefaultCli",
|
|
10
|
+
"HistoryCli",
|
|
11
|
+
"MalCli",
|
|
12
|
+
"SeasonalCli",
|
|
13
|
+
"BingeCli",
|
|
14
|
+
"DownloadCli",
|
|
15
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from anipy_cli.arg_parser import CliArgs
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CliBase(ABC):
|
|
9
|
+
def __init__(self, options: "CliArgs"):
|
|
10
|
+
self.options = options
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def print_header(self): ...
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def take_input(self): ...
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def process(self): ...
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def show(self): ...
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def post(self): ...
|
|
26
|
+
|
|
27
|
+
def run(self):
|
|
28
|
+
self.print_header()
|
|
29
|
+
self.take_input()
|
|
30
|
+
self.process()
|
|
31
|
+
self.show()
|
|
32
|
+
self.post()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from anipy_api.locallist import LocalList
|
|
5
|
+
|
|
6
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
7
|
+
from anipy_cli.colors import colors, cprint
|
|
8
|
+
from anipy_cli.config import Config
|
|
9
|
+
from anipy_cli.prompts import (
|
|
10
|
+
pick_episode_range_prompt,
|
|
11
|
+
search_show_prompt,
|
|
12
|
+
lang_prompt,
|
|
13
|
+
parse_auto_search,
|
|
14
|
+
)
|
|
15
|
+
from anipy_cli.util import DotSpinner, get_configured_player, migrate_locallist
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from anipy_cli.arg_parser import CliArgs
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BingeCli(CliBase):
|
|
22
|
+
def __init__(self, options: "CliArgs"):
|
|
23
|
+
super().__init__(options)
|
|
24
|
+
|
|
25
|
+
self.player = get_configured_player(self.options.optional_player)
|
|
26
|
+
self.history_list = LocalList(
|
|
27
|
+
Config()._history_file_path, migrate_cb=migrate_locallist
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
self.anime = None
|
|
31
|
+
self.episodes = None
|
|
32
|
+
self.lang = None
|
|
33
|
+
|
|
34
|
+
def print_header(self):
|
|
35
|
+
cprint(colors.GREEN, "***Binge Mode***")
|
|
36
|
+
|
|
37
|
+
def take_input(self):
|
|
38
|
+
if self.options.search is not None:
|
|
39
|
+
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
40
|
+
"binge", self.options.search
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
anime = search_show_prompt("binge")
|
|
45
|
+
|
|
46
|
+
if anime is None:
|
|
47
|
+
sys.exit(0)
|
|
48
|
+
|
|
49
|
+
self.lang = lang_prompt(anime)
|
|
50
|
+
|
|
51
|
+
episodes = pick_episode_range_prompt(anime, self.lang)
|
|
52
|
+
|
|
53
|
+
self.anime = anime
|
|
54
|
+
self.episodes = episodes
|
|
55
|
+
|
|
56
|
+
def process(self): ...
|
|
57
|
+
|
|
58
|
+
def show(self):
|
|
59
|
+
assert self.episodes is not None
|
|
60
|
+
assert self.anime is not None
|
|
61
|
+
assert self.lang is not None
|
|
62
|
+
|
|
63
|
+
for e in self.episodes:
|
|
64
|
+
with DotSpinner(
|
|
65
|
+
"Extracting streams for ",
|
|
66
|
+
colors.BLUE,
|
|
67
|
+
f"{self.anime.name} ({self.lang})",
|
|
68
|
+
colors.END,
|
|
69
|
+
" Episode ",
|
|
70
|
+
e,
|
|
71
|
+
"...",
|
|
72
|
+
) as s:
|
|
73
|
+
stream = self.anime.get_video(
|
|
74
|
+
e, self.lang, preferred_quality=self.options.quality
|
|
75
|
+
)
|
|
76
|
+
s.ok("✔")
|
|
77
|
+
|
|
78
|
+
self.history_list.update(self.anime, episode=e, language=self.lang)
|
|
79
|
+
self.player.play_title(self.anime, stream)
|
|
80
|
+
self.player.wait()
|
|
81
|
+
|
|
82
|
+
def post(self):
|
|
83
|
+
self.player.kill_player()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
|
|
4
|
+
from anipy_api.locallist import LocalList
|
|
5
|
+
|
|
6
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
7
|
+
from anipy_cli.colors import colors
|
|
8
|
+
from anipy_cli.config import Config
|
|
9
|
+
from anipy_cli.menus import Menu
|
|
10
|
+
from anipy_cli.prompts import (
|
|
11
|
+
pick_episode_prompt,
|
|
12
|
+
search_show_prompt,
|
|
13
|
+
lang_prompt,
|
|
14
|
+
parse_auto_search,
|
|
15
|
+
)
|
|
16
|
+
from anipy_cli.util import (
|
|
17
|
+
DotSpinner,
|
|
18
|
+
get_configured_player,
|
|
19
|
+
migrate_locallist,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from anipy_api.anime import Anime
|
|
24
|
+
from anipy_api.provider import Episode, ProviderStream, LanguageTypeEnum
|
|
25
|
+
from anipy_cli.arg_parser import CliArgs
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# TODO: Add Resume feature
|
|
29
|
+
class DefaultCli(CliBase):
|
|
30
|
+
def __init__(self, options: "CliArgs"):
|
|
31
|
+
super().__init__(options)
|
|
32
|
+
|
|
33
|
+
self.player = get_configured_player(self.options.optional_player)
|
|
34
|
+
self.history_list = LocalList(
|
|
35
|
+
Config()._history_file_path, migrate_cb=migrate_locallist
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.anime: Optional["Anime"] = None
|
|
39
|
+
self.epsiode: Optional["Episode"] = None
|
|
40
|
+
self.stream: Optional["ProviderStream"] = None
|
|
41
|
+
self.lang: Optional["LanguageTypeEnum"] = None
|
|
42
|
+
|
|
43
|
+
def print_header(self):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
def take_input(self):
|
|
47
|
+
if self.options.search is not None:
|
|
48
|
+
self.anime, self.lang, episodes = parse_auto_search(
|
|
49
|
+
"default", self.options.search
|
|
50
|
+
)
|
|
51
|
+
self.epsiode = episodes[0]
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
anime = search_show_prompt("default")
|
|
55
|
+
|
|
56
|
+
if anime is None:
|
|
57
|
+
sys.exit(0)
|
|
58
|
+
|
|
59
|
+
self.lang = lang_prompt(anime)
|
|
60
|
+
|
|
61
|
+
episode = pick_episode_prompt(anime, self.lang)
|
|
62
|
+
|
|
63
|
+
if episode is None:
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
self.anime = anime
|
|
67
|
+
self.epsiode = episode
|
|
68
|
+
|
|
69
|
+
def process(self):
|
|
70
|
+
assert self.anime is not None
|
|
71
|
+
assert self.epsiode is not None
|
|
72
|
+
assert self.lang is not None
|
|
73
|
+
|
|
74
|
+
with DotSpinner(
|
|
75
|
+
"Extracting streams for ",
|
|
76
|
+
colors.BLUE,
|
|
77
|
+
f"{self.anime.name} ({self.lang})",
|
|
78
|
+
" Episode ",
|
|
79
|
+
self.epsiode,
|
|
80
|
+
"...",
|
|
81
|
+
):
|
|
82
|
+
self.stream = self.anime.get_video(
|
|
83
|
+
self.epsiode, self.lang, preferred_quality=self.options.quality
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def show(self):
|
|
87
|
+
assert self.anime is not None
|
|
88
|
+
assert self.stream is not None
|
|
89
|
+
|
|
90
|
+
self.history_list.update(
|
|
91
|
+
self.anime, episode=self.epsiode, language=self.stream.language
|
|
92
|
+
)
|
|
93
|
+
self.player.play_title(self.anime, self.stream)
|
|
94
|
+
|
|
95
|
+
def post(self):
|
|
96
|
+
assert self.anime is not None
|
|
97
|
+
assert self.stream is not None
|
|
98
|
+
|
|
99
|
+
Menu(
|
|
100
|
+
options=self.options,
|
|
101
|
+
anime=self.anime,
|
|
102
|
+
stream=self.stream,
|
|
103
|
+
player=self.player,
|
|
104
|
+
).run()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import TYPE_CHECKING, Optional, List
|
|
3
|
+
|
|
4
|
+
from anipy_api.download import Downloader
|
|
5
|
+
|
|
6
|
+
from anipy_cli.clis.base_cli import CliBase
|
|
7
|
+
from anipy_cli.colors import colors, cprint
|
|
8
|
+
from anipy_cli.config import Config
|
|
9
|
+
from anipy_cli.prompts import (
|
|
10
|
+
pick_episode_range_prompt,
|
|
11
|
+
search_show_prompt,
|
|
12
|
+
lang_prompt,
|
|
13
|
+
parse_auto_search,
|
|
14
|
+
)
|
|
15
|
+
from anipy_cli.util import (
|
|
16
|
+
DotSpinner,
|
|
17
|
+
get_download_path,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from anipy_api.anime import Anime
|
|
22
|
+
from anipy_api.provider import Episode, LanguageTypeEnum
|
|
23
|
+
|
|
24
|
+
from anipy_cli.arg_parser import CliArgs
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DownloadCli(CliBase):
|
|
28
|
+
def __init__(self, options: "CliArgs"):
|
|
29
|
+
super().__init__(options)
|
|
30
|
+
|
|
31
|
+
self.anime: Optional["Anime"] = None
|
|
32
|
+
self.episodes: Optional[List["Episode"]] = None
|
|
33
|
+
self.lang: Optional["LanguageTypeEnum"] = None
|
|
34
|
+
|
|
35
|
+
self.dl_path = Config().download_folder_path
|
|
36
|
+
if options.location:
|
|
37
|
+
self.dl_path = options.location
|
|
38
|
+
|
|
39
|
+
def print_header(self):
|
|
40
|
+
cprint(colors.GREEN, "***Download Mode***")
|
|
41
|
+
cprint(colors.GREEN, "Downloads are stored in: ", colors.END, str(self.dl_path))
|
|
42
|
+
|
|
43
|
+
def take_input(self):
|
|
44
|
+
if self.options.search is not None:
|
|
45
|
+
self.anime, self.lang, self.episodes = parse_auto_search(
|
|
46
|
+
"download", self.options.search
|
|
47
|
+
)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
anime = search_show_prompt("download")
|
|
51
|
+
|
|
52
|
+
if anime is None:
|
|
53
|
+
sys.exit(0)
|
|
54
|
+
|
|
55
|
+
self.lang = lang_prompt(anime)
|
|
56
|
+
|
|
57
|
+
episodes = pick_episode_range_prompt(anime, self.lang)
|
|
58
|
+
|
|
59
|
+
self.anime = anime
|
|
60
|
+
self.episodes = episodes
|
|
61
|
+
|
|
62
|
+
def process(self):
|
|
63
|
+
assert self.episodes is not None
|
|
64
|
+
assert self.anime is not None
|
|
65
|
+
assert self.lang is not None
|
|
66
|
+
|
|
67
|
+
config = Config()
|
|
68
|
+
with DotSpinner("Starting Download...") as s:
|
|
69
|
+
|
|
70
|
+
def progress_indicator(percentage: float):
|
|
71
|
+
s.set_text(f"Progress: {percentage:.1f}%")
|
|
72
|
+
|
|
73
|
+
def info_display(message: str):
|
|
74
|
+
s.write(f"> {message}")
|
|
75
|
+
|
|
76
|
+
downloader = Downloader(progress_indicator, info_display)
|
|
77
|
+
|
|
78
|
+
for e in self.episodes:
|
|
79
|
+
s.set_text(
|
|
80
|
+
"Extracting streams for ",
|
|
81
|
+
colors.BLUE,
|
|
82
|
+
f"{self.anime.name} ({self.lang})",
|
|
83
|
+
colors.END,
|
|
84
|
+
" Episode ",
|
|
85
|
+
e,
|
|
86
|
+
"...",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
stream = self.anime.get_video(
|
|
90
|
+
e, self.lang, preferred_quality=self.options.quality
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
info_display(
|
|
94
|
+
f"Downloading Episode {stream.episode} of {self.anime.name} ({self.lang})"
|
|
95
|
+
)
|
|
96
|
+
s.set_text("Downloading...")
|
|
97
|
+
|
|
98
|
+
downloader.download(
|
|
99
|
+
stream,
|
|
100
|
+
get_download_path(
|
|
101
|
+
self.anime, stream, parent_directory=self.dl_path
|
|
102
|
+
),
|
|
103
|
+
container=config.remux_to,
|
|
104
|
+
ffmpeg=self.options.ffmpeg or config.ffmpeg_hls,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def show(self):
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
def post(self):
|
|
111
|
+
pass
|