Compare commits

..

39 Commits

Author SHA1 Message Date
benex
0b1a27b223 chore: bump version (v2.8.1) 2024-11-19 14:08:42 +03:00
benex
bafd04b788 chore: update lock file 2024-11-19 14:08:08 +03:00
benex
fb5f51eea5 feat(config): add customization options 2024-11-19 14:08:08 +03:00
benex
799e1f0681 fix(updater): handle no internet 2024-11-19 14:08:08 +03:00
benex
53a2d953f8 feat: enable customization of the preview window 2024-11-19 14:08:08 +03:00
Benex
9ce5bc3c76 Update README.md 2024-11-19 11:38:33 +03:00
benex
dc58fc8536 chore: bump version (v2.8.0) 2024-11-19 10:06:57 +03:00
benex
1d5c3016fc chore: update lock file 2024-11-19 10:06:38 +03:00
benex
8737aea746 fix(player): set character encoding for compatibility with windows 2024-11-19 10:06:38 +03:00
Benedict Xavier
bd03866f5e Update README.md 2024-11-19 08:40:09 +03:00
Benedict Xavier
81690a8015 Update README.md 2024-11-19 08:37:26 +03:00
Benedict Xavier
933112a52b Update README.md 2024-11-19 08:34:23 +03:00
Benedict Xavier
eb513dfe0e Update README.md 2024-11-19 08:23:45 +03:00
Benedict Xavier
3928b77506 Update README.md 2024-11-19 08:17:44 +03:00
Benedict Xavier
95cb2bd78c Update README.md 2024-11-19 08:03:33 +03:00
Benedict Xavier
4fa1c45eb2 Update DISCLAIMER.md 2024-11-19 08:02:41 +03:00
Benedict Xavier
b9051bc792 Create DISCLAIMER.md 2024-11-19 08:01:23 +03:00
Benedict Xavier
a590024f1c Merge pull request #15 from piradata/patch-1
Update README.md typo
2024-11-19 07:42:10 +03:00
Benedict Xavier
2f51936679 Merge pull request #16 from AtticusHelvig/uvREADME
Update README.md for uv Instructions
2024-11-19 07:37:26 +03:00
Atticus Helvig
327c50d290 Update README.md 2024-11-18 20:32:38 -07:00
Piradata
031dfbb9b5 Update README.md typo 2024-11-19 00:15:47 -03:00
benex
050365302a chore: bump flake.nix 2024-11-18 20:10:21 +03:00
benex
0f248b1119 chore: update flake.nix to include external deps and all python deps 2024-11-18 20:04:57 +03:00
benex
871d5cf758 chore: add result to .gitignore 2024-11-18 20:04:22 +03:00
benex
320376d2e8 chore: bump version (v2.7.9) 2024-11-18 18:19:21 +03:00
benex
02e7fdff6f chore: update lock file 2024-11-18 18:17:03 +03:00
benex
2c5c28f295 refactor(config): remove useless functions 2024-11-18 18:17:03 +03:00
benex
2d3509ccc1 chore: bump version (v2.7.8) 2024-11-18 17:10:26 +03:00
benex
30babf2d69 feat(cli): alert the user that the cli is checking for updates 2024-11-18 17:10:10 +03:00
benex
cfbbabf898 chore: update uv.lock 2024-11-18 17:10:10 +03:00
benex
5ac6c45fdf fix(allanime_provider): give all the servers the referer header 2024-11-18 17:10:10 +03:00
Benedict Xavier
a14645b563 Update README.md 2024-11-18 15:17:32 +03:00
Benedict Xavier
90dbc26c46 Merge pull request #9 from KaylorBen/master
fix: for nix flake
2024-11-18 13:31:31 +03:00
Benjamin Kaylor
54cc830c35 sed flake preBuild 2024-11-18 03:23:32 -07:00
Benjamin Kaylor
4928ff5b74 undo pyproject.toml edit 2024-11-18 03:20:55 -07:00
Benjamin Kaylor
bb481fe21a fix for nix flake 2024-11-17 16:21:56 -07:00
benex
0d27b8f652 chore: bump version (v2.7.7) 2024-11-17 20:08:37 +03:00
benex
bdd3aae399 chore: update lockfile 2024-11-17 20:08:08 +03:00
benex
af94cd7eb5 fix: recent anime 2024-11-17 20:07:46 +03:00
17 changed files with 371 additions and 222 deletions

1
.gitignore vendored
View File

@@ -176,3 +176,4 @@ app/View/SearchScreen/.search_screen.py.un~
app/View/SearchScreen/search_screen.py~
app/user_data.json
.buildozer
result

40
DISCLAIMER.md Normal file
View File

