Skip to content

Feature/playlist curator port#417

Open
rendyhd wants to merge 22 commits intoNeptuneHub:mainfrom
rendyhd:feature/playlist-curator-port
Open

Feature/playlist curator port#417
rendyhd wants to merge 22 commits intoNeptuneHub:mainfrom
rendyhd:feature/playlist-curator-port

Conversation

@rendyhd
Copy link
Copy Markdown
Contributor

@rendyhd rendyhd commented Apr 12, 2026

Adds the Playlist Curator page — a tool for building and extending playlists using embedding-based similarity search, smart filters, and weighted track influence.

Core Features

  • Smart Search: Filter tracks by genre, mood/style, artist, album, BPM, energy, key, scale, year, and rating. Genre and mood filters use a dropdown + threshold slider for score-aware matching against AI-classified confidence values
  • Playlist Extender: Load an existing playlist from the media server (Jellyfin, Emby, Navidrome, Lyrion) and find similar songs using a weighted centroid of track embeddings
  • Duplicate Detection: Extend results are annotated with a warning badge when they are embedding-near-duplicates of source tracks (e.g. same song on a compilation).
  • Sensitivity slider adjusts instantly client-side without re-querying. Separate from the in-playlist "Find Duplicates" tool that scans tracks against each other
  • Influence-Based Weights: Four named levels (x1, Boost ~5%, Strong ~15%, Focus ~30%) whose actual weight scales with playlist size — a "Boost" on a 20-song playlist ≈ x2, on a 2000-song playlist ≈ x105
  • Save to Server: Save curated playlists back to the configured media server

Architecture

  • Backend blueprint (app_playlist_curator.py) with centroid computation, filter query builder, duplicate annotation, and media server integration
  • Frontend template with tabbed UI, source track drawer with weight controls, inline audio preview, and deduplication panel
afbeelding afbeelding

rendyhd and others added 8 commits April 10, 2026 22:19
Port the Playlist Curator backend from multi-provider-gui to main.
Adapts track_id (INTEGER) to item_id (TEXT), strips multi-provider
concerns (provider_track joins, fan-out save, app_setup imports),
and adds single-server helpers for /server_playlists and
/server_playlist_tracks that branch on config.MEDIASERVER_TYPE.
MPD is intentionally unsupported (returns 501).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port the Playlist Curator template from multi-provider-gui, stripping
provider picker UI, provider-selector container, and provider-selector.js
script tag. Replace loadProviderPlaylists/loadProviderPlaylistTracks with
single-server loadServerPlaylists/loadServerPlaylistTracks that auto-load
on Playlist Extender tab activation. Stream URLs use encodeURIComponent
to handle item_ids with special chars. Save payload drops provider_ids.
Find-duplicates no longer parseInt the item_ids.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When extending a playlist, results that are embedding-near-duplicates of
source tracks are now annotated with a warning badge showing the cosine
distance and which source track they match. Users can adjust sensitivity
and optionally hide flagged results — useful for compilations where the
same song appears on multiple albums.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ams grid

Move duplicate detection controls inside the 2-column grid as a
full-width row with centered layout, subtle border-top separator,
info tooltip, and fixed-width sensitivity slider.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Default threshold 0.010, slider range 0.005-0.050 based on real usage
- Tooltips updated to reflect actual false positive rates
- Sensitivity slider and toggles now react instantly (client-side filter)
- Backend always returns duplicates up to slider max for instant re-render
- Fix vertical centering of Include/Exclude buttons (flex off td, into div)
- Distance column narrowed and right-aligned
- Album column truncates with ellipsis on overflow
- Duplicate badge shows source track name, truncates on overflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… UX fixes

Weights:
- Replace fixed power-of-2 multipliers with influence-based levels
  (x1, Boost ~5%, Strong ~15%, Focus ~30%) that scale with playlist size
- Color-coded buttons (gray/blue/purple/gold) with hover tooltips

Filters:
- Genre and Mood/Style now use dropdown + threshold slider (default 0.55)
- Backend extracts scores numerically via SQL UNNEST for accurate filtering
- Fix features query to return unique labels instead of every label:score pair
- Merge redundant Genre/Mood dropdown into single Genre field

