Files
ani-cli-web/README.md
T

426 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ani-cli web
Small local web UI for a system-wide `ani-cli` install.
Current version: `0.45.10`
## Project Layout
```text
ani-cli-web/
app.py Main entrypoint and runtime wiring.
app_support.py Shared config, state, and helper utilities.
Dockerfile Container image build.
docker-compose.yaml Example Compose service.
queue_jobs.py Download queue and watchlist refresh workers.
watchlist_identity.py Download-to-watchlist identity matching.
title_matching.py Shared title normalization and matching.
http_handler.py HTTP routing and access control.
web_templates.py Shared page aggregation.
search_page.py Search page template.
queue_page.py Queue page template.
config_page.py Config page template.
watchlist_page.py Watchlist page template.
docker-entrypoint.sh Container startup wrapper.
ani-cli-web Launcher script.
test_app.py Regression tests.
VERSION Project version.
CHANGELOG.md Change history.
README.md Project documentation.
```
## Quick Start
Run the app:
```sh
./ani-cli-web
```
Run with debug logging:
```sh
./ani-cli-web --debug
```
Open:
```text
http://127.0.0.1:8421/
```
Default bind address is `127.0.0.1:8421`.
## Docker
The image installs `ani-cli` system-wide, downloads the latest upstream `yt-dlp` release binary from GitHub, and clones `ani-cli-web` into `/app` during build.
Default build args:
```text
ANI_CLI=https://github.com/pystardust/ani-cli.git
ANI_CLI_BRANCH=master
ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git
```
Build:
```sh
docker build -t ani-cli-web .
```
Build with a custom `ani-cli` branch:
```sh
docker build \
--build-arg ANI_CLI_BRANCH=my-branch \
--build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \
--build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \
-t ani-cli-web .
```
Run:
```sh
docker run --rm \
-p 8421:8421 \
-e UPDATE_ON_START=true \
-e USER_UID="$(id -u)" \
-e USER_GID="$(id -g)" \
-v "$(pwd)/downloads:/downloads" \
-v "$(pwd)/.ani-cli-web:/app/.ani-cli-web" \
ani-cli-web
```
Compose:
```sh
docker compose up --build -d
```
Use host ownership for mounted downloads:
```sh
USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d
```
Container notes:
- `ani-cli` is installed at `/usr/local/bin/ani-cli`.
- `yt-dlp` is installed at `/usr/local/bin/yt-dlp` from the latest GitHub release during build.
- `ani-cli-web` runs from `/app`.
- Downloads go to `/downloads`.
- App state lives in `/app/.ani-cli-web`.
- The container binds `0.0.0.0:8421`.
- `UPDATE_ON_START=true` runs `sudo ani-cli --update` before startup.
- `USER_UID` and `USER_GID` keep mounted files from being owned by `root`.
## What It Does
- Search AllAnime in `sub` or `dub`.
- Queue downloads with editable folder, quality, media type, season, and episode range.
- Save defaults on `/config`.
- Track shows on `/watchlist`.
- Refresh watchlist episode counts in the background.
- Schedule periodic watchlist refresh jobs.
- Auto-download newly discovered episodes for `Watching` entries after later refreshes.
- Send Discord webhook notifications for refresh discoveries and failures.
- Send Discord webhook notifications for completed downloads.
- Optionally move fully completed watchlist libraries into separate Jellyfin TV and movie folders.
- Cache watchlist thumbnails locally.
- Sync successful downloads back into the watchlist.
- Keep queue and watchlist data in SQLite under `.ani-cli-web/`.
- Browse host folders directly from Config page path fields when choosing library locations.
- Prefer title-based queueing and unique-match fallback identity checks so search and watchlist downloads do not depend on fragile cross-tool result ordering.
## Pages
### Search Page
Path: `/`
Use it to:
1. Search for an anime.
2. Load episodes.
3. Adjust folder, quality, media type, season, and episode range.
4. Add the show to the queue or watchlist.
Highlights:
- Search stays focused on finding shows, loading episodes, and queueing new downloads.
- Search-page queueing now sends the selected title directly, which avoids relying on `ani-cli` search result numbers matching the web API ordering.
- Added-to-queue notices now point to the dedicated Queue page for progress tracking.
- Search request guards still avoid stale responses overwriting newer results.
- The shared sidebar menu now shows one navigation entry per line for cleaner balance across all pages.
### Queue Page
Path: `/queue`
Use it to:
1. Monitor active, pending, failed, and finished download jobs.
2. Inspect recent per-job logs with newest lines first.
3. Retry failed or canceled jobs.
4. Remove finished and canceled jobs.
5. Cancel running or pending jobs.
Highlights:
- The queue list loads once, then only visible pending or running jobs are refreshed every 2.5 seconds.
- Terminal jobs (`done`, `failed`, `canceled`) stop log polling automatically.
- Retry actions are only shown for `failed` and `canceled` jobs, so finished downloads cannot be re-queued accidentally from the UI.
- Dedicated queue pagination and cleanup controls.
- Search and queue workflows are now separated into distinct pages.
- Each queue job card can be collapsed, and finished jobs start collapsed by default to keep the page compact.
### Config Page
Path: `/config`
Use it to set:
1. Default download folder.
2. Default `sub` or `dub` mode.
3. Default quality.
4. Scheduled watchlist refresh enable or disable.
5. Scheduled refresh period in minutes.
6. Delay between shows during `Refresh All`.
7. Global auto-download enable or disable.
8. Default auto-download language and quality.
9. Jellyfin automatic handoff enable or disable.
10. Jellyfin TV and movie library paths plus a manual trigger action.
11. Discord webhook URL.
12. Discord notification events plus a test action.
Layout:
- `Dependencies` stays at the top as a full-width runtime status card.
- The remaining Config sections use two columns on larger screens and collapse to one column at `1080px` width and below.
- Runtime info shows both the `ani-cli-web` version and the installed `ani-cli` version.
- Path fields include `Browse` buttons that open a host-side folder picker modal in the browser.
Saved settings are written to `.ani-cli-web/config.json`.
Jellyfin handoff:
- Automatic Jellyfin moves run after a watchlist refresh updates an entry and the show now looks complete.
- TV entries only move after the anime is marked `Finished` and at least one downloaded language covers the expected episode total.
- Movie entries move after they are downloaded and the local movie library folder exists.
- Existing Jellyfin targets are only treated as already moved when the destination library still contains the expected media files.
- The manual `Run now` action on `/config` scans the whole watchlist and moves anything already eligible.
Watchlist queueing:
- Manual `Download all` and automatic watchlist downloads now queue directly from the stored watchlist title and `show_id`, so they no longer fail just because a fresh provider search returns a shifted or incomplete result list.
- Removing a watchlist entry now detaches its existing queue jobs from later watchlist sync, so finishing an old queued download cannot silently recreate that deleted show.
- Manual watchlist `Download all` now uses the anime's own configured auto-download quality when one is set.
- Completed movie downloads now keep their `movie` media type during watchlist sync and move movie-tagged watchlist entries into `Finished`, which keeps later Jellyfin handoff eligibility consistent.
Discord notification events:
- Download completed.
- Jellyfin move succeeded.
- Jellyfin move failed.
- Refresh finished and found new episodes.
- Refresh failed or finished with per-show refresh errors.
- Refresh interrupted by restart or shutdown.
- Unexpected runtime errors.
Webhook details:
- Download-completed notifications include the full saved file list, split across extra Discord embeds when needed.
- Jellyfin handoff notifications fire when a finished library is moved successfully and also on actionable move failures such as missing targets or move errors.
- Watchlist entries only move to `Finished` automatically when the downloaded mode itself is complete, and Jellyfin handoff for TV entries follows that same mode-aware completion rule.
- Queue jobs that exit successfully but produce no downloaded files are marked as failed with a clearer explanation instead of looking like a generic rename problem.
- Watchlist-driven downloads no longer force `ani-cli -S <index>` from API search ordering, which avoids bad result mismatches when `ani-cli`s own search order differs from the web apps result list.
### Watchlist
Path: `/watchlist`
Categories:
- `Watching`
- `Planned`
- `Finished`
- `Dropped`
Ways to add shows:
1. From the Search page.
2. Manually on `/watchlist`.
Main behavior:
1. `Refresh` updates one show.
2. `Refresh All` runs in the background.
3. `Download all` expands an inline picker for `Sub` or `Dub` and then queues every currently available episode directly.
4. Clicking the title opens the source page.
5. `Remove` deletes the entry and cached poster.
6. Each card has inline category and media-type selectors.
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
8. Pagination shows 30 cards per page.
9. `Alternative titles` opens a centered overlay with English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.
10. `Auto-download` opens a centered overlay with per-series settings for language, quality, series number, optional episode offset, and the sanitized library name used for automatically queued episodes.
Auto-download layout note: `Name source` and `Name` share a wider half-and-half row inside the modal for easier editing.
11. Adding a show from Search reuses the current `Season` field as the initial auto-download series value for that new watchlist entry.
12. Each watchlist entry can be tagged as `TV` or `Movie`, which changes where finished downloads are stored.
13. Card actions are split into two rows so `Refresh` and `Remove` sit on the first line, with `Download all` and `Auto-download` on the second.
Episode offset:
- Leave it empty to keep normal episode numbering.
- Set it to a starting number such as `13` when a split season continues inside a new folder, so downloaded files are named like `Show Name - S02E13`, `Show Name - S02E14`, and so on.
Status behavior:
- `Sub` and `Dub` pills turn green when that track looks complete.
- Cards with both `sub` and `dub` fully ready get a thin green border.
- Finished cards show a small check badge.
- Green check means downloaded through the app.
- Gray check means finished manually.
Download sync:
- Existing watchlist entries move to `Finished` when a download succeeds.
- Missing entries are added to `Finished`.
- If ani-cli resolves a different `show_id`, the app can reconcile a unique title match instead of creating a duplicate finished row.
Thumbnail behavior:
- Thumbnails are served from the local `/api/watchlist/thumb/...` route.
- Cached thumbnails are reused.
- Missing covers are warmed in small batches.
- Failed lookups back off before retrying.
- You can force `Reload` or use `Upload` when no thumbnail exists.
Refresh behavior:
- Per-show refreshes are queued in the background.
- Queued refresh state survives restarts.
- Duplicate refresh queueing is avoided.
- Auto-download does not trigger when a show is first added to the watchlist.
- Auto-download only runs for entries in `Watching` after later manual refreshes or scheduled refreshes.
- Auto-download uses stored downloaded-episode history per language, so it can queue any still-missing episodes after a later refresh, including backlog episodes that existed before the show was first tracked.
- Scheduled refresh runs log start, stop, success, and failure to stdout.
- `Refresh All` works one show at a time with a configurable delay.
- When a Discord webhook is configured, new-episode refresh results include one notification card per anime with sub and dub counts and a cached thumbnail attachment when available.
## Access Guard
By default the app only serves loopback clients such as `127.0.0.1` and `::1`.
To allow LAN or remote access:
```sh
ANI_CLI_WEB_ALLOW_REMOTE=1
ANI_CLI_WEB_AUTH_USERNAME=admin
ANI_CLI_WEB_AUTH_PASSWORD=choose-a-long-random-password
```
Non-local browser visits are redirected to a sign-in form automatically when no valid session cookie is present.
API clients can authenticate with:
- `Authorization: Basic <base64(username:password)>`
The browser login stores a persistent HTTP-only session cookie, so you usually only need to sign in once per browser.
You can also set the same credentials directly in `.ani-cli-web/config.json`:
```json
{
"auth_username": "admin",
"auth_password": "choose-a-long-random-password"
}
```
Environment variables take precedence over values saved in `config.json`.
## Download Queue
Path: `/queue`
The queue is stored in SQLite, shown 10 jobs at a time, and managed from its dedicated page.
Available actions:
- `Retry`
- `Cancel`
- `Remove`
- `Retry failed`
- `Remove finished`
Downloads are staged first, then moved into a media-friendly layout:
```text
ANI_CLI_DOWNLOAD_DIR/tv/anime_name/Season 01/anime_name - S01E01.mp4
ANI_CLI_DOWNLOAD_DIR/movie/anime_name/anime_name.mp4
```
## Runtime State
All local state is stored under:
```text
ani-cli-web/.ani-cli-web/
```
Main contents:
- `config.json`
- `state.sqlite3`
- `thumbnails/`
- AniDB title cache data
- staging files for active downloads
Legacy state from older locations is migrated on startup.
## Configuration
Environment variables:
- `ANI_CLI_BIN`: path or command name for `ani-cli`
- `ANI_CLI_DOWNLOAD_DIR`: initial default download folder
- `ANI_CLI_MODE`: initial mode, `sub` or `dub`
- `ANI_CLI_QUALITY`: initial quality such as `best`, `1080`, or `720`
- `ANI_CLI_WEB_HOST`: server host, default `127.0.0.1`
- `ANI_CLI_WEB_PORT`: server port, default `8421`
- `ANI_CLI_WEB_ALLOW_REMOTE`: allow non-loopback clients
- `ANI_CLI_WEB_AUTH_USERNAME`: single remote-login username
- `ANI_CLI_WEB_AUTH_PASSWORD`: single remote-login password
- `ANI_CLI_WEB_DEBUG`: enable debug logging
- `ANI_CLI_WEB_STATE_ROOT`: override the project-local state directory
- `UPDATE_ON_START`: run `sudo ani-cli --update` in the container before startup
- `USER_UID` and `USER_GID`: run the container as a specific host user/group
Saved config fields include:
- `auth_username`
- `auth_password`
- `jellyfin_sync_enabled`
- `jellyfin_tv_dir`
- `jellyfin_movie_dir`
- `discord_webhook_url`
- `discord_webhook_events`
- `watchlist_auto_refresh_enabled`
- `watchlist_auto_refresh_minutes`
- `watchlist_refresh_delay_seconds`
## Testing
Run the regression suite with:
```sh
python3 -m unittest test_app.py
```
## Notes
- Runtime boot is lazy. Importing `app.py` does not immediately start worker threads.
- Queue and watchlist data share the same SQLite database: `.ani-cli-web/state.sqlite3`.
- Runtime folders such as `.ani-cli-web/` and `downloads/` are ignored by git.
- The Docker image clones both upstream repositories during build.