A plugin for beets to sync with your Plex server.
- AI-Generated Playlists: Use
beet plexsonic -p "YOUR_PROMPT"to create a playlist based on YOUR_PROMPT. Modify the playlist name using-mflag, change the number of tracks requested with-nflag, and clear the playlist before adding new songs with-cflag.
Use beet plex_smartplaylists [-o ONLY] to generate or manage custom playlists in Plex. The plugin currently supports various types of playlists:
You can use the -o or --only option to specify a comma-separated list of playlist IDs to update. This is useful for updating only certain playlists (e.g., just the AI playlists) on a schedule:
beet plex_smartplaylists -o daily_discovery,forgotten_gemsThe command will only generate the specified playlists, skipping others in your configuration.
-
Daily Discovery:
- Uses tracks you've played in the last 15 days as a base to learn about listening habits (configurable via
history_days) - Excludes tracks played in the last 30 days (configurable via
exclusion_days) - Uses an intelligent scoring system that considers:
- Track popularity relative to your library
- Rating for rated tracks
- Recency of addition to library
- Release year (favors newer releases)
- Introduces controlled randomization to ensure variety
- Matches genres with your recent listening history using both sonic analysis and library-wide genre preferences
- Uses Plex's Sonic Analysis to find sonically similar tracks
- Also discovers tracks from your entire library that match your preferred genres
- Limits the playlist size (configurable via
max_tracks, default 20) - Controls discovery vs. familiar ratio (configurable via
discovery_ratio, default 30% - more familiar tracks)
- Uses tracks you've played in the last 15 days as a base to learn about listening habits (configurable via
-
70s/80s Flashback:
- Creates a nostalgic playlist featuring tracks from 1970-1989
- Prioritizes well-rated tracks from the 70s and 80s that may not have been played recently
- Uses a specialized scoring algorithm that emphasizes nostalgic value and age
- Balances between familiar favorites and forgotten gems from the era
- Limits the playlist size (configurable via
max_tracks, default 20) - Controls discovery vs. nostalgia ratio (configurable via
discovery_ratio, default 30%)
-
Highly Rated Tracks:
- Curates tracks with high user ratings (7.0 and above)
- Focuses on quality by prioritizing highly-rated content
- Includes tracks that have stood the test of time according to your ratings
- Adds slight recency factor to maintain variety in the playlist
- Limits the playlist size (configurable via
max_tracks, default 20)
-
Most Played Tracks:
- Features your most frequently played tracks
- Ranks tracks based on cumulative play counts (plex_viewcount)
- Uses weighted selection to add variety while prioritizing popular tracks
- Sorts all tracks by play count in descending order
- Limits the playlist size (configurable via
max_tracks, default 20)
-
Forgotten Gems:
- Creates a playlist of tracks that deserve more attention
- Uses your highly-rated tracks to establish a quality baseline
- Prioritizes unrated tracks with popularity comparable to your favorites
- Only includes tracks matching your genre preferences
- Automatically adjusts selection criteria based on your library's characteristics
- Limits the playlist size (configurable via
max_tracks, default 20) - Controls maximum play count (configurable via
max_plays, default 2) - Minimum rating for rated tracks to be included (configurable via
min_rating, default 4) - Percentage of playlist to fill with unrated but popular tracks (configurable via
discovery_ratio, default 30%) - Excludes tracks played recently (configurable via
exclusion_days)
-
Recent Hits:
- Curates a playlist of recent, high-energy tracks
- Applies a default release-year guard covering roughly the last 3 years whenever no year filter is provided; override with
filters.include.yearsor the playlist-levelmax_age_years/min_yearoptions - Updated scoring leans harder on release recency and last-play data, with popularity and ratings acting as the tie-breakers
- Uses weighted randomness for track selection while respecting your genre preferences
- Automatically adjusts selection criteria and limits size (configurable via
max_tracks, default 20) - Requires a minimum rating (
min_rating, default 4) and lets you control the discovery ratio (default 20%) - Set
exclusion_daysif you want to keep very recent listens out (default 30 days)
-
Fresh Favorites:
- Creates a playlist of high-quality tracks that deserve more plays
- Enforces a default release window spanning roughly the last 7 years unless you supply custom year filters or specify
max_age_years/min_year - Updated scoring strongly favors release recency and recent spins while still rewarding strong ratings and popularity
- Skips tracks without a trusted release year when the recency guard is active to keep the mix on-theme
- Defaults:
max_tracks: 100,discovery_ratio: 25,min_rating: 6,exclusion_days: 21
-
Imported Playlists:
- Import playlists from external services (Spotify, Apple Music, YouTube, etc.) and local M3U8 files
- Configure multiple source URLs and file paths per playlist
- For M3U8 files, use paths relative to beets config directory or absolute paths
- Support for custom HTTP POST requests to fetch playlists
- Control playlist behavior with options:
manual_search: Enable/disable manual matching for unmatched tracksclear_playlist: Clear existing playlist before adding new tracksmax_tracks: Limit the number of tracks in the playlist
You can use config filters to finetune any playlist. You can specify the genre, year, and UserRating to be included and excluded from any of the playlists. See the extended example below.
- Plex Library Sync:
beet plexsync [-f]imports all the data from your Plex library inside beets. Use the-fflag to force update the entire library with fresh information from Plex. - Recent Sync:
beet plexsyncrecent [--days N]updates the information for tracks listened in the last N days (default: 7). For example,beet plexsyncrecent [--days 14]will update tracks played in the last 14 days.
- Playlist Manipulation:
beet plexplaylistadd [-m PLAYLIST] [QUERY]andbeet plexplaylistremove [-m PLAYLIST] [QUERY]add or remove tracks from Plex playlists. Use the-mflag to provide the playlist name. You can use any beets query as an optional filter. - Playlist Clear:
beet plexplaylistclear [-m PLAYLIST]clears a Plex playlist. Use the-mflag to specify the playlist name.
-
Playlist Import:
beet plexplaylistimport [-m PLAYLIST] [-u URL] [-l]imports individual playlists from Spotify, Apple Music, Gaana.com, JioSaavn, Youtube, Tidal, M3U8 files, custom APIs, and ListenBrainz. Use the-mflag to specify the playlist name and:- For online services: use the
-uflag to supply the full playlist url - For M3U8 files: use the
-uflag with the file path (relative to beets config directory or absolute path) - For custom APIs: configure POST requests in config.yaml (see Configuration section)
- For ListenBrainz: use the
-lor--listenbrainzflag to import "Weekly Jams" and "Weekly Exploration" playlists
You can define multiple sources per playlist in your config including custom POST endpoints:
- name: "Mixed Sources Playlist" type: "imported" sources: - "https://open.spotify.com/playlist/37i9dQZF1DX0kbJZpiYdZl" # Spotify - "playlists/local.m3u8" # Local M3U8 - type: "post" # Custom API server_url: "http://localhost:8000/api/playlist" headers: Authorization: "Bearer your-token" payload: playlist_url: "https://example.com/playlist/123"
For each import session, a detailed log file is created in your beets config directory (named
<playlist_name>_import.log) that records:- Tracks that couldn't be found in your Plex library
- Low-rated tracks that were skipped
- Import statistics and summary
- The log file helps you identify which tracks need manual attention
- For online services: use the
-
Youtube Search Import:
beet plexsearchimport [-m PLAYLIST] [-s SEARCH] [-l LIMIT]imports playlists based on Youtube search. Use the-mflag to specify the playlist name, the-sflag for the search query, and the-lflag to limit the number of search results.
-
Plex to Spotify:
beet plex2spotify [-m PLAYLIST] [QUERY]copies a Plex playlist to Spotify. Use the-mflag to specify the playlist name.You can use beets queries with this command to filter which tracks are sent to Spotify. For example, to add only tracks with a
plex_userratinggreater than 2 to the "Sufiyana" playlist, use:beet plex2spotify -m "Sufiyana" plex_userrating:2..Additional filtering examples:
- Only transfer highly-rated tracks:
beet plex2spotify -m "My Playlist" plex_userrating:8.. - Transfer tracks by specific artist:
beet plex2spotify -m "Rock Hits" artist:"The Beatles" - Transfer tracks from a specific year range:
beet plex2spotify -m "2000s Hits" year:2000..2009 - Combine multiple filters:
beet plex2spotify -m "Recent Favorites" plex_userrating:7.. year:2020..
- Only transfer highly-rated tracks:
-
Playlist to Collection:
beet plexplaylist2collection [-m PLAYLIST]converts a Plex playlist to a collection. Use the-mflag to specify the playlist name. -
Album Collage:
beet plexcollage [-i INTERVAL] [-g GRID]creates a collage of most played albums. Use the-iflag to specify the number of days and-gflag to specify the grid size.
The plugin creates detailed import logs for each playlist import session. You can manually process failed imports using:
beet plex_smartplaylists [--import-failed] [--log-file LOGFILE]: Process all import logs and attempt manual matching for failed tracks, or process a specific log file.
This is especially useful when:
- You've added new music to your library and want to retry matching previously failed tracks
- You want to manually match specific tracks from a particular playlist's import log
- You need to clean up import logs by removing successfully matched tracks
This plugin allows you to sync your Plex library with beets, create playlists based on AI-generated prompts, import playlists from other online services, and more.
Install the plugin using pip:
pip install git+https://github.com/arsaboo/beets-plexsync.gitThen, configure the plugin in your config.yaml file.
To upgrade, use the command:
pip install --upgrade --force-reinstall --no-deps git+https://github.com/arsaboo/beets-plexsync.gitAdd plexsync to your list of enabled plugins.
plugins: plexsync
# If you want to use the ListenBrainz import feature, you'll need to configure
# the ListenBrainz plugin. See https://github.com/arsaboo/beets-listenbrainz for setup.
listenbrainz:
user_token: YOUR_USER_TOKEN
username: YOUR_USERNAMENext, you can configure your Plex server and library like following (see instructions to obtain Plex token here).
plex:
host: '192.168.2.212'
port: 32400
token: PLEX_TOKEN
library_name: 'Music'If you want to import spotify playlists, you will also need to configure the spotify plugin. If you are already using the Spotify plugin, plexsync will reuse the same configuration.
spotify:
client_id: CLIENT_ID
client_secret: CLIENT_SECRET-
The
beet plexsoniccommand allows you to create AI-based playlists using an OpenAI-compatible language model. To use this feature, you will need to configure the AI model with an API key. Once you have obtained an API key, you can configurebeetsto use it by adding the following to yourconfig.yamlfile:llm: api_key: API_KEY model: "gpt-3.5-turbo" base_url: "https://api.openai.com/v1" # Optional, for other providers search: # provider is auto-detected: OpenAI if llm.api_key is set, otherwise Ollama # Explicitly set to "ollama" if you want to use Ollama instead brave_api_key: "your-brave-api-key" # Optional Brave Search API key searxng_host: "http://your-searxng-instance.com" # Optional SearxNG instance exa_api_key: "your-exa-api-key" # Optional Exa search API key tavily_api_key: "your-tavily-api-key" # Optional Tavily API key # Advanced: Override settings from main llm config # api_key: "" # Uses llm.api_key if empty # base_url: "" # Uses llm.base_url if empty # model: "" # Uses llm.model if empty (for OpenAI) or "qwen3:latest" (for Ollama) # ollama_host: "http://localhost:11434" # Only used when provider is "ollama"
Using OpenAI or OpenAI-compatible APIs for search:
The plugin automatically uses OpenAI-compatible models (via OpenAILike) for LLM search if you have
llm.api_keyconfigured. No additional configuration needed!Simple configuration (auto-detects OpenAI):
llm: api_key: YOUR_OPENAI_API_KEY model: "gpt-4.1-mini" # Or your preferred model base_url: "https://api.openai.com/v1" # Or your preferred endpoint search: brave_api_key: "your-brave-api-key" # At least one search provider is required
Using Ollama instead (explicit override):
llm: search: provider: "ollama" # Explicitly use Ollama model: "qwen3:latest" ollama_host: "http://localhost:11434" brave_api_key: "your-brave-api-key"
Advanced: Override search-specific settings:
llm: api_key: YOUR_MAIN_API_KEY model: "gpt-4" search: api_key: YOUR_SEARCH_SPECIFIC_KEY # Use different key for search model: "gpt-3.5-turbo" # Use cheaper model for search brave_api_key: "your-brave-api-key"
Note: To enable LLM search, you must also set
use_llm_search: yesin yourplexsyncconfiguration (see Advanced Usage section).Structured Output with instructor:
The plugin uses the instructor library for reliable structured output from LLMs (>99% reliability). This works with both Ollama (via
/v1endpoint) and OpenAI-compatible APIs. Theinstructorlibrary ensures that LLM responses match the expected Pydantic models, with built-in retry logic. Ifinstructoris not available, the plugin gracefully falls back to the Agno framework.When multiple search providers are configured, they're used in the following priority order:
- SearxNG (tried first if configured)
- Exa (used if SearxNG fails or isn't configured)
- Brave Search (used if both SearxNG and Exa fail or aren't configured)
- Tavily (used if all above fail or aren't configured)
You can get started with
beet plexsonic -p "YOUR_PROMPT"to create the playlist based on YOUR_PROMPT. The default playlist name isSonicSage(wink wink), you can modify it using-mflag. By default, it requests 10 tracks from the AI model. Use the-nflag to change the number of tracks requested. Finally, if you prefer to clear the playlist before adding the new songs, you can add-cflag. So, to create a new classical music playlist, you can use something likebeet plexsonic -c -n 10 -p "classical music, romanticism era, like Schubert, Chopin, Liszt".Please note that not all tracks returned by the AI model may be available in your library or matched perfectly, affecting the size of the playlist created. The command will log the tracks that could not be found on your library. You can improve the matching by enabling
manual_search(see Advanced Usage). This is working extremely well for me. I would love to hear your comments/feedback to improve this feature. -
To configure imported playlists, you can use various source types including custom POST requests:
plexsync: playlists: items: - name: "Custom Playlist" type: "imported" sources: # Standard URL sources - "https://open.spotify.com/playlist/37i9dQZF1DX0kbJZpiYdZl" - "playlists/local_hits.m3u8" # POST request source - type: "post" server_url: "http://localhost:8000/api/playlist" headers: Authorization: "Bearer your-token" Content-Type: "application/json" payload: playlist_url: "https://example.com/playlist/123"
The POST request expects a JSON response with this format:
{ "song_list": [ { "title": "Song Title", "artist": "Artist Name", "album": "Album Name", # Optional "year": "2024" # Optional } ] }
Plex matching may be less than perfect and it can miss tracks if the tags don't match perfectly. There are few tools you can use to improve searching:
- You can enable manual search to improve the matching by enabling
manual_searchin your config (default:False). - You can enable LLM-powered search using Ollama with optional integration for SearxNG, Exa, or Tavily (used in that order if all of them are configured). This provides intelligent search capabilities that can better match tracks with incomplete or variant metadata. See the
llmconfiguration section above.
plexsync:
manual_search: yes
use_llm_search: yes # Enable LLM searching; see llm config
playlists:
defaults:
max_tracks: 20
items:
- id: daily_discovery
name: "Daily Discovery"
max_tracks: 20 # Maximum number of tracks for Daily Discovery playlist
exclusion_days: 30 # Number of days to exclude recently played tracks. Tracks played in the last 30 days will not be included in the playlist.
history_days: 15 # Number of days to use to learn listening habits
discovery_ratio: 70 # Percentage of unrated tracks (0-100)
# Higher values = more discovery
# Example: 30 = 30% unrated + 70% rated tracks
# 70 = 70% unrated + 30% rated tracks
- id: forgotten_gems
name: "Forgotten Gems"
max_tracks: 50 # Maximum number of tracks for playlist
max_plays: 2 # Maximum number of plays for tracks to be included
min_rating: 4 # Minimum rating for rated tracks
discovery_ratio: 30 # Percentage of unrated tracks (0-100); Higher values = more discovery
exclusion_days: 30 # Number of days to exclude recently played tracks
filters:
include:
genres:
- Filmi
- Indi Pop
- Punjabi
- Sufi
- Ghazals
years:
after: 1970
exclude:
genres:
- Religious
- Bollywood Unwind
- Bollywood Instrumental
years:
before: 1960
min_rating: 5
- id: recent_hits
name: "Recent Hits"
max_tracks: 20
discovery_ratio: 20
exclusion_days: 0 # Number of days to exclude recently played tracks (default: 0 = include all)
filters:
include:
genres:
- Pop
- Rock
years:
after: 2022
min_rating: 4
- id: bollywood_hits
name: "Bollywood Hits"
type: imported
sources: # full playlist urls or M3U8 file paths
- https://music.youtube.com/playlist?list=RDCLAK5uy_kjNBBWqyQ_Cy14B0P4xrcKgd39CRjXXKk
- "playlists/local_hits.m3u8" # Relative to beets config dir
- "/absolute/path/to/playlist.m3u8"
max_tracks: 100 # Optional limit
manual_search: no
clear_playlist: no