Core Services¶
Surface-agnostic business logic from musicseed-core (musicseed.services.*). These services
return result models and raise typed exceptions; CLI, API handlers, and MCP adapters map them.
Database-backed operations accept an optional MusicSeedContext; see
runtime and models for configuration, sessions, vector caching, and DTOs.
Recommendation results contain scalar ServiceTrack / ServiceRecommendation models, so they
remain JSON-serializable after the service closes its session. For a preview/approval workflow,
write the approved IDs with playlist_tracks.create_playlist_from_tracks or
populate.populate_playlist(track_ids=...). recommend.create_playlist and populate without
track_ids are separate generate-and-write paths; they must not replace an approved selection.
Library¶
musicseed.services.library
¶
Library service — surface-agnostic entry points for import, DB, and status operations.
EnrichmentCoverage
¶
Bases: BaseModel
Per-source enrichment coverage counts over the local track library.
LibraryStatus
¶
Bases: BaseModel
Point-in-time statistics for the local library and its configuration.
ImportResult
¶
Bases: BaseModel
Counts of rows written by one Plex import run.
CountCompare
¶
Bases: BaseModel
Plex vs local row counts for one entity type.
missing
property
¶
How many Plex rows are not yet imported locally (never negative).
ImportCoverage
¶
Bases: BaseModel
Comparison of Plex library counts against the imported local counts.
initialize_database(context=None)
¶
Create the SQLite database file and tables. Idempotent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
optimize_database(context=None)
¶
Create performance indexes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
list[IndexResult]
|
Per-index results describing success or failure for each index. |
import_library(plex_db_path=None, plex_db_ssh=None, library_name=None, full_import=False, progress_callback=None, should_cancel=None, context=None)
¶
Import metadata from the Plex database into the local library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plex_db_path
|
Path | None
|
path to the Plex SQLite database; overrides the configured source when given. |
None
|
plex_db_ssh
|
str | None
|
scp-style SSH target of a remote Plex database
directory; overrides |
None
|
library_name
|
str | None
|
Plex library to import; defaults to the configured
|
None
|
full_import
|
bool
|
re-import everything instead of an incremental import. |
False
|
progress_callback
|
Callable[[int, int, str], None] | None
|
optional |
None
|
should_cancel
|
Callable[[], bool] | None
|
optional callable polled by the importer; the import stops early when it returns True. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
ImportResult
|
Counts of imported artists, albums, tracks, and play history rows. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
if the Plex database file does not exist. |
has_succeeded_import(context=None)
¶
Whether this source/library completed an import, independent of job history.
get_import_coverage(context=None)
¶
Compare MusicSeed artist/album/track counts to the configured Plex library.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
ImportCoverage | None
|
The coverage comparison, or None when the Plex database cannot be |
ImportCoverage | None
|
read. Does not raise. |
get_status(context=None)
¶
Return library statistics and enrichment coverage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
LibraryStatus
|
Entity counts (artists, albums, tracks, plays, tags), per-source |
LibraryStatus
|
enrichment coverage, import coverage against the configured Plex |
LibraryStatus
|
library, and the resolved configuration paths. |
Recommend¶
musicseed.services.recommend
¶
Recommendation service — surface-agnostic entry points for the recommendation flow.
RecommendationResult
¶
Bases: BaseModel
Result of a recommendation request.
PlaylistCreateResult
¶
Bases: BaseModel
Result of a playlist creation request.
get_recommendations(*, seed_texts=None, seed_ids=None, limit=50, method='average', per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, context=None)
¶
Return seed tracks and scored recommendations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed_texts
|
list[str] | None
|
seed tracks as |
None
|
seed_ids
|
list[int] | None
|
seed tracks by local database id. |
None
|
limit
|
int
|
maximum number of recommendations to return. |
50
|
method
|
RecommendMethod
|
|
'average'
|
per_seed_limit
|
int
|
candidates gathered per seed ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
RecommendationResult
|
The resolved seed tracks, the selected recommendations, and the |
RecommendationResult
|
sonic coverage of the candidate pool. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
if one or more seed tracks cannot be resolved. |
create_playlist(name, *, seed_texts=None, seed_ids=None, limit=50, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, context=None)
¶
Generate recommendations and create a Plex playlist.
The created playlist contains the resolved seed tracks followed by the
recommendations. Accepts the same recommendation arguments as
get_recommendations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
title of the Plex playlist to create. |
required |
seed_texts
|
list[str] | None
|
seed tracks as |
None
|
seed_ids
|
list[int] | None
|
seed tracks by local database id. |
None
|
limit
|
int
|
maximum number of recommendations to include. |
50
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
PlaylistCreateResult
|
The resolved seed tracks, the recommendations, and the created Plex |
PlaylistCreateResult
|
playlist. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if seed tracks cannot be resolved. |
PlexAPIError
|
if the Plex API call fails. |
Approved playlist selections¶
musicseed.services.playlist_tracks
¶
Write an approved ordered track selection, without recomputing recommendations.
PlaylistTracksResult
¶
Bases: BaseModel
The created playlist and exact selected tracks, with no invented scores.
resolve_track_selection(session, track_ids)
¶
Resolve every selected local ID in order; reject stale/unmapped selections.
Duplicates are removed in first-occurrence order. Missing tracks fail the entire selection before any Plex write rather than silently writing a subset.
create_playlist_from_tracks(name, track_ids, *, context=None)
¶
Create a Plex playlist from exactly the approved local IDs, in order.
No recommendations are generated, and no score/filter policy is reapplied. Callers should include approved seed IDs in the selection when desired.
Enrichment¶
musicseed.services.enrichment
¶
Enrichment service — surface-agnostic entry points for metadata enrichment.
enrich_tracks(source='listenbrainz', batch_size=50, limit=None, artist=None, album=None, resume=False, concurrency=5, progress_callback=None, should_cancel=None, context=None)
¶
Enrich tracks with external metadata from Spotify or ListenBrainz.
Runs the async enrichment pipeline via asyncio.run() internally —
never call this from inside a running event loop; offload to a thread
instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
enrichment source, |
'listenbrainz'
|
batch_size
|
int
|
tracks processed per batch. |
50
|
limit
|
int | None
|
maximum number of tracks to enrich (None for all). |
None
|
artist
|
str | None
|
only enrich tracks whose artist name matches this pattern. |
None
|
album
|
str | None
|
only enrich tracks whose album title matches this pattern. |
None
|
resume
|
bool
|
skip tracks that were already attempted. |
False
|
concurrency
|
int
|
maximum concurrent requests inside the async pipeline. |
5
|
progress_callback
|
Callable[[int, int, str], None] | None
|
optional |
None
|
should_cancel
|
Callable[[], bool] | None
|
optional callable polled by the pipeline; enrichment stops early when it returns True. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
EnrichmentStats
|
Aggregate enrichment statistics (processed, matched, unmatched, |
EnrichmentStats
|
errors). |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if Spotify credentials are missing when source='spotify', or the ListenBrainz user token is missing when source='listenbrainz'. |
Discovery¶
musicseed.services.discovery
¶
Local environment discovery: Plex server, databases, and config state.
Surface-agnostic and strictly read-only: probes the filesystem and the Plex
HTTP API (GET requests only) but never starts imports, sonic analysis,
enrichment, or any Plex mutation. Expected failures are returned as
structured data (reason codes) so surfaces can render actionable fixes
instead of parsing exceptions. Plex tokens are never included in results.
Reason
¶
Bases: StrEnum
Machine-readable discovery outcome codes.
PathCandidate
¶
Bases: BaseModel
One probed filesystem candidate for a required local file.
FileDiscovery
¶
Bases: BaseModel
Discovery result for a required local file (e.g. a Plex database).
DatabasePathDiscovery
¶
Bases: BaseModel
Discovery result for MusicSeed's own SQLite database location.
PlexServerDiscovery
¶
Bases: BaseModel
Discovery result for the Plex HTTP API.
SpotifyCredentialsCheck
¶
Bases: BaseModel
Presence of Spotify enrichment credentials (values never exposed).
ListenBrainzTokenCheck
¶
Bases: BaseModel
Presence of the ListenBrainz user token (value never exposed).
EnrichmentDiscovery
¶
Bases: BaseModel
Enrichment-provider readiness. Either provider's credentials suffice.
SonicVectorsDiscovery
¶
Bases: BaseModel
Locally persisted Plex sonic vectors (imported via import_plex_sonic).
FirstRunStatus
¶
Bases: BaseModel
Derived first-run state and the reasons it is considered a first run.
DiscoveryResult
¶
Bases: BaseModel
Complete, read-only picture of the local MusicSeed environment.
read_plex_token(preferences_path=None, local_admin_token_path=None)
¶
Read a usable Plex token from the local server's data directory.
Prefers the account token (PlexOnlineToken in Preferences.xml),
then falls back to .LocalAdminToken (which only works from localhost).
When no explicit paths are given, probes macOS and Linux Plex data dirs.
Returns None when neither is readable. Read-only — the token value is
never logged or returned in discovery results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
preferences_path
|
str | None
|
explicit path to |
None
|
local_admin_token_path
|
str | None
|
explicit path to |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
The token string, or None when no readable token is found. |
discover(*, musicseed_db_path=None, plex_db_path=None, plex_db_ssh=None, plex_url=None, plex_token=None, plex_library=None, check_server=True, timeout=5.0, config=None)
¶
Probe the local MusicSeed/Plex environment (read-only).
Never raises on expected failures — they are reported via reason
codes instead.
Overrides apply to this call only and never mutate global configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
musicseed_db_path
|
str | None
|
override for the MusicSeed database path. |
None
|
plex_db_path
|
str | None
|
override for the Plex library database path. |
None
|
plex_db_ssh
|
str | None
|
override for the scp-style SSH target (remote Plex). |
None
|
plex_url
|
str | None
|
override for the Plex server URL. |
None
|
plex_token
|
str | None
|
override for the Plex token. |
None
|
plex_library
|
str | None
|
override for the Plex library name. |
None
|
check_server
|
bool
|
whether to probe the Plex HTTP API (False skips the
network call and reports the server check as |
True
|
timeout
|
float
|
seconds before the Plex HTTP probe gives up. |
5.0
|
config
|
Config | None
|
explicit config to probe against; defaults to the global
|
None
|
Returns:
| Type | Description |
|---|---|
DiscoveryResult
|
The complete discovery result, including per-check |
DiscoveryResult
|
enrichment readiness, missing inputs, the count of locally stored |
DiscoveryResult
|
sonic vectors, and the derived first-run status. The Plex blobs |
DiscoveryResult
|
database is reported but no longer blocks |
DiscoveryResult
|
to import sonic vectors). The Plex token is never included. |
Plex Discovery¶
musicseed.services.plex_discovery
¶
Plex server discovery — local network (GDM + SSDP) and account (plex.tv).
Two independent, read-only discovery paths feed one result list:
- Local network — GDM ("Good Day Mate") multicast on
239.0.0.250:32414with an SSDP fallback on239.255.255.250:1900. Both use stdlibsocketonly and find servers on the same subnet (multicast never crosses a router). GDM replies carry the friendly name, port, product, version, and machine identifier. - Account —
plex.tv/api/resourceslists every server linked to the user's Plex account, including servers on other subnets that multicast cannot reach. Requires a Plex token and internet access.
Both paths are strictly read-only and never store or return a token.
This is a deliberately separate, opt-in probe — it is not folded into
services.discovery.discover(), which runs on frequent dashboard polls and
must stay cheap. The first-run wizard calls it once.
DiscoveredPlexServer
¶
Bases: BaseModel
One Plex server discovered on the local network or the Plex account.
url
property
¶
The server's base URL (scheme://host:port).
discover_plex_account_servers(token, timeout=5.0)
¶
Discover servers linked to the Plex account via plex.tv/api/resources.
Requires a Plex token and internet access. Returns an empty list (never raises) when the token is missing or the call fails — e.g. offline, invalid token, or no servers on the account.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Plex account token used to authenticate against plex.tv. |
required |
timeout
|
float
|
HTTP timeout in seconds for the plex.tv request. |
5.0
|
Returns:
| Type | Description |
|---|---|
list[DiscoveredPlexServer]
|
One entry per best-ranked connection of each account server. |
discover_plex_servers(timeout=3.0, token=None)
¶
Discover Plex servers — local subnet via GDM/SSDP, plus the account.
With token set, also queries plex.tv/api/resources so servers on
other subnets (invisible to multicast) are included. Results are deduplicated
by address. Returns an empty list when nothing responds; never raises.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
timeout
|
float
|
seconds to listen for local multicast replies; also the
plex.tv request timeout when |
3.0
|
token
|
str | None
|
optional Plex account token enabling cross-subnet discovery. |
None
|
Returns:
| Type | Description |
|---|---|
list[DiscoveredPlexServer]
|
Discovered servers sorted by |
Populate¶
musicseed.services.populate
¶
Populate service — fill an existing Plex playlist with complementary recommendations.
PopulateResult
¶
Bases: BaseModel
Result of a populate preview request.
PopulateApplyResult
¶
list_plex_playlists(context=None)
¶
Return every audio playlist currently on the Plex server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
list[Playlist]
|
All audio playlists on the server. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
PlexAPIError
|
if the Plex API call fails. |
get_populate_recommendations(playlist_id, *, method='average', limit=10, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, context=None)
¶
Preview complementary recommendations for an existing Plex playlist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
playlist_id
|
str
|
Plex rating key of the playlist to populate. |
required |
method
|
PopulateMethod
|
populate strategy (see |
'average'
|
limit
|
int
|
maximum number of recommendations to return. |
10
|
per_seed_limit
|
int
|
candidates gathered per playlist track ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
PopulateResult
|
The playlist identity, how many of its tracks matched the local |
PopulateResult
|
library, and the previewed recommendations. Nothing is written to |
PopulateResult
|
Plex. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the playlist doesn't exist or none of its tracks are in the local library. |
PlexAPIError
|
if the Plex API call fails. |
populate_playlist(playlist_id, *, method='average', limit=10, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None, track_ids=None, context=None)
¶
Generate recommendations and add them to an existing Plex playlist.
When track_ids is provided, only those local track ids are added (the
recommendation step is skipped). This supports surfaces that let a user
prune a preview before confirming.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
playlist_id
|
str
|
Plex rating key of the playlist to populate. |
required |
method
|
PopulateMethod
|
populate strategy (see |
'average'
|
limit
|
int
|
maximum number of recommendations to add. |
10
|
per_seed_limit
|
int
|
candidates gathered per playlist track ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
track_ids
|
list[int] | None
|
explicit local track ids to add instead of recommending. |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
PopulateApplyResult
|
The playlist identity, match counts, the recommendations (empty when |
PopulateApplyResult
|
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the playlist or its local tracks cannot be resolved. |
PlexAPIError
|
if the Plex API call fails. |
Plex Analysis¶
musicseed.services.plex_analysis
¶
Plex sonic analysis service — inspect and trigger Plex's own sonic analysis.
Keeps Plex's own sonic analysis up to date so MusicSeed can import fresh vectors
into its local store (see musicseed.sonic):
- Which tracks has Plex already analyzed sonically (
musicAnalysisVersion)? - Trigger the
MusicAnalysisButler task on demand (POST /butler/…) and watch a date-scoped window of recently added music until it is analyzed.
Note: the Butler task always processes Plex's entire pending backlog; the date window scopes what we target, watch, and report — not what Plex runs.
UnanalyzedAlbum
¶
Bases: BaseModel
An album that has at least one track without Plex sonic analysis.
SonicStatusResult
¶
SonicTriggerProbeResult
¶
Bases: BaseModel
Outcome of triggering Plex analysis on one album and re-checking it.
sonic_triggered
property
¶
True when the analyzed-track count increased after the trigger.
SonicRefreshResult
¶
get_sonic_status(library_name=None, *, recent_days=7, context=None)
¶
Report Plex sonic analysis coverage for a music library.
Fetches every track in the library over the HTTP API, so it can be slow on
large libraries. recent_days defines the "recent additions" window
(based on Plex's addedAt).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_name
|
str | None
|
Plex music library to inspect; defaults to the
configured |
None
|
recent_days
|
int
|
size of the "recent additions" window in days. |
7
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
SonicStatusResult
|
Coverage counts for the whole library and the recent window, plus the |
SonicStatusResult
|
albums that still have unanalyzed tracks. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the library name doesn't match a music section. |
PlexAPIError
|
if the Plex API call fails. |
refresh_album(album_rating_key, context=None)
¶
Ask Plex to refresh one album's metadata (re-reads its files from disk).
This recreates the album's media items, which clears the failed-analysis state that prevents sonic analysis from being queued.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str
|
Plex rating key of the album to refresh. |
required |
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
PlexAPIError
|
if the Plex API call fails. |
probe_sonic_trigger(album_rating_key=None, library_name=None, *, wait_seconds=120.0, poll_interval=5.0, context=None)
¶
Trigger Plex analysis on one album and check if sonic analysis follows.
If album_rating_key is None, the most recently added album with
unanalyzed tracks is picked automatically. Polls the album's tracks until
the number of sonically analyzed tracks increases or wait_seconds
elapses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str | None
|
Plex rating key of the album to analyze and watch; auto-picked when None. |
None
|
library_name
|
str | None
|
library used for the auto-pick; defaults to the
configured |
None
|
wait_seconds
|
float
|
maximum time to watch for analysis. |
120.0
|
poll_interval
|
float
|
seconds between polls. |
5.0
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
SonicTriggerProbeResult
|
The probe outcome, including analyzed counts before/after and the |
SonicTriggerProbeResult
|
Plex activities observed while waiting. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if no unanalyzed album can be found. |
PlexAPIError
|
if the Plex API call fails. |
probe_butler_trigger(album_rating_key=None, library_name=None, *, butler_task='MusicAnalysis', wait_seconds=120.0, poll_interval=5.0, context=None)
¶
Trigger a Plex Butler task and check if sonic analysis follows.
Unlike per-item analyze, the MusicAnalysis Butler task works
through every album pending sonic analysis on the server, which can be
CPU-heavy and long-running — it keeps going after this probe returns.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
album_rating_key
|
str | None
|
Plex rating key of the album to watch; auto-picked when None. |
None
|
library_name
|
str | None
|
library used for the auto-pick; defaults to the
configured |
None
|
butler_task
|
str
|
name of the Butler task to run. |
'MusicAnalysis'
|
wait_seconds
|
float
|
maximum time to watch for analysis. |
120.0
|
poll_interval
|
float
|
seconds between polls. |
5.0
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
SonicTriggerProbeResult
|
The probe outcome for the watched album, including analyzed counts |
SonicTriggerProbeResult
|
before/after and the Plex activities observed while waiting. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if no unanalyzed album can be found. |
PlexAPIError
|
if the Plex API call fails. |
refresh_sonic_analysis(library_name=None, *, days=7, wait_seconds=900.0, poll_interval=15.0, stall_after=4, on_poll=None, context=None)
¶
Refresh sonic analysis for music added in the last days days.
Triggers the Plex MusicAnalysis Butler task and watches the window's
pending track count until it reaches zero, stalls (no progress across
stall_after consecutive polls), or wait_seconds elapses. The Butler
task keeps running on the server after this function returns.
on_poll(pending_now, pending_before) is called after each poll so a
surface can render progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
library_name
|
str | None
|
Plex music library to refresh; defaults to the
configured |
None
|
days
|
int
|
size of the "recent additions" window in days. |
7
|
wait_seconds
|
float
|
maximum time to watch the refresh. |
900.0
|
poll_interval
|
float
|
seconds between polls. |
15.0
|
stall_after
|
int
|
consecutive polls without progress before giving up. |
4
|
on_poll
|
Callable[[int, int], None] | None
|
optional |
None
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
SonicRefreshResult
|
The refresh outcome, including pending counts before/after, whether a |
SonicRefreshResult
|
stall was detected, and the albums still pending in the window. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
if plex.token is not configured. |
NotFoundError
|
if the library name doesn't match a music section. |
PlexAPIError
|
if the Plex API call fails. |
Local sonic vector import¶
musicseed.services.sonic_vectors
¶
Sonic vector persistence — import Plex vectors into the local database.
MusicSeed reads Plex sonic-analysis vectors from Plex's blobs database and
persists them into the local track_vectors table so recommendations no
longer depend on that file at query time. The blobs database is only read by
import_plex_sonic; scoring reads the local store via the context.
SonicVectorImportResult
¶
Bases: BaseModel
Counts from one sonic-vector import run.
import_plex_sonic(context=None, progress_callback=None, should_cancel=None, *, batch_size=500)
¶
Import Plex sonic-analysis vectors into the local database.
Reads the Plex blobs database once (via sonic.load_sonic_vectors) and
upserts every vector into track_vectors keyed by plex_id. The
operation is idempotent: existing rows are refreshed in place and new rows
are inserted, so re-running after Plex analyzes more tracks is safe. After
writing, the context's cached vectors are dropped so the next scoring run
reads the fresh store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
progress_callback
|
Callable[[int, int, str], None] | None
|
optional callback after each committed batch. |
None
|
should_cancel
|
Callable[[], bool] | None
|
polled before source reads and between committed batches. Source backup/decoding must finish before cancellation is checked again. |
None
|
batch_size
|
int
|
maximum vector upserts per committed transaction. |
500
|
Returns:
| Type | Description |
|---|---|
SonicVectorImportResult
|
The total number of vectors found in Plex and how many rows were newly |
SonicVectorImportResult
|
inserted versus updated in place. |
Raises:
| Type | Description |
|---|---|
NotFoundError
|
if the Plex blobs database is unavailable. |
Dashboard¶
musicseed.services.dashboard
¶
Dashboard aggregation service — combines discovery, library stats, and job status into one surface-agnostic snapshot.
DashboardSnapshot
¶
Bases: BaseModel
Combined dashboard view: discovery, library stats, and job state.
ready_for_recommendations
property
¶
True when at least one track has been imported locally.
get_dashboard(check_server=False, context=None)
¶
Aggregate a dashboard snapshot.
check_server gates the live Plex HTTP probe inside discovery. It
defaults to False so frequent dashboard polls never touch Plex; the
caller (a web surface) fetches the full probe separately and less often.
When the library status cannot be read (e.g. no database yet), an empty
LibraryStatus is used instead of failing the whole snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
check_server
|
bool
|
whether to probe the Plex server over HTTP as part of the embedded discovery result. |
False
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
DashboardSnapshot
|
A snapshot with discovery, library status, active and recent jobs, |
DashboardSnapshot
|
and the last successful import ( |
DashboardSnapshot
|
has succeeded yet). |
Jobs¶
musicseed.services.jobs
¶
Persistent, in-process job abstraction for long-running MusicSeed work.
Every operation (create, start, update progress, complete, fail,
cancel-request) writes through a dedicated SQLAlchemy session. The
JobManager singleton runs workers in daemon threads — no external
queue, no Redis, no containers. On first access it reconciles jobs
left in any nonterminal state by a dead process into interrupted.
JobKind
¶
Bases: StrEnum
The kinds of long-running work the job system tracks.
JobState
¶
Bases: StrEnum
Lifecycle states of a job row.
JobManager
¶
In-process runner with a bounded concurrency pool.
Workers are daemon threads. Job state lives in the jobs table (shared
across processes); the in-memory bookkeeping only tracks this process's
threads and concurrency. Cancel is cooperative (should_cancel reads the
DB — workers poll it at safe checkpoints).
__init__(max_concurrent=1)
¶
Create a manager that runs at most max_concurrent jobs at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_concurrent
|
int
|
maximum number of worker threads allowed to be active simultaneously; further submissions are rejected. |
1
|
submit(kind, target, *args, **kwargs)
¶
Create a job and run target for it in a daemon thread.
The target is called as target(job_id, *args, **kwargs) — the job
id is always the first positional argument.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
job kind (see |
required |
target
|
Callable[..., None]
|
blocking callable to run in the worker thread. |
required |
*args
|
Any
|
extra positional arguments forwarded to |
()
|
**kwargs
|
Any
|
keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
int
|
The id of the newly created job row. |
Raises:
| Type | Description |
|---|---|
JobConflictError
|
if a job is already active for this database, or the concurrency pool is full. |
request_cancel(job_id)
¶
Ask a job to stop (cooperative; see request_cancel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job to cancel. |
required |
should_cancel(job_id)
¶
Return True when cancellation has been requested for a job.
Workers poll this at safe checkpoints and then wind down on their own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the job exists and its state is |
shutdown()
¶
Request cancellation of every active job (threads are daemons).
configuration_change()
¶
Serialize config replacement with submissions; never initialize a database.
current_job_id()
¶
The operation-bound job ID, if called from a claimed writer.
exclusive_writer(kind)
¶
Use the same persisted writer claim for synchronous CLI/service imports.
A service called inside a managed worker reuses its claim. There is no separate queue or scheduler; the SQLite job row is the reservation.
create_job(kind)
¶
Insert a new pending job and return its id.
start_job(job_id)
¶
Mark a job running and stamp its start time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
update_progress(job_id, current, total=0, checkpoint='', phases=None)
¶
Record progress for a running job.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
current
|
int
|
units of work completed so far. |
required |
total
|
int
|
total units of work expected (0 when unknown). |
0
|
checkpoint
|
str
|
human-readable status line; only stored when non-empty. |
''
|
phases
|
dict | None
|
per-phase |
None
|
complete_job(job_id, result_summary='')
¶
Mark a job succeeded and stamp its completion time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
result_summary
|
str
|
optional JSON-serialized outcome summary; only stored when non-empty. Managed targets defer completion until they return. |
''
|
fail_job(job_id, error_summary)
¶
Mark a job failed and stamp its completion time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
error_summary
|
str
|
failure description, truncated to 500 characters. |
required |
request_cancel(job_id)
¶
Set a job's state to cancel_requested (cooperative cancellation).
The worker still has to observe the request (via
JobManager.should_cancel) and wind itself down; nothing is interrupted
forcibly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to update. Unknown ids are ignored. |
required |
cancel_job(job_id)
¶
Mark canceled, retaining a managed worker's claim until its target exits.
get_job(job_id)
¶
Return a snapshot of one job, or None when it does not exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
job_id
|
int
|
id of the job row to read. |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
The job's fields as a plain dict, or None for an unknown id. |
delete_job(job_id)
¶
Delete a job row by id. Returns True if a row was deleted.
list_jobs(limit=20)
¶
Return the most recent jobs, newest first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int
|
maximum number of jobs to return. |
20
|
Returns:
| Type | Description |
|---|---|
list[dict]
|
Job snapshots as plain dicts, ordered by creation time descending. |
get_latest_job(kind)
¶
Return the most recent job of a given kind, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
str
|
job kind to filter on (see |
required |
Returns:
| Type | Description |
|---|---|
dict | None
|
The newest matching job snapshot as a plain dict, or None when no |
dict | None
|
job of that kind exists. |
get_active_jobs()
¶
Return pending/running/cancel-requested jobs; all still reserve the writer.
Returns:
| Type | Description |
|---|---|
list[dict]
|
Job snapshots as plain dicts. |
reconcile_running_jobs()
¶
Reconcile every nonterminal state owned by a dead process.
Pending jobs and cancellation requests can also survive a crash. Live owners are left alone, including other API/CLI processes.
get_manager()
¶
Return the module-level JobManager singleton, creating it lazily.
On first access, nonterminal jobs owned by dead processes are reconciled
to interrupted before the manager is returned.
Returns:
| Type | Description |
|---|---|
JobManager
|
The shared |
Import provenance¶
musicseed.services.import_state
¶
Source-specific durable import checkpoints and read-only status queries.
source_key(context)
¶
Identity excludes credentials and includes library, source and Plex server.
snapshot_id(path)
¶
Identify the observed input generation, including local file changes.
read_import_state(context)
¶
Read without initializing/migrating; unknown is not a successful import.
checkpoint(context, state, *, snapshot=None, expected=None, phase=None, processed=None)
¶
Write a checkpoint outside the importer's write transaction.
Typeahead¶
musicseed.services.typeahead
¶
Track typeahead search service — the reusable lookup behind autocomplete.
TypeaheadTrack
¶
Bases: BaseModel
A minimal, JSON-safe view of a track for autocomplete results.
search_tracks(query, exclude_ids=None, limit=10, context=None)
¶
Search tracks by title or artist name for autocomplete.
Returns an empty list when the query is shorter than 2 characters. Results
exclude exclude_ids, are ordered by title, and capped at limit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
substring matched (case-insensitively) against track titles and artist names. |
required |
exclude_ids
|
list[int] | None
|
local track ids to leave out of the results. |
None
|
limit
|
int
|
maximum number of matches to return. |
10
|
context
|
MusicSeedContext | None
|
runtime context to use; defaults to the default context. |
None
|
Returns:
| Type | Description |
|---|---|
list[TypeaheadTrack]
|
Matching tracks as minimal JSON-safe views, ordered by title. |
Offline evaluation¶
musicseed.services.evaluation
¶
Deterministic offline recommendation evaluation using synthetic temporary libraries.
This is a developer diagnostic, not a listening-quality benchmark. It never loads the owner's config, database, Plex server, or enrichment credentials.
FixtureTrack
¶
Bases: BaseModel
Public synthetic facts only; no private library exports or file paths.
EvaluationRequest
¶
Bases: BaseModel
Scoring, eligibility and selection controls recorded alongside the metrics.
EvaluationCase
¶
Bases: EvaluationRequest
Synthetic library facts and the complete recommendation request to evaluate.
StrategyMetrics
¶
Bases: BaseModel
Constraint, fill, overlap and evidence metrics under one strategy's objective.
CaseEvaluation
¶
Bases: BaseModel
Three strategy results and a bounded-retrieval diagnostic for one fixture.
EvaluationReport
¶
Bases: BaseModel
Reproducible synthetic results; intentionally not a musical-preference verdict.
evaluation_cases(seed=7)
¶
Nine versioned synthetic cases, reproducible from a nonnegative RNG seed.
evaluate_recommendations(*, seed=7, case_names=None)
¶
Evaluate disposable synthetic libraries and return deterministic JSON-safe metrics.
Timestamps, elapsed times and temporary paths are intentionally absent. Retrieval gaps are observations, not safety-invariant failures. Native scores use different objectives across strategies and are not preference ratings. No owner library/context argument is accepted by this diagnostic.