From fb396c175b1673579630ba4e69c9b363c1f2ae1b Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Tue, 27 Jul 2021 22:38:45 -0400 Subject: [PATCH] mpdmenu: Improve support for standalone recordings. Standalone recordings have a slightly different metadata structure from tracks associated with releases. Let's handle them by presenting all the standalone recordings for an artist together as if they were an actual release by that artist. --- mpdmenu.pl | 160 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 133 insertions(+), 27 deletions(-) diff --git a/mpdmenu.pl b/mpdmenu.pl index 3cc0e35..2b5fcad 100755 --- a/mpdmenu.pl +++ b/mpdmenu.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl # -# Copyright © 2008,2010,2012,2020 Nick Bowler +# Copyright © 2008,2010,2012,2020-2021 Nick Bowler # # Silly little script to generate an FVWM menu with various bits of MPD # status information and controls. @@ -37,7 +37,7 @@ my $SELF = "$FindBin::Bin/$FindBin::Script"; my $MUSIC = $ENV{MUSIC} // "/srv/music"; my $sock; -my ($albumid, $trackid); +my ($albumid, $albumname, $trackid, $recordingid); my ($topmenu, $menu); my $mode = "top"; my %artistids; @@ -228,7 +228,13 @@ sub top_track_title { my @submenu; my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_RELEASETRACKID"); - @submenu = make_submenu("$menu-$mbid", "--track-id=$mbid") if $mbid; + if ($mbid) { + @submenu = make_submenu("$menu-$mbid", "--track-id=$mbid") + } else { + ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_TRACKID"); + @submenu = make_submenu("$menu-track-$mbid", "--recording-id=$mbid") + if ($mbid); + } fvwm_cmd("AddToMenu", $menu, fvwm_label_escape("Title:\t$entry->{Title}"), @@ -257,9 +263,19 @@ sub top_track_artist { sub top_track_album { my ($entry) = @_; my @submenu; + my $mbid; + + if (($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID")) { + @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid"); + } elsif (($mbid) = get_track_metadata($entry, "MUSICBRAINZ_TRACKID")) { + # Standalone recording + my @a = get_track_metadata($entry, "MUSICBRAINZ_ARTISTID"); + my ($album) = get_track_metadata($entry, "Album"); - my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID"); - @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid") if $mbid; + @submenu = make_submenu("$menu-$mbid", "--album-name=$album", + map { "--artist-id=$_" } @a); + + } fvwm_cmd("AddToMenu", $menu, fvwm_label_escape("Album:\t$entry->{Album}"), @@ -308,7 +324,7 @@ sub get_tracks_by_track_mbid { my $entry; return \%matches unless ($mbid); - MPD::exec("search", "(MUSICBRAINZ_RELEASETRACKID == \"$mbid\")"); + MPD::exec("search", "($tagname == \"$mbid\")"); while (<$sock>) { last if (/^OK/); die($_) if (/^ACK/); @@ -332,6 +348,10 @@ sub get_tracks_by_track_mbid { return \%matches; } +sub get_tracks_by_recording_mbid { + return get_tracks_by_track_mbid($_[0], "MUSICBRAINZ_TRACKID"); +} + # Given a release MBID, return a hash reference containing all its # associated tracks in the MPD database. The hash keys are filenames. sub get_tracks_by_release_mbid { @@ -362,13 +382,28 @@ sub get_tracks_by_release_mbid { return \%matches; } -# Given an artist MBID, return a hash reference containing associated -# releases in the MPD database. The hash keys are release MBIDs. +# Insert the given entry into the referenced hash if it represents a +# standalone recording (not associated with a release). The recording +# MBID is used as the hash key. +sub check_standalone { + my ($outhash, $entry) = @_; + my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_TRACKID"); + + return if exists $entry->{MUSICBRAINZ_ALBUMID}; + $outhash->{$mbid} = $entry if ($mbid); +} + +# Given an artist MBID, return a list of two hash refererences. The +# first contains the associated releases in the MPD database and the +# hash keys are release MBIDs. The second contains the artist's +# standalone recordings and the hash keys are recording MBIDs. +# +# In scalar context only the release hash is returned. # # Since MPD returns results on a per-track basis, each entry in the # hash has the metadata for one unspecified track from that release. sub get_releases_by_artist_mbid { - my %releases; + my (%releases, %standalones); my $entry; foreach my $mbid (@_) { @@ -379,6 +414,7 @@ sub get_releases_by_artist_mbid { if (/^(\w+): (.*)$/) { if ($1 eq "file") { + check_standalone(\%standalones, $entry); $entry = {}; } elsif ($1 eq "MUSICBRAINZ_ALBUMID") { $releases{$2} //= $entry; @@ -387,9 +423,10 @@ sub get_releases_by_artist_mbid { add_track_metadata($entry, $1, $2); } } + check_standalone(\%standalones, $entry); } - return \%releases; + return wantarray ? (\%releases, values %standalones) : \%releases; } # Given a filename, return the IDs (if any) for that file in the @@ -411,6 +448,24 @@ sub get_ids_by_filename { return @results; } +sub update_entry_ids { + my @notqueued = (); + + foreach my $entry (@_) { + unless (exists $entry->{Id}) { + my ($id) = get_ids_by_filename($entry->{file}); + if (defined $id) { + $entry->{Id} = $id; + } else { + push @notqueued, $entry; + next; + } + } + } + + return @notqueued; +} + # albumsort(matches, a, b) # # Sort hash keys (a, b) by disc/track number for album menus. @@ -444,8 +499,8 @@ sub menu_trackname { sub print_version { print < \$MPD::host, - 'port|p=s' => \$MPD::port, - 'menu|m=s' => \$menu, + 'host|h=s' => \$MPD::host, + 'port|p=s' => \$MPD::port, + 'menu|m=s' => \$menu, - 'artist-id=s' => sub { $artistids{$_[1]} = 1; $mode = "artist"; }, - 'album-id=s' => sub { $albumid = $_[1]; $mode = "album"; }, - 'track-id=s' => sub { $trackid = $_[1]; $mode = "track"; }, + 'artist-id=s' => sub { $artistids{$_[1]} = 1; $mode = "artist"; }, + 'album-id=s' => sub { $albumid = $_[1]; $mode = "album"; }, + 'album-name=s' => sub { $albumname = $_[1]; $mode = "albumname"; }, + 'track-id=s' => sub { $trackid = $_[1]; $mode = "track"; }, + 'recording-id=s' => sub { $recordingid = $_[1]; $mode = "recording"; }, - 'V|version' => sub { print_version(); exit }, - 'H|help' => sub { print_help(); exit }, + 'V|version' => sub { print_version(); exit }, + 'H|help' => sub { print_help(); exit }, - 'topmenu=s' => \$topmenu, # top menu name (for submenu generation) + 'topmenu=s' => \$topmenu, # top menu name (for submenu generation) ) or do { print_usage; exit 1 }; +$mode = "albumname" if ($albumname && $mode eq "artist"); + unless (defined $menu) { $topmenu //= "MenuMPD"; $menu = $topmenu . ($mode ne "top" ? $mode : ""); @@ -613,15 +677,17 @@ if ($mode eq "top") { } } elsif ($mode eq "artist") { # Create an artist menu. - my $matches = get_releases_by_artist_mbid(keys %artistids); - my $entry; + my ($matches, @recs) = get_releases_by_artist_mbid(keys %artistids); $menu //= "MenuMPDArtist"; my @mbids = sort { datesort($matches, $a, $b) } keys %$matches; my @files = map { $matches->{$_}->{file} } @mbids; my @thumbs = get_item_thumbnails({ small => 1 }, @files); - fvwm_cmd("AddToMenu", $menu, "No releases found", "Title") unless @mbids; + + unless (@mbids) { + fvwm_cmd("AddToMenu", $menu, "No releases found", "Title") + } foreach my $mbid (@mbids) { my $entry = $matches->{$mbid}; @@ -631,13 +697,53 @@ if ($mode eq "top") { "--album-id=$mbid"); fvwm_cmd("AddToMenu", $menu, $thumb . fvwm_label_escape($entry->{Album}), - @submenu); + @submenu); + } + + my @artists = map { "--artist-id=$_" } keys %artistids; + my %nonalbums = map { $_->{Album} => $_ } @recs; + foreach my $name (sort keys %nonalbums) { + my $mbid = $nonalbums{$name}->{MUSICBRAINZ_TRACKID}; + my @submenu = make_submenu("$topmenu-$mbid", @artists, + "--album-name=$name"); + fvwm_cmd("AddToMenu", $menu, fvwm_label_escape($name), @submenu); + } +} elsif ($mode eq "albumname") { + # Create a standalone recordings menu + my ($releases, @recs) = get_releases_by_artist_mbid(keys %artistids); + + $menu //= "MenuMPDRecordings"; + my @tracks = sort { $a->{Title} cmp $b->{Title} } + grep { $_->{Album} eq $albumname } @recs; + my @notqueued = update_entry_ids(@tracks); + + fvwm_cmd("AddToMenu", $menu); + fvwm_cmd("+", "No tracks found", "Title") unless @tracks; + + foreach my $entry (@tracks) { + next unless exists $entry->{Id}; + + fvwm_cmd("+", menu_trackname($entry), "Exec", + "exec", "$FindBin::Bin/mpdexec.pl", + "playid", $entry->{Id}); + } + + fvwm_cmd("+", "Not in play queue", "Title") if @notqueued; + foreach my $entry (@notqueued) { + fvwm_cmd("+", menu_trackname($entry)); } -} elsif ($mode eq "track") { - my $matches = get_tracks_by_track_mbid($trackid); +} elsif ($mode eq "track" || $mode eq "recording") { + my ($matches, $mbid); my @notqueued; + if ($mode eq "track") { + $matches = get_tracks_by_track_mbid($trackid) + } else { + $matches = get_tracks_by_recording_mbid($recordingid) + } + $menu //= "MenuMPDTrack"; + fvwm_cmd("DestroyMenu", $menu); my @files = sort { datesort($matches, $a, $b) } keys %$matches; my @thumbs = get_item_thumbnails({ small => 1 }, @files); -- 2.43.0