diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index ee5d947..6c383d2 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -66,7 +66,9 @@ def search(config: "Config", anime_titles: str, episode_range: str): from yt_dlp.utils import sanitize_filename from ...MangaProvider import MangaProvider + from ..utils.feh import feh_manga_viewer + from ..utils.icat import icat_manga_viewer manga_title = anime_titles[0] @@ -136,7 +138,12 @@ def search(config: "Config", anime_titles: str, episode_range: str): print( f"[purple bold]Now Reading: [/] {search_result_manga_title} [cyan bold]Chapter:[/] {chapter_info['title']}" ) - feh_manga_viewer(chapter_info["thumbnails"], str(chapter_info["title"])) + if config.manga_viewer == "feh": + feh_manga_viewer(chapter_info["thumbnails"], str(chapter_info["title"])) + elif config.manga_viewer == "icat": + icat_manga_viewer( + chapter_info["thumbnails"], str(chapter_info["title"]) + ) if anilist_helper: anilist_helper.update_anime_list( {"mediaId": anilist_id, "progress": chapter_number} diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index f074d03..198f94a 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -42,6 +42,7 @@ class Config(object): default_config = { "auto_next": "False", "menu_order": "", + "manga_viewer": "feh", "auto_select": "True", "cache_requests": "true", "check_for_updates": "True", @@ -187,6 +188,7 @@ class Config(object): self.skip = self.configparser.getboolean("stream", "skip") self.sort_by = self.configparser.get("anilist", "sort_by") self.menu_order = self.configparser.get("general", "menu_order") + self.manga_viewer = self.configparser.get("general", "manga_viewer") 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") @@ -476,6 +478,11 @@ mpv_args = {self.mpv_args} # useful incase of wanting to run sth like: kitty mpv --vo=kitty mpv_pre_args = {self.mpv_pre_args} +# choose manga viewer [feh/icat] +# feh is the default and requires feh to be installed +# icat is for kitty terminal users only +manga_viewer = {self.manga_viewer} + [stream] # the quality of the stream [1080,720,480,360] # this option is usually only reliable when: diff --git a/fastanime/cli/utils/icat.py b/fastanime/cli/utils/icat.py new file mode 100644 index 0000000..75fec45 --- /dev/null +++ b/fastanime/cli/utils/icat.py @@ -0,0 +1,102 @@ +import shutil +import subprocess +import sys +import termios +import tty +from sys import exit + +from rich.console import Console +from rich.panel import Panel +from rich.align import Align +from rich.text import Text + +console = Console() + + +def get_key(): + """Read a single keypress (including arrows).""" + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + try: + tty.setraw(fd) + ch1 = sys.stdin.read(1) + if ch1 == "\x1b": + ch2 = sys.stdin.read(2) + return ch1 + ch2 + return ch1 + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + + +def draw_banner_at(msg: str, row: int): + """Move cursor to `row`, then render a centered, cyan-bordered panel.""" + sys.stdout.write(f"\x1b[{row};1H") + text = Text(msg, justify="center") + panel = Panel(Align(text, align="center"), border_style="cyan", padding=(1, 2)) + console.print(panel) + + +def icat_manga_viewer(image_links: list[str], window_title: str): + ICAT = shutil.which("kitty") + if not ICAT: + console.print("[bold red]kitty (for icat) not found[/]") + exit(1) + + idx, total = 0, len(image_links) + title = f"{window_title} ({total} images)" + show_banner = True + + try: + while True: + console.clear() + term_width, term_height = shutil.get_terminal_size((80, 24)) + panel_height = 0 + + # Calculate space for image based on banner visibility + if show_banner: + msg_lines = 3 # Title + blank + controls + panel_height = msg_lines + 4 # Padding and borders + image_height = term_height - panel_height - 1 + else: + image_height = term_height + + subprocess.run( + [ + ICAT, + "+kitten", + "icat", + "--clear", + "--scale-up", + "--place", + f"{term_width}x{image_height}@0x0", + "--z-index", + "-1", + image_links[idx], + ] + ) + + if show_banner: + controls = ( + f"[{idx + 1}/{total}] Prev: [h/←] Next: [l/→] " + f"Toggle Banner: [b] Quit: [q/Ctrl-C]" + ) + msg = f"{title}\n\n{controls}" + start_row = term_height - panel_height + draw_banner_at(msg, start_row) + + # key handling + key = get_key() + if key in ("l", "\x1b[C"): + idx = (idx + 1) % total + elif key in ("h", "\x1b[D"): + idx = (idx - 1) % total + elif key == "b": + show_banner = not show_banner + elif key in ("q", "\x03"): + break + + except KeyboardInterrupt: + pass + finally: + console.clear() + console.print("Exited viewer.", style="bold")