anipy-cli 2.7.31__tar.gz → 3.0.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.

Files changed (70) hide show
  1. anipy_cli-3.0.0/PKG-INFO +67 -0
  2. anipy_cli-3.0.0/README.md +39 -0
  3. anipy_cli-3.0.0/pyproject.toml +33 -0
  4. anipy_cli-3.0.0/src/anipy_cli/__init__.py +2 -0
  5. {anipy_cli-2.7.31 → anipy_cli-3.0.0/src}/anipy_cli/arg_parser.py +30 -20
  6. anipy_cli-3.0.0/src/anipy_cli/cli.py +66 -0
  7. anipy_cli-3.0.0/src/anipy_cli/clis/__init__.py +15 -0
  8. anipy_cli-3.0.0/src/anipy_cli/clis/base_cli.py +32 -0
  9. anipy_cli-3.0.0/src/anipy_cli/clis/binge_cli.py +83 -0
  10. anipy_cli-3.0.0/src/anipy_cli/clis/default_cli.py +104 -0
  11. anipy_cli-3.0.0/src/anipy_cli/clis/download_cli.py +111 -0
  12. anipy_cli-3.0.0/src/anipy_cli/clis/history_cli.py +93 -0
  13. anipy_cli-3.0.0/src/anipy_cli/clis/mal_cli.py +71 -0
  14. {anipy_cli-2.7.31/anipy_cli/cli → anipy_cli-3.0.0/src/anipy_cli}/clis/seasonal_cli.py +9 -6
  15. {anipy_cli-2.7.31 → anipy_cli-3.0.0/src}/anipy_cli/colors.py +4 -4
  16. anipy_cli-3.0.0/src/anipy_cli/config.py +412 -0
  17. anipy_cli-3.0.0/src/anipy_cli/discord.py +34 -0
  18. anipy_cli-3.0.0/src/anipy_cli/mal_proxy.py +216 -0
  19. anipy_cli-3.0.0/src/anipy_cli/menus/__init__.py +5 -0
  20. {anipy_cli-2.7.31/anipy_cli/cli → anipy_cli-3.0.0/src/anipy_cli}/menus/base_menu.py +8 -12
  21. anipy_cli-3.0.0/src/anipy_cli/menus/mal_menu.py +660 -0
  22. anipy_cli-3.0.0/src/anipy_cli/menus/menu.py +194 -0
  23. anipy_cli-3.0.0/src/anipy_cli/menus/seasonal_menu.py +263 -0
  24. anipy_cli-3.0.0/src/anipy_cli/prompts.py +231 -0
  25. anipy_cli-3.0.0/src/anipy_cli/util.py +262 -0
  26. anipy_cli-2.7.31/LICENSE +0 -674
  27. anipy_cli-2.7.31/PKG-INFO +0 -162
  28. anipy_cli-2.7.31/README.md +0 -137
  29. anipy_cli-2.7.31/anipy_cli/__init__.py +0 -20
  30. anipy_cli-2.7.31/anipy_cli/cli/__init__.py +0 -1
  31. anipy_cli-2.7.31/anipy_cli/cli/cli.py +0 -37
  32. anipy_cli-2.7.31/anipy_cli/cli/clis/__init__.py +0 -6
  33. anipy_cli-2.7.31/anipy_cli/cli/clis/base_cli.py +0 -43
  34. anipy_cli-2.7.31/anipy_cli/cli/clis/binge_cli.py +0 -54
  35. anipy_cli-2.7.31/anipy_cli/cli/clis/default_cli.py +0 -46
  36. anipy_cli-2.7.31/anipy_cli/cli/clis/download_cli.py +0 -92
  37. anipy_cli-2.7.31/anipy_cli/cli/clis/history_cli.py +0 -64
  38. anipy_cli-2.7.31/anipy_cli/cli/clis/mal_cli.py +0 -27
  39. anipy_cli-2.7.31/anipy_cli/cli/menus/__init__.py +0 -3
  40. anipy_cli-2.7.31/anipy_cli/cli/menus/mal_menu.py +0 -411
  41. anipy_cli-2.7.31/anipy_cli/cli/menus/menu.py +0 -108
  42. anipy_cli-2.7.31/anipy_cli/cli/menus/seasonal_menu.py +0 -177
  43. anipy_cli-2.7.31/anipy_cli/cli/util.py +0 -125
  44. anipy_cli-2.7.31/anipy_cli/config.py +0 -191
  45. anipy_cli-2.7.31/anipy_cli/download.py +0 -467
  46. anipy_cli-2.7.31/anipy_cli/history.py +0 -83
  47. anipy_cli-2.7.31/anipy_cli/mal.py +0 -651
  48. anipy_cli-2.7.31/anipy_cli/misc.py +0 -227
  49. anipy_cli-2.7.31/anipy_cli/player/__init__.py +0 -1
  50. anipy_cli-2.7.31/anipy_cli/player/player.py +0 -35
  51. anipy_cli-2.7.31/anipy_cli/player/players/__init__.py +0 -3
  52. anipy_cli-2.7.31/anipy_cli/player/players/base.py +0 -107
  53. anipy_cli-2.7.31/anipy_cli/player/players/mpv.py +0 -19
  54. anipy_cli-2.7.31/anipy_cli/player/players/mpv_control.py +0 -37
  55. anipy_cli-2.7.31/anipy_cli/player/players/syncplay.py +0 -19
  56. anipy_cli-2.7.31/anipy_cli/player/players/vlc.py +0 -18
  57. anipy_cli-2.7.31/anipy_cli/query.py +0 -100
  58. anipy_cli-2.7.31/anipy_cli/run_anipy_cli.py +0 -14
  59. anipy_cli-2.7.31/anipy_cli/seasonal.py +0 -112
  60. anipy_cli-2.7.31/anipy_cli/url_handler.py +0 -470
  61. anipy_cli-2.7.31/anipy_cli/version.py +0 -1
  62. anipy_cli-2.7.31/anipy_cli.egg-info/PKG-INFO +0 -162
  63. anipy_cli-2.7.31/anipy_cli.egg-info/SOURCES.txt +0 -47
  64. anipy_cli-2.7.31/anipy_cli.egg-info/dependency_links.txt +0 -1
  65. anipy_cli-2.7.31/anipy_cli.egg-info/entry_points.txt +0 -2
  66. anipy_cli-2.7.31/anipy_cli.egg-info/requires.txt +0 -13
  67. anipy_cli-2.7.31/anipy_cli.egg-info/top_level.txt +0 -1
  68. anipy_cli-2.7.31/pyproject.toml +0 -6
  69. anipy_cli-2.7.31/setup.cfg +0 -4
  70. 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
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.0.0,<4.0.0)
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"
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"
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"
@@ -0,0 +1,2 @@
1
+ __appname__ = "anipy-cli"
2
+ __version__ = "3.0.0"
@@ -1,8 +1,9 @@
1
1
  import argparse
2
- from pathlib import Path
3
2
  from dataclasses import dataclass
4
- from typing import Union, Optional
5
- from anipy_cli.version import __version__
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 gogoanime in local video-player or Download them.",
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="auto",
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 list from start EP to newest.",
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