UX:
- Recalc spinner next to "Search Results" title during weight changes
- Weight changes no longer show disruptive "Finding similar songs..." status
- Flag-duplicates checkbox toggle re-renders results instantly
- Clear stale results on search error (e.g. "No songs found")
- Year shown in Smart Search album column
- Recalc skips status/button disruption entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The playlist dropdown's change handler was clearing smartFilterSourceIds
on every selection change, including when selecting the smart filter
option itself. Now restores IDs from cached smartFilterResults when
the smart filter option is selected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rendyhd
Copy link
Copy Markdown
Contributor Author

rendyhd commented Apr 13, 2026

Code review

Found 1 issue:

  1. When the "Flag Near-Duplicates" checkbox is unchecked, the frontend sends duplicate_threshold: 999 as a sentinel for "disabled." The backend clamps this to max(0.005, min(999, 0.3)) = 0.3, the most aggressive setting. This means the O(N*M) cosine distance loop over all results and source vectors still runs on every search even when the feature is off. The output looks correct because client-side filtering hides the annotations, but the backend silently performs maximum-breadth duplicate detection for no reason. Either the backend should check for out-of-range values and skip the annotation loop, or the frontend should send a dedicated flag_duplicates: false flag.

try:
duplicate_threshold = max(0.005, min(float(payload.get('duplicate_threshold', 0.01)), 0.3))
except (TypeError, ValueError):
duplicate_threshold = 0.01

if source_vectors:
source_meta_map = {m['item_id']: m for m in source_tracks_meta}
for result in filtered_results:
cand_vec = get_vector_by_id(result['item_id'])
if cand_vec is None:
continue
v_cand = np.array(cand_vec, dtype=np.float32)
norm_cand = np.linalg.norm(v_cand)
if norm_cand > 0:
v_cand = v_cand / norm_cand
best_dist = float('inf')
best_source_id = None
for sid, v_src in source_vectors.items():
cosine = np.clip(np.dot(v_src, v_cand), -1.0, 1.0)
dist = float(1.0 - cosine)
if dist < best_dist:
best_dist = dist
best_source_id = sid
if best_dist < duplicate_threshold and best_source_id is not None:
src_meta = source_meta_map.get(best_source_id, {})
result['duplicate_of'] = {
'item_id': best_source_id,
'title': src_meta.get('title'),
'author': src_meta.get('author'),
'album': src_meta.get('album'),
'distance': round(best_dist, 4)
}

const flagDuplicates = document.getElementById('flag-duplicates').checked;
// Always request all potential duplicates up to slider max so the slider can filter client-side
const dupSliderMax = parseFloat(document.getElementById('dup-threshold-slider').max);
const dupThreshold = flagDuplicates ? dupSliderMax : 999;

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

rendyhd and others added 3 commits April 13, 2026 10:57
…edup keep toggle

When "Flag Near-Duplicates" is unchecked, the backend now skips the
O(N×M) cosine distance loop entirely instead of silently running it at
maximum sensitivity. The dedup keep toggle also now correctly allows
un-keeping the default first track once another track has been marked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NeptuneHub
Copy link
Copy Markdown
Owner

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Playlist Curator feature, allowing users to search for tracks using smart filters and extend playlists by finding similar tracks based on weighted embedding centroids. It integrates with various media servers for playlist management and includes audio-similarity-based duplicate detection. Feedback points out a security vulnerability where media server credentials are exposed in redirect URLs for streaming, recommending a proxying approach. Additionally, a performance optimization was suggested to limit the number of results returned by broad filter queries.

Comment thread app_playlist_curator.py
Comment thread app_playlist_curator.py
@NeptuneHub
Copy link
Copy Markdown
Owner

I did a first round of test and here some feedback:

  • There is still the error of LF that bring it to don't compile but probably you're still working on this fix;
  • The stream directly from the browser to the music server is not ok for this reason:
    • Security issue: as Gemini highlighted, because you have a password in the url. If we do multiple user in the future you run the risk also to show it to user not needed. But also a total external attacker could use it
    • Network: User with the browser need only to reach AudioMuse-AI till now, not the music server. If you add this you add an additional constraints. I know that most probably an user that reach AudioMuse-AI frontend probably will reach also the Music Server, but better not add not needed constrainsts. So better that AudioMuse-Ai backend proxy the stream without showing credential on the browser
  • I'm not 100% sure to have a play button in the browser. If we do here, we need to do in ALL the functionality.
    • This means that you need to create a centrilized result object, that have the play button and the other table. Possible also the play/stop/forward/backward tab, and you need to re-use this same object everywhere without duplicating code. This object should be configurable for number of column because some functionality return different value
  • Graphically have some small difect
    • When you play pause, the reproduction bar become a bit smaller
    • It doesn't fit a smartphone browser or any small browser, when you shrink the windows most things go out of the screen instead of a next line
    • Same configuration are not clear: In playlist extender, what the weight button does? Why you hate 2 checkbox one that say hide flagged and the other flag dupplicates?
    • Find song / search button don't have the right css for the button apply
    • The save playlist tab in all the functionality of AudioMuse-AI is on the bottom, here is on the top
  • In smart search mood (and other) filter can return thousands of song => you need to limit to the top one, maybe 100, 200 at most.. not thousands

