Compare commits

..
Author SHA1 Message Date
Dymas bcf9e15d40 Fix anipy fallback episode numbering
Use the queued episode spec when finalizing anipy-cli fallback TV downloads so retries keep the requested episode numbers instead of being renumbered from 1, and update tests plus release docs for the fix.
2026-07-09 15:16:53 +02:00
Dymas 0c17499efa Install anipy-cli in Docker image
Install anipy-cli systemwide during the Docker build so fallback downloads work inside the container without extra setup, and update the README to describe the new Docker behavior.
2026-07-09 15:03:27 +02:00
6 changed files with 70 additions and 5 deletions
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## 0.47.1 - 2026-07-09
- Fixed `anipy-cli` fallback renaming for queued TV downloads so retried jobs now keep the requested episode numbers from the queue, avoiding cases like a retried `S02E11` being renamed as `S02E01`.
## 0.47.0 - 2026-07-09
- Added an optional `anipy-cli` fallback toggle on the Config page so failed `ani-cli` queue jobs can automatically retry once with `anipy-cli`.
+3
View File
@@ -48,6 +48,9 @@ RUN curl -fL "${YT_DLP_RELEASE_URL}" -o /usr/local/bin/yt-dlp \
&& chmod 0755 /usr/local/bin/yt-dlp \
&& yt-dlp --version
RUN python3 -m pip install --no-cache-dir anipy-cli \
&& anipy-cli --version
RUN git clone --depth 1 --branch "${ANI_CLI_WEB_BRANCH}" "${ANI_CLI_WEB}" /app \
&& rm -rf /app/.git \
&& rm -f \
+5 -3
View File
@@ -2,7 +2,7 @@
Local web UI for a system-wide `ani-cli` install.
Current version: `0.47.0`
Current version: `0.47.1`
## What it does
@@ -28,7 +28,7 @@ Current version: `0.47.0`
Requirements:
- `ani-cli` installed and available in `PATH`
- Optional: `anipy-cli` installed and available in `PATH` if you want automatic fallback retries
- Optional: `anipy-cli` installed and available in `PATH` if you want automatic fallback retries outside Docker
- Common runtime tools used by `ani-cli`, especially `curl`, `ffmpeg`, `fzf`, `grep`, `openssl`, `sed`, `aria2c`, and `yt-dlp`
Run locally:
@@ -93,6 +93,7 @@ Docker notes:
- `USER_UID` and `USER_GID` help keep mounted files owned by your host user
- `UPDATE_ON_START=true` runs `ani-cli --update` before startup
- `ANI_CLI_WEB_BRANCH` lets the image clone a specific `ani-cli-web` branch at build time
- `anipy-cli` is installed systemwide inside the container, so fallback retries are available without extra container setup
## Key behavior
@@ -115,7 +116,8 @@ Docker notes:
- Optional and configured from `/config`
- When enabled, a failed `ani-cli` queue job retries once with `anipy-cli`
- `anipy-cli` should be installed separately on the host or inside your container if you want the retry path to be available
- Fallback retries keep the queued episode numbers when the downloaded `anipy-cli` filenames are renumbered from `1`
- Docker images install `anipy-cli` automatically; non-Docker installs should provide it in `PATH` themselves
### Jellyfin handoff
+1 -1
View File
@@ -1 +1 @@
0.47.0
0.47.1
+26
View File
@@ -1076,6 +1076,26 @@ def extract_episode_from_filename(path):
return match.group(1) if match else None
def episode_values_from_spec(value):
spec = str(value or "").strip()
if not spec:
return []
values = []
for part in re.split(r"\s+", spec):
if not part:
continue
if "-" in part:
start_text, end_text = [piece.strip() for piece in part.split("-", 1)]
if start_text.isdigit() and end_text.isdigit():
start = int(start_text, 10)
end = int(end_text, 10)
if start <= end:
values.extend(str(number) for number in range(start, end + 1))
continue
values.append(part)
return values
def unique_destination(path):
if not path.exists():
return path
@@ -1104,10 +1124,16 @@ def finalize_library_files(job):
moved = []
fallback_episode = 1
requested_episode_values = episode_values_from_spec(job.get("episodes"))
backend = str(job.get("download_backend") or "").strip().lower()
for source in files:
if media_type == "movie":
final_name = f"{library_name}{source.suffix}"
else:
ep = None
if backend == "anipy-cli" and requested_episode_values:
ep = requested_episode_values.pop(0)
if ep is None:
ep = extract_episode_from_filename(source)
if ep is None:
ep = str(fallback_episode)
+30
View File
@@ -589,6 +589,36 @@ class QueueApiTests(unittest.TestCase):
self.assertTrue(Path(moved[0]).exists())
self.assertTrue(Path(moved[1]).exists())
def test_tv_finalizer_uses_requested_episode_numbers_for_anipy_fallback(self):
with tempfile.TemporaryDirectory() as temp_root:
job = APP.app_support.build_job(
{
"query": "Fallback Show",
"title": "Fallback Show",
"anime_name": "Fallback Show",
"media_type": "tv",
"mode": "sub",
"quality": "best",
"episodes": "11",
"download_dir": temp_root,
"season": "2",
},
{"mode": "sub", "quality": "best", "download_dir": temp_root},
)
job["download_backend"] = "anipy-cli"
staging_dir = APP.app_support.job_staging_dir(job)
staging_dir.mkdir(parents=True, exist_ok=True)
source = staging_dir / "Fallback Show - S02E01.mp4"
source.write_bytes(b"fallback")
moved = APP.app_support.finalize_library_files(job)
self.assertEqual(
moved,
[f"{temp_root}/tv/Fallback Show/Season 02/Fallback Show - S02E11.mp4"],
)
self.assertTrue(Path(moved[0]).exists())
def test_shutdown_wait_cancels_active_process_for_deterministic_teardown(self):
queue = object.__new__(APP.DownloadQueue)
queue.lock = threading.RLock()