2026-05-11 11:53:51 +02:00
2026-05-26 07:26:54 +02:00
2026-05-26 07:26:54 +02:00
2026-05-26 07:26:54 +02:00

ani-cli web

Small local web UI for a system-wide ani-cli install.

Current version: 0.45.10

Project Layout

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:

./ani-cli-web

Run with debug logging:

./ani-cli-web --debug

Open:

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:

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:

docker build -t ani-cli-web .

Build with a custom ani-cli branch:

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:

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:

docker compose up --build -d

Use host ownership for mounted downloads:

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-clis 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:

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:

{
  "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:

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:

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:

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.
S
Description
No description provided
Readme
530 KiB
Languages
Python 99.4%
Dockerfile 0.4%
Shell 0.2%