In short I like the functionality but the interface need maybe some parameter less to being more easy to understand/usabke AND if we had play button here we need to add everywhere.

@rendyhd
Copy link
Copy Markdown
Contributor Author

rendyhd commented Apr 14, 2026

Let me look into proxying to audio stream.

My thoughts:

  • The LF error isn't merged to main yet, so not in this one yet.
  • Play button is here vs other places because the design and purpose of this section is to manually curate a playlist. When manually creating a playlist it's important to hear the track to decide. It's a different philosophy - not just generate a playlist, but manually curate. You select and choose what to include and exclude. If you want it automatically you can just hit radio mix. That's why it's not meant to be a full media player, but just a feature for you to preview a track so you can decide if you want it in your curated playlist.
  • Weight button allows you to add weight to a track in the playlist, on hover it explains it. E.g. you have a playlist with many songs, not every song is equal, if you want to get more recommendations in a certain direction you can add a higher weight to those songs and it will shift the recommendations.
  • In the filter section, Flag duplicates flags duplicates in the result, you can choose to hide them or only flag them to choose on potential false positives yourself. I notice without this there are many duplicates of tracks that are popular across compilations. But sometimes you want to also add the live version - so flag shows you the warning and to which track it duplicates, hide hides them.
  • When loading a playlist or using smart filter, you can use find duplicates. Here it finds the duplicates in your existing playlist (or selection), opens an additional UI and you can inspect the groups and choose to delete or delete all. E.g. I have a CD series of 20 years of compilations plus a few specials that have Top 100's, there are many duplicates in this (300 out of 1700 results).
  • I put the save button up top because the search results can get very long, especially in the smart filter.
  • There is no cap on purpose, I really wouldn't want only 100 or 200 results - that would defeat the purpose of the smart filter, I want to see all songs that meet the criteria. I have playlists that are 1000+ songs. Smart search doesn't need to calculate, just query.

I see this as an advanced page for a more elaborate workflow. It's to manually curate, spend a long time choosing, making decisions, and tweaking your results. It's different from the other features that are meant to be quicker and fast exploration. I have already spend hours on just using it (not the development part), to tweak some old playlists of mine, that I got stuck on, finally be able to shape them further. In the future I would like to see a few more tools for playlist management, e.g. to really analyze and explore existing ones and see track outliers (maybe using the map feature)

rendyhd and others added 9 commits April 14, 2026 14:03
The stream_track endpoint previously issued a 302 redirect to the media
server URL, leaking api_key / Navidrome password in the browser address
bar, history, and logs. Now the backend fetches the stream server-side
and pipes it to the client with Range forwarded so <audio> seek still
works. Adds item_id allowlist to block SSRF, a 60s read timeout to
prevent stalled upstreams from pinning workers, call_on_close for
reliable connection cleanup, and a narrow RequestException handler so
credential-bearing URLs never land in tracebacks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…earch button

- Progress bar no longer shrinks on play/pause: time labels get a fixed
  min-width with tabular-nums so the seek bar keeps a stable width as
  current-time/duration text grows.
- Added @media breakpoints (768px, 480px) so the page works on phones:
  sticky player wraps, extend-params grid collapses to one column,
  results table gains horizontal scroll, hardcoded min-widths drop.
- Replaced the confusing "Flag duplicates" + "Hide flagged" checkbox
  pair with a single Off/Mark/Hide selector and an info tooltip that
  explicitly distinguishes it from the "Find Duplicates in Playlist"
  scan (which was also relabeled for clarity).
- Renamed the "Weight" column header to "Influence" with a tooltip
  explaining the x1/Boost/Strong/Focus cycle.
- Gave the Smart-Filters Search button its own styled class so it
  visually pairs with the Send-to-Extend button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ooltip

