Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcf9e15d40 | ||
|
|
0c17499efa |
@@ -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`.
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+27
-1
@@ -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,11 +1124,17 @@ 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 = extract_episode_from_filename(source)
|
||||
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)
|
||||
fallback_episode += 1
|
||||
|
||||
+30
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user