mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-27 05:03:41 -08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c66cc52d53 | ||
|
|
e5f4a61a4e | ||
|
|
739d041c58 | ||
|
|
f12d5ab06c | ||
|
|
c3a3041cfb | ||
|
|
594c687c8b | ||
|
|
91b5d3ea40 | ||
|
|
8c30a7667c | ||
|
|
179fbe59ac | ||
|
|
5bfc210f59 | ||
|
|
eb9c200fca | ||
|
|
603efd56e8 | ||
|
|
4d74dfa339 | ||
|
|
4681e38153 | ||
|
|
242003500d | ||
|
|
66ab365657 | ||
|
|
bc8d7b2e28 | ||
|
|
db3a1f7175 | ||
|
|
3a51e0225e | ||
|
|
7b3388939c | ||
|
|
fcf875bdb2 | ||
|
|
0e4624297c | ||
|
|
91b54dfcb9 | ||
|
|
7011489ce5 | ||
|
|
316ca62ca4 |
105
README.md
105
README.md
@@ -2,18 +2,17 @@
|
||||
|
||||
Welcome to **FastAnime**, anime site experience from the terminal.
|
||||
|
||||
|
||||
**fzf mode**
|
||||
|
||||
[fa_fzf_demo.webm](https://github.com/user-attachments/assets/b1fecf25-e358-4e8b-a144-bcb7947210cf)
|
||||
|
||||
|
||||
**other modes:**
|
||||
|
||||
<details>
|
||||
<summary><b>rofi mode</b></summary>
|
||||
|
||||
[fa_rofi_mode.webm](https://github.com/user-attachments/assets/2ce669bf-b62f-4c44-bd79-cf0dcaddf37a)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -23,11 +22,8 @@ Welcome to **FastAnime**, anime site experience from the terminal.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magic-tape](https://gitlab.com/christosangel/magic-tape/-/tree/main?ref_type=heads) and [ani-cli](https://github.com/pystardust/ani-cli).
|
||||
|
||||
|
||||
|
||||
<!--toc:start-->
|
||||
|
||||
- [FastAnime](#fastanime)
|
||||
@@ -40,13 +36,17 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magi
|
||||
- [External Dependencies](#external-dependencies)
|
||||
- [Usage](#usage)
|
||||
- [The Commandline interface :fire:](#the-commandline-interface-fire)
|
||||
- [The anilist command](#the-anilist-command)
|
||||
- [The anilist command :fire: :fire: :fire:](#the-anilist-command-fire-fire-fire)
|
||||
- [Running without any subcommand](#running-without-any-subcommand)
|
||||
- [Subcommands](#subcommands)
|
||||
- [download subcommand](#download-subcommand)
|
||||
- [search subcommand](#search-subcommand)
|
||||
- [downloads subcommand](#downloads-subcommand)
|
||||
- [config subcommand](#config-subcommand)
|
||||
- [cache subcommand](#cache-subcommand)
|
||||
- [MPV specific commands](#mpv-specific-commands)
|
||||
- [Added keybindings](#added-keybindings)
|
||||
- [Added script messages](#added-script-messages)
|
||||
- [Configuration](#configuration)
|
||||
- [Contributing](#contributing)
|
||||
- [Receiving Support](#receiving-support)
|
||||
@@ -57,7 +57,6 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [magi
|
||||
>
|
||||
> This project currently scrapes allanime and is in no way related to them. The site is in the public domain and can be access by any one with a browser.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The app can run wherever python can run. So all you need to have is python installed on your device.
|
||||
@@ -75,12 +74,21 @@ Preferred method of installation since [Pipx](https://github.com/pypa/pipx) crea
|
||||
```bash
|
||||
|
||||
pipx install fastanime
|
||||
# -- or for the development version --
|
||||
pipx install 'fastanime==<latest-pre-release-tag>.dev1'
|
||||
# example
|
||||
# pipx install 'fastanime==0.60.1.dev1'
|
||||
|
||||
```
|
||||
|
||||
#### Using pip
|
||||
|
||||
```bash
|
||||
pip install fastanime
|
||||
# -- or for the development version --
|
||||
pip install 'fastanime==<latest-pre-release-tag>.dev1'
|
||||
# example
|
||||
# pip install 'fastanime==0.60.1.dev1'
|
||||
```
|
||||
|
||||
### Installing the bleeding edge version
|
||||
@@ -143,7 +151,7 @@ fastanime --version
|
||||
|
||||
### External Dependencies
|
||||
|
||||
The only required external dependency, unless you won't be streaming, is [MPV](https://mpv.io/installation/), which i recommend installing with [uosc](https://github.com/tomasklaen/uosc) and [thumbfast](https://github.com/po5/thumbfast) for the best experience since they add a better interface to it.
|
||||
The only required external dependency, unless you won't be streaming, is [MPV](https://mpv.io/installation/), which i recommend installing with [uosc](https://github.com/tomasklaen/uosc) :fire: and [thumbfast](https://github.com/po5/thumbfast) for the best experience since they add a better interface to it.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@@ -152,13 +160,13 @@ The only required external dependency, unless you won't be streaming, is [MPV](h
|
||||
> everything you could ever need with a small footprint.
|
||||
> But if you have a reason feel free to encourage as to do so.
|
||||
|
||||
**Other dependencies that will just make your experience better:**
|
||||
**Other external dependencies that will just make your experience better:**
|
||||
|
||||
- [fzf](https://github.com/junegunn/fzf) :fire: which is used as a better alternative to the ui.
|
||||
- [chafa](https://github.com/hpjansson/chafa) currently the best cross platform and cross terminal image viewer for the terminal.
|
||||
- [icat](https://sw.kovidgoyal.net/kitty/kittens/icat/) an image viewer that only works in [kitty terminal](https://sw.kovidgoyal.net/kitty/), which is currently the best terminal in my opinion, and by far the best image renderer for the terminal thanks to kitty's terminal graphics protocol. Its terminal graphics is so op that you can [run a browser on it](https://github.com/chase/awrit?tab=readme-ov-file)!!
|
||||
- [bash](https://www.gnu.org/software/bash/) is used as the preview script language.
|
||||
- [ani-skip](https://github.com/synacktraa/ani-skip) :fire: used for skipping the opening and ending theme songs
|
||||
- [ani-skip](https://github.com/synacktraa/ani-skip) used for skipping the opening and ending theme songs
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -181,6 +189,7 @@ Overview of main commands:
|
||||
- `fastanime search`: Powerful command meant for binging since it doesn't require the interfaces
|
||||
- `fastanime downloads`: View downloaded anime and watch with MPV.
|
||||
- `fastanime config`: Quickly edit configuration settings.
|
||||
- `fastanime cache`: Quickly manage the cache fastanime uses
|
||||
|
||||
Configuration is directly passed into this command at run time to override your config.
|
||||
|
||||
@@ -204,6 +213,10 @@ Available options include:
|
||||
- `--rofi-theme <path>` theme to use with rofi
|
||||
- `--rofi-theme-input <path>` theme to use with rofi input
|
||||
- `--rofi-theme-confirm <path>` theme to use with rofi confirm
|
||||
- `--log` allow logging to stdout
|
||||
- `--log-file` allow logging to a file
|
||||
- `--rich-traceback` allow rich traceback
|
||||
- `--use-mpv-mod/--use-default-player` whether to use python-mpv
|
||||
|
||||
#### The anilist command :fire: :fire: :fire:
|
||||
|
||||
@@ -331,11 +344,77 @@ fastanime config
|
||||
|
||||
# to get config path which is useful if you want to use it for another program.
|
||||
fastanime config --path
|
||||
|
||||
# add a desktop entry
|
||||
fastanime config --desktop-entry
|
||||
```
|
||||
|
||||
> [!Note]
|
||||
>
|
||||
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` in case you don't know.
|
||||
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` .
|
||||
|
||||
#### cache subcommand
|
||||
|
||||
Easily manage the data fastanime has cached; for the previews.
|
||||
|
||||
**Syntax:**
|
||||
|
||||
```bash
|
||||
# delete everything in the cache dir
|
||||
fastanime cache --clean
|
||||
|
||||
# print the path to the cache dir and exit
|
||||
fastanime cache --path
|
||||
|
||||
# print the current size of the cache dir and exit
|
||||
fastanime cache --size
|
||||
|
||||
# open the cache dir and exit
|
||||
fastanime cache
|
||||
```
|
||||
|
||||
#### completions subcommand
|
||||
|
||||
Helper command to setup shell completions
|
||||
|
||||
**Syntax:**
|
||||
|
||||
```bash
|
||||
# print fish completions
|
||||
fastanime completions --fish
|
||||
# print bash completions
|
||||
fastanime completions --bash
|
||||
# print zsh completions
|
||||
fastanime completions --zsh
|
||||
```
|
||||
|
||||
## MPV specific commands
|
||||
|
||||
The project now allows on the fly media controls directly from mpv. This means you can go to the next or previous episode without the window ever closing thus offering a seamless experience.
|
||||
This is all powered with [python-mpv]() which enables writing mpv scripts with python just like how it would be done in lua.
|
||||
|
||||
### Added keybindings
|
||||
|
||||
`<shift>+n` fetch the next episode
|
||||
|
||||
`<shift>+p` fetch the previous episode
|
||||
|
||||
`<shift>+t` toggle the translation type from dub to sub
|
||||
|
||||
`<shift>+a` toggle auto next episode
|
||||
|
||||
`<shit>+r` reload episode
|
||||
|
||||
### Added script messages
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# to select episode from mpv without window closing
|
||||
script-message select-episode <episode-number>
|
||||
# to select server from mpv without window closing
|
||||
script-message select-server <server-name>
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -356,6 +435,7 @@ skip=false
|
||||
# the maximum delta time in minutes after which the episode should be considered as completed
|
||||
# used in the continue from time stamp
|
||||
error=3
|
||||
use_mpv_mod=False
|
||||
|
||||
# the format of downloaded anime and trailer
|
||||
# based on yt-dlp format and passed directly to it
|
||||
@@ -393,7 +473,7 @@ notification_duration=2
|
||||
|
||||
We welcome your issues and feature requests. However, due to time constraints, we currently do not plan to add another provider.
|
||||
|
||||
If you wish to contribute directly, please first open an issue describing your proposed changes so it can be discussed.
|
||||
If you wish to contribute directly, please first open an issue describing your proposed changes so it can be discussed or if you are in a rush for the feature to be merged just open a pr.
|
||||
|
||||
## Receiving Support
|
||||
|
||||
@@ -405,7 +485,6 @@ For inquiries, join our [Discord Server](https://discord.gg/4NUTj5Pt).
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
## Supporting the Project
|
||||
|
||||
Show your support by starring our GitHub repository or [buying us a coffee](https://ko-fi.com/benex254).
|
||||
|
||||
@@ -6,7 +6,7 @@ if sys.version_info < (3, 10):
|
||||
) # noqa: F541
|
||||
|
||||
|
||||
__version__ = "v0.50.0"
|
||||
__version__ = "v0.61.5"
|
||||
|
||||
APP_NAME = "FastAnime"
|
||||
AUTHOR = "Benex254"
|
||||
|
||||
@@ -15,6 +15,7 @@ commands = {
|
||||
"config": "config.config",
|
||||
"downloads": "downloads.downloads",
|
||||
"cache": "cache.cache",
|
||||
"completions": "completions.completions",
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +127,9 @@ signal.signal(signal.SIGINT, handle_exit)
|
||||
help="Rofi theme to use for the user input prompt",
|
||||
type=click.Path(),
|
||||
)
|
||||
@click.option(
|
||||
"--use-mpv-mod/--use-default-player", help="Whether to use python-mpv", type=bool
|
||||
)
|
||||
@click.pass_context
|
||||
def run_cli(
|
||||
ctx: click.Context,
|
||||
@@ -155,6 +159,7 @@ def run_cli(
|
||||
rofi_theme,
|
||||
rofi_theme_confirm,
|
||||
rofi_theme_input,
|
||||
use_mpv_mod,
|
||||
):
|
||||
from .config import Config
|
||||
|
||||
@@ -217,6 +222,11 @@ def run_cli(
|
||||
== click.core.ParameterSource.COMMANDLINE
|
||||
):
|
||||
ctx.obj.auto_select = auto_select
|
||||
if (
|
||||
ctx.get_parameter_source("use_mpv_mod")
|
||||
== click.core.ParameterSource.COMMANDLINE
|
||||
):
|
||||
ctx.obj.use_mpv_mod = use_mpv_mod
|
||||
if sort_by:
|
||||
ctx.obj.sort_by = sort_by
|
||||
if downloads_dir:
|
||||
|
||||
@@ -33,3 +33,7 @@ def cache(clean, path, size):
|
||||
fp = os.path.join(dirpath, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
print("Total Size: ", sizeof_fmt(total_size))
|
||||
else:
|
||||
import click
|
||||
|
||||
click.launch(APP_CACHE_DIR)
|
||||
|
||||
110
fastanime/cli/commands/completions.py
Normal file
110
fastanime/cli/commands/completions.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import click
|
||||
|
||||
|
||||
@click.command(help="Helper command to get shell completions")
|
||||
@click.option("--fish", is_flag=True)
|
||||
@click.option("--zsh", is_flag=True)
|
||||
@click.option("--bash", is_flag=True)
|
||||
def completions(fish, zsh, bash):
|
||||
if fish:
|
||||
print(
|
||||
"""
|
||||
function _fastanime_completion;
|
||||
set -l response (env _FASTANIME_COMPLETE=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) fastanime);
|
||||
|
||||
for completion in $response;
|
||||
set -l metadata (string split "," $completion);
|
||||
|
||||
if test $metadata[1] = "dir";
|
||||
__fish_complete_directories $metadata[2];
|
||||
else if test $metadata[1] = "file";
|
||||
__fish_complete_path $metadata[2];
|
||||
else if test $metadata[1] = "plain";
|
||||
echo $metadata[2];
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
complete --no-files --command fastanime --arguments "(_fastanime_completion)";
|
||||
"""
|
||||
)
|
||||
elif zsh:
|
||||
print(
|
||||
"""
|
||||
#compdef fastanime
|
||||
|
||||
_fastanime_completion() {
|
||||
local -a completions
|
||||
local -a completions_with_descriptions
|
||||
local -a response
|
||||
(( ! $+commands[fastanime] )) && return 1
|
||||
|
||||
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) _FASTANIME_COMPLETE=zsh_complete fastanime)}")
|
||||
|
||||
for type key descr in ${response}; do
|
||||
if [[ "$type" == "plain" ]]; then
|
||||
if [[ "$descr" == "_" ]]; then
|
||||
completions+=("$key")
|
||||
else
|
||||
completions_with_descriptions+=("$key":"$descr")
|
||||
fi
|
||||
elif [[ "$type" == "dir" ]]; then
|
||||
_path_files -/
|
||||
elif [[ "$type" == "file" ]]; then
|
||||
_path_files -f
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$completions_with_descriptions" ]; then
|
||||
_describe -V unsorted completions_with_descriptions -U
|
||||
fi
|
||||
|
||||
if [ -n "$completions" ]; then
|
||||
compadd -U -V unsorted -a completions
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
||||
# autoload from fpath, call function directly
|
||||
_fastanime_completion "$@"
|
||||
else
|
||||
# eval/source/. command, register function for later
|
||||
compdef _fastanime_completion fastanime
|
||||
fi
|
||||
"""
|
||||
)
|
||||
elif bash:
|
||||
print(
|
||||
"""
|
||||
_fastanime_completion() {
|
||||
local IFS=$'\n'
|
||||
local response
|
||||
|
||||
response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD _FASTANIME_COMPLETE=bash_complete $1)
|
||||
|
||||
for completion in $response; do
|
||||
IFS=',' read type value <<< "$completion"
|
||||
|
||||
if [[ $type == 'dir' ]]; then
|
||||
COMPREPLY=()
|
||||
compopt -o dirnames
|
||||
elif [[ $type == 'file' ]]; then
|
||||
COMPREPLY=()
|
||||
compopt -o default
|
||||
elif [[ $type == 'plain' ]]; then
|
||||
COMPREPLY+=($value)
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_fastanime_completion_setup() {
|
||||
complete -o nosort -F _fastanime_completion fastanime
|
||||
}
|
||||
|
||||
_fastanime_completion_setup;
|
||||
"""
|
||||
)
|
||||
else:
|
||||
print("Specify either --zsh/--fish/--bash")
|
||||
@@ -47,7 +47,8 @@ class Config(object):
|
||||
"rofi_theme": "",
|
||||
"rofi_theme_input": "",
|
||||
"rofi_theme_confirm": "",
|
||||
"use_mpv_mod": "true",
|
||||
"use_mpv_mod": "false",
|
||||
"force_window": "immediate",
|
||||
}
|
||||
)
|
||||
self.configparser.add_section("stream")
|
||||
@@ -78,6 +79,7 @@ class Config(object):
|
||||
self.error = self.get_error()
|
||||
self.server = self.get_server()
|
||||
self.format = self.get_format()
|
||||
self.force_window = self.get_force_window()
|
||||
self.preferred_language = self.get_preferred_language()
|
||||
self.rofi_theme = self.get_rofi_theme()
|
||||
Rofi.rofi_theme = self.rofi_theme
|
||||
@@ -145,6 +147,9 @@ class Config(object):
|
||||
def get_skip(self):
|
||||
return self.configparser.getboolean("stream", "skip")
|
||||
|
||||
def get_force_window(self):
|
||||
return self.configparser.get("stream", "force_window")
|
||||
|
||||
def get_icons(self):
|
||||
return self.configparser.getboolean("general", "icons")
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ def player_controls(config: "Config", anilist_config: QueryDict):
|
||||
from ..utils.player import player
|
||||
|
||||
mpv = player.create_player(
|
||||
current_link,
|
||||
config.anime_provider,
|
||||
anilist_config,
|
||||
config,
|
||||
@@ -92,7 +93,6 @@ def player_controls(config: "Config", anilist_config: QueryDict):
|
||||
mpv._set_property("chapters-file", chapters_file[1])
|
||||
mpv._set_property("script-opts", script_opts[1])
|
||||
mpv.start = start_time
|
||||
mpv.play(current_link)
|
||||
mpv.wait_for_shutdown()
|
||||
mpv.terminate()
|
||||
stop_time = player.last_stop_time
|
||||
@@ -373,7 +373,11 @@ def fetch_streams(config: "Config", anilist_config: QueryDict):
|
||||
from ..utils.player import player
|
||||
|
||||
mpv = player.create_player(
|
||||
anime_provider, anilist_config, config, selected_server["episode_title"]
|
||||
stream_link,
|
||||
anime_provider,
|
||||
anilist_config,
|
||||
config,
|
||||
selected_server["episode_title"],
|
||||
)
|
||||
|
||||
if custom_args and None:
|
||||
@@ -382,7 +386,6 @@ def fetch_streams(config: "Config", anilist_config: QueryDict):
|
||||
mpv._set_property("chapters-file", chapters_file[1])
|
||||
mpv._set_property("script-opts", script_opts[1])
|
||||
mpv.start = start_time
|
||||
mpv.play(stream_link)
|
||||
mpv.wait_for_shutdown()
|
||||
mpv.terminate()
|
||||
stop_time = player.last_stop_time
|
||||
@@ -928,9 +931,9 @@ def anilist(config: "Config", anilist_config: QueryDict):
|
||||
return AniList.search(id_in=anime_list)
|
||||
|
||||
def edit_config():
|
||||
import subprocess
|
||||
from click import edit
|
||||
|
||||
subprocess.run([os.environ.get("EDITOR", "open"), USER_CONFIG_PATH])
|
||||
edit(filename=USER_CONFIG_PATH)
|
||||
if config.use_rofi:
|
||||
config.load_config()
|
||||
config.use_rofi = True
|
||||
|
||||
@@ -27,9 +27,13 @@ class MpvPlayer(object):
|
||||
last_stop_time_secs = 0
|
||||
last_total_time_secs = 0
|
||||
current_media_title = ""
|
||||
player_fetching = False
|
||||
|
||||
def get_episode(
|
||||
self, type: "Literal['next','previous','reload','custom']", ep_no=None
|
||||
self,
|
||||
type: "Literal['next','previous','reload','custom']",
|
||||
ep_no=None,
|
||||
server="top",
|
||||
):
|
||||
anilist_config = self.anilist_config
|
||||
config = self.config
|
||||
@@ -62,7 +66,9 @@ class MpvPlayer(object):
|
||||
elif type == "custom":
|
||||
if not ep_no or ep_no not in episodes:
|
||||
self.mpv_player.show_text("Episode number not specified or invalid")
|
||||
self.mpv_player.show_text(f"Acceptable episodes are: {episodes}")
|
||||
self.mpv_player.show_text(
|
||||
f"Acceptable episodes are: {episodes}",
|
||||
)
|
||||
return
|
||||
|
||||
self.mpv_player.show_text(f"Fetching episode {ep_no}")
|
||||
@@ -97,7 +103,19 @@ class MpvPlayer(object):
|
||||
return None
|
||||
|
||||
# always select the first
|
||||
selected_server = next(episode_streams)
|
||||
if server == "top":
|
||||
selected_server = next(episode_streams)
|
||||
else:
|
||||
episode_streams_dict = {
|
||||
episode_stream["server"]: episode_stream
|
||||
for episode_stream in episode_streams
|
||||
}
|
||||
selected_server = episode_streams_dict.get(server)
|
||||
if selected_server is None:
|
||||
self.mpv_player.show_text(
|
||||
f"Invalid server!!; servers available are: {episode_streams_dict.keys()}",
|
||||
)
|
||||
return None
|
||||
self.current_media_title = selected_server["episode_title"]
|
||||
links = selected_server["links"]
|
||||
if quality > len(links) - 1:
|
||||
@@ -108,7 +126,12 @@ class MpvPlayer(object):
|
||||
return stream_link
|
||||
|
||||
def create_player(
|
||||
self, anime_provider: "AnimeProvider", anilist_config, config: "Config", title
|
||||
self,
|
||||
stream_link,
|
||||
anime_provider: "AnimeProvider",
|
||||
anilist_config,
|
||||
config: "Config",
|
||||
title,
|
||||
):
|
||||
self.anime_provider = anime_provider
|
||||
self.anilist_config = anilist_config
|
||||
@@ -120,13 +143,52 @@ class MpvPlayer(object):
|
||||
self.current_media_title = ""
|
||||
|
||||
mpv_player = mpv.MPV(
|
||||
log_handler=print,
|
||||
loglevel="error",
|
||||
config=True,
|
||||
input_default_bindings=True,
|
||||
input_vo_keyboard=True,
|
||||
osc=True,
|
||||
ytdl=True,
|
||||
)
|
||||
mpv_player.force_window = config.force_window
|
||||
# mpv_player.cache = "yes"
|
||||
# mpv_player.cache_pause = "no"
|
||||
mpv_player.title = title
|
||||
|
||||
mpv_player.play(stream_link)
|
||||
|
||||
# -- events --
|
||||
@mpv_player.event_callback("file-loaded")
|
||||
def set_total_time(event, *args):
|
||||
d = mpv_player._get_property("duration")
|
||||
self.player_fetching = False
|
||||
if isinstance(d, float):
|
||||
self.last_total_time = format_time(d)
|
||||
|
||||
@mpv_player.property_observer("time-pos")
|
||||
def handle_time_start_update(*args):
|
||||
if len(args) > 1:
|
||||
value = args[1]
|
||||
if value is not None:
|
||||
self.last_stop_time = format_time(value)
|
||||
|
||||
@mpv_player.property_observer("time-remaining")
|
||||
def handle_time_remaining_update(
|
||||
property, time_remaining: float | None = None, *args
|
||||
):
|
||||
if time_remaining is not None:
|
||||
if time_remaining < 1 and config.auto_next and not self.player_fetching:
|
||||
print("Auto Fetching Next Episode")
|
||||
self.player_fetching = True
|
||||
url = self.get_episode("next")
|
||||
if url:
|
||||
mpv_player.loadfile(
|
||||
url,
|
||||
)
|
||||
mpv_player.title = self.current_media_title
|
||||
|
||||
# -- keybindings --
|
||||
@mpv_player.on_key_press("shift+n")
|
||||
def _next_episode():
|
||||
url = self.get_episode("next")
|
||||
@@ -134,18 +196,6 @@ class MpvPlayer(object):
|
||||
mpv_player.loadfile(url, options=f"title={self.current_media_title}")
|
||||
mpv_player.title = self.current_media_title
|
||||
|
||||
@mpv_player.event_callback("file-loaded")
|
||||
def set_total_time(event, *args):
|
||||
d = mpv_player._get_property("duration")
|
||||
if isinstance(d, float):
|
||||
self.last_total_time = format_time(d)
|
||||
|
||||
@mpv_player.event_callback("shutdown")
|
||||
def set_total_time_on_shutdown(event, *args):
|
||||
d = mpv_player._get_property("duration")
|
||||
if isinstance(d, float):
|
||||
self.last_total_time = format_time(d)
|
||||
|
||||
@mpv_player.on_key_press("shift+p")
|
||||
def _previous_episode():
|
||||
url = self.get_episode("previous")
|
||||
@@ -190,31 +240,11 @@ class MpvPlayer(object):
|
||||
)
|
||||
mpv_player.title = self.current_media_title
|
||||
|
||||
@mpv_player.property_observer("time-pos")
|
||||
def handle_time_start_update(*args):
|
||||
if len(args) > 1:
|
||||
value = args[1]
|
||||
if value is not None:
|
||||
self.last_stop_time_secs = value
|
||||
self.last_stop_time = format_time(value)
|
||||
|
||||
@mpv_player.property_observer("time-remaining")
|
||||
def handle_time_remaining_update(*args):
|
||||
if len(args) > 1:
|
||||
value = args[1]
|
||||
if value is not None:
|
||||
rem_time = value
|
||||
if rem_time < 10 and config.auto_next:
|
||||
url = self.get_episode("next")
|
||||
if url:
|
||||
mpv_player.loadfile(
|
||||
url,
|
||||
)
|
||||
mpv_player.title = self.current_media_title
|
||||
|
||||
# -- script messages --
|
||||
@mpv_player.message_handler("select-episode")
|
||||
def select_episode(episode: bytes | None = None, *args):
|
||||
if not episode:
|
||||
mpv_player.show_text("No episode was selected")
|
||||
return
|
||||
url = self.get_episode("custom", episode.decode())
|
||||
if url:
|
||||
@@ -223,11 +253,29 @@ class MpvPlayer(object):
|
||||
)
|
||||
mpv_player.title = self.current_media_title
|
||||
|
||||
mpv_player.register_message_handler("select-episode", select_episode)
|
||||
@mpv_player.message_handler("select-server")
|
||||
def select_server(server: bytes | None = None, *args):
|
||||
if not server:
|
||||
mpv_player.show_text("No server was selected")
|
||||
return
|
||||
url = self.get_episode("reload", server=server.decode())
|
||||
if url:
|
||||
mpv_player.loadfile(
|
||||
url,
|
||||
)
|
||||
mpv_player.title = self.current_media_title
|
||||
else:
|
||||
pass
|
||||
|
||||
# -- events --
|
||||
mpv_player.observe_property("time-pos", handle_time_start_update)
|
||||
mpv_player.register_event_callback(set_total_time)
|
||||
mpv_player.register_event_callback(set_total_time_on_shutdown)
|
||||
mpv_player.observe_property("time-remaining", handle_time_remaining_update)
|
||||
mpv_player.register_event_callback(set_total_time)
|
||||
|
||||
# --script-messages --
|
||||
mpv_player.register_message_handler("select-episode", select_episode)
|
||||
mpv_player.register_message_handler("select-server", select_server)
|
||||
|
||||
self.mpv_player = mpv_player
|
||||
return mpv_player
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ if PLATFORM == "Windows":
|
||||
ICON_PATH = os.path.join(ASSETS_DIR, "logo.ico")
|
||||
else:
|
||||
ICON_PATH = os.path.join(ASSETS_DIR, "logo.png")
|
||||
PREVIEW_IMAGE = os.path.join(ASSETS_DIR, "preview")
|
||||
|
||||
|
||||
# ----- user configs and data -----
|
||||
APP_DATA_DIR = dirs.user_config_dir
|
||||
|
||||
@@ -6,7 +6,6 @@ import sys
|
||||
from typing import Callable, List
|
||||
|
||||
# TODO: will probably scrap art not to useful
|
||||
from art import text2art
|
||||
from click import clear
|
||||
from rich import print
|
||||
|
||||
@@ -22,6 +21,17 @@ FZF_DEFAULT_OPTS = """
|
||||
--marker=">" --pointer="◆" --separator="─" --scrollbar="│"
|
||||
"""
|
||||
|
||||
HEADER = """
|
||||
|
||||
███████╗░█████╗░░██████╗████████╗░█████╗░███╗░░██╗██╗███╗░░░███╗███████╗
|
||||
██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔══██╗████╗░██║██║████╗░████║██╔════╝
|
||||
█████╗░░███████║╚█████╗░░░░██║░░░███████║██╔██╗██║██║██╔████╔██║█████╗░░
|
||||
██╔══╝░░██╔══██║░╚═══██╗░░░██║░░░██╔══██║██║╚████║██║██║╚██╔╝██║██╔══╝░░
|
||||
██║░░░░░██║░░██║██████╔╝░░░██║░░░██║░░██║██║░╚███║██║██║░╚═╝░██║███████╗
|
||||
╚═╝░░░░░╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░░░░╚═╝╚══════╝
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class FZF:
|
||||
"""an abstraction over the fzf commandline utility
|
||||
@@ -149,7 +159,7 @@ class FZF:
|
||||
_commands = [
|
||||
*self.default_options,
|
||||
"--header",
|
||||
text2art(header),
|
||||
HEADER,
|
||||
"--header-first",
|
||||
"--prompt",
|
||||
prompt.title(),
|
||||
|
||||
16
poetry.lock
generated
16
poetry.lock
generated
@@ -1,19 +1,5 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "art"
|
||||
version = "6.2"
|
||||
description = "ASCII Art Library For Python"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "art-6.2-py3-none-any.whl", hash = "sha256:d632d1d3f5fabcaf8673abe934b51df0017bc914d106e89d45ae4ebef0e3149a"},
|
||||
{file = "art-6.2.tar.gz", hash = "sha256:506a0c4f261289a0e0d088de7beffcb1835078c4e44b0c5353bdaf47b490e76f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["bandit (>=1.5.1)", "coverage (>=4.1)", "pydocstyle (>=3.0.0)", "vulture (>=1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "autoflake"
|
||||
version = "2.3.1"
|
||||
@@ -1421,4 +1407,4 @@ test = ["pytest (>=8.1,<9.0)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "5305621bc02d824065519913f0d754e269f1e4525ba8796be08504120614259d"
|
||||
content-hash = "83ec7de7d9466dcd1fadef4b21eec2a879cc9a7d526992ed280b6af53b49d9f1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "fastanime"
|
||||
version = "0.50.0"
|
||||
version = "0.61.5.dev1"
|
||||
description = "A browser anime site experience from the terminal"
|
||||
authors = ["Benextempest <benextempest@gmail.com>"]
|
||||
license = "UNLICENSE"
|
||||
@@ -13,7 +13,6 @@ rich = "^13.7.1"
|
||||
click = "^8.1.7"
|
||||
inquirerpy = "^0.3.4"
|
||||
platformdirs = "^4.2.2"
|
||||
art = "^6.2"
|
||||
python-dotenv = "^1.0.1"
|
||||
thefuzz = "^0.22.1"
|
||||
requests = "^2.32.3"
|
||||
|
||||
@@ -45,6 +45,16 @@ def test_search_help(runner: CliRunner):
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_cache_help(runner: CliRunner):
|
||||
result = runner.invoke(run_cli, ["cache", "--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_completions_help(runner: CliRunner):
|
||||
result = runner.invoke(run_cli, ["completions", "--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_anilist_help(runner: CliRunner):
|
||||
result = runner.invoke(run_cli, ["anilist", "--help"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
Reference in New Issue
Block a user