{"id":49290438,"url":"https://github.com/u2ezneko/ppg-public","last_synced_at":"2026-04-26T00:01:26.751Z","repository":{"id":272286523,"uuid":"916079044","full_name":"U2EZNeko/PPG-Public","owner":"U2EZNeko","description":"Plex music Playlist Generator - PPG","archived":false,"fork":false,"pushed_at":"2026-04-24T18:18:29.000Z","size":11046,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-24T19:29:14.499Z","etag":null,"topics":["playlist-generator","plex","plex-media-server","plexapi"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/U2EZNeko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2025-01-13T12:19:43.000Z","updated_at":"2026-04-24T18:18:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"06bedcf3-1566-48ec-860f-f281045d6145","html_url":"https://github.com/U2EZNeko/PPG-Public","commit_stats":null,"previous_names":["u2ezneko/ppg-public"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/U2EZNeko/PPG-Public","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/U2EZNeko%2FPPG-Public","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/U2EZNeko%2FPPG-Public/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/U2EZNeko%2FPPG-Public/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/U2EZNeko%2FPPG-Public/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/U2EZNeko","download_url":"https://codeload.github.com/U2EZNeko/PPG-Public/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/U2EZNeko%2FPPG-Public/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32280981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","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":["playlist-generator","plex","plex-media-server","plexapi"],"created_at":"2026-04-26T00:01:25.915Z","updated_at":"2026-04-26T00:01:26.723Z","avatar_url":"https://github.com/U2EZNeko.png","language":"Python","readme":"# PPG - Plex Playlist Generator\n\n**Automation scripts to generate music Playlists on your Plex Server.** \n\n**Since Spotify disabled playlists through their API, I had to do it myself.**\n\n\n![Daily](https://github.com/user-attachments/assets/b8c2842a-84d9-433e-a5d1-0367af1799d6)\n\n![Weekly](https://github.com/user-attachments/assets/bbfd1053-b59e-4b52-b2e2-3958ff299e2a)\n\n### Introduction\n\nThese scripts are designed for rather big Plex instances, it will work with smaller databases but will obviously be less random.\n\nI run the scripts with cronjobs to generate playlists for me. My Plex has over 300k tracks on it, your experience may vary.\n\nI'm more than happy to extend the scripts myself and through your Pull Requests. \n\nThe .json files can easily be extended, you can find a list of genres and moods in the .idea folder -\u003e Usefulstuff Folder contains genres.txt it's a list of all unique genres on MY server. You may have a genre on your server that I do not have.\n\n**Genre JSON — two files:** `daily_weekly_genre_pools.json` is only for **PPG-Daily / PPG-Weekly** (each entry is a *pool*; each playlist randomly uses one pool). `named_genre_mix_playlists.json` is only for **PPG-Genres** (each entry becomes a Plex playlist `{name} Mix`). Same JSON shape; different scripts. I used AI to help author these; keep pool/mix *names* distinct so logs and playlists stay readable.\n\n### Content table\n\n- [Introduction](#introduction)\n- [Requirements](#requirements)\n- [Setup](#setup)\n- [Web UI](#web-ui)\n  - [Mobile-compatible UI](#mobile-compatible-ui)\n  - [Screenshots](#web-ui-screenshots)\n- [Telegram notifications](#telegram-notifications)\n- [Track filters (regex)](#track-filters-regex)\n- [Cronjob Examples](#cronjob-examples)\n- [Shared Python module](#shared-python-module)\n- [Usage Description](#usage-description)\n  - [PPG-Daily and PPG-Weekly](#ppg-daily-and-ppg-weekly)\n  - [PPG-Moods](#ppg-moods)\n  - [PPG-Genres](#ppg-genres)\n  - [Fetch-Artist-Cache](#fetch-artist-cache)\n  - [Copy-Playlist-To-Subuser](#copy-playlist-to-subuser)\n- [Update Log](#update-log)\n- [Information](#information)\n- [Not Working](#not-working)\n- [Planned](#planned)\n\n### Requirements:\n  - Plex server and Access Token (Navigate to some item on your Plex -\u003e click \"view XML\" -\u003e Copy token from URL\n  - Python3\n\n### Setup:\n  1. Grab your Plex Token and IP and put it into the .env (remove the example from the file name). If you are upgrading from an older tree, rename `genre_groups.json` → `daily_weekly_genre_pools.json` and `genre_mixes.json` → `named_genre_mix_playlists.json` (or set `DAILY_GENRE_GROUPS_FILE`, `WEEKLY_GENRE_GROUPS_FILE`, and `GENRE_MIXES_FILE` to your old paths).\n  2. **Dependencies:** Use a virtual environment (required on Debian/Ubuntu and other distros that show `externally-managed-environment` / PEP 668):\n     ```bash\n     cd /path/to/PPG\n     python3 -m venv .venv\n     .venv/bin/pip install -r requirements.txt\n     ```\n     Run scripts with `.venv/bin/python PPG-Daily.py` (etc.) or activate the venv first. For the web UI systemd unit, point `ExecStart` at `.venv/bin/python webui/app.py`.\n  3. Test run the script once, check your Playlists.\n  4. Optional: Set Playlist posters manually, there's no way to do it through API.\n     I've included a few obviously self-drawn examples. ;)\n  5. Create cronjobs/Windows Scheduled Tasks (Make sure to use full paths in the config and your cronjob)\n  6. **Optional — Web UI:** install dependencies (`flask` is in `requirements.txt`), then run from the repo root:\n     ```bash\n     python webui/app.py\n     ```\n     Bind address defaults come from `webui/config.json`; override with `PPG_WEB_HOST` and `PPG_WEB_PORT` in `.env` (see `example.env`). Use a real browser for best results (mobile layout and live logs are tuned for normal clients).\n\n## Web UI\n\nThe **PPG Web UI** (`webui/app.py`) is a local Flask app to run the same generator scripts you would start from the CLI, watch **live stdout**, edit **genre / mood / pool JSON**, browse and save **`.env`**, manage **Plex playlists** linked to PPG, and inspect **run statistics** from `log.txt` without leaving the browser.\n\n\u003cimg width=\"2544\" height=\"1112\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9aa544dd-beab-4654-99a4-8cdade3a66e6\" /\u003e\n\n\n\n### Tabs\n\n| Tab | Purpose |\n| --- | --- |\n| **Scripts** | Start Daily / Weekly / Genres / Moods / Liked Artists (and related flows). One output card per script with progress and a live log streamed over SSE. |\n| **Errors** | Playlist-level failures surfaced during runs (also persisted in the browser). Points you to chronic failure tracking in **Statistics** when the same title keeps failing. |\n| **Group JSON** | Load and edit `daily_weekly_genre_pools.json`, `named_genre_mix_playlists.json`, `mood_groups.json`, etc., with search and structure helpers. |\n| **Configs** | View and edit environment variables (backed by project `.env`). |\n| **Playlists** | List Plex music playlists, filter, **multi-select**, delete with an in-page confirmation dialog (not `window.confirm`), and trigger **regenerate** for PPG-managed titles where supported. |\n| **Statistics** | Aggregates from `log.txt`: slowest successful builds, failed playlists, runs per script, recent runs, and **Playlists needing attention** (see below). |\n\n### Runs, reconnects, and statistics\n\n- Subprocesses are started **on the server**; closing a tab does **not** stop a run. Reopening the UI (or reconnecting the event stream) **reloads buffered output** and continues live updates.\n- Completed jobs expose **`GET /api/job/\u003cjob_id\u003e/info`** so the page can recover **exit code** and **done** state even if the browser missed the last SSE message.\n- **Structured events** for each run are appended to `webui/data/ppg_events.jsonl`. Active web-started jobs are also tracked under `webui/data/active_web_jobs.json` so a **server restart** can reconnect to still-running PIDs when possible.\n- **Chronic failures:** repeated failures for the same real playlist title (streak resets after a **successful** build) are recorded in `webui/data/playlist_chronic_failures.json` and listed under **Statistics → Playlists needing attention**. Threshold: `PPG_CHRONIC_FAILURE_THRESHOLD` (default **3**); see `example.env`.\n\n### Mobile-compatible UI\n\nThe Web UI is built to work on **phones and tablets**, not only desktop:\n\n- **Viewport:** `viewport-fit=cover` so notched devices respect **safe-area** insets; padding on `.wrap` uses `env(safe-area-inset-*)` so content stays clear of the status bar and home indicator.\n- **Full width:** The main column uses the **whole screen width** (no narrow max-width column), with comfortable side padding that tightens slightly on very small screens.\n- **Navigation:** Tab labels (**Scripts**, **Errors**, **Group JSON**, etc.) sit in a **horizontally scrollable** strip. On narrow screens you **swipe** the strip to reach **Statistics** and the rest. The scrollbar uses **`overflow-x: auto`**, so it **only appears when the row actually overflows** (no permanent empty scrollbar on desktop).\n- **Sticky tabs (tablet / narrow desktop):** On viewports up to ~960px wide, the tab bar can **stick** under the top of the viewport while you scroll long pages (e.g. Statistics), so you can switch tabs without scrolling back up.\n- **Touch-friendly:** Run buttons and other controls use **larger tap targets** where it matters; output and playlist tables use **horizontal scrolling** inside their panels so wide tables do not blow up the page layout.\n- **Dialogs:** Destructive actions (for example **deleting Plex playlists**) use an **in-page `\u003cdialog\u003e`** with proper focus and layout on small screens instead of the browser’s tiny `confirm()` box.\n- **Live logs:** Script output panes and the JSON editor use **dynamic viewport units (`dvh`)** where helpful so visible height adapts on mobile browsers with collapsing chrome.\n\nUse a normal mobile browser (or responsive mode in devtools) for the best match; embedded preview panes may not reproduce scrolling and touch behavior perfectly.\n\n### Configuration highlights (`example.env`)\n\n- **`PPG_MIN_SONGS_REQUIRED_PERCENT`** — optional **global** minimum pool size as a fraction of `SONGS_PER_PLAYLIST` for all generators; when set, you can rely on this instead of each script’s own min-percent variable.\n- **`PPG_CHRONIC_FAILURE_THRESHOLD`** — consecutive failures before a playlist is flagged for review (see above).\n- **`PPG_WEB_HOST` / `PPG_WEB_PORT`** — Web UI bind address.\n- **`TELEGRAM_*`** — see [Telegram notifications](#telegram-notifications).\n- **`SKIP_SONG_TITLE_REGEX` / `SKIP_ALBUM_TITLE_REGEX`** — see [Track filters (regex)](#track-filters-regex).\n\n### Dev server console\n\nWhen you run `webui/app.py` in a terminal, high-frequency **`GET /api/status`** polling is **not** printed for every request. Other requests are summarized in a **rolling “last 10”** panel in the **lower half** of the terminal (upper half stays as the normal Flask banner). This keeps logs readable while you develop.\n\n### Web UI screenshots\n\n\n| --- | --- |\n| \u003cimg width=\"2520\" height=\"650\" alt=\"image\" src=\"https://github.com/user-attachments/assets/0fa76edf-750c-410e-83a2-34dd4b614546\" /\u003e | **Scripts** — grid of generators + live log / progress |\n| \u003cimg width=\"2527\" height=\"656\" alt=\"image\" src=\"https://github.com/user-attachments/assets/03da883c-2313-42d4-9980-9cc21cc7342c\" /\u003e | **Playlists** — list, search, selection, delete/regenerate |\n| \u003cimg width=\"2506\" height=\"678\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f304a502-3b73-4157-9937-b96802b627dd\" /\u003e | **Statistics** — slowest builds, failures, **Playlists needing attention** |\n| \u003cimg width=\"2527\" height=\"941\" alt=\"image\" src=\"https://github.com/user-attachments/assets/e757fc0e-beab-445b-865a-d60ee6f870bc\" /\u003e | **Group JSON** editor (optional) |\n\n\n## Telegram notifications\n\nOptional **Telegram** messages when a **generator run finishes** (success or uncaught crash), so you get a summary on your phone without watching the console. This applies to runs started from the **CLI**, **cron / Task Scheduler**, or the **Web UI** (the UI runs the same scripts as subprocesses).\n\n**What you get in one message (typical):**\n\n- Script name and optional run id  \n- Total **duration**  \n- **Result** (completed vs crashed)  \n- Count of playlists updated successfully  \n- **Per-playlist** lines with duration and ok/failed (and short failure notes when present)  \n- A **Failures** section when anything failed  \n\nLong summaries are **truncated** to Telegram’s size limit (~4096 characters) with a clear “truncated” marker.\n\n**Environment variables** (set in `.env`; see `example.env`):\n\n| Variable | Meaning |\n| --- | --- |\n| `TELEGRAM_BOT_TOKEN` | Bot token from [@BotFather](https://t.me/BotFather). |\n| `TELEGRAM_CHAT_ID` | Chat or channel id to send to (numeric id or string for supergroups). |\n| `TELEGRAM_NOTIFICATIONS` | If `false`, `0`, `no`, or `off`, **no messages are sent** but tokens stay in `.env` (handy for testing). Default behavior sends when token + chat are set. |\n\nIf either **token** or **chat id** is missing, nothing is sent (no error). Failed HTTP calls are printed to **stderr** only.\n\nImplementation lives in **`module/ppg_telegram.py`** and is invoked from the shared run logger when a run completes. Dependencies: **`requests`** (already in `requirements.txt`).\n\n## Track filters (regex)\n\nYou can **globally exclude** tracks from generator pools and candidate lists by matching **song title** and/or **album title** with **Python regular expressions** (case-insensitive). This is useful for skits, live-only cuts, demos, interludes, or any pattern you want to keep out of automated playlists.\n\n**Environment variables** (in `.env`):\n\n| Variable | Effect |\n| --- | --- |\n| `SKIP_SONG_TITLE_REGEX` | If non-empty, any track whose **title** matches this regex is dropped. |\n| `SKIP_ALBUM_TITLE_REGEX` | If non-empty, any track whose **album** title matches this regex is dropped. |\n\n**Rules:**\n\n- Matching is **case-insensitive** (`IGNORECASE` + `UNICODE`).  \n- Leave a variable **empty or unset** to disable that side of the filter.  \n- If a regex is **invalid**, the process **exits immediately** with a clear error on stderr (fail-fast so you do not get silent “no filters” behavior).  \n- When tracks are removed, scripts log a short line (for example how many were removed from the pool vs the current candidate list).\n\n**Where it applies:** the filters are loaded in **PPG-Daily**, **PPG-Weekly**, **PPG-Genres**, **PPG-Moods**, **PPG-LikedArtists**, **PPG-LikedArtistsCollection**, and **fetch-liked-artists** so cached liked data and generated playlists stay consistent with the same rules.\n\n**Example** (one line in `.env`; adjust for your library):\n\n```env\n# Example: drop obvious skits / live / demo patterns (tune to taste)\nSKIP_SONG_TITLE_REGEX=\\b(skit|live(\\s+from|\\s+at)?|demo(\\s+version)?|interlude|acoustic session)\\b\n```\n\n`example.env` includes commented examples and notes for these variables.\n\nImplementation: **`module/ppg_track_filters.py`**.\n\n## Shared Python module\n\nShared helpers live under **`module/`** (import as `module.*` from repo root scripts): run logging and `log.txt` / `ppg_events.jsonl`, minimum-song / pool thresholds, track title/album regex filters, single-playlist (`PPG_ONLY_PLAYLIST_TITLE`) helpers, Telegram summaries, and **chronic failure** tracking for the Web UI. Generator scripts at the repo root stay the main entry points.\n\n# Cronjob examples:\n\n![cron](https://github.com/user-attachments/assets/94063b48-99f4-42f7-b149-6034984218fe)\n\n\n\nMake sure to remove the \"/user/bin/xterm -hold -e\" if you do not want your terminal window to stay open. I just like seeing that it ran through over night.\n\n\n\n# Usage description:\n\n### PPG-Daily and PPG-Weekly\n  \n  These are there to replace Spotify's Daily Mixes and Weekly Mixes\n\n  They randomly pick **one entry** from `daily_weekly_genre_pools.json` for each new playlist (each entry is a *pool* of Plex genres, not a separate Plex playlist name).\n\n  It writes used pools to a log file to avoid repeating the same pool too soon.\n\n  JSON example\n```\n   \"Rock\": [\"Classic Rock\", \"Alternative Rock\", \"Hard Rock\", \"Indie Rock\", \"Psychedelic Rock\", \"Grunge\", \"Proto-punk\"],\n```\n### PPG-Moods\n  \n  Used to update \"mood Mix\", similar to Spotify.\n\n  You can set the moods to create mixes for in mood_groups.\n  \n  JSON example\n```\n    \"Melancholy\": [\n    \"Melancholy\",\n    \"Sad\",\n    \"Wistful\",\n    \"Lonely\",\n    \"Nostalgic\",\n    \"Poignant\",\n    \"Somber\"\n  ]\n  ```\n\n### PPG-Genres\n  \n  Creates or updates \"genre Mix\" playlists, similar to Spotify.\n\n  Definitions live in `named_genre_mix_playlists.json`: one Plex playlist per key, named `{key} Mix`.\n\n  This allows you to select multiple similar genres and pick random songs from those. You can also extend the json entry with a date filter, you can chose before, after or between release years. \n\n  Since plex does not save the release date for each song, I have to use the Album's year to filter. This still does the same, the problem is Plex being unable to keep up with my database so I'm missing a bunch of metadata.\n\n  JSON example\n```\n  \"90s Gangster Rap Underground\": {\n    \"genres\": [\n      \"Country rap\",\n      \"Rap/r\u0026b\",\n      \"Cali rap\",\n      \"Pop rap / rock\",\n      \"Vapor trap\",\n      \"Gangsta rap\",\n      \"Mixtape\"\n    ],\n    \"release_date_filter\": {\n      \"condition\": \"between\",\n      \"start_date\": \"1990\",\n      \"end_date\": \"1999\"\n    }\n  },\n  ```\n\n### PPG-LikedArtists\n\n  Uses Liked artists to create playlists.\n  \n  Should use similar artists or similar tracks depending on the playlist. \n\n\n### Fetch-liked-artists\n  \n  Fetches liked artists from Plex and writes them to a cache file.\n  \n  Will fetch directly liked artists and grabs artists from liked tracks. \n\n  Best to run once weekly, takes a hot minute to fetch all data.\n\n  Also now fetches all liked songs for faster access.\n\n\n### Copy-Playlist-To-Subuser\n\n  As the name suggests, lets you copy playlists to sub-users. \n\n  You will have to set Posters manually.\n\n  To get sub-user plex token:\n  \n  Log into sub user -\u003e Go to some item -\u003e CTRL Shift I -\u003e Go to network tab -\u003e Find \"x-Plex-Token\" in the Header (might need to click on another item with the network tab open)\n\n  Largely deprecated now that they actually show shared playlists in Plexamp\n\n\n![collection](https://github.com/user-attachments/assets/1862f8eb-1854-41c3-b288-f6c39a4cb0b2)\n\n# Update log\n\n### 23.04.2026:\n\n- **Web UI:** Scripts, Errors, Group JSON, Configs, Playlists (multi-select delete + confirm dialog, regenerate), Statistics; **full-width**, **mobile-oriented** layout (safe areas, sticky tabs on smaller viewports, horizontal tab strip with overflow only when needed, touch-friendly controls, in-page dialogs, `dvh`-aware panes).\n- **Run recovery:** server-side job buffers, SSE reconnect, `GET /api/job/\u003cid\u003e/info`, and polling so finished runs report exit state even if the tab was closed or the stream dropped.\n- **Chronic failures:** `webui/data/playlist_chronic_failures.json`, Statistics section **Playlists needing attention**, `PPG_CHRONIC_FAILURE_THRESHOLD`.\n- **Telegram:** optional end-of-run summaries via `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID` (`module/ppg_telegram.py`); `TELEGRAM_NOTIFICATIONS=false` disables sends without removing credentials.\n- **Track filters:** `SKIP_SONG_TITLE_REGEX` and `SKIP_ALBUM_TITLE_REGEX` in `.env` for case-insensitive exclusion by track/album title across generators and liked-artist tooling (`module/ppg_track_filters.py`).\n- **Config:** optional global `PPG_MIN_SONGS_REQUIRED_PERCENT` for minimum pool size across generators (`example.env`).\n- **Code layout:** shared helpers in **`module/`** (`ppg_run_logger`, `ppg_min_songs`, `ppg_chronic_failures`, `ppg_track_filters`, `ppg_single_playlist`, etc.).\n- **Dev UX:** quieter Flask access log; rolling last-10 HTTP summary in the lower half of the terminal.\n\n### 10.11.2025:\n\n  - Added PPG-LikedArtists\n  - Uses cache to get artists and creates a playlist with similar songs or artists.\n  - Cache file will now hold all liked tracks. \n\n\n### 03.11.2025:\n\n  - Liked artist fetching is now its own script\n  - Automatically sets posters for genre and mood mixes (yoinked from Spotify)\n  - Added cache validation script. (only checks if the artist in cache returns any songs)\n  - Removed caching code from scripts\n\n### 29.10.2025:\n  - Date filters for genre pools / named mixes (JSON entries)\n  - Multithreading!\n  - Log levels\n  - Moved everything to .env (scripts check for all values)\n  - Mood-grouping for final track list (not possible all the time, Plex doesnt hold this all the time)\n  - Prevent consecutive artists in playlists\n  - Prevent multiple songs from single albums spamming playlists\n  - Progress Bars!\n  - Turns out theres never been a requirements.txt lol\n\n### 27.10.2025:\n\n  - Added randomized playlist posters\n\n### 24.10.2025:\n\n  - Moved most config values to .env, alternatively you can still define them in the scripts.\n  - Hopefully final adjustment to Description\n  - Updated the groups a bit\n  - Prettied up output\n  - Used AI to comment out code because im lazy\n  - Fixed playlist shuffler, only works on regular playlists (Useful for smart home automations that cannot use the shuffle function)\n\n### 03.10.2025:\n\n  - Added Preference for liked artists\n  - Added logic to avoid artists filling whole playlists\n  - Clearer debug output\n\n### 16.01.2025:\n\n  - Added before, between and after time filters.   \n  - Added logging to reduce getting the same playlists.\n  - Removed useless fetch of all available genres from Daily script.\n\n\n# Information:\n\n- We're getting into territories with filters that will one way or another take a while to run. I'm multithreading a few things where possible but on slower CPUs this will be unavoidable.\n  The bigger your library the more fetching it has to do, with my close to 400k track library fetching certain genres returns a solid 20k+ songs, running through all those will take a while. \n\n- I've created this script using a database of 300k+ songs. This left me with over 4000 unique genres and 300 moods which should cover quite a broad spectrum of songs.\n\n- If you run the script through cronjobs, use full paths to the jsons and log files!\n\n- If you add genre pools, named genre mixes, or mood_groups, make each top-level key unique (it identifies the pool or the `{name} Mix` playlist).\n\n- Because sometimes the scripts cannot find enough songs to fill a playlist, it will try again if it cannot find at least 80% (can be defined in the script) of the SONGS_PER_PLAYLIST. It will retry this 10 times.\n\n- The script is supposed to add used genres to the Playlist Description. This works on my PC but not on my VM for some reason, i had to change 2 lines of code there, check the \"Not Working\" section.\n\n- Depending on your database size and processor power it may take a good chunk of time to fetch the unique genres and songs. This is expected, not much you can do to speed it up.\n\n- Here's how you can get your own Spotify thumbnails: \n\n  https://seed-mix-image.spotifycdn.com/v6/img/desc/Nevergonnagiveyouupnevergonnaletyoudown/en/default\n\n\n![example](https://github.com/user-attachments/assets/e7d246cb-2d09-4632-8778-c093415ccbf3)\n\n\n# Update infos:\n\n\n  change the URL to whatever you want and save the image. ezpz\n  (or add the link directly in Plex)\n\n  Update:\n  - Date filters for genre pools / named mixes (JSON entries)\n  - Multithreading! - For fetching operations and filtering\n  - Log levels\n  - Moved everything to .env (scripts check for all values)\n  - Mood-grouping for final track list (not possible all the time, Plex doesnt hold this all the time)\n  - Prevent consecutive artists in playlists\n  - Prevent multiple songs from single albums spamming playlists\n  - Progress Bars!\n  - Turns out theres never been a requirements.txt lol\n\n\n  27.10.2025 Update:\n  - Added a toggle-able option to replace Playlist Posters on every run\n    Will not use a poster twice per run, you can easily add your own to the folder. \n    Images are AI generated, if you end up making cool ones go ahead and add them to the repo. \n\n    UPDATE_POSTERS=true  is the .env value, it's true by default. Sorry if it replaced your images.\n  \n  \n  24.10.2025 Update:\n  - Config values were moved to .env, check example.env to see whats available. \n    Make sure you use full paths for log files just to be sure.\n    Alternatively you can always have a value after the .env reference, i kept them in the scripts so everyone sees how. \n\n\n  03.10.2025 Update:\n\n  - Liked Artist Preference:\n    \n    Once a week, the scripts will fetch all liked tracks and extract the artists from it.\n    It will cache this data. Limited to Weekly as it can take forever to do on large libraries. Mine takes a solid 10 minutes. lol\n\n    This should ensure more relevant playlists as a whole, I've tested it a bunch and I like it. \n\n    You can set a percentage of how many liked artist tracks to use in the script.\n    Enabled by default, can be disabled for playlists in the json's like this.\n```\n  \"Rock\": {\n    \"genres\": [\"Classic Rock\", \"Alternative Rock\", \"Hard Rock\", \"Indie Rock\", \"Psychedelic Rock\", \"Grunge\", \"Proto-punk\"],\n    \"prefer_liked_artists\": true\n  },\n  \"Classical\": {\n    \"genres\": [\"Classical Music\", \"Baroque\", \"Opera\", \"Romantic Classical\", \"Classical Crossover\", \"Symphonic\", \"Chamber Music\"],\n    \"prefer_liked_artists\": false\n  }\n```\n\n  - The scripts will now check a playlist once created and re-fetch tracks if an artist takes too many slots.\n    \n    Can be configured in the script.\n\n\n# Not working\n\n- Older versions of PlexAPI do not have \"existing_playlist.editSummary\". To set a Summary on an old version change the previous to \"existing_playlist.edit(summary=f\"Genres used: {genre_description}\")\"\n\n# Planned\n\n- Prefer artists the user has listened to before?\n  This would probably have to be cached as it would take a long long time to fetch. Not sure if its worth yet. \n\n- Come up with more useful filters\n  Tried but not working: BPM, Moods (semi working)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fu2ezneko%2Fppg-public","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fu2ezneko%2Fppg-public","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fu2ezneko%2Fppg-public/lists"}