Merge branch 'master' of gitea.suda.codes:sudacode/jimaku-dl

This commit is contained in:
sudacode 2025-03-12 20:40:41 -07:00
commit 9b4519ba3d
Signed by: sudacode
SSH Key Fingerprint: SHA256:lT5C2bB398DcX6daCF/gYFNSTK3y+Du3oTGUnYzfTEw
5 changed files with 86 additions and 16 deletions

@ -26,7 +26,6 @@ A tool for downloading Japanese subtitles for anime from <a href="https://jimaku
- Cross-platform support (Windows, macOS, Linux)
- Smart filename and directory parsing for anime detection
- Cache AniList IDs for faster repeat usage
- Interactive subtitle selection with fzf
## Installation
@ -39,7 +38,15 @@ pip install jimaku-dl
- Python 3.8+
- fzf for interactive selection menus (required)
- MPV for video playback (optional)
- ffsubsync for subtitle synchronization (optional)
- # ffsubsync for subtitle synchronization (optional)
```sh
paru -S python-jimaku-dl
# or
yay -S python-jimaku-dl
```
> > > > > > > a646fd2395e751096ccf9f40c9dc52cfc53273b7
## Usage

@ -36,6 +36,22 @@ def parse_args(args: Optional[Sequence[str]] = None) -> argparse.Namespace:
parser.add_argument(
"-v", "--version", action="version", version=f"jimaku-dl {__version__}"
)
parser = ArgumentParser(description="Download anime subtitles from Jimaku")
parser.add_argument("media_path", help="Path to the media file or directory")
parser.add_argument(
"-d",
"--dest",
help=(
"Directory to save downloaded subtitles "
"(default: same directory as video/input directory)"
),
)
parser.add_argument(
"-p",
"--play",
action="store_true",
help="Launch MPV with the subtitle(s) loaded",
)
# Global options
parser.add_argument(

@ -8,7 +8,7 @@ from functools import lru_cache
from importlib.util import find_spec
from logging import Logger, basicConfig, getLogger
from os import environ
from os.path import abspath, basename, dirname, exists, isdir, join, normpath, splitext
from os.path import abspath, basename, dirname, exists, isdir, join, normpath
from re import IGNORECASE
from re import compile as re_compile
from re import search, sub
@ -63,7 +63,7 @@ class JimakuDownloader:
self.api_token = api_token or environ.get("JIMAKU_API_TOKEN", "")
if not self.api_token:
self.logger.warning(
"No API token provided. Will need to be set before downloading."
"No API token provided. " "Will need to be set before downloading."
)
def _setup_logging(self, log_level: str) -> Logger:
@ -1519,7 +1519,8 @@ class JimakuDownloader:
elif play and is_directory:
print("Cannot play media with MPV when input is a directory. Skipping.")
self.logger.warning(
"Cannot play media with MPV when input is a directory. Skipping."
"Cannot play media with MPV when input is a directory. "
"Skipping playback."
)
self.logger.info("Subtitle download process completed successfully")

@ -96,9 +96,16 @@ class TestJimakuDownloader:
# Make sure the response object has a working raise_for_status method
mock_requests["response"].raise_for_status = MagicMock()
# Test with special characters in the title
result = downloader.query_anilist(
"KonoSuba Gods blessing on this wonderful world!! (2016)", season=3
)
assert result == 123456
# Patch requests.post directly to use our mock
with patch("jimaku_dl.downloader.requests_post", return_value=mock_requests["response"]):
with patch(
"jimaku_dl.downloader.requests_post", return_value=mock_requests["response"]
):
# Test the function with title and season
result = downloader.query_anilist("Test Anime", season=1)
assert result == 123456
@ -152,7 +159,9 @@ class TestJimakuDownloader:
mock_requests["response"].raise_for_status = MagicMock()
# Patch requests.post directly to use our mock
with patch("jimaku_dl.downloader.requests_post", return_value=mock_requests["response"]):
with patch(
"jimaku_dl.downloader.requests_post", return_value=mock_requests["response"]
):
# Test the function with title and season - should work even without API token
result = downloader.query_anilist("Test Anime", season=1)
assert result == 123456
@ -320,7 +329,9 @@ class TestJimakuDownloader:
mock_requests["response"].raise_for_status = MagicMock()
# Patch the requests.get function directly to use our mock
with patch("jimaku_dl.downloader.requests_get", return_value=mock_requests["response"]):
with patch(
"jimaku_dl.downloader.requests_get", return_value=mock_requests["response"]
):
# Call the function and check the result
result = downloader.query_jimaku_entries(123456)
assert result == mock_jimaku_entries_response
@ -349,8 +360,10 @@ class TestJimakuDownloader:
mock_get.assert_called_once()
url = mock_get.call_args[0][0]
assert "entries/1/files" in url
headers = mock_get.call_args[1].get('headers', {})
assert headers.get('Authorization') == 'test_token' # Changed from 'Bearer test_token'
headers = mock_get.call_args[1].get("headers", {})
assert (
headers.get("Authorization") == "test_token"
) # Changed from 'Bearer test_token'
def test_get_entry_files_no_token(self, monkeypatch):
"""Test getting entry files without API token."""

@ -110,6 +110,36 @@ class TestParseFilename:
# Simple number in season directory
file_path = os.path.join("path", "to", "My Anime", "Season 2", "5.mkv")
title, season, episode = downloader.parse_filename(file_path)
# Mock _prompt_for_title_info to avoid reading from stdin for the entire test function
with patch.object(self.downloader, "_prompt_for_title_info") as mock_prompt:
# Configure mock to return appropriate values for different test cases
mock_prompt.side_effect = [
("Show Name", 1, 2), # First call return value
("Show Name", 3, 4), # Second call return value
("My Anime", 2, 5), # Third call return value
("Long Anime Title With Spaces", 1, 3), # Fourth call return value
]
# Standard Season-## format
title, season, episode = self.downloader.parse_filename(
"/path/to/Show Name/Season-1/Show Name - 02 [1080p].mkv"
)
assert title == "Show Name"
assert season == 1
assert episode == 2
# Season ## format
title, season, episode = self.downloader.parse_filename(
"/path/to/Show Name/Season 03/Episode 4.mkv"
)
assert title == "Show Name"
assert season == 3
assert episode == 4
# Simple number in season directory
title, season, episode = self.downloader.parse_filename(
"/path/to/My Anime/Season 2/5.mkv"
)
assert title == "My Anime"
assert season == 2
assert episode == 5
@ -124,6 +154,9 @@ class TestParseFilename:
"Long Anime Title With Spaces - 03.mkv",
)
title, season, episode = downloader.parse_filename(file_path)
title, season, episode = self.downloader.parse_filename(
"/media/user/Anime/Long Anime Title With Spaces/Season-1/Long Anime Title With Spaces - 03.mkv"
)
assert title == "Long Anime Title With Spaces"
assert season == 1
assert episode == 3