{"id":50486108,"url":"https://github.com/bartekmp/filmweb-arr-sync","last_synced_at":"2026-06-01T22:30:37.559Z","repository":{"id":360029699,"uuid":"1247860918","full_name":"bartekmp/filmweb-arr-sync","owner":"bartekmp","description":"Sync your Filmweb.pl lists with Radarr and Sonarr libraries.","archived":false,"fork":false,"pushed_at":"2026-05-24T18:17:32.000Z","size":55,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T18:25:38.876Z","etag":null,"topics":["arr","filmweb","radarr","sonarr"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bartekmp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-23T22:02:54.000Z","updated_at":"2026-05-24T18:17:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bartekmp/filmweb-arr-sync","commit_stats":null,"previous_names":["bartekmp/filmweb-arr-sync"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/bartekmp/filmweb-arr-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartekmp%2Ffilmweb-arr-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartekmp%2Ffilmweb-arr-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartekmp%2Ffilmweb-arr-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartekmp%2Ffilmweb-arr-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bartekmp","download_url":"https://codeload.github.com/bartekmp/filmweb-arr-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bartekmp%2Ffilmweb-arr-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33797126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arr","filmweb","radarr","sonarr"],"created_at":"2026-06-01T22:30:33.471Z","updated_at":"2026-06-01T22:30:37.552Z","avatar_url":"https://github.com/bartekmp.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# filmweb-arr-sync\n\nWatches a [Filmweb.pl](https://www.filmweb.pl) user's **\"Want to See\"** list and automatically adds new entries to [Radarr](https://radarr.video) (movies) and [Sonarr](https://sonarr.tv) (TV series).\n\nRuns as a daemon on a configurable interval or cron schedule, or as a one-shot CLI command. Designed for homelab Docker Compose stacks.\n\n---\n\n## How it works\n\n```\nFilmweb \"Want to See\"\n  └── /api/v1/user/{username}/want2see/film    → Radarr\n  └── /api/v1/user/{username}/want2see/serial  → Sonarr\n```\n\nEach sync cycle:\n\n1. Fetch the full watchlist from Filmweb (public API, no login required)\n2. Skip entries already recorded in the local state file\n3. **Phase 1 — lookups:** query Radarr/Sonarr for every new entry by title + year; collect the ones that need adding\n4. **Phase 2 — adds:** add collected entries one by one, with a configurable delay between each, to avoid overloading Radarr/Sonarr while they are searching for previously added items\n5. Record each successfully added Filmweb ID in the state file so it is never processed again\n\nItems that fail to match or fail to add are **not** recorded, so they are retried on the next sync.\n\nTitle matching tries `originalTitle` first (better for TMDb/TVDb), then falls back to the localized `title` — which helps with Japanese films whose romanized original title does not appear in Western databases.\n\n---\n\n## Requirements\n\n- Python 3.12+ (for local use)\n- Docker + Docker Compose (for container deployment)\n- A Filmweb account with a public username\n- Radarr and/or Sonarr running and reachable with an API key\n\n---\n\n## Quick start (Docker Compose)\n\nAdd the service to your existing stack:\n\n```yaml\nservices:\n  filmweb-arr-sync:\n    build: .\n    restart: unless-stopped\n    volumes:\n      - filmweb_sync_data:/data\n    environment:\n      FILMWEB_USERNAME: your_filmweb_username\n\n      RADARR_URL: http://radarr:7878\n      RADARR_API_KEY: your_radarr_api_key\n      RADARR_ROOT_FOLDER: /movies\n      RADARR_QUALITY_PROFILE_ID: 1\n\n      SONARR_URL: http://sonarr:8989\n      SONARR_API_KEY: your_sonarr_api_key\n      SONARR_ROOT_FOLDER: /tv\n      SONARR_QUALITY_PROFILE_ID: 1\n\n      SYNC_INTERVAL_MINUTES: 30\n      ADD_DELAY_SECONDS: 5\n\nvolumes:\n  filmweb_sync_data:\n```\n\nThen:\n\n```sh\ndocker compose up -d filmweb-arr-sync\n```\n\n\u003e **First run tip:** if you have a large watchlist (100+ items), consider enabling batch queue mode (`BATCH_QUEUE_ENABLED=true`) so Radarr/Sonarr are not overwhelmed during the initial import. See [Batch queue](#batch-queue) below.\n\n---\n\n## Configuration\n\nConfiguration can be provided via **environment variables** (recommended for Docker) or a **`config.yaml`** file (see `config.yaml.example`). Environment variables take precedence over the file.\n\n### Environment variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `FILMWEB_USERNAME` | Yes | — | Your public Filmweb username |\n| `RADARR_URL` | Yes* | — | Radarr base URL, e.g. `http://radarr:7878` |\n| `RADARR_API_KEY` | Yes* | — | Radarr API key (Settings → General) |\n| `RADARR_ROOT_FOLDER` | Yes* | `/movies` | Root folder path configured in Radarr |\n| `RADARR_QUALITY_PROFILE_ID` | No | `1` | Radarr quality profile ID |\n| `RADARR_ENABLED` | No | `true` | Set to `false` to disable Radarr sync |\n| `RADARR_TAG` | No | `filmweb` | Tag applied to every added movie; set to `\"\"` to disable |\n| `SONARR_URL` | Yes* | — | Sonarr base URL, e.g. `http://sonarr:8989` |\n| `SONARR_API_KEY` | Yes* | — | Sonarr API key (Settings → General) |\n| `SONARR_ROOT_FOLDER` | Yes* | `/tv` | Root folder path configured in Sonarr |\n| `SONARR_QUALITY_PROFILE_ID` | No | `1` | Sonarr quality profile ID |\n| `SONARR_LANGUAGE_PROFILE_ID` | No | — | Sonarr v3 only; omit for Sonarr v4 |\n| `SONARR_ENABLED` | No | `true` | Set to `false` to disable Sonarr sync |\n| `SONARR_TAG` | No | `filmweb` | Tag applied to every added show; set to `\"\"` to disable |\n| `SYNC_INTERVAL_MINUTES` | No | `30` | How often to poll Filmweb; ignored when `SYNC_CRON` is set |\n| `SYNC_CRON` | No | — | Cron expression for sync schedule, e.g. `0 4 * * *` (takes precedence over `SYNC_INTERVAL_MINUTES`) |\n| `SYNC_DRY_RUN` | No | `false` | Log what would be added without making changes |\n| `ADD_DELAY_SECONDS` | No | `5` | Seconds to wait between adding items (non-batch mode only) |\n| `STATE_FILE` | No | `/data/state.json` | Path to the state file |\n| `BATCH_QUEUE_ENABLED` | No | `false` | Enable batch queue mode (see below) |\n| `BATCH_SIZE` | No | `5` | Items added per batch (batch mode only) |\n| `BATCH_INTERVAL_MINUTES` | No | `10` | Minutes to wait between batches (batch mode only) |\n\n*Required only if the respective service is enabled.\n\n### Tagging\n\nBy default every movie and show added by this tool is tagged `filmweb` in Radarr/Sonarr. The tag is created automatically if it doesn't exist. This lets you filter, manage, or bulk-delete the items later using Radarr/Sonarr's built-in tag filters.\n\nTo use a different tag name:\n```\nRADARR_TAG: my-watchlist\nSONARR_TAG: my-watchlist\n```\n\nTo disable tagging entirely, set the variable to an empty string:\n```\nRADARR_TAG: \"\"\nSONARR_TAG: \"\"\n```\n\n---\n\n### Batch queue\n\nBy default the sync adds every new item immediately, one by one, with `ADD_DELAY_SECONDS` between each. On a large watchlist this can still send dozens of requests in a short window and cause Radarr/Sonarr to queue a lot of searches at once.\n\nBatch queue mode spreads additions out over time. Enable it with:\n\n```\nBATCH_QUEUE_ENABLED=true\nBATCH_SIZE=5           # how many items to add per batch (default: 5)\nBATCH_INTERVAL_MINUTES=10  # minutes to wait between batches (default: 10)\n```\n\nHow it works:\n\n- Each sync cycle only does the **lookup** phase and puts new matches into a persistent queue in the state file\n- A background thread processes the queue: adds `BATCH_SIZE` items, sleeps `BATCH_INTERVAL_MINUTES`, then repeats until the queue is empty\n- The sync interval (`SYNC_INTERVAL_MINUTES`) is completely unaffected — the background thread runs independently\n- The queue is persisted to disk, so a container restart picks up where it left off\n- Items that fail to add are **not** marked as processed and will be re-queued on the next sync (same behaviour as the default mode)\n\n---\n\n### Health check\n\nThe daemon exposes a lightweight HTTP endpoint on port **8080**:\n\n```\nGET http://localhost:8080/health\n```\n\nResponse (always `200 OK`):\n```json\n{\"status\": \"ok\", \"last_sync_at\": \"2026-05-24T19:00:47+00:00\"}\n```\n\n`last_sync_at` is `null` until the first sync completes. The Docker image includes a `HEALTHCHECK` directive that polls this endpoint every 60 seconds, so container orchestration tools (Compose, Portainer, Uptime Kuma) will report the container as healthy once it is running.\n\nTo expose the port externally, uncomment the `ports` entry in `docker-compose.yml`.\n\n\u003e **Note:** The health endpoint is only started in daemon mode. It is not available when using `--run-once`.\n\n---\n\n### Finding quality profile IDs\n\nIn Radarr/Sonarr, go to **Settings → Quality Profiles** and count the ordinal number of a profile, or use the API:\n\n```sh\ncurl http://radarr:7878/api/v3/qualityprofile?apikey=YOUR_KEY\n```\n\n---\n\n## Local development\n\n```sh\n# Install dependencies\npip install -r requirements-dev.txt\n\n# Copy and fill in the example env file\ncp .env.example .env\n# edit .env — set STATE_FILE=./data/state.json for a local path\n\n# Preview what would be synced (no changes made)\npython main.py --run-once --dry-run\n\n# Run one real sync pass and exit\npython main.py --run-once\n\n# Run as a daemon (syncs every SYNC_INTERVAL_MINUTES)\npython main.py\n\n# Use a config file instead of env vars\npython main.py --config config.yaml --run-once --dry-run\n```\n\n### Running tests\n\n```sh\npytest tests/ -v\n```\n\n---\n\n## State file\n\nThe state file (`/data/state.json` by default) records every Filmweb ID that has been successfully processed. On each sync, already-processed IDs are skipped entirely — no Filmweb `/info` call, no Radarr/Sonarr lookup.\n\n**Resetting the state** (e.g. after reinstalling Radarr/Sonarr):\n\n```sh\n# Docker\ndocker compose run --rm filmweb-arr-sync sh -c \"rm /data/state.json\"\n\n# Local\nrm data/state.json\n```\n\n---\n\n## Known limitations\n\n- **Title matching is fuzzy** — the sync uses Radarr/Sonarr's own search, so a very obscure or ambiguously-titled entry may match the wrong result. Use `--dry-run` to review matches before committing.\n- **Upcoming films** — entries added to your Filmweb watchlist before a film is indexed in TMDb/TVDb will not match and will be retried each sync until the entry appears in the database.\n- **Duplicate editions** — if two separate Filmweb entries (e.g. a 2001 series and a 2006 OVA) resolve to the same TVDb entry, only one will be added; the other is silently skipped as a duplicate.\n- **No removal sync** — removing an entry from your Filmweb list does not remove it from Radarr/Sonarr.\n\n---\n\n## Project structure\n\n```\nfilmweb_arr_sync/\n├── __main__.py         — CLI entry point (also enables python -m filmweb_arr_sync)\n├── config.py           — loads config from YAML and environment variables\n├── state.py            — JSON state file (tracks processed and pending Filmweb IDs)\n├── sync.py             — two-phase sync orchestration (lookup → add / enqueue)\n├── batch_processor.py  — background thread for batch queue mode\n├── scheduler.py        — daemon loop with graceful SIGTERM/SIGINT shutdown\n├── health.py           — HTTP health check server (:8080/health)\n├── filmweb/\n│   ├── client.py  — Filmweb public API client\n│   └── models.py  — FilmwebItem dataclass\n└── arr/\n    ├── radarr.py  — Radarr REST API client\n    └── sonarr.py  — Sonarr REST API client\ntests/             — pytest unit tests (no network required)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartekmp%2Ffilmweb-arr-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbartekmp%2Ffilmweb-arr-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbartekmp%2Ffilmweb-arr-sync/lists"}