Skip to content

MusicMagic: add genre exclusion filter for MusicIP mixes - #1612

Merged
michaelherger merged 1 commit into
LMS-Community:public/9.2from
jonstahl:feature/musicmagic-genre-filter
Jul 26, 2026
Merged

MusicMagic: add genre exclusion filter for MusicIP mixes#1612
michaelherger merged 1 commit into
LMS-Community:public/9.2from
jonstahl:feature/musicmagic-genre-filter

Conversation

@jonstahl

Copy link
Copy Markdown
Contributor

Summary

  • Adds an "Excluded Genres" multi-select (global and per-player) that filters MusicIP mix results against the genres already in your local Lyrion library, so you can keep specific genres out of any mix regardless of seed.
  • Since MusicIP has already picked the mix before this exclusion is applied locally, the mix request now asks for extra tracks up front and trims back to the configured size, so excluding genres doesn't leave you with a short mix.
  • Adds full translations for the new setting strings (CS/DA/DE/ES/FI/FR/HE/HU/IT/NL/NO/PL/RU/SV/ZH_CN).

Why not the existing "Mix Filter" setting?

MusicMagic already has a "Mix Filter" option, but it doesn't cover this case:

  • It applies a single named filter that has to be predefined inside the standalone MusicIP Mixer desktop app itself (fetched via MusicIP's /filters API) — there's no way to build or edit one from within Lyrion, and it's one filter at a time, not an arbitrary combinable set of genres.
  • MusicIP also supports inline "Power Search" filter expressions (e.g. ?genre==Rock) that could in principle express a genre condition, but Power Search is an advanced MusicIP feature gated behind an active MusicIP registration/license key, so it's unavailable to a chunk of users and would fail silently for them.
  • Genre matching against MusicIP's own tag data also isn't guaranteed to line up with how a track's genre(s) are recorded in the local library.

This feature instead reads genres directly from the local library (the same list you already browse/see in Lyrion), needs no MusicIP-side setup or paid registration, and supports selecting any number of genres to exclude.

Test plan

  • Verified settings save/load correctly for both the global and per-player MusicMagic settings pages
  • Verified excluded genres are correctly dropped from mix results
  • Verified mixes are topped back up to the configured size after exclusion
  • Manually tested end-to-end against a running lmscommunity/lyrionmusicserver Docker instance

🤖 Generated with Claude Code

Lets users select one or more local library genres to exclude from
MusicIP mix results, configurable globally and per-player. Since
exclusion happens after MusicIP has already picked the mix, request
extra tracks up front and trim back to the configured size so
exclusions don't leave mixes short.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Jon Stahl <jonstahl@gmail.com>

@michaelherger michaelherger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Never mind my comment... Consider them informational. I'll fix that on my end. Thanks!

my $selected = $params->{'pref_mix_genre_filter'};
my @genres = ref $selected eq 'ARRAY' ? @$selected : (defined $selected ? ($selected) : ());

$cprefs->set('mix_genre_filter', \@genres);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You could simplify this by setting $params->{'pref_mix_genre_filter'} to the list ref, then leave the storing to $class->SUPER::handler($client, $params). That way you wouldn't have to deal with $cprefs at all.

I'm going to push a small change which will make ClientSettings inherit from Settings. That way you will only have to implement this in Settings.pm.


my $url = Slim::Utils::Misc::fileURLFromPath($songs[$j]);

if (%genreFilterHash && _trackHasExcludedGenre($url, \%genreFilterHash)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

%genreFilterHash will always be truthy. What you really want to do here is to check whether it's empty or not: if (keys %genreFilterHash && ...). Or do the check first thing in _trackHasExcludedGenre().


sub _trackHasExcludedGenre {
my ($url, $genreFilterHash) = @_;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

return unless $url && keys %$genreFilterHash;

($prefs->client($client)->get('mix_genre_filter') || $prefs->get('mix_genre_filter')) :
$prefs->get('mix_genre_filter');

my %genreFilterHash = map { lc($_) => 1 } @{ $genreFilter || [] };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you make this a hash ref right on, you won't have to reference it further down:

my $genreFilterHash = { map { lc($_) => 1 } @{ $genreFilter || [] } };

@michaelherger
michaelherger merged commit 30de1fc into LMS-Community:public/9.2 Jul 26, 2026
1 check passed

[% FOREACH genre = genre_list %]

<option [% IF prefs.pref_mix_genre_filter.item(genre) %]selected [% END %]value="[% genre | html %]">[% genre | html %]</option>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did this work for you? prefs.pref_mix_genre_filter.item() would expect a numerical index value, rather than the genre name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Never mind - was caused by my own changes...

@michaelherger

Copy link
Copy Markdown
Member

As mentioned before, I re-factored some of the plugin. Please let me know if you encounter any issue. Thanks!

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.

2 participants