{"id":42237720,"url":"https://github.com/dadav/kikusan","last_synced_at":"2026-02-22T11:22:06.754Z","repository":{"id":333182794,"uuid":"1136469643","full_name":"dadav/kikusan","owner":"dadav","description":"Kikusan is the glue between navidrome and youtube music.","archived":false,"fork":false,"pushed_at":"2026-02-03T18:35:29.000Z","size":988,"stargazers_count":23,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-03T20:54:42.981Z","etag":null,"topics":["music","navidrome","youtube","yt-dlp"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dadav.png","metadata":{"files":{"readme":"README.md","changelog":null,"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-01-17T18:42:24.000Z","updated_at":"2026-02-03T18:35:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dadav/kikusan","commit_stats":null,"previous_names":["dadav/kikusan"],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/dadav/kikusan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadav%2Fkikusan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadav%2Fkikusan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadav%2Fkikusan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadav%2Fkikusan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dadav","download_url":"https://codeload.github.com/dadav/kikusan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dadav%2Fkikusan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29178735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T20:14:21.878Z","status":"ssl_error","status_checked_at":"2026-02-06T20:14:21.443Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["music","navidrome","youtube","yt-dlp"],"created_at":"2026-01-27T03:42:27.855Z","updated_at":"2026-02-22T11:22:06.742Z","avatar_url":"https://github.com/dadav.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Kikusan\n\n**Search, download and sync music from YouTube Music and other places (reddit, listenbrainz, billboard) with lyrics.**\n\n[![Release](https://img.shields.io/github/v/tag/dadav/kikusan)](https://github.com/dadav/kikusan/releases)\n[![License](https://img.shields.io/github/license/dadav/kikusan)](https://github.com/dadav/kikusan/blob/main/LICENSE)\n\n![UI](ui.png)\n\n\u003c/div\u003e\n\n## Features\n\n- **Search \u0026 Download**: Search YouTube Music and download audio in OPUS/MP3/FLAC format\n- **Playlist Support**: Download entire playlists from YouTube Music, YouTube, and Deezer\n- **Quick Download**: Search and download first match with a single command\n- **Automatic Lyrics**: Fetch and embed synchronized lyrics from lrclib.net (LRC format)\n- **Web Interface**: Modern web UI with search, download, theme toggle, and format selection\n- **Docker Support**: Easy deployment with Docker and docker-compose\n- **Plugin System**: Extensible architecture for custom music sources\n- **Scheduled Sync**: Automated playlist monitoring with cron scheduling\n- **M3U Playlists**: Automatic playlist file generation for downloads\n- **Hooks**: Run custom commands when events occur (e.g., import playlists to Navidrome)\n- **Retroactive Tagging**: Add lyrics and ReplayGain tags to existing audio files without re-downloading\n\n## Usecase\n\nI use navidrome as my music server. My music is stored on a NAS and mounted in the navidrome container as read-only.\nKikusan syncs my youtube music playlists on this shared mount and creates local m3u playlists. If kikusan has a discovery playlist configured (sync=True), songs that hav been removed from the upstream playlist are also removed from navidrome. There are some exceptions: They won't be removed if the songs are referenced by another playlist or starred in navidrome or in the `keep` playlist. Navidrome imports these playlist daily. Then I use [symfonium](https://play.google.com/store/apps/details?id=app.symfonik.music.player) to access my music via subsonic api.\n\n## Plugin System\n\nKikusan supports plugins for syncing music from various sources beyond standard playlists:\n\n**Built-in Plugins:**\n\n- **`listenbrainz`** - Weekly recommendations from listenbrainz.org\n  - Required: `user` (listenbrainz username)\n  - Optional: `recommendation_type` (weekly-exploration, weekly-jams)\n\n- **`rss`** - Generic RSS/Atom feed parser for music podcasts, blogs, etc.\n  - Required: `url` (RSS/Atom feed URL)\n  - Optional: `artist_field`, `title_field`, `timeout`, `user_agent`\n\n- **`reddit`** - Fetch songs from music subreddits (r/listentothis, r/Music, r/IndieHeads, etc.)\n  - Required: `subreddit` (subreddit name)\n  - Optional: `sort` (hot/new/top/rising), `time_filter`, `limit`, `min_score`\n\n- **`billboard`** - Fetch songs from Billboard charts (hot-100, pop-songs, etc.)\n  - Required: `chart_name` (e.g., 'hot-100', 'pop-songs')\n  - Optional: `date` (YYYY-MM-DD), `year` (for year-end charts), `limit`\n\n**Usage:**\n\n```bash\n# List available plugins\nkikusan plugins list\n\n# Run a plugin once\nkikusan plugins run listenbrainz --config '{\"user\": \"myuser\"}'\nkikusan plugins run reddit --config '{\"subreddit\": \"listentothis\", \"limit\": 25}'\nkikusan plugins run billboard --config '{\"chart_name\": \"hot-100\", \"limit\": 50}'\n\n# Schedule in cron.yaml\n# See cron.example.yaml for configuration examples\n```\n\n**Creating Third-Party Plugins:**\n\nSee [`examples/third-party-plugin/`](examples/third-party-plugin/) for a complete example of creating your own plugin. Plugins are distributed as Python packages and automatically discovered via entry points.\n\n## Installation\n\nRun from git:\n\n```bash\ngit clone https://github.com/dadav/kikusan\ncd kikusan\nuv sync\nuv run kikusan --help\n```\n\nInstall as uv tool:\n\n```bash\nuv tool install kikusan\nkikusan --help\n```\n\nOr via [docker-compose](./docker-compose.yml).\n\n## Usage\n\n### CLI\n\n```bash\n# Search for music\nkikusan search \"Bohemian Rhapsody\"\n\n# Download by video ID\nkikusan download bSnlKl_PoQU\n\n# Download by URL\nkikusan download --url \"https://music.youtube.com/watch?v=bSnlKl_PoQU\"\n\n# Search and download first match\nkikusan download --query \"Bohemian Rhapsody Queen\"\n\n# Download entire playlist (YouTube Music, YouTube, or Deezer)\nkikusan download --url \"https://music.youtube.com/playlist?list=...\"\nkikusan download --url \"https://www.deezer.com/playlist/...\"\n\n# Custom filename format\nkikusan download bSnlKl_PoQU --filename \"%(title)s\"\n\n# Options\nkikusan download bSnlKl_PoQU --output ~/Music --format mp3\n```\n\n### Tag Existing Files\n\nAdd lyrics and ReplayGain tags to audio files you already have, without re-downloading:\n\n```bash\n# Tag all files in a directory (recursively)\nkikusan tag /path/to/music\n\n# Preview what would be done without making changes\nkikusan tag --dry-run /path/to/music\n\n# Only add lyrics (skip ReplayGain)\nkikusan tag --no-replaygain /path/to/music\n\n# Only add ReplayGain (skip lyrics)\nkikusan tag --no-lyrics /path/to/music\n```\n\n**Features:**\n\n- Recursively processes `.opus`, `.mp3`, `.flac` files\n- Extracts metadata via mutagen (title, artist, album, duration)\n- Fetches lyrics from lrclib.net using exact match, fuzzy search, and cleaned metadata retries\n- Applies ReplayGain/R128 loudness normalization tags via rsgain\n- Skips files that already have `.lrc` sidecar files (for lyrics)\n- Skips files that already have ReplayGain tags (for ReplayGain)\n- Non-fatal per-file errors with summary statistics\n- Both lyrics and ReplayGain are enabled by default\n\n**Requirements:**\n\n- For ReplayGain: `rsgain` binary must be installed (included in Docker image)\n\n### Web Interface\n\n```bash\nkikusan web\n# Open http://localhost:8000\n```\n\n**Features:**\n\n- Search YouTube Music with real-time results\n- Download individual tracks with format selection (OPUS/MP3/FLAC)\n- Dark/light theme toggle with automatic system preference detection\n- View counts displayed for each track\n- Responsive design for mobile and desktop\n\n### Scheduled Sync (Cron)\n\nAutomatically monitor and sync playlists, plugins, and explore sources on a schedule:\n\n```bash\n# Run continuously with cron.yaml configuration\nkikusan cron\n\n# Run all syncs once and exit\nkikusan cron --once\n\n# Use custom config file\nkikusan cron --config /path/to/cron.yaml\n```\n\nCreate a `cron.yaml` file to configure:\n\n- **Playlists**: YouTube Music, YouTube, or Deezer playlists\n- **Plugins**: Listenbrainz, Reddit, Billboard, RSS feeds\n- **Explore**: YouTube Music charts and mood/genre categories\n- **Schedule**: Standard cron expressions (e.g., \"0 9 \\* \\* \\*\" for daily at 9am)\n- **Sync Mode**: Keep or delete files when removed from source\n\n#### Explore Sources\n\nSync tracks from YouTube Music charts or mood/genre categories:\n\n```yaml\nexplore:\n  # Sync US music charts daily\n  us-charts:\n    type: charts\n    country: US # ISO 3166-1 Alpha-2 code (ZZ = global)\n    sync: true # Remove tracks that fall off the charts\n    schedule: \"0 6 * * *\"\n    limit: 10 # Optional: Only get top 10 songs from charts\n\n  # Sync a mood/genre category weekly\n  chill-vibes:\n    type: mood\n    params: \"ggMPOg1uX1J\" # Get params from: kikusan explore moods\n    playlist_id: \"RDCLAK5uy_...\" # Optional: target specific playlist (get from explore mood-playlists)\n    sync: false\n    schedule: \"0 12 * * 0\"\n```\n\nUse `kikusan explore moods` to discover available mood/genre categories and their `params` values, and `kikusan explore charts --country XX` to preview chart contents.\n\nSee `cron.example.yaml` for detailed configuration examples.\n\n### Notifications\n\nKikusan can send push notifications via [Gotify](https://gotify.net/) for scheduled sync operations:\n\n- **Summary notifications only** - One notification per sync operation, not per track\n- **Includes download/skip/fail counts** - See results at a glance\n- **Optional** - Gracefully disabled if not configured\n- **Non-blocking** - Notification failures don't stop downloads\n\n**Setup:**\n\n1. Install a Gotify server or use an existing instance\n2. Create an application token in Gotify\n3. Set environment variables:\n   ```bash\n   export GOTIFY_URL=\"https://push.example.com\"\n   export GOTIFY_TOKEN=\"your-app-token\"\n   ```\n\n**Notifications are sent for:**\n\n- Scheduled playlist syncs (via `kikusan cron`)\n- Scheduled plugin syncs (via `kikusan cron`)\n- Scheduled explore syncs (via `kikusan cron`)\n\nNotifications are **not** sent for CLI operations or web UI downloads, as these are interactive and the user already sees the results.\n\n### Navidrome Protection\n\nPrevent deletion of songs during sync if they are starred or in a designated playlist in Navidrome:\n\n**Features:**\n\n- Protect songs starred/favorited in Navidrome (via Symfonium or other Subsonic clients)\n- Protect songs in a designated \"keep\" playlist\n- Real-time API checks during each sync operation\n- Gracefully disabled if not configured\n- Fails safe: keeps files if Navidrome is unreachable\n\n**Setup:**\n\n1. Configure environment variables:\n\n   ```bash\n   export NAVIDROME_URL=\"https://music.example.com\"\n   export NAVIDROME_USER=\"your-username\"\n   export NAVIDROME_PASSWORD=\"your-password\"\n   export NAVIDROME_KEEP_PLAYLIST=\"keep\"  # optional, defaults to \"keep\"\n   ```\n\n2. Star songs in your Subsonic client (Symfonium, DSub, etc.) or add them to your \"keep\" playlist\n\n3. When kikusan syncs playlists with `sync: true`, protected songs won't be deleted even if removed from the source playlist\n\n**Behavior:**\n\n- Checks both starred songs AND songs in the keep playlist\n- Protected files are skipped during deletion with detailed logging\n- Works alongside existing cross-playlist/plugin reference protection\n- Minimal performance impact (~3 API calls per sync operation)\n\n**Example workflow:**\n\n1. Sync YouTube Music playlist with `sync: true`\n2. Song gets removed from YouTube Music playlist\n3. You've starred the song in Symfonium (synced to Navidrome)\n4. Kikusan detects the star and keeps the file on disk\n5. File remains available in Navidrome/Symfonium\n\n### Hooks\n\nHooks allow you to run custom commands when certain events occur during sync operations. This is useful for integrating with external systems like Navidrome.\n\n**Supported Events:**\n\n- `playlist_updated`: Triggered when an M3U playlist is created or updated\n- `sync_completed`: Triggered after every sync operation (success or failure)\n\n**Configuration:**\n\nAdd a `hooks` section to your `cron.yaml`:\n\n```yaml\nhooks:\n  # Import playlist to Navidrome when updated\n  - event: playlist_updated\n    command: |\n      NAVIDROME_TOKEN=$(curl -s -X POST \\\n        -H \"Content-Type: application/json\" \\\n        -d \"{\\\"username\\\": \\\"${NAVIDROME_USER}\\\", \\\"password\\\": \\\"${NAVIDROME_PASSWORD}\\\"}\" \\\n        \"${NAVIDROME_URL}/auth/login\" | jq -r '.token')\n      curl -X POST \\\n        -H \"Content-Type: audio/x-mpegurl\" \\\n        -H \"X-ND-Authorization: Bearer ${NAVIDROME_TOKEN}\" \\\n        --data-binary @\"${KIKUSAN_PLAYLIST_PATH}\" \\\n        \"${NAVIDROME_URL}/api/playlist\"\n    timeout: 30  # seconds (default: 60)\n\n  # Log sync results\n  - event: sync_completed\n    command: echo \"Sync: ${KIKUSAN_PLAYLIST_NAME}\" \u003e\u003e /var/log/sync.log\n    run_on_error: true  # Run even if sync failed (default: false)\n```\n\n**Environment Variables:**\n\nHooks receive context via environment variables:\n\n| Variable                | Description                                   |\n| ----------------------- | --------------------------------------------- |\n| `KIKUSAN_EVENT`         | Event type (playlist_updated, sync_completed) |\n| `KIKUSAN_PLAYLIST_NAME` | Name of the playlist/plugin                   |\n| `KIKUSAN_PLAYLIST_PATH` | Absolute path to the M3U file (if exists)     |\n| `KIKUSAN_SYNC_TYPE`     | Type: \"playlist\", \"plugin\", or \"explore\"      |\n| `KIKUSAN_DOWNLOADED`    | Number of tracks downloaded                   |\n| `KIKUSAN_SKIPPED`       | Number of tracks skipped                      |\n| `KIKUSAN_DELETED`       | Number of tracks deleted                      |\n| `KIKUSAN_FAILED`        | Number of tracks that failed                  |\n| `KIKUSAN_SUCCESS`       | \"true\" or \"false\"                             |\n\n**Navidrome Integration Example:**\n\nTo automatically import playlists to Navidrome using its [playlist import API](https://github.com/navidrome/navidrome/pull/2273):\n\n1. Set environment variables (these are already used for Navidrome Protection):\n\n   ```bash\n   export NAVIDROME_URL=\"https://music.example.com\"\n   export NAVIDROME_USER=\"your-username\"\n   export NAVIDROME_PASSWORD=\"your-password\"\n   ```\n\n2. Add hook to `cron.yaml`:\n\n   ```yaml\n   hooks:\n     - event: playlist_updated\n       command: |\n         NAVIDROME_TOKEN=$(curl -s -X POST \\\n           -H \"Content-Type: application/json\" \\\n           -d \"{\\\"username\\\": \\\"${NAVIDROME_USER}\\\", \\\"password\\\": \\\"${NAVIDROME_PASSWORD}\\\"}\" \\\n           \"${NAVIDROME_URL}/auth/login\" | jq -r '.token')\n         curl -X POST \\\n           -H \"Content-Type: audio/x-mpegurl\" \\\n           -H \"X-ND-Authorization: Bearer ${NAVIDROME_TOKEN}\" \\\n           --data-binary @\"${KIKUSAN_PLAYLIST_PATH}\" \\\n           \"${NAVIDROME_URL}/api/playlist\"\n   ```\n\n   Note: This requires `jq` to be installed for parsing the JSON response.\n\n### Docker\n\n```bash\ndocker compose up -d\n# Open http://localhost:8000\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable                             | Default                           | Description                                                     |\n| ------------------------------------ | --------------------------------- | --------------------------------------------------------------- |\n| `KIKUSAN_DOWNLOAD_DIR`               | `./downloads`                     | Download directory                                              |\n| `KIKUSAN_AUDIO_FORMAT`               | `opus`                            | Audio format (opus, mp3, flac)                                  |\n| `KIKUSAN_FILENAME_TEMPLATE`          | `%(artist,uploader)s - %(title)s` | Filename template (yt-dlp format)                               |\n| `KIKUSAN_ORGANIZATION_MODE`          | `flat`                            | File organization mode (flat, album)                            |\n| `KIKUSAN_USE_PRIMARY_ARTIST`         | `false`                           | Use primary artist for folders (true, false)                    |\n| `KIKUSAN_WEB_PORT`                   | `8000`                            | Web server port                                                 |\n| `KIKUSAN_WEB_PLAYLIST`               | `None`                            | M3U playlist name for web downloads (optional)                  |\n| `KIKUSAN_CORS_ORIGINS`               | `*`                               | CORS allowed origins (comma-separated)                          |\n| `KIKUSAN_COOKIE_MODE`                | `auto`                            | Cookie usage: auto, always, or never                            |\n| `KIKUSAN_COOKIE_RETRY_DELAY`         | `1.0`                             | Delay in seconds before retrying with cookies                   |\n| `KIKUSAN_LOG_COOKIE_USAGE`           | `true`                            | Log cookie usage statistics (true, false)                       |\n| `GOTIFY_URL`                         | `None`                            | Gotify server URL for notifications (optional)                  |\n| `GOTIFY_TOKEN`                       | `None`                            | Gotify application token (optional)                             |\n| `NAVIDROME_URL`                      | `None`                            | Navidrome server URL for protection (optional)                  |\n| `NAVIDROME_USER`                     | `None`                            | Navidrome username (optional)                                   |\n| `NAVIDROME_PASSWORD`                 | `None`                            | Navidrome password (optional)                                   |\n| `NAVIDROME_KEEP_PLAYLIST`            | `keep`                            | Playlist name for protection (optional)                         |\n| `YT_DLP_COOKIE_FILE`                 | `None`                            | Path to cookies.txt file for yt-dlp (optional)                  |\n| `KIKUSAN_MULTI_USER`                 | `false`                           | Enable per-user M3U playlists via `Remote-User` header          |\n| `KIKUSAN_UNAVAILABLE_COOLDOWN_HOURS` | `168`                             | Hours to wait before retrying unavailable videos (0 = disabled) |\n\n### Cookie Authentication\n\nKikusan supports two methods for providing cookies to yt-dlp:\n\n1. **Web UI Upload** (Recommended):\n   - Open the web UI\n   - Click the settings icon (⚙️) in the header\n   - Upload your cookies.txt file\n   - The file is stored securely at `.kikusan/cookies.txt`\n\n2. **Environment Variable**:\n   ```bash\n   export YT_DLP_COOKIE_FILE=/path/to/cookies.txt\n   ```\n\n**Priority**: Web-uploaded cookies take precedence over environment variable.\n\n**Exporting Cookies**:\n\n- Chrome/Edge: Install \"Get cookies.txt LOCALLY\" extension\n- Firefox: Install \"cookies.txt\" extension\n- See [yt-dlp FAQ](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp) for detailed instructions\n\n### File Organization\n\nKikusan supports two file organization modes:\n\n#### Flat Mode (Default)\n\nAll files stored in the download directory with the filename template:\n\n```\ndownloads/\n├── Queen - Bohemian Rhapsody.opus\n├── Pink Floyd - Comfortably Numb.opus\n└── ...\n```\n\n#### Album Mode\n\nFiles organized by artist and album with automatic metadata extraction:\n\n```\ndownloads/\n├── Queen/\n│   ├── 1975 - A Night at the Opera/\n│   │   ├── 01 - Death on Two Legs.opus\n│   │   ├── 11 - Bohemian Rhapsody.opus\n│   │   └── 12 - God Save the Queen.opus\n│   └── 1991 - Innuendo/\n│       ├── 01 - Innuendo.opus\n│       └── 06 - The Show Must Go On.opus\n└── Pink Floyd/\n    └── 1979 - The Wall/\n        ├── 01 - In the Flesh.opus\n        └── 26 - Outside the Wall.opus\n```\n\n**Enable album mode:**\n\n```bash\nexport KIKUSAN_ORGANIZATION_MODE=album\n```\n\n**Behavior:**\n\n- **Full metadata**: `Artist/Year - Album/NN - Track.ext`\n- **Missing track number**: `Artist/Year - Album/Track.ext`\n- **Missing album**: `Artist/Track.ext`\n- **Path sanitization**: Invalid filesystem characters are automatically removed\n\n**Multi-Artist Handling:**\n\nBy default, album mode uses the full artist string from metadata:\n\n- `Queen feat. David Bowie` → folder: `Queen feat. David Bowie/`\n- `Artist1, Artist2` → folder: `Artist1, Artist2/`\n\nTo use only the primary artist for cleaner folder organization:\n\n```bash\nexport KIKUSAN_USE_PRIMARY_ARTIST=true\n```\n\nThis extracts the main artist (before separators) for folder names:\n\n- `Queen feat. David Bowie` → folder: `Queen/`\n- `Artist1, Artist2` → folder: `Artist1/`\n- `Artist \u0026 Guest` → folder: `Artist/`\n\nSupported separators (in priority order): `feat.`, `ft.`, `featuring`, `with`, `\u0026`, `, `\n\nThe full artist metadata is still preserved in the audio file tags.\n\n**Notes:**\n\n- Album mode is opt-in; flat mode remains the default for backward compatibility\n- Primary artist extraction is optional (disabled by default)\n- Existing files are not reorganized when switching modes\n- New downloads will use the selected organization mode\n- File existence checking works in both modes to prevent duplicates\n\n### State Files \u0026 Playlists\n\nKikusan tracks downloaded files and generates M3U playlists automatically:\n\n- **State Files**: Stored in `{download_dir}/.kikusan/state/` (for playlists) and `{download_dir}/.kikusan/plugin_state/` (for plugins)\n- **M3U Playlists**: Generated at `{download_dir}/{name}.m3u` for each sync configuration\n\n### Unavailable Video Cooldown\n\nKikusan automatically prevents repeated failed downloads of unavailable videos to reduce wasted bandwidth and API requests.\n\n**How it works:**\n\nWhen a video returns a \"Video unavailable\" error (distinct from authentication or network errors), Kikusan records the video ID with a timestamp in `{download_dir}/.kikusan/unavailable.json`. The video will be skipped during subsequent sync operations until the cooldown period expires.\n\n### Filename Length Safety\n\nKikusan automatically truncates long filenames to prevent filesystem errors while preserving readability.\n\n## CLI Reference\n\nThis section documents all CLI commands and their options.\n\n### Global Options\n\nThese options apply to all commands:\n\n| Option                   | Env Variable                            | Description                                                                     |\n| ------------------------ | --------------------------------------- | ------------------------------------------------------------------------------- |\n| `--cookie-mode`          | `KIKUSAN_COOKIE_MODE`                   | Cookie usage: `auto` (retry on auth errors), `always`, `never`. Default: `auto` |\n| `--cookie-retry-delay`   | `KIKUSAN_COOKIE_RETRY_DELAY`            | Delay in seconds before retrying with cookies. Default: `1.0`                   |\n| `--no-log-cookie-usage`  | (inverse of `KIKUSAN_LOG_COOKIE_USAGE`) | Disable logging of cookie usage statistics                                      |\n| `--unavailable-cooldown` | `KIKUSAN_UNAVAILABLE_COOLDOWN_HOURS`    | Hours to wait before retrying unavailable videos (0 = disabled). Default: `168` |\n| `--version`              | -                                       | Show version and exit                                                           |\n\n### kikusan search\n\nSearch for music on YouTube Music.\n\n```bash\nkikusan search \"query\" [OPTIONS]\n```\n\n| Option        | Description                             |\n| ------------- | --------------------------------------- |\n| `-l, --limit` | Maximum number of results (default: 10) |\n\n### kikusan download\n\nDownload a track by video ID, URL, or search query.\n\n```bash\nkikusan download [VIDEO_ID] [OPTIONS]\n```\n\n| Option                                         | Env Variable                 | Description                                                           |\n| ---------------------------------------------- | ---------------------------- | --------------------------------------------------------------------- |\n| `-u, --url`                                    | -                            | YouTube, YouTube Music, or Deezer URL                                 |\n| `-q, --query`                                  | -                            | Search query (downloads first match)                                  |\n| `-o, --output`                                 | `KIKUSAN_DOWNLOAD_DIR`       | Output directory                                                      |\n| `-f, --format`                                 | `KIKUSAN_AUDIO_FORMAT`       | Audio format: `opus`, `mp3`, `flac`. Default: `opus`                  |\n| `-n, --filename`                               | `KIKUSAN_FILENAME_TEMPLATE`  | Filename template (yt-dlp format)                                     |\n| `--no-lyrics`                                  | -                            | Skip fetching lyrics                                                  |\n| `-p, --add-to-playlist`                        | -                            | Add downloaded track(s) to M3U playlist                               |\n| `--organization-mode`                          | `KIKUSAN_ORGANIZATION_MODE`  | File organization: `flat` or `album`. Default: `flat`                 |\n| `--use-primary-artist/--no-use-primary-artist` | `KIKUSAN_USE_PRIMARY_ARTIST` | Use only primary artist for folder names in album mode                |\n| `--replaygain/--no-replaygain`                 | `KIKUSAN_REPLAYGAIN`         | Apply ReplayGain/R128 tags via rsgain. Default: enabled when flag set |\n\n### kikusan tag\n\nTag existing audio files with lyrics and ReplayGain (no re-download).\n\n```bash\nkikusan tag DIRECTORY [OPTIONS]\n```\n\n| Option                         | Description                                             |\n| ------------------------------ | ------------------------------------------------------- |\n| `--lyrics/--no-lyrics`         | Fetch and save lyrics from lrclib.net. Default: enabled |\n| `--replaygain/--no-replaygain` | Apply ReplayGain/R128 tags via rsgain. Default: enabled |\n| `--dry-run`                    | Preview what would be done without making changes       |\n\n**Notes:**\n\n- Recursively processes `.opus`, `.mp3`, `.flac` files in the specified directory\n- Skips files that already have `.lrc` sidecar files (for lyrics)\n- Non-fatal errors: continues processing remaining files and reports summary statistics\n- Requires `rsgain` binary for ReplayGain support (included in Docker image)\n\n### kikusan web\n\nStart the web interface.\n\n```bash\nkikusan web [OPTIONS]\n```\n\n| Option                                         | Env Variable                 | Description                                                 |\n| ---------------------------------------------- | ---------------------------- | ----------------------------------------------------------- |\n| `--host`                                       | -                            | Host to bind to. Default: `0.0.0.0`                         |\n| `-p, --port`                                   | `KIKUSAN_WEB_PORT`           | Port to listen on. Default: `8000`                          |\n| `-o, --output`                                 | `KIKUSAN_DOWNLOAD_DIR`       | Override download directory                                  |\n| `--cors-origins`                               | `KIKUSAN_CORS_ORIGINS`       | CORS allowed origins (comma-separated or `*`). Default: `*` |\n| `--web-playlist`                               | `KIKUSAN_WEB_PLAYLIST`       | M3U playlist name for web downloads (optional)              |\n| `--multi-user/--no-multi-user`                 | `KIKUSAN_MULTI_USER`         | Per-user playlists via `Remote-User` header. Default: off   |\n| `--organization-mode`                          | `KIKUSAN_ORGANIZATION_MODE`  | File organization: `flat` or `album`. Default: `flat`       |\n| `--use-primary-artist/--no-use-primary-artist` | `KIKUSAN_USE_PRIMARY_ARTIST` | Use only primary artist for folder names in album mode      |\n\n### kikusan cron\n\nRun continuous sync based on cron.yaml (playlists, plugins, and explore sources).\n\n```bash\nkikusan cron [OPTIONS]\n```\n\n| Option                                         | Env Variable                 | Description                                            |\n| ---------------------------------------------- | ---------------------------- | ------------------------------------------------------ |\n| `-c, --config`                                 | -                            | Path to cron configuration file. Default: `cron.yaml`  |\n| `-o, --output`                                 | `KIKUSAN_DOWNLOAD_DIR`       | Override download directory                            |\n| `--once`                                       | -                            | Run all sync jobs once and exit (skip scheduling)      |\n| `-f, --format`                                 | `KIKUSAN_AUDIO_FORMAT`       | Audio format: `opus`, `mp3`, `flac`. Default: `opus`   |\n| `--organization-mode`                          | `KIKUSAN_ORGANIZATION_MODE`  | File organization: `flat` or `album`. Default: `flat`  |\n| `--use-primary-artist/--no-use-primary-artist` | `KIKUSAN_USE_PRIMARY_ARTIST` | Use only primary artist for folder names in album mode |\n\n### kikusan plugins list\n\nList all available plugins.\n\n```bash\nkikusan plugins list\n```\n\nNo options.\n\n### kikusan plugins run\n\nRun a plugin sync once (without cron.yaml).\n\n```bash\nkikusan plugins run PLUGIN_NAME --config '{\"key\": \"value\"}' [OPTIONS]\n```\n\n| Option                                         | Env Variable                 | Description                                            |\n| ---------------------------------------------- | ---------------------------- | ------------------------------------------------------ |\n| `-c, --config`                                 | -                            | Plugin config as JSON string (required)                |\n| `-o, --output`                                 | `KIKUSAN_DOWNLOAD_DIR`       | Download directory                                     |\n| `-f, --format`                                 | `KIKUSAN_AUDIO_FORMAT`       | Audio format: `opus`, `mp3`, `flac`. Default: `opus`   |\n| `--organization-mode`                          | `KIKUSAN_ORGANIZATION_MODE`  | File organization: `flat` or `album`. Default: `flat`  |\n| `--use-primary-artist/--no-use-primary-artist` | `KIKUSAN_USE_PRIMARY_ARTIST` | Use only primary artist for folder names in album mode |\n\n## Authentication\n\nkikusan does not use any kind of authentication. If you need to secure it, I suggest to use **Caddy** with **authelia**. This caddy config works for me:\n\n```Caddy\n(authelia_forwarder) {\n  forward_auth http://192.168.1.10:9091 {\n    uri /api/authz/forward-auth\n    copy_headers Remote-User Remote-Groups Remote-Email Remote-Name\n  }\n}\n\nkikusan.foobar.test {\n  import authelia_forwarder\n  reverse_proxy http://192.168.1.11:8007\n}\n```\n\n### Multi-User Playlists\n\nWhen running behind a reverse proxy with SSO (e.g. Authelia), kikusan can create separate M3U playlists per user by reading the `Remote-User` header. Each user's playlist is prefixed with their username (e.g. `alice-webplaylist.m3u`).\n\n```bash\nkikusan web --web-playlist webplaylist --multi-user\n```\n\nIf the header is absent (e.g. direct access without the proxy), the shared playlist is used as fallback.\n\n## Requirements\n\n- Python 3.12+\n- ffmpeg (for audio processing)\n\n## Disclaimer\n\nKikusan is intended for **private, personal use only**.\nIt must not be used for commercial purposes or in any way that violates copyright laws.\n\nUsers are responsible for ensuring their usage complies with applicable laws and YouTubes terms of service.  \nThe developer does not condone copyright infringement and is not liable for misuse of this tool.\n\n## LICENSE\n\n[MIT](./LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdadav%2Fkikusan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdadav%2Fkikusan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdadav%2Fkikusan/lists"}