@@ -0,0 +1,40 @@
<h1 align="center">Disclaimer</h1>
<div align="center">
<h2>This project: fastanime</h2>
<br>
The core aim of this project is to co-relate automation and efficiency to extract what is provided to a user on the internet. All content available through the project is hosted by external non-affiliated sources.
<br>
<b>All content served through this project is publicly accessible. If your site is listed in this project, the code is pretty much public. Take necessary measures to counter the exploits used to extract content in your site.</b>
Think of this project as your normal browser, but a bit more straight-forward and specific. While an average browser makes hundreds of requests to get everything from a site, this project goes on to only make requests associated with getting the content served by the sites.
<b>
This project is to be used at the user's own risk, based on their government and laws.
This project has no control on the content it is serving, using copyrighted content from the providers is not going to be accounted for by the developer. It is the user's own risk.
</b>
<br>
<h2>DMCA and Copyright Infrigements</h3>
<br>
<b>
A browser is a tool, and the maliciousness of the tool is directly based on the user.
</b>
This project uses client-side content access mechanisms. Hence, the copyright infrigements or DMCA in this project's regards are to be forwarded to the associated site by the associated notifier of any such claims. This is one of the main reasons the sites are listed in this project.
<b>Do not harass the developer. Any personal information about the developer is intentionally not made public. Exploiting such information without consent in regards to this topic will lead to legal actions by the developer themselves.</b>

View File

@@ -1,12 +1,27 @@
# **FastAnime**
<p align="center">
<h1 align="center">FastAnime</h1>
</p>
<p align="center">
<sup>
Browse anime from the terminal
</sup>
</p>
<div align="center">
![PyPI - Downloads](https://img.shields.io/pypi/dm/fastanime) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/FastAnime/FastAnime/test.yml?label=Tests)
![Discord](https://img.shields.io/discord/1250887070906323096?label=Discord)
![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/FastAnime/FastAnime)
![GitHub deployments](https://img.shields.io/github/deployments/FastAnime/fastanime/pypi?label=PyPi%20Publish)
![PyPI - License](https://img.shields.io/pypi/l/fastanime)
![Static Badge](https://img.shields.io/badge/lines%20of%20code-13k%2B-green)
</div>
<p align="center">
<a href="https://discord.gg/HBEmAwvbHV">
<img src="https://invidget.switchblade.xyz/C4rhMA4mmK">
</a>
</p>
Welcome to **FastAnime**, anime site experience from the terminal.
![fastanime](https://github.com/user-attachments/assets/9ab09f26-e4a8-4b70-a315-7def998cec63)
@@ -31,7 +46,6 @@ Welcome to **FastAnime**, anime site experience from the terminal.
</details>
Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [jerry](https://github.com/justchokingaround/jerry/tree/main),[magic-tape](https://gitlab.com/christosangel/magic-tape/-/tree/main?ref_type=heads) and [ani-cli](https://github.com/pystardust/ani-cli).
<!--toc:start-->
@@ -67,11 +81,6 @@ Heavily inspired by [animdl](https://github.com/justfoolingaround/animdl), [jerr
- [Receiving Support](#receiving-support)
- [Supporting the Project](#supporting-the-project)
<!--toc:end-->
> [!IMPORTANT]
>
> This project currently scrapes allanime, hianime and animepahe, nyaa. The site is in the public domain and can be accessed by any one with a browser.
## Installation
![Windows](https://img.shields.io/badge/-Windows_x64-blue.svg?style=for-the-badge&logo=windows)
@@ -100,13 +109,13 @@ Recommended method of installation is using [uv](https://docs.astral.sh/uv/).
```bash
# generally:
uv tool install fastanime[standard]
uv tool install "fastanime[standard]"
# or stripped down installations:
uv tool install fastanime
uv tool install fastanime[api]
uv tool install fastanime[mpv]
uv tool install fastanime[notifications]
uv tool install "fastanime[api]"
uv tool install "fastanime[mpv]"
uv tool install "fastanime[notifications]"
```
@@ -201,7 +210,7 @@ The only required external dependency, unless you won't be streaming, is [MPV](h
- [webtorrent-cli](https://github.com/webtorrent/webtorrent-cli) used when the provider is nyaa
- [ffmpeg](https://www.ffmpeg.org/) is required to be in your path environment variables to properly download [hls](https://www.cloudflare.com/en-gb/learning/video/what-is-http-live-streaming/) streams.
- [fzf](https://github.com/junegunn/fzf) 🔥 which is used as a better alternative to the ui.
- [rofi](https://github.com/davatorium/rofi) 🔥 which is used as another alternative ui + the the desktop entry ui
- [rofi](https://github.com/davatorium/rofi) 🔥 which is used as another alternative ui + the desktop entry 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.
@@ -600,7 +609,7 @@ fastanime config --view
> [!Note]
>
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` .
> If it opens [vim](https://www.vim.org/download.php) you can exit by typing `:q` 😉.
#### cache subcommand
@@ -1142,7 +1151,7 @@ Result
"subtitles": [],
"links": [
{
"link": "https://tools.fast4speed.rsvp//media9/videos/8aM5BBoEGLvjG3MZm/sub/3",
"link": "",
"quality": "1080"
}
]
@@ -1154,10 +1163,10 @@ Result
"episode_title": "Sayounara Ryuusei, Konnichiwa Jinsei; Episode 3",
"links": [
{
"link": "https://myanime.sharepoint.com/sites/chartlousty/_layouts/15/download.aspx?share=ERpIT0CTmOVHmO8386bNGZMBf7Emtoda_3bUMzCleWhp4g",
"link": "",
"mp4": true,
"resolutionStr": "Mp4",
"src": "https://myanime.sharepoint.com/sites/chartlousty/_layouts/15/download.aspx?share=ERpIT0CTmOVHmO8386bNGZMBf7Emtoda_3bUMzCleWhp4g",
"src": "",
"quality": "1080"
}
]
@@ -1169,7 +1178,7 @@ Result
"episode_title": "Sayounara Ryuusei, Konnichiwa Jinsei; Episode 3",
"links": [
{
"link": "https://www114.anzeat.pro/streamhls/6454b50a557e9fa52a60cfdee0b0906e/ep.3.1729188150.m3u8",
"link": "",
"hls": true,
"mp4": false,
"resolutionStr": "hls P",
@@ -1177,7 +1186,7 @@ Result
"quality": "1080"
},
{
"link": "https://www114.anicdnstream.info/videos/hls/h1IUtAefmoWTc8hJhtr8OQ/1731106912/235294/6454b50a557e9fa52a60cfdee0b0906e/ep.3.1729188150.m3u8",
"link": "",
"hls": true,
"mp4": false,
"resolutionStr": "HLS1",
@@ -1185,10 +1194,10 @@ Result
"quality": "720"
},
{
"link": "https://workfields.maverickki.lol/7d2473746a243c246e727276753c29297171713737322867686f65626875727463676b286f68606929706f62636975296e6a75296e374f53724763606b695152653e6e4c6e72743e495729373135373736303f373429343533343f32293032333264333667333331633f6067333467303665606263633664363f3630632963762835283731343f373e3e373336286b35733e242a2476677475634e6a75243c727473632a2462677263243c373135373634363236363636367b",
"link": "",
"hls": true,
"resolutionStr": "Alt",
"src": "https://workfields.maverickki.lol/7d2473746a243c246e727276753c29297171713737322867686f65626875727463676b286f68606929706f62636975296e6a75296e374f53724763606b695152653e6e4c6e72743e495729373135373736303f373429343533343f32293032333264333667333331633f6067333467303665606263633664363f3630632963762835283731343f373e3e373336286b35733e242a2476677475634e6a75243c727473632a2462677263243c373135373634363236363636367b",
"src": "",
"priority": 1,
"quality": "480"
}
@@ -1414,11 +1423,10 @@ player = mpv
## Contributing
We welcome your issues and feature requests. However, due to time constraints, we currently do not plan to add another provider.
We welcome your issues and feature requests. However, due to time constraints, I currently do not plan to add another provider.
But if you are willing to add one yourself pr's are welcome.
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.
If you find an anime title that does not correspond with a provider or is just weird just [edit the data file](https://github.com/FastAnime/FastAnime/blob/master/fastanime/Utility/data.py) and open a pr or if you don't want to do that open an issue.
If you find an anime title that does not correspond with a provider or is just weird just [edit the data file](https://github.com/FastAnime/FastAnime/blob/master/fastanime/Utility/data.py) and open a pr, i will ignore issues 😝.
## Receiving Support
@@ -1431,5 +1439,13 @@ For inquiries, join our [Discord Server](https://discord.gg/HBEmAwvbHV).
</p>
## Supporting the Project
More pr's less issues 🙃
Show your support by starring our GitHub repository or [buying us a coffee](https://ko-fi.com/benex254).
Show your support by starring the GitHub repository or [buying me a coffee](https://ko-fi.com/benex254).
## Disclaimer
> [!IMPORTANT]
>
> This project currently scrapes allanime, hianime, nyaa, yugen and animepahe.
> The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
> [DISCLAIMER](https://github.com/Benex254/FastAnime/blob/master/DISCLAIMER.md)

View File

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

View File

@@ -229,6 +229,11 @@ def run_cli(
if ctx.obj.check_for_updates:
from .app_updater import check_for_updates
print("Checking for updates...")
print("So you can enjoy the latest features and bug fixes")
print(
"You can disable this by setting check_for_updates to False in the config"
)
is_latest, github_release_data = check_for_updates()
if not is_latest:
from rich.console import Console

View File

@@ -15,14 +15,18 @@ API_URL = f"https://api.{GIT_REPO}/repos/{AUTHOR}/{APP_NAME}/releases/latest"
def check_for_updates():
USER_AGENT = f"{APP_NAME} user"
request = requests.get(
API_URL,
headers={
"User-Agent": USER_AGENT,
"X-GitHub-Api-Version": "2022-11-28",
"Accept": "application/vnd.github+json",
},
)
try:
request = requests.get(
API_URL,
headers={
"User-Agent": USER_AGENT,
"X-GitHub-Api-Version": "2022-11-28",
"Accept": "application/vnd.github+json",
},
)
except Exception:
print("You are not connected to the internet")
return True, {}
if request.status_code == 200:
release_json = request.json()

View File

@@ -3,6 +3,7 @@ import logging
import os
from configparser import ConfigParser
from typing import TYPE_CHECKING
from ..libs.fzf import FZF_DEFAULT_OPTS, HEADER
from ..constants import (
USER_CONFIG_PATH,
@@ -42,6 +43,9 @@ class Config(object):
"ffmpegthumbnailer_seek_time": "-1",
"force_forward_tracking": "true",
"force_window": "immediate",
"fzf_opts": FZF_DEFAULT_OPTS,
"header_color": "95,135,175",
"header_ascii_art": HEADER,
"format": "best[height<=1080]/bestvideo[height<=1080]+bestaudio/best",
"icons": "false",
"image_previews": "True" if S_PLATFORM != "win32" else "False",
@@ -51,6 +55,8 @@ class Config(object):
"preferred_history": "local",
"preferred_language": "english",
"preview": "False",
"preview_header_color": "215,0,95",
"preview_separator_color": "208,208,208",
"provider": "allanime",
"quality": "1080",
"recent": "50",
@@ -83,55 +89,80 @@ class Config(object):
if os.path.exists(USER_CONFIG_PATH):
self.configparser.read(USER_CONFIG_PATH, encoding="utf-8")
# TODO: rewrite all this removing the useless functions
# hate technical debt
# why did i do this lol
self.auto_next = self.get_auto_next()
self.auto_select = self.get_auto_select()
self.cache_requests = self.get_cache_requests()
self.check_for_updates = self.configparser.get("general", "check_for_updates")
self.continue_from_history = self.get_continue_from_history()
self.default_media_list_tracking = self.get_default_media_list_tracking()
# get the configuration
self.auto_next = self.configparser.getboolean("stream", "auto_next")
self.auto_select = self.configparser.getboolean("stream", "auto_select")
self.cache_requests = self.configparser.getboolean("general", "cache_requests")
self.check_for_updates = self.configparser.getboolean(
"general", "check_for_updates"
)
self.continue_from_history = self.configparser.getboolean(
"stream", "continue_from_history"
)
self.default_media_list_tracking = self.configparser.get(
"general", "default_media_list_tracking"
)
self.disable_mpv_popen = self.configparser.getboolean(
"stream", "disable_mpv_popen"
)
self.downloads_dir = self.get_downloads_dir()
self.episode_complete_at = self.get_episode_complete_at()
self.ffmpegthumbnailer_seek_time = self.get_ffmpegthumnailer_seek_time()
self.force_forward_tracking = self.get_force_forward_tracking()
self.force_window = self.get_force_window()
self.format = self.get_format()
self.icons = self.get_icons()
self.image_previews = self.get_image_previews()
self.normalize_titles = self.get_normalize_titles()
self.notification_duration = self.get_notification_duration()
self.player = self.get_player()
self.preferred_history = self.get_preferred_history()
self.preferred_language = self.get_preferred_language()
self.preview = self.get_preview()
self.provider = self.get_provider()
self.quality = self.get_quality()
self.downloads_dir = self.configparser.get("general", "downloads_dir")
self.episode_complete_at = self.configparser.getint(
"stream", "episode_complete_at"
)
self.ffmpegthumbnailer_seek_time = self.configparser.getint(
"general", "ffmpegthumbnailer_seek_time"
)
self.force_forward_tracking = self.configparser.getboolean(
"general", "force_forward_tracking"
)
self.force_window = self.configparser.get("stream", "force_window")
self.format = self.configparser.get("stream", "format")
self.fzf_opts = self.configparser.get("general", "fzf_opts")
self.header_color = self.configparser.get("general", "header_color")
self.header_ascii_art = self.configparser.get("general", "header_ascii_art")
self.icons = self.configparser.getboolean("general", "icons")
self.image_previews = self.configparser.getboolean("general", "image_previews")
self.normalize_titles = self.configparser.getboolean(
"general", "normalize_titles"
)
self.notification_duration = self.configparser.getint(
"general", "notification_duration"
)
self.player = self.configparser.get("stream", "player")
self.preferred_history = self.configparser.get("stream", "preferred_history")
self.preferred_language = self.configparser.get("general", "preferred_language")
self.preview = self.configparser.getboolean("general", "preview")
self.preview_separator_color = self.configparser.get(
"general", "preview_separator_color"
)
self.preview_header_color = self.configparser.get(
"general", "preview_header_color"
)
self.provider = self.configparser.get("general", "provider")
self.quality = self.configparser.get("stream", "quality")
self.recent = self.configparser.getint("general", "recent")
self.rofi_theme_confirm = self.configparser.get("general", "rofi_theme_confirm")
self.rofi_theme_input = self.configparser.get("general", "rofi_theme_input")
self.rofi_theme = self.configparser.get("general", "rofi_theme")
self.rofi_theme_preview = self.configparser.get("general", "rofi_theme_preview")
self.server = self.configparser.get("stream", "server")
self.skip = self.configparser.getboolean("stream", "skip")
self.sort_by = self.configparser.get("anilist", "sort_by")
self.sub_lang = self.configparser.get("general", "sub_lang")
self.translation_type = self.configparser.get("stream", "translation_type")
self.use_fzf = self.configparser.getboolean("general", "use_fzf")
self.use_python_mpv = self.configparser.getboolean("stream", "use_python_mpv")
self.use_rofi = self.configparser.getboolean("general", "use_rofi")
self.use_persistent_provider_store = self.configparser.getboolean(
"general", "use_persistent_provider_store"
)
self.recent = self.get_recent()
self.rofi_theme_confirm = self.get_rofi_theme_confirm()
self.rofi_theme_input = self.get_rofi_theme_input()
self.rofi_theme = self.get_rofi_theme()
self.rofi_theme_preview = self.get_rofi_theme_preview()
Rofi.rofi_theme_confirm = self.rofi_theme_confirm
Rofi.rofi_theme_input = self.rofi_theme_input
Rofi.rofi_theme = self.rofi_theme
Rofi.rofi_theme_input = self.rofi_theme_input
Rofi.rofi_theme_confirm = self.rofi_theme_confirm
Rofi.rofi_theme_preview = self.rofi_theme_preview
self.server = self.get_server()
self.skip = self.get_skip()
self.sort_by = self.get_sort_by()
self.sub_lang = self.get_sub_lang()
self.translation_type = self.get_translation_type()
self.use_fzf = self.get_use_fzf()
self.use_python_mpv = self.get_use_mpv_mod()
self.use_rofi = self.get_use_rofi()
self.use_persistent_provider_store = self.get_use_persistent_provider_store()
os.environ["FZF_DEFAULT_OPTS"] = self.fzf_opts
# ---- setup user data ------
self.anime_list: list = self.user_data.get("animelist", [])
@@ -155,7 +186,7 @@ class Config(object):
def update_recent(self, recent_anime: list):
recent_anime_ids = []
_recent_anime = []
for anime in recent_anime[::-1]:
for anime in recent_anime:
if (
anime["id"] not in recent_anime_ids
and len(recent_anime_ids) <= self.recent
@@ -208,122 +239,14 @@ class Config(object):
with open(USER_DATA_PATH, "w") as f:
json.dump(self.user_data, f)
# getters for user configuration
# --- general section ---
def get_provider(self):
return self.configparser.get("general", "provider")
def get_ffmpegthumnailer_seek_time(self):
return self.configparser.getint("general", "ffmpegthumbnailer_seek_time")
def get_preferred_language(self):
return self.configparser.get("general", "preferred_language")
def get_sub_lang(self):
return self.configparser.get("general", "sub_lang")
def get_downloads_dir(self):
return self.configparser.get("general", "downloads_dir")
def get_icons(self):
return self.configparser.getboolean("general", "icons")
def get_image_previews(self):
return self.configparser.getboolean("general", "image_previews")
def get_preview(self):
return self.configparser.getboolean("general", "preview")
def get_use_fzf(self):
return self.configparser.getboolean("general", "use_fzf")
def get_use_persistent_provider_store(self):
return self.configparser.getboolean("general", "use_persistent_provider_store")
# rofi conifiguration
def get_use_rofi(self):
return self.configparser.getboolean("general", "use_rofi")
def get_rofi_theme(self):
return self.configparser.get("general", "rofi_theme")
def get_rofi_theme_preview(self):
return self.configparser.get("general", "rofi_theme_preview")
def get_rofi_theme_input(self):
return self.configparser.get("general", "rofi_theme_input")
def get_rofi_theme_confirm(self):
return self.configparser.get("general", "rofi_theme_confirm")
def get_force_forward_tracking(self):
return self.configparser.getboolean("general", "force_forward_tracking")
def get_cache_requests(self):
return self.configparser.getboolean("general", "cache_requests")
def get_default_media_list_tracking(self):
return self.configparser.get("general", "default_media_list_tracking")
def get_normalize_titles(self):
return self.configparser.getboolean("general", "normalize_titles")
def get_recent(self):
return self.configparser.getint("general", "recent")
# --- stream section ---
def get_skip(self):
return self.configparser.getboolean("stream", "skip")
def get_auto_next(self):
return self.configparser.getboolean("stream", "auto_next")
def get_auto_select(self):
return self.configparser.getboolean("stream", "auto_select")
def get_continue_from_history(self):
return self.configparser.getboolean("stream", "continue_from_history")
def get_use_mpv_mod(self):
return self.configparser.getboolean("stream", "use_python_mpv")
def get_notification_duration(self):
return self.configparser.getint("general", "notification_duration")
def get_episode_complete_at(self):
return self.configparser.getint("stream", "episode_complete_at")
def get_force_window(self):
return self.configparser.get("stream", "force_window")
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")
def get_server(self):
return self.configparser.get("stream", "server")
def get_format(self):
return self.configparser.get("stream", "format")
def get_player(self):
return self.configparser.get("stream", "player")
def get_sort_by(self):
return self.configparser.get("anilist", "sort_by")
def update_config(self, section: str, key: str, value: str):
self.configparser.set(section, key, value)
with open(USER_CONFIG_PATH, "w") as config:
self.configparser.write(config)
def __repr__(self):
new_line = "\n"
tab = "\t"
current_config_state = f"""\
#
# ███████╗░█████╗░░██████╗████████╗░█████╗░███╗░░██╗██╗███╗░░░███╗███████╗ ░█████╗░░█████╗░███╗░░██╗███████╗██╗░██████╗░
@@ -334,11 +257,44 @@ class Config(object):
# ╚═╝░░░░░╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░░░░╚═╝╚══════╝ ░╚════╝░░╚════╝░╚═╝░░╚══╝╚═╝░░░░░╚═╝░╚═════╝░
#
[general]
# well recently somebody made a post @unixporn subreddit
# the rice contained fastanime in preview mode
# Did not even realise you rice with it
# or i would have cross-posted
# 'A new way to rice'
# and immediately one of the mods removed it
# am not sure why though
# cause the preview is actually pretty good looking.
# just like you would use fastfetch or cava
# as means to protest against this injustice i have addded
# the following
# for you all ricers out there
# and be sure to post a rice with fastanime on it
# if you also think this is unfair
# for the preview pane
preview_separator_color = {self.preview_separator_color}
preview_header_color = {self.preview_header_color}
# for the header
# be sure to indent
header_ascii_art = {new_line.join([tab+line for line in self.header_ascii_art.split(new_line)])}
header_color = {self.header_color}
# to be passed to fzf
# may break it down further for now just pass the options as is
# be sure to indent
fzf_opts = {new_line.join([tab+line for line in self.fzf_opts.split(new_line)])}
# whether to show the icons in the tui [True/False]
# more like emojis
# by the way if you have any recommendations to which should be used where please
# by the way if you have any recommendations
# to which should be used where please
# don't hesitate to share your opinion
# cause it's a lot of work to look for the right one for each menu option
# cause it's a lot of work
# to look for the right one for each menu option
# be sure to also give the replacement emoji
icons = {self.icons}
@@ -353,10 +309,20 @@ normalize_titles = {self.normalize_titles}
# cause there are always new features being added 😄
check_for_updates = {self.check_for_updates}
# can be [allanime, animepahe, hianime]
# can be [allanime, animepahe, hianime, nyaa, yugen]
# allanime is the most realible
# animepahe provides different links to streams of different quality so a quality can be selected reliably with --quality option
# hianime which is now hianime usually provides subs in different languuages and its servers are generally faster
# hianime usually provides subs in different languuages and its servers are generally faster
# NOTE: currently they are encrypting the video links
# though am working on it
# however, you can still get the links to the subs
# with ```fastanime grab``` command
# yugen meh
# nyaa those who prefer torrents, though not reliable due to auto selection of results
# as most of the data in nyaa is not structured
# though works relatively well for new anime
# esp with subsplease and horriblesubs
# oh and you should have webtorrent cli to use this
provider = {self.provider}
# Display language [english, romaji]
@@ -377,6 +343,11 @@ downloads_dir = {self.downloads_dir}
preview = {self.preview}
# whether to show images in the preview [true/false]
# windows users just swtich to linux 😄
# cause even if you enable it
# it won't look pretty
# just be satisfied with the text previews
# so forget it exists 🤣
image_previews = {self.image_previews}
# the time to seek when using ffmpegthumbnailer [-1 to 100]
@@ -394,11 +365,13 @@ use_fzf = {self.use_fzf}
# though if you want it to be your sole interface even when fastanime is run directly from the terminal
use_rofi = {self.use_rofi}
# rofi themes to use
# rofi themes to use <path>
# the values of this option is the path to the rofi config files to use
# i choose to split it into three since it gives the best look and feel
# i choose to split it into 4 since it gives the best look and feel
# you can refer to the rofi demo on github to see for your self
# by the way i recommend getting the rofi themes from this project;
# i need help designing the default rofi themes
# if you fancy yourself a rofi ricer please contribute to making
# the default theme better
rofi_theme = {self.rofi_theme}
rofi_theme_preview = {self.rofi_theme_preview}
@@ -414,7 +387,7 @@ notification_duration = {self.notification_duration}
# used when the provider gives subs of different languages
# currently its the case for:
# hianime
# the values for this option are the short names for countries
# the values for this option are the short names for languages
# regex is used to determine what you selected
sub_lang = {self.sub_lang}
@@ -478,6 +451,7 @@ translation_type = {self.translation_type}
# allanime: [dropbox, sharepoint, wetransfer, gogoanime, wixmp]
# animepahe: [kwik]
# hianime: [HD1, HD2, StreamSB, StreamTape]
# yugen: [gogoanime]
# 'top' can also be used as a value for this option
# 'top' will cause fastanime to auto select the first server it sees
# this saves on resources and is faster since not all servers are being fetched
@@ -495,14 +469,23 @@ auto_next = {self.auto_next}
# that are there own preference rather than the official names
# But 99% of the time will be accurate
# if this happens just turn of auto_select in the menus or from the commandline and manually select the correct anime title
# and then please open an issue at <> highlighting the normalized title and the title given by the provider for the anime you wished to watch
# or even better edit this file <> and open a pull request
# and then please open an issue
# highlighting the normalized title
# and the title given by the provider for the anime you wished to watch
# or even better edit this file <https://github.com/Benex254/FastAnime/blob/master/fastanime/Utility/data.py>
# and open a pull request
# prefrably, so you can give me a small break
# of doing everything 😄
# and its always nice to see people contributing
# to projects they love and use
auto_select = {self.auto_select}
# whether to skip the opening and ending theme songs [True/False]
# NOTE: requires ani-skip to be in path
# for python-mpv users am planning to create this functionality n python without the use of an external script
# so its disabled for now
# and anyways Dan Da Dan
# taught as the importance of letting it flow 🙃
skip = {self.skip}
# at what percentage progress should the episode be considered as completed [0-100]
@@ -514,7 +497,8 @@ episode_complete_at = {self.episode_complete_at}
# whether to use python-mpv [True/False]
# to enable superior control over the player
# adding more options to it
# Enable this one and you will be wonder why you did not discover fastanime sooner
# Enable this one and you will be wonder
# why you did not discover fastanime sooner 🙃
# Since you basically don't have to close the player window
# to go to the next or previous episode, switch servers,
# change translation type or change to a given episode x
@@ -568,6 +552,7 @@ player = {self.player}
# since we may not always have the time to immediately implement the changes
#
# HOPE YOU ENJOY FASTANIME AND BE SURE TO STAR THE PROJECT ON GITHUB
# https://github.com/Benex254/FastAnime
#
"""
return current_config_state

View File

@@ -542,8 +542,8 @@ def provider_anime_episode_servers_menu(
if config.recent:
config.update_recent(
[
*config.user_data["recent_anime"],
fastanime_runtime_state.selected_anime_anilist,
*config.user_data["recent_anime"],
]
)
print("Updating recent anime...")
@@ -1590,7 +1590,10 @@ def fastanime_main_menu(
return AniList.search(id_in=watch_history, sort="TRENDING_DESC")
def _recent():
return (True, {"data": {"Page": {"media": config.user_data["recent_anime"]}}})
return (
True,
{"data": {"Page": {"media": config.user_data["recent_anime"]}}},
)
# WARNING: Will probably be depracated
def _anime_list():

View File

@@ -46,8 +46,12 @@ def aniskip(mal_id: int, episode: str):
# NOTE: May change this to a temp dir but there were issues so later
WORKING_DIR = APP_CACHE_DIR # tempfile.gettempdir()
HEADER_COLOR = 215, 0, 95
SEPARATOR_COLOR = 208, 208, 208
_HEADER_COLOR = os.environ.get("FASTANIME_PREVIEW_HEADER_COLOR", "215,0,95").split(",")
HEADER_COLOR = _HEADER_COLOR[0], _HEADER_COLOR[1], _HEADER_COLOR[2]
_SEPARATOR_COLOR = os.environ.get(
"FASTANIME_PREVIEW_SEPARATOR_COLOR", "208,208,208"
).split(",")
SEPARATOR_COLOR = _SEPARATOR_COLOR[0], _SEPARATOR_COLOR[1], _SEPARATOR_COLOR[2]
SINGLE_QUOTE = "'"
IMAGES_CACHE_DIR = os.path.join(WORKING_DIR, "images")
if not os.path.exists(IMAGES_CACHE_DIR):
@@ -65,7 +69,7 @@ def save_image_from_url(url: str, file_name: str):
file_name: filename to use
"""
image = requests.get(url)
with open(os.path.join(IMAGES_CACHE_DIR,f"{file_name}.png"), "wb") as f:
with open(os.path.join(IMAGES_CACHE_DIR, f"{file_name}.png"), "wb") as f:
f.write(image.content)
@@ -76,7 +80,14 @@ def save_info_from_str(info: str, file_name: str):
info: the information anilist has on the anime
file_name: the filename to use
"""
with open(os.path.join(ANIME_INFO_CACHE_DIR,file_name,), "w",encoding="utf-8") as f:
with open(
os.path.join(
ANIME_INFO_CACHE_DIR,
file_name,
),
"w",
encoding="utf-8",
) as f:
f.write(info)

View File

@@ -59,7 +59,10 @@ def stream_video(MPV, url, mpv_args, custom_args):
process.wait()
else:
proc = subprocess.run(
[MPV, url, *mpv_args, *custom_args], capture_output=True, text=True
[MPV, url, *mpv_args, *custom_args],
capture_output=True,
text=True,
encoding="utf-8",
)
if proc.stdout:
for line in reversed(proc.stdout.split("\n")):
@@ -97,7 +100,7 @@ def run_mpv(
time.sleep(120)
return "0", "0"
cmd = [WEBTORRENT_CLI, link, f"--{player}"]
subprocess.run(cmd)
subprocess.run(cmd, encoding="utf-8")
return "0", "0"
if player == "vlc":
VLC = shutil.which("vlc")
@@ -148,7 +151,7 @@ def run_mpv(
if title:
args.append("--video-title")
args.append(title)
subprocess.run(args)
subprocess.run(args, encoding="utf-8")
return "0", "0"
else:
# Determine if mpv is available

View File

@@ -108,7 +108,7 @@ def format_bytes_to_human(num_of_bytes: float, suffix: str = "B"):
return f"{num_of_bytes:.1f}Yi{suffix}"
def get_true_fg(string: str, r: int, g: int, b: int, bold: bool = True) -> str:
def get_true_fg(string: str, r, g, b, bold: bool = True) -> str:
"""Custom helper function that enables colored text in the terminal
Args:

View File

@@ -233,7 +233,7 @@ class AllAnimeAPI(AnimeProvider):
logger.debug("allanime:Found streams from gogoanime")
return {
"server": "gogoanime",
"headers": {},
"headers": {"Referer": f"https://{ALLANIME_BASE}/"},
"subtitles": [],
"episode_title": (
allanime_episode["notes"] or f"{anime_title}"
@@ -245,7 +245,7 @@ class AllAnimeAPI(AnimeProvider):
logger.debug("allanime:Found streams from wetransfer")
return {
"server": "wetransfer",
"headers": {},
"headers": {"Referer": f"https://{ALLANIME_BASE}/"},
"subtitles": [],
"episode_title": (
allanime_episode["notes"] or f"{anime_title}"
@@ -257,7 +257,7 @@ class AllAnimeAPI(AnimeProvider):
logger.debug("allanime:Found streams from sharepoint")
return {
"server": "sharepoint",
"headers": {},
"headers": {"Referer": f"https://{ALLANIME_BASE}/"},
"subtitles": [],
"episode_title": (
allanime_episode["notes"] or f"{anime_title}"
@@ -269,7 +269,7 @@ class AllAnimeAPI(AnimeProvider):
logger.debug("allanime:Found streams from dropbox")
return {
"server": "dropbox",
"headers": {},
"headers": {"Referer": f"https://{ALLANIME_BASE}/"},
"subtitles": [],
"episode_title": (
allanime_episode["notes"] or f"{anime_title}"
@@ -281,7 +281,7 @@ class AllAnimeAPI(AnimeProvider):
logger.debug("allanime:Found streams from wixmp")
return {
"server": "wixmp",
"headers": {},
"headers": {"Referer": f"https://{ALLANIME_BASE}/"},
"subtitles": [],
"episode_title": (
allanime_episode["notes"] or f"{anime_title}"

View File

@@ -41,8 +41,8 @@ class FZF:
stdout: [TODO:attribute]
"""
if not os.getenv("FZF_DEFAULT_OPTS"):
os.environ["FZF_DEFAULT_OPTS"] = FZF_DEFAULT_OPTS
# if not os.getenv("FZF_DEFAULT_OPTS"):
# os.environ["FZF_DEFAULT_OPTS"] = FZF_DEFAULT_OPTS
FZF_EXECUTABLE = shutil.which("fzf")
default_options = [
"--cycle",
@@ -157,10 +157,18 @@ class FZF:
Returns:
[TODO:return]
"""
_HEADER_COLOR = os.environ.get("FASTANIME_HEADER_COLOR", "215,0,95").split(",")
header = os.environ.get("FASTANIME_HEADER_ASCII_ART", HEADER)
header = "\n".join(
[
f"\033[38;2;{_HEADER_COLOR[0]};{_HEADER_COLOR[1]};{_HEADER_COLOR[2]};m{line}\033[0m"
for line in header.split("\n")
]
)
_commands = [
*self.default_options,
"--header",
HEADER,
header,
"--header-first",
"--prompt",
f"{prompt.title()}: ",
@@ -182,6 +190,7 @@ class FZF:
print(info)
input("Enter to try again")
return self.run(fzf_input, prompt, header, preview, expect, validator)
# os.environ["FZF_DEFAULT_OPTS"] = ""
return result

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1731676054,
"narHash": "sha256-OZiZ3m8SCMfh3B6bfGC/Bm4x3qc1m2SVEAlkV6iY7Yg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5e4fbfb6b3de1aa2872b76d49fafc942626e2add",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -10,14 +10,18 @@
let
pkgs = import nixpkgs { inherit system; };
python = pkgs.python310;
python = pkgs.python312;
pythonPackages = python.pkgs;
fastanimeEnv = pythonPackages.buildPythonApplication {
pname = "fastanime";
version = "2.7.5";
version = "2.8.0";
src = ./.;
preBuild = ''
sed -i 's/rich>=13.9.2/rich>=13.8.1/' pyproject.toml
'';
# Add runtime dependencies
propagatedBuildInputs = with pythonPackages; [
click
@@ -28,6 +32,9 @@
yt-dlp
dbus-python
hatchling
plyer
mpv
fastapi
];
# Ensure compatibility with the pyproject.toml
@@ -43,6 +50,10 @@
buildInputs = [
fastanimeEnv
pythonPackages.hatchling
pkgs.mpv
pkgs.libmpv
pkgs.fzf
pkgs.rofi
];
};
});

View File

@@ -1,6 +1,6 @@
[project]
name = "fastanime"
version = "2.7.6"
version = "2.8.1"
description = "A browser anime site experience from the terminal"
license = "UNLICENSE"
readme = "README.md"

2
uv.lock generated
View File

@@ -170,7 +170,7 @@ wheels = [
[[package]]
name = "fastanime"
version = "2.7.5"
version = "2.8.0"
source = { editable = "." }
dependencies = [
{ name = "click" },