- Stop "Search-result duplicates:" label wrapping and the <select>
  stretching full-width: override the global select{width:100%} rule
  with width:auto and flex:0 0 auto, plus white-space:nowrap on the
  label so it stays on one line.
- Swap the Influence column's custom info-tooltip for a native title
  attribute. The fancy tooltip was being clipped by the table cell's
  overflow rules no matter how I positioned it; the native tooltip is
  unclipped and predictable. Column width reduced back to 90px since
  the info icon is no longer needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rewrote the curator page description to introduce the two tabs
  (Smart Search vs Playlist Extender) and how they relate, in place of
  the previous generic line.
- Renamed Playlist Extender's "Exclude" button to "Avoid" (and the
  EXCLUDED status badge to AVOIDED) to distinguish it from Smart
  Search's hide-only Exclude — Avoid also actively steers the
  similarity search away from the track. Renamed influence level
  "x1" to "Normal" and the source-drawer column "Weight" to
  "Influence" so it matches the main results table.
- Added discoverable info-tooltip widgets (the blue "i" icon used in
  the rest of the form) to the Influence and Actions column headers
  in Extender mode. The previous native title= attribute was an
  invisible affordance; the icon advertises that hover info exists.
  Also added per-button title= tooltips to Include / Avoid / Smart-
  Search Exclude explaining their steering effect on recommendations.
- Tooltip text cleanup: replaced em dashes with colons across the
  influence-level and search-result-duplicate tooltips; tightened
  Mark/Hide copy and dropped the stale cross-reference from the dup
  sensitivity tooltip.
- Tooltip CSS: added white-space: pre-line so authored line breaks
  render; let table headers overflow visible so the icon and popup
  aren't clipped; flipped header tooltips to show below the icon; and
  right-anchored the rightmost-column tooltip so it doesn't overflow
  the page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These are served from inside the Linux container alongside the .py /
.sh / .conf files already covered, but were missing from the LF rule.
Without it, editing them on Windows quietly converts working-tree LF
back to CRLF, which floods diffs with whole-file noise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rendyhd
Copy link
Copy Markdown
Contributor Author

rendyhd commented Apr 15, 2026

  • Added a proxy audio stream
  • Fixed progress bar, small display
  • Optimized UI for search-result duplicates
  • Renamed weight to influence, changed 1x to normal
  • Polished descriptions, added tooltips for all buttons, added info-widgets for influence and actions.

@rendyhd
Copy link
Copy Markdown
Contributor Author

rendyhd commented Apr 15, 2026

@NeptuneHub ready for review

Rapid weight changes could race: a slow in-flight fetch could overwrite
fresher results after landing second. Wire an AbortController through
findSimilarSongs so a new request aborts the previous one, and ignore
AbortError in the catch/finally so the UI doesn't flash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ballfather
Copy link
Copy Markdown
Contributor

Hi there, my (humble) review:

I've only looked at the changes to the existing code, i have not looked at any of the new files, so this is only an adjacent review. I just want to check what changed merging this branch would have on the whole project.

In that respect it's very good. Minimal impact to the rest of the project and no risk of those changes being hard to integrate for someone rebasing their git branches against this changeset. 👍

Copy link
Copy Markdown
Owner

@NeptuneHub NeptuneHub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
here some point:

  1. you still leve the possibility of return any possible number of result, with a mood/style filter I was able to return 14k songs that was near to crash the browser frozying it for some seconds. If you still want to return everything without max add a way to get the result in a paginated way in both backend and frotnend html page.
  2. The play/pause button, on Chrome (windows) still slight change dimension when clicked.
  3. The feature is not mobile friendly, when you resize in a small windows things need to go to the next line not go out of the screen: I know that this is more a desktop feature but still we need to keep the style usable.
  4. When you click to send to playlist extender, and you have a big number of song (in the test 5000) it tooks around 1 minutes to calculate. This is a direct issue of the point 1 when we didn't set a max, we need to think about this. 1 minutes on a i7 14th gen means several minutes on low end computer hope you didn't crash
  5. Still don't understand what influence means, can you explain better and then do in a way that the user can understand from the UI?
  6. In playlist extender search song button still don't have the right button css applied
  7. With high number of song (tested with 5000) everything become unresponsive like go from one tab to the other, or change some filter. Another time some kind of "max" or other workearound is needed.
  8. You have in one tab "Search-result duplicates:". You hit search and in another tab you have find duplicate in playlist. If the functionality is "remove the duplicate" try to create a tab named in this way with all the functionality grouped together
  9. In the search table the button avoid I suppose is a way to remove from the final playlist that you go to save, but Include? shouldn't be all included by default? or at least can we add an include all / exclude all with maybe all included by default? otherwise the user need to do a lot of click here.

In general I like the functionality I find it useful, but the perception is of complexity: can you find a way to improve the UI ?

@Chaphasilor
Copy link
Copy Markdown

@rendyhd does this PR address #187? If so, Neptune should probably link it.
While I like the smart filters to create a playlist, and then using that created playlist as the source playlist for the extender, I feel like this is vastly out-of-scope for this feature.
Having some kind of smart filter system that brings together multiple existing functions like moods, bpm, CLAP, etc. is a nice feature, but in my opinion unrelated to a playlist extender/curator.
So maybe consider re-scoping this to only include the actual curator that suggests fitting tracks for a given existing playlist? And at some point, maybe a "playlist creator" could be added as a separate feature that can integrate with this.

I'm also wondering if the source playlist panel and the search result panel should be side-by-side instead of below each other, or maybe if the search results could be minimized. At the moment the playlist can only be seen 5 tracks at a time, which seems rather limiting.

Also, is there some kind of confirmation (or even better, undo) for removing tracks from the source playlist?

@NeptuneHub NeptuneHub linked an issue Apr 16, 2026 that may be closed by this pull request
@rendyhd
Copy link
Copy Markdown
Contributor Author

rendyhd commented Apr 16, 2026

@rendyhd does this PR address #187? If so, Neptune should probably link it. While I like the smart filters to create a playlist, and then using that created playlist as the source playlist for the extender, I feel like this is vastly out-of-scope for this feature. Having some kind of smart filter system that brings together multiple existing functions like moods, bpm, CLAP, etc. is a nice feature, but in my opinion unrelated to a playlist extender/curator. So maybe consider re-scoping this to only include the actual curator that suggests fitting tracks for a given existing playlist? And at some point, maybe a "playlist creator" could be added as a separate feature that can integrate with this.

I'm also wondering if the source playlist panel and the search result panel should be side-by-side instead of below each other, or maybe if the search results could be minimized. At the moment the playlist can only be seen 5 tracks at a time, which seems rather limiting.

Also, is there some kind of confirmation (or even better, undo) for removing tracks from the source playlist?

Hi there!
To the question if it's out of scope, not for the feature I envision. When I first made the suggestion I was still think of making the playlist somewhere else and then loading it into AudioMuse-AI to extend, but during my initial drafts in November I already realized that didn't make sense and added it here - same two tabs. It doesn't make sense because it breaks your workflow and AudioMuse-AI can do it better (more variables to filter on and a superior dedupe). That's also when I renamed it from Playlist Extender to Playlist Curator.

Over the last months I've been actively using and improving on it, the two features Smart Search and Playlist Extender are designed to be used next to each other. You can of course use either separate, but the true fun is when you spend some time with them together. It opens so many doors to explore; whether it's expanding on compilation series, artist combinations, or any discover through any of variables AudioMuseAI extracts. I've gotten some real gems. And then once you have that base, you explore further in the Playlist Extender. For me, the combination of the two tabs is what makes the Curator, and allows me to rediscover my library in unique ways.

There is an important difference with other features, this is not meant to give you a recommended playlist fast, it's to manually curate and discover. I can honestly spend hours playing around in it.

That doesn't mean it's in scope for AudioMuseAI or a fit for the product, I'm just saying that's how I use and enjoy it. I'm opening to changing the name, but cutting half it out wouldn't make sense to me. But at the end of the day it's not up to me.

I do like your UI suggestion and way to see removed items. I'll look into those!

@Chaphasilor
Copy link
Copy Markdown

What I wanted to say is that these features probably shouldn't share a UI. I get that AudioMuse can be great for creating playlists, but creating a playlist and augmenting a playlist seems like two separate use cases to me, that deserve their own page. You use one after the other, if you use the creator at all.
Creating the playlist first and then moving on to the fine-tuning should add almost no friction. There could even be a button at the end of the playlist creator that opens the curator with the new playlist pre-selected.

All of this is just to make the UI more manageable and approachable, and to make the feature less specific to your own use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Expand Playlist

4 participants