Merge pull request #93 from iMithrellas/manga-icat

This pull request introduces a new manga viewer option, icat.
This commit is contained in:
Benedict Xavier
2025-05-12 13:04:55 +03:00
committed by GitHub
3 changed files with 117 additions and 1 deletions

View File

@@ -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}

View File

@@ -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 <url>
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:

102
fastanime/cli/utils/icat.py Normal file
View File

@@ -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")