Compare commits

...

14 Commits

Author SHA1 Message Date
Benex254
8409fa7d43 chore: bump version 2024-08-16 13:51:57 +03:00
Benex254
c81da78190 chore: bump version 2024-08-16 13:51:36 +03:00
Benex254
e17ea4bb89 fix(interface): incorrect loading of episode during replat 2024-08-16 13:50:58 +03:00
Benex254
0087728aa8 docs: update readme 2024-08-16 13:22:35 +03:00
Benex254
9e48e02f7a feat(downloads command): improve local downloads experience 2024-08-16 13:12:10 +03:00
Benex254
1291d55ab0 feat(downloads command): add previews 2024-08-16 11:38:18 +03:00
Benex254
b5c6a1e39e feat: improve path handling on windows 2024-08-16 10:54:13 +03:00
Benex254
d6adb30802 feat(download command): remove unused option and improve help message 2024-08-16 10:47:25 +03:00
Benex254
1d08a69a85 feat(search command): improve help message 2024-08-16 10:46:37 +03:00
Benex254
1087ab3408 chore: add error checking todo 2024-08-16 10:46:05 +03:00
Benex254
51afd504df chore: update config obj 2024-08-16 10:45:40 +03:00
Benex254
75efc9d73a docs: update readme 2024-08-16 10:45:18 +03:00
Benex254
6b68086cff feat(interface): improve watch history experience 2024-08-16 10:10:47 +03:00
Benex254
3686cdfdb3 feat(completions): enhance speed of loading completion functions 2024-08-15 12:21:29 +03:00
15 changed files with 481 additions and 171 deletions

View File

@@ -200,6 +200,7 @@ Available options for the fastanime command include:
- `--server <server>` or `-s <server>` set the default server to auto select
- `--continue/--no-continue` or `-c/-no-c` whether to continue from the last episode you were watching
- `--local-history/--remote-history` whether to use remote or local history defaults to local
- `--quality <1080/720/480/360>` or `-q <1080/720/480/360>` the link to choose from server
- `--translation-type <dub/sub>` or `-t <dub/sub>` what language for anime
- `--dub` dubbed anime
@@ -222,16 +223,17 @@ Available options for the fastanime command include:
- `--log-file` allow logging to a file
- `--rich-traceback` allow rich traceback
- `--use-mpv-mod/--use-default-player` whether to use python-mpv
- `--provider <allanime/animepahe>` anime site of choice to scrape from **NOTE:** animepahe is still experimental and requires node to decode one line of js thats hard to decode manually
- `--provider <allanime>` anime site of choice to scrape from
Example usage of the above options
```bash
# downloading dubbed anime
fastanime --dub download <anime>
# use icons and fzf for a more elegant ui with preview
# only for anilist
fastanime --icons --preview --fzf anilist
# use icons with default ui
fastanime --icons --default anilist
```
@@ -346,9 +348,21 @@ View and stream the anime you downloaded using MPV.
```bash
fastanime downloads
# view individual episodes
fastanime downloads --view-episodes
# --- or ---
fastanime downloads -v
# to set seek time when using ffmpegthumbnailer for local previews
# -1 means random and is the default
fastanime downloads --time-to-seek <intRange(-1,100)>
# --- or ---
fastanime downloads --t <intRange(-1,100)>
# to get the path to the downloads folder set
fastanime downloads --path
# useful when you want to use the value for other programs
```
#### config subcommand
@@ -449,29 +463,43 @@ 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>
# to select quality
script-message select-quality <1080/720/480/360>
```
## Configuration
## configuration
The app includes sensible defaults but can be customized extensively. Configuration is stored in `.ini` format at `~/.config/FastAnime/config.ini` on Linux and mac or somewhere on windows; you can check by running `fastanime config --path`.
The app includes sensible defaults but can be customized extensively. Configuration is stored in `.ini` format at `~/.config/FastAnime/config.ini` on arch linux; for the other operating systems you can check by running `fastanime config --path`.
```ini
[stream]
continue_from_history = True # Auto continue from watch history
# which history to use [local/remote]
preferred_history = local
translation_type = sub # Preferred language for anime (options: dub, sub)
server = top # Default server (options: dropbox, sharepoint, wetransfer.gogoanime, top, wixmp)
auto_next = False # Auto-select next episode
# Auto select the anime provider results with fuzzy find.
# Note this wont always be correct.But 99% of the time will be.
auto_select=True
# whether to skip the opening and ending theme songs
# note requires ani-skip to be in path
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
@@ -487,14 +515,19 @@ format=best[height<=1080]/bestvideo[height<=1080]+bestaudio/best # default
provider = allanime
preferred_language = romaji # Display language (options: english, romaji)
downloads_dir = <Default-videos-dir>/FastAnime # Download directory
preview=false # whether to show a preview window when using fzf or rofi
use_fzf=False # whether to use fzf as the interface for the anilist command and others.
use_rofi=false # whether to use rofi for the ui
rofi_theme=<path-to-rofi-theme-file>
rofi_theme_input=<path-to-rofi-theme-file>
rofi_theme_confirm=<path-to-rofi-theme-file>

View File

@@ -6,7 +6,7 @@ if sys.version_info < (3, 10):
) # noqa: F541
__version__ = "v1.6.3"
__version__ = "v1.7.0"
APP_NAME = "FastAnime"
AUTHOR = "Benex254"

View File

@@ -68,6 +68,11 @@ signal.signal(signal.SIGINT, handle_exit)
type=bool,
help="Continue from last episode?",
)
@click.option(
"--local-history/--remote-history",
type=bool,
help="Whether to continue from local history or remote history",
)
@click.option(
"--skip/--no-skip",
type=bool,
@@ -146,6 +151,7 @@ def run_cli(
server,
format,
continue_,
local_history,
skip,
translation_type,
quality,
@@ -216,6 +222,11 @@ def run_cli(
ctx.obj.auto_next = auto_next
if ctx.get_parameter_source("icons") == click.core.ParameterSource.COMMANDLINE:
ctx.obj.icons = icons
if (
ctx.get_parameter_source("local_history")
== click.core.ParameterSource.COMMANDLINE
):
ctx.obj.preferred_history = "local" if local_history else "remote"
if (
ctx.get_parameter_source("auto_select")
== click.core.ParameterSource.COMMANDLINE

View File

@@ -1,6 +1,6 @@
import click
from ...utils.completion_types import anime_titles_shell_complete
from ...completion_functions import anime_titles_shell_complete
@click.command(

View File

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
import click
from ..utils.completion_types import anime_titles_shell_complete
from ..completion_functions import anime_titles_shell_complete
if TYPE_CHECKING:
from ..config import Config
@@ -19,16 +19,14 @@ if TYPE_CHECKING:
@click.option(
"--episode-range",
"-r",
help="A range of episodes to download",
)
@click.option(
"--highest_priority",
"-h",
help="Choose stream indicated as highest priority",
is_flag=True,
help="A range of episodes to download (start-end)",
)
@click.pass_obj
def download(config: "Config", anime_title, episode_range, highest_priority):
def download(
config: "Config",
anime_title,
episode_range,
):
from click import clear
from rich import print
from rich.progress import Progress
@@ -55,7 +53,11 @@ def download(config: "Config", anime_title, episode_range, highest_priority):
if not search_results:
print("Search results failed")
input("Enter to retry")
download(config, anime_title, episode_range, highest_priority)
download(
config,
anime_title,
episode_range,
)
return
search_results = search_results["results"]
search_results_ = {
@@ -86,7 +88,11 @@ def download(config: "Config", anime_title, episode_range, highest_priority):
if not anime:
print("Sth went wring anime no found")
input("Enter to continue...")
download(config, anime_title, episode_range, highest_priority)
download(
config,
anime_title,
episode_range,
)
return
episodes = anime["availableEpisodesDetail"][config.translation_type]

View File

@@ -1,7 +1,9 @@
import logging
from typing import TYPE_CHECKING
import click
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from ..config import Config
@@ -10,8 +12,16 @@ if TYPE_CHECKING:
help="View and watch your downloads using mpv", short_help="Watch downloads"
)
@click.option("--path", "-p", help="print the downloads folder and exit", is_flag=True)
@click.option("--view-episodes", "-v", help="View individual episodes", is_flag=True)
@click.option(
"--ffmpegthumbnailer-seek-time",
"--time-to-seek",
"-t",
type=click.IntRange(-1, 100),
help="ffmpegthumbnailer seek time [0-100]",
)
@click.pass_obj
def downloads(config: "Config", path: bool):
def downloads(config: "Config", path: bool, view_episodes, ffmpegthumbnailer_seek_time):
import os
from ...cli.utils.mpv import run_mpv
@@ -20,6 +30,8 @@ def downloads(config: "Config", path: bool):
from ..utils.tools import exit_app
from ..utils.utils import fuzzy_inquirer
if not ffmpegthumbnailer_seek_time:
ffmpegthumbnailer_seek_time = config.ffmpegthumbnailer_seek_time
USER_VIDEOS_DIR = config.downloads_dir
if path:
print(USER_VIDEOS_DIR)
@@ -27,24 +39,250 @@ def downloads(config: "Config", path: bool):
if not os.path.exists(USER_VIDEOS_DIR):
print("Downloads directory specified does not exist")
return
playlists = os.listdir(USER_VIDEOS_DIR)
playlists.append("Exit")
anime_downloads = os.listdir(USER_VIDEOS_DIR)
anime_downloads.append("Exit")
def stream():
def create_thumbnails(video_path, anime_title, downloads_thumbnail_cache_dir):
import os
import shutil
import subprocess
FFMPEG_THUMBNAILER = shutil.which("ffmpegthumbnailer")
if not FFMPEG_THUMBNAILER:
return
out = os.path.join(downloads_thumbnail_cache_dir, anime_title)
if ffmpegthumbnailer_seek_time == -1:
import random
seektime = str(random.randrange(0, 100))
else:
seektime = str(ffmpegthumbnailer_seek_time)
_ = subprocess.run(
[
FFMPEG_THUMBNAILER,
"-i",
video_path,
"-o",
out,
"-s",
"0",
"-t",
seektime,
],
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
def get_previews_anime(workers=None, bg=True):
import concurrent.futures
import shutil
from pathlib import Path
if not shutil.which("ffmpegthumbnailer"):
print("ffmpegthumbnailer not found")
logger.error("ffmpegthumbnailer not found")
return
from ...constants import APP_CACHE_DIR
from ..utils.scripts import fzf_preview
downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
def _worker():
# use concurrency to download the images as fast as possible
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
for anime_title in anime_downloads:
anime_path = os.path.join(USER_VIDEOS_DIR, anime_title)
if not os.path.isdir(anime_path):
continue
playlist = os.listdir(anime_path)
if playlist:
# actual link to download image from
video_path = os.path.join(anime_path, playlist[0])
future_to_url[
executor.submit(
create_thumbnails,
video_path,
anime_title,
downloads_thumbnail_cache_dir,
)
] = anime_title
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
future.result()
except Exception as e:
logger.error("%r generated an exception: %s" % (url, e))
if bg:
from threading import Thread
worker = Thread(target=_worker)
worker.daemon = True
worker.start()
else:
_worker()
os.environ["SHELL"] = shutil.which("bash") or "bash"
preview = """
%s
if [ -s %s/{} ]; then
if ! fzf-preview %s/{} 2>/dev/null; then
echo Loading...
fi
else echo Loading...
fi
""" % (
fzf_preview,
downloads_thumbnail_cache_dir,
downloads_thumbnail_cache_dir,
)
return preview
def get_previews_episodes(anime_playlist_path, workers=None, bg=True):
import shutil
from pathlib import Path
from ...constants import APP_CACHE_DIR
from ..utils.scripts import fzf_preview
if not shutil.which("ffmpegthumbnailer"):
print("ffmpegthumbnailer not found")
logger.error("ffmpegthumbnailer not found")
return
downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
def _worker():
import concurrent.futures
# use concurrency to download the images as fast as possible
# anime_playlist_path = os.path.join(USER_VIDEOS_DIR, anime_playlist_path)
if not os.path.isdir(anime_playlist_path):
return
anime_episodes = os.listdir(anime_playlist_path)
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
for episode_title in anime_episodes:
episode_path = os.path.join(anime_playlist_path, episode_title)
# actual link to download image from
future_to_url[
executor.submit(
create_thumbnails,
episode_path,
episode_title,
downloads_thumbnail_cache_dir,
)
] = episode_title
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
future.result()
except Exception as e:
logger.error("%r generated an exception: %s" % (url, e))
if bg:
from threading import Thread
worker = Thread(target=_worker)
worker.daemon = True
worker.start()
else:
_worker()
os.environ["SHELL"] = shutil.which("bash") or "bash"
preview = """
%s
if [ -s %s/{} ]; then
if ! fzf-preview %s/{} 2>/dev/null; then
echo Loading...
fi
else echo Loading...
fi
""" % (
fzf_preview,
downloads_thumbnail_cache_dir,
downloads_thumbnail_cache_dir,
)
return preview
def stream_episode(
anime_playlist_path,
):
if view_episodes:
if not os.path.isdir(anime_playlist_path):
print(anime_playlist_path, "is not dir")
exit_app(1)
return
episodes = os.listdir(anime_playlist_path)
downloaded_episodes = [*episodes, "Back"]
if config.use_fzf:
if not config.preview:
episode_title = fzf.run(
downloaded_episodes,
"Enter Episode ",
)
else:
preview = get_previews_episodes(anime_playlist_path)
episode_title = fzf.run(
downloaded_episodes,
"Enter Episode ",
preview=preview,
)
elif config.use_rofi:
episode_title = Rofi.run(downloaded_episodes, "Enter Episode")
else:
episode_title = fuzzy_inquirer(
downloaded_episodes,
"Enter Playlist Name: ",
)
if episode_title == "Back":
stream_anime()
return
episode_path = os.path.join(anime_playlist_path, episode_title)
run_mpv(episode_path)
stream_episode(anime_playlist_path)
def stream_anime():
if config.use_fzf:
playlist_name = fzf.run(playlists, "Enter Playlist Name", "Downloads")
if not config.preview:
playlist_name = fzf.run(
anime_downloads,
"Enter Playlist Name",
)
else:
preview = get_previews_anime()
playlist_name = fzf.run(
anime_downloads,
"Enter Playlist Name",
preview=preview,
)
elif config.use_rofi:
playlist_name = Rofi.run(playlists, "Enter Playlist Name")
playlist_name = Rofi.run(anime_downloads, "Enter Playlist Name")
else:
playlist_name = fuzzy_inquirer(
playlists,
anime_downloads,
"Enter Playlist Name: ",
)
if playlist_name == "Exit":
exit_app()
return
playlist = os.path.join(USER_VIDEOS_DIR, playlist_name)
run_mpv(playlist)
stream()
if view_episodes:
stream_episode(
playlist,
)
else:
run_mpv(playlist)
stream_anime()
stream()
stream_anime()

View File

@@ -1,7 +1,7 @@
import click
from ...cli.config import Config
from ..utils.completion_types import anime_titles_shell_complete
from ..completion_functions import anime_titles_shell_complete
@click.command(
@@ -11,7 +11,7 @@ from ..utils.completion_types import anime_titles_shell_complete
@click.option(
"--episode-range",
"-r",
help="A range of episodes to binge",
help="A range of episodes to binge (start-end)",
)
@click.argument(
"anime_title", required=True, shell_complete=anime_titles_shell_complete

View File

@@ -1,10 +1,3 @@
from typing import TYPE_CHECKING
import requests
if TYPE_CHECKING:
from ...libs.anilist.types import AnilistDataSchema
import logging
logger = logging.getLogger(__name__)
@@ -43,13 +36,15 @@ def get_anime_titles(query: str, variables: dict = {}):
Returns:
a boolean indicating success and none or an anilist object depending on success
"""
from requests import post
try:
response = requests.post(
response = post(
ANILIST_ENDPOINT,
json={"query": query, "variables": variables},
timeout=10,
)
anilist_data: AnilistDataSchema = response.json()
anilist_data = response.json()
# ensuring you dont get blocked
if (
@@ -78,20 +73,10 @@ def get_anime_titles(query: str, variables: dict = {}):
]
return [*eng_titles, *romaji_titles]
else:
return ["non 200 status code"]
except requests.exceptions.Timeout:
logger.warning(
"Timeout has been exceeded this could mean anilist is down or you have lost internet connection"
)
return ["timeout exceeded"]
except requests.exceptions.ConnectionError:
logger.warning(
"ConnectionError this could mean anilist is down or you have lost internet connection"
)
return ["connection error"]
return []
except Exception as e:
logger.error(f"Something unexpected occured {e}")
return ["unexpected error"]
return []
def anime_titles_shell_complete(ctx, param, incomplete):

View File

@@ -78,6 +78,7 @@ class Config(object):
"translation_type": "sub",
"server": "top",
"continue_from_history": "True",
"preferred_history": "local",
"use_mpv_mod": "false",
"force_window": "immediate",
"preferred_language": "english",
@@ -93,6 +94,7 @@ class Config(object):
"rofi_theme": "",
"rofi_theme_input": "",
"rofi_theme_confirm": "",
"ffmpegthumnailer_seek_time": "-1",
}
)
self.configparser.add_section("stream")
@@ -125,12 +127,14 @@ class Config(object):
self.format = self.get_format()
self.force_window = self.get_force_window()
self.preferred_language = self.get_preferred_language()
self.preferred_history = self.get_preferred_history()
self.rofi_theme = self.get_rofi_theme()
Rofi.rofi_theme = self.rofi_theme
self.rofi_theme_input = self.get_rofi_theme_input()
Rofi.rofi_theme_input = self.rofi_theme_input
self.rofi_theme_confirm = self.get_rofi_theme_confirm()
Rofi.rofi_theme_confirm = self.rofi_theme_confirm
self.ffmpegthumbnailer_seek_time = self.get_ffmpegthumnailer_seek_time()
# ---- setup user data ------
self.watch_history: dict = self.user_data.get("watch_history", {})
self.anime_list: list = self.user_data.get("animelist", [])
@@ -142,7 +146,7 @@ class Config(object):
self._update_user_data()
def update_watch_history(
self, anime_id: int, episode: str | None, start_time="0", total_time="0"
self, anime_id: int, episode: str, start_time="0", total_time="0"
):
self.watch_history.update(
{
@@ -176,6 +180,9 @@ class Config(object):
def get_provider(self):
return self.configparser.get("general", "provider")
def get_ffmpegthumnailer_seek_time(self):
return self.configparser.getint("general", "ffmpegthumnailer_seek_time")
def get_preferred_language(self):
return self.configparser.get("general", "preferred_language")
@@ -232,6 +239,9 @@ class Config(object):
def get_translation_type(self):
return self.configparser.get("stream", "translation_type")
def get_preferred_history(self):
return self.configparser.get("stream", "preferred_history")
def get_quality(self):
return self.configparser.get("stream", "quality")
@@ -255,6 +265,10 @@ class Config(object):
# Auto continue from watch history
continue_from_history = {self.continue_from_history}
# which hostory to use [local/remote]
preferred_history = {self.preferred_history}
# Preferred language for anime (options: dub, sub)
translation_type = {self.translation_type}
@@ -303,6 +317,10 @@ downloads_dir = {self.downloads_dir}
# whether to show a preview window when using fzf or rofi
preview = {self.preview}
# the time to seek when using ffmpegthumbnailer [-1 to 100]
# -1 means random and is the default
ffmpegthumbnailer_seek_time = {self.ffmpegthumbnailer_seek_time}
# whether to use fzf as the interface for the anilist command and others.
use_fzf = {self.use_fzf}
@@ -311,9 +329,12 @@ use_rofi = {self.use_rofi}
# rofi theme to use
rofi_theme = {self.rofi_theme}
rofi_theme_input = {self.rofi_theme_input}
rofi_theme_confirm = {self.rofi_theme_confirm}
# whether to show the icons
icons = {self.icons}

View File

@@ -31,6 +31,7 @@ if TYPE_CHECKING:
from ..utils.tools import FastAnimeRuntimeState
# TODO: make the error handling more sane
def calculate_time_delta(start_time, end_time):
"""helper function used to calculate the difference between two timestamps in seconds
@@ -97,8 +98,14 @@ def media_player_controls(
current_episode_number,
)
start_time = config.watch_history[str(anime_id_anilist)]["start_time"]
print("[green]Continuing from:[/] ", start_time)
if (
config.watch_history[str(anime_id_anilist)]["episode"]
== current_episode_number
):
start_time = config.watch_history[str(anime_id_anilist)]["start_time"]
print("[green]Continuing from:[/] ", start_time)
else:
start_time = "0"
custom_args = []
if config.skip:
if args := aniskip(
@@ -365,35 +372,18 @@ def provider_anime_episode_servers_menu(
# no need to get all servers if top just works
with Progress() as progress:
progress.add_task("Fetching top server...", total=None)
try:
selected_server = next(episode_streams_generator, None)
if not selected_server:
if config.use_rofi:
if Rofi.confirm("Sth went wrong enter to continue"):
provider_anime_episode_servers_menu(
config, fastanime_runtime_state
)
else:
exit_app(1)
else:
print("Sth went wrong")
input("Enter to continue...")
provider_anime_episode_servers_menu(
config, fastanime_runtime_state
)
return
server_name = "top"
except Exception as e:
print("Failed to get streams. Reason:", e)
if not config.use_rofi:
input("Enter to coninue...")
selected_server = next(episode_streams_generator, None)
if not selected_server:
if config.use_rofi:
if Rofi.confirm("Sth went wrong enter to continue"):
media_actions_menu(config, fastanime_runtime_state)
else:
if not Rofi.confirm(f"!!Sth went wrong!!: {e} Enter to continue"):
exit_app(1)
server_name = None
selected_server = ""
exit_app(1)
else:
print("Sth went wrong")
input("Enter to continue...")
media_actions_menu(config, fastanime_runtime_state)
return
return
else:
with Progress() as progress:
progress.add_task("Fetching servers...", total=None)
@@ -402,6 +392,17 @@ def provider_anime_episode_servers_menu(
for episode_stream in episode_streams_generator
}
if not episode_streams_dict:
if config.use_rofi:
if Rofi.confirm("Sth went wrong enter to continue"):
media_actions_menu(config, fastanime_runtime_state)
else:
exit_app(1)
else:
print("Sth went wrong")
input("Enter to continue...")
media_actions_menu(config, fastanime_runtime_state)
return
# check if user server exists and is actually a valid serrver then sets it
if config.server and config.server in episode_streams_dict.keys():
server_name = config.server
@@ -478,7 +479,7 @@ def provider_anime_episode_servers_menu(
AniList.update_anime_list(
{
"mediaId": anime_id_anilist,
"progress": current_episode_number,
"progress": int(float(current_episode_number)),
}
)
@@ -486,7 +487,10 @@ def provider_anime_episode_servers_menu(
start_time = config.watch_history.get(str(anime_id_anilist), {}).get(
"start_time", "0"
)
if start_time != "0":
episode_in_history = config.watch_history.get(str(anime_id_anilist), {}).get(
"episode", ""
)
if start_time != "0" and episode_in_history == current_episode_number:
print("[green]Continuing from:[/] ", start_time)
custom_args = []
if config.skip:
@@ -512,7 +516,7 @@ def provider_anime_episode_servers_menu(
script_opts = custom_args[1].split("=", 1)
mpv._set_property("chapters-file", chapters_file[1])
mpv._set_property("script-opts", script_opts[1])
if not start_time == "0":
if not start_time == "0" and episode_in_history == current_episode_number:
mpv.start = start_time
mpv.wait_for_shutdown()
mpv.terminate()
@@ -520,6 +524,8 @@ def provider_anime_episode_servers_menu(
total_time = player.last_total_time
current_episode_number = fastanime_runtime_state.provider_current_episode_number
else:
if not episode_in_history == current_episode_number:
start_time = "0"
stop_time, total_time = run_mpv(
current_stream_link,
selected_server["episode_title"],
@@ -602,9 +608,19 @@ def provider_anime_episodes_menu(
user_watch_history.get(str(anime_id_anilist), {}).get("episode")
in total_episodes
):
current_episode_number = user_watch_history[str(anime_id_anilist)][
"episode"
]
if (
config.preferred_history == "local"
or not selected_anime_anilist["mediaListEntry"]
):
current_episode_number = user_watch_history[str(anime_id_anilist)][
"episode"
]
else:
current_episode_number = str(
(selected_anime_anilist["mediaListEntry"] or {"progress": 0}).get(
"progress"
)
)
print(
f"[bold cyan]Continuing from Episode:[/] [bold]{current_episode_number}[/]"
)
@@ -626,7 +642,7 @@ def provider_anime_episodes_menu(
current_episode_number = ""
# prompt for episode number if not set
if not current_episode_number:
if not current_episode_number or current_episode_number not in total_episodes:
choices = [*total_episodes, "Back"]
if config.use_fzf:
current_episode_number = fzf.run(
@@ -662,7 +678,6 @@ def provider_anime_episodes_menu(
provider_anime_episode_servers_menu(config, fastanime_runtime_state)
# WARNING: Marked for deletion, the function is quite useless and function calls in python are expensive
def fetch_anime_episode(config, fastanime_runtime_state: "FastAnimeRuntimeState"):
selected_anime: "SearchResult" = (
fastanime_runtime_state.provider_anime_search_result
@@ -1133,7 +1148,7 @@ def media_actions_menu(
f"{'💠 ' if icons else ''}Toggle auto next episode": _toggle_auto_next,
f"{'🔘 ' if icons else ''}Toggle continue from history": _toggle_continue_from_history,
f"{'🔙 ' if icons else ''}Back": anilist_results_menu,
f"{'' if icons else ''}Exit": exit_app,
f"{'' if icons else ''}Exit": lambda *_: exit_app(),
}
choices = list(options.keys())
if config.use_fzf:

View File

@@ -12,89 +12,11 @@ from yt_dlp.utils import clean_html
from ...constants import APP_CACHE_DIR
from ...libs.anilist.types import AnilistBaseMediaDataSchema
from ...Utility import anilist_data_helper
from ..utils.scripts import fzf_preview
from ..utils.utils import get_true_fg
logger = logging.getLogger(__name__)
# this script was written by the fzf devs as an example on how to preview images
# its only here for convinience
fzf_preview = r"""
#
# The purpose of this script is to demonstrate how to preview a file or an
# image in the preview window of fzf.
#
# Dependencies:
# - https://github.com/sharkdp/bat
# - https://github.com/hpjansson/chafa
# - https://iterm2.com/utilities/imgcat
fzf-preview(){
if [[ $# -ne 1 ]]; then
>&2 echo "usage: $0 FILENAME"
exit 1
fi
file=${1/#\~\//$HOME/}
type=$(file --dereference --mime -- "$file")
if [[ ! $type =~ image/ ]]; then
if [[ $type =~ =binary ]]; then
file "$1"
exit
fi
# Sometimes bat is installed as batcat.
if command -v batcat > /dev/null; then
batname="batcat"
elif command -v bat > /dev/null; then
batname="bat"
else
cat "$1"
exit
fi
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
exit
fi
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
if [[ $dim = x ]]; then
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
# * https://github.com/junegunn/fzf/issues/2544
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
fi
# 1. Use kitty icat on kitty terminal
if [[ $KITTY_WINDOW_ID ]]; then
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
# you have to use 'stream'.
#
# 2. The last line of the output is the ANSI reset code without newline.
# This confuses fzf and makes it render scroll offset indicator.
# So we remove the last line and append the reset code to its previous line.
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
# 2. Use chafa with Sixel output
elif command -v chafa > /dev/null; then
chafa -f sixel -s "$dim" "$file"
# Add a new line character so that fzf can display multiple images in the preview window
echo
# 3. If chafa is not found but imgcat is available, use it on iTerm2
elif command -v imgcat > /dev/null; then
# NOTE: We should use https://iterm2.com/utilities/it2check to check if the
# user is running iTerm2. But for the sake of simplicity, we just assume
# that's the case here.
imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"
# 4. Cannot find any suitable method to preview the image
else
file "$file"
fi
}
"""
# ---- aniskip intergration ----
def aniskip(mal_id: int, episode: str):

View File

@@ -101,7 +101,7 @@ class MpvPlayer(object):
AniList.update_anime_list(
{
"mediaId": anime_id_anilist,
"progress": current_episode_number,
"progress": int(float(current_episode_number)),
}
)
# get them juicy streams
@@ -141,6 +141,7 @@ class MpvPlayer(object):
return
self.mpv_player._set_property("start", "0")
stream_link = stream_link_["link"]
fastanime_runtime_state.provider_current_episode_stream_link = stream_link
return stream_link
def create_player(

View File

@@ -0,0 +1,78 @@
# this script was written by the fzf devs as an example on how to preview images
# its only here for convinience
fzf_preview = r"""
#
# The purpose of this script is to demonstrate how to preview a file or an
# image in the preview window of fzf.
#
# Dependencies:
# - https://github.com/sharkdp/bat
# - https://github.com/hpjansson/chafa
# - https://iterm2.com/utilities/imgcat
fzf-preview(){
if [[ $# -ne 1 ]]; then
>&2 echo "usage: $0 FILENAME"
exit 1
fi
file=${1/#\~\//$HOME/}
type=$(file --dereference --mime -- "$file")
if [[ ! $type =~ image/ ]]; then
if [[ $type =~ =binary ]]; then
file "$1"
exit
fi
# Sometimes bat is installed as batcat.
if command -v batcat > /dev/null; then
batname="batcat"
elif command -v bat > /dev/null; then
batname="bat"
else
cat "$1"
exit
fi
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
exit
fi
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
if [[ $dim = x ]]; then
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
# * https://github.com/junegunn/fzf/issues/2544
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
fi
# 1. Use kitty icat on kitty terminal
if [[ $KITTY_WINDOW_ID ]]; then
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
# you have to use 'stream'.
#
# 2. The last line of the output is the ANSI reset code without newline.
# This confuses fzf and makes it render scroll offset indicator.
# So we remove the last line and append the reset code to its previous line.
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
# 2. Use chafa with Sixel output
elif command -v chafa > /dev/null; then
chafa -f sixel -s "$dim" "$file"
# Add a new line character so that fzf can display multiple images in the preview window
echo
# 3. If chafa is not found but imgcat is available, use it on iTerm2
elif command -v imgcat > /dev/null; then
# NOTE: We should use https://iterm2.com/utilities/it2check to check if the
# user is running iTerm2. But for the sake of simplicity, we just assume
# that's the case here.
imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"
# 4. Cannot find any suitable method to preview the image
else
file "$file"
fi
}
"""

View File

@@ -34,7 +34,7 @@ if S_PLATFORM == "win32":
APP_CACHE_DIR = os.path.join(APP_DATA_DIR, "cache")
# videos dir
video_dir_base = os.path.expanduser("~/Videos")
video_dir_base = os.path.join(Path().home(), "Videos")
USER_VIDEOS_DIR = os.path.join(video_dir_base, APP_NAME)
elif S_PLATFORM == "darwin":

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "fastanime"
version = "1.6.3.dev1"
version = "1.7.0"
description = "A browser anime site experience from the terminal"
authors = ["Benextempest <benextempest@gmail.com>"]
license = "UNLICENSE"