]> git.draconx.ca Git - mpdhacks.git/blobdiff - mpdmenu.pl
mpdmenu: Use MBIDs for albums instead of name matching.
[mpdhacks.git] / mpdmenu.pl
index d22193c960fa2a778a4ef9b0227f76235830233c..241ab8e39076d49d7a07f3502a7c4d2a6b363849 100755 (executable)
@@ -21,7 +21,7 @@ binmode(STDOUT, ":utf8");
 use IO::Socket::INET6;
 use Getopt::Long qw(:config gnu_getopt);
 use Scalar::Util qw(reftype);
-use List::Util qw(any);
+use List::Util qw(any max);
 use FindBin;
 
 use constant {
@@ -37,6 +37,7 @@ my $host  = $ENV{MPD_HOST} // "localhost";
 my $port  = $ENV{MPD_PORT} // "6600";
 my $sock;
 
+my ($albumid, $artist, $title);
 my $menu;
 my $mode = "top";
 
@@ -276,20 +277,93 @@ sub top_track_album {
        my ($entry) = @_;
        my @submenu;
 
-       my @submenu = make_submenu("$menu-TopAlbum",
-                                  "--artist=$entry->{Artist}",
-                                  "--album=$entry->{Album}");
+       my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID");
+       @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid") if $mbid;
 
        fvwm_cmd("AddToMenu", $menu,
                 fvwm_label_escape("Album:\t$entry->{Album}"),
                 @submenu);
 }
 
-# Global hash for tracking what is to be "accepted".
-my %accept = ();
+# 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 {
+       my ($mbid) = @_;
+       my %matches;
+       my $entry;
+
+       return \%matches unless ($mbid);
+       mpd_exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")");
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       if ($1 eq "file") {
+                               if (exists($matches{$2})) {
+                                       $entry = $matches{$2};
+                               } else {
+                                       $entry = {};
+                                       $matches{$2} = $entry;
+                               }
+                       }
+
+                       add_track_metadata($entry, $1, $2);
+               }
+       }
+
+       return \%matches;
+}
+
+# Given a filename, return the IDs (if any) for that file in the
+# current MPD play queue.
+sub get_ids_by_filename {
+       my ($file) = @_;
+       my @results = ();
+
+       mpd_exec("playlistfind", "file", $file);
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       push @results, $2 if ($1 eq "Id");
+               }
+       }
+
+       return @results;
+}
+
+# albumsort(matches, a, b)
+#
+# Sort hash keys (a, b) by disc/track number for album menus.
+sub albumsort {
+       my ($matches, $a, $b) = @_;
+
+       return $matches->{$a}->{Disc} <=> $matches->{$b}->{Disc}
+           || $matches->{$a}->{Track} <=> $matches->{$b}->{Track}
+           || $a cmp $b;
+}
 
-# Default values for stuff.
-my ($album, $artist, $title);
+# datesort(matches, a, b)
+#
+# Sort hash keys (a, b) by release date
+sub datesort {
+       my ($matches, $a, $b) = @_;
+
+       return $matches->{$a}->{Date} cmp $matches->{$b}->{Date}
+           || $a cmp $b;
+}
+
+# menu_trackname(entry)
+#
+# Format the track name for display in an FVWM menu, where entry
+# is a hash reference containing the track metadata.
+sub menu_trackname {
+       my ($entry) = @_;
+       my $fmt = "$entry->{trackfmt}$entry->{Artist} - $entry->{Title}";
+       return "$entry->{thumb}" . fvwm_label_escape($fmt);
+}
 
 sub print_version {
        print <<EOF
@@ -317,6 +391,7 @@ Options:
   -h, --host=HOST   Connect to the MPD server on HOST, overriding defaults.
   -p, --port=PORT   Connect to the MPD server on PORT, overriding defaults.
   -m, --menu=NAME   Set the name of the generated menu.
+  --album-id=MBID   Generate a menu for the given release MBID.
   -V, --version     Print a version message and then exit.
   -H, --help        Print this message and then exit.
 EOF
@@ -327,9 +402,8 @@ GetOptions(
        'port|p=s'   => \$port,
        'menu|m=s'   => \$menu,
 
-       'album=s'    => sub { $album = $_[1]; $mode = "album"; },
-       'artist=s'   => sub { $artist = $_[1];
-                             $mode = "artist" unless $mode eq "album"; },
+       'album-id=s' => sub { $albumid = $_[1]; $mode = "album"; },
+       'artist=s'   => sub { $artist = $_[1]; $mode = "artist"; },
        'title=s'    => sub { $title = $_[1]; $mode = "track"; },
 
        'V|version'  => sub { print_version(); exit },
@@ -412,106 +486,89 @@ if ($mode eq "top") {
                       "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "play");
        }
 } elsif ($mode eq "album") {
-       # Create an album menu.
-       my @playlist = ();
-       my $entry;
-
-       $menu = "MenuMPDAlbum" unless defined $menu;
-
-       $album =~ s/"/\\"/g;
-       print $sock "playlistfind album \"$album\"\n";
-       while (<$sock>) {
-               last if (/^OK/);
-               die($_) if (/^ACK/);
-
-               if (/^(\w+): (.*)$/) {
-                       if ($1 eq "file") {
-                               if (keys(%$entry) > 0) {
-                                       addalbumentry(\@playlist, $entry)
-                               }
-
-                               $entry = {};
+       my $matches = get_tracks_by_release_mbid($albumid);
+       my @notqueued = ();
+
+       $menu //= "MenuMPDAlbum";
+
+       my $track_max = max(map { $_->{Track} } values %$matches);
+       my $disc_max = max(map { $_->{Disc} } values %$matches);
+
+       # CDs have a max of 99 tracks and I hope 100+-disc-releases
+       # don't exist so this is fine.
+       my $track_digits = $track_max >= 10 ? 2 : 1;
+       my $disc_digits = $disc_max > 1 ? $disc_max >= 10 ? 2 : 1 : 0;
+       my $currentdisc;
+
+       fvwm_cmd("AddToMenu", $menu);
+       fvwm_cmd("+", "Release not found", "Title") unless keys %$matches;
+       foreach my $file (sort { albumsort($matches, $a, $b) } keys %$matches) {
+               my $entry = $matches->{$file};
+
+               # Format disc/track numbers
+               $entry->{trackfmt} = sprintf("%*.*s%.*s%*d\t",
+                                 $disc_digits, $disc_digits, $entry->{Disc},
+                                 $disc_digits, "-",
+                                 $track_digits, $entry->{Track});
+               $entry->{trackfmt} =~ s/ /\N{U+2007}/g;
+
+               unless (exists $entry->{Id}) {
+                       my ($id) = get_ids_by_filename($file);
+                       if (defined $id) {
+                               $entry->{Id} = $id;
+                       } else {
+                               push @notqueued, $entry;
+                               next;
                        }
-
-                       $entry->{$1} = $2;
                }
-       }
-       addalbumentry(\@playlist, $entry) if (keys(%$entry) > 0);
 
-       die("No tracks found.\n") if (!@playlist);
-       foreach (sort albumsort @playlist) {
-               my ($t_file, $t_trackno, $t_artist, $t_title, $t_id) = (
-                       $_->{file},
-                       $_->{Track},
-                       $_->{Artist},
-                       $_->{Title},
-                       $_->{Id},
-               );
-
-               next if (defined $artist && !$accept{albumdir($t_file)});
-
-               $t_artist = sanitise($t_artist, 0);
-               $t_title  = sanitise($t_title, 0);
+               if (defined $currentdisc && $currentdisc != $entry->{Disc}) {
+                       fvwm_cmd("+", "", "Nop");
+               }
+               $currentdisc = $entry->{Disc};
 
-               my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\""
-                                 ." Exec exec $FindBin::Bin/mpdexec.pl"
-                                 ." playid %d",
-                                 $t_trackno, $t_artist, $t_title, $t_id;
+               fvwm_cmd("+", menu_trackname($entry), "Exec",
+                        "exec", "$FindBin::Bin/mpdexec.pl",
+                        "playid", $entry->{Id});
+       }
 
-               cmd($cmd);
+       fvwm_cmd("+", "Not in play queue", "Title") if @notqueued;
+       foreach my $entry (@notqueued) {
+               fvwm_cmd("+", menu_trackname($entry));
        }
 } elsif ($mode eq "artist") {
        # Create an artist menu.
-       my %albums = ();
-       my $file;
-       my $quoteartist = $artist;
+       my %matches;
+       my $entry;
 
-       $menu = "MenuMPDArtist" unless defined $menu;
+       $menu //= "MenuMPDArtist";
 
-       $quoteartist =~ s/"/\\"/g;
-       print $sock "playlistfind artist \"$quoteartist\"\n";
+       mpd_exec("playlistfind", "artist", $artist);
        while (<$sock>) {
                last if (/^OK/);
                die($_) if (/^ACK/);
 
                if (/^(\w+): (.*)$/) {
-                       $file       = $2    if ($1 eq "file");
-                       $albums{$2} = $file if ($1 eq "Album");
+                       $entry = {} if ($1 eq "file");
+                       $matches{$2} //= $entry if ($1 eq "MUSICBRAINZ_ALBUMID");
+                       add_track_metadata($entry, $1, $2);
                }
        }
 
-       die("No albums found.\n") if (!keys(%albums));
-
-{ # work around 'use locale' breaking s///i
-       my $i = 0;
-       use locale;
-
-       my @keys = sort keys %albums;
-       my @thumbs = get_item_thumbnails({ small => 1 },
-                                         map { $albums{$_} } @keys);
+       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;
 
-       foreach my $key (@keys) {
-               my $a_album  = sanitise($key, 1);
+       foreach my $mbid (@mbids) {
+               my $entry = $matches{$mbid};
                my $thumb = shift @thumbs;
 
-               cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i");
-
-               cmd("AddToMenu MenuMPDArt_$i DynamicPopUpAction MakeMenuMPDArt_$i");
-
-               cmd("DestroyFunc MakeMenuMPDArt_$i");
-               cmd("AddToFunc   MakeMenuMPDArt_$i
-                    + I DestroyMenu MenuMPDArt_$i
-                    + I -PipeRead \"exec $SELF "
-                          ."--menu MenuMPDArt_$i "
-                          ."--album  ".shellify($key, 1)." "
-                          ."--artist ".shellify($artist, 1)."\"");
-
-               cmd("AddToFunc KillMenuMPD I DestroyMenu MenuMPDArt_$i");
-               cmd("AddToFunc KillMenuMPD I DestroyFunc MakeMenuMPDArt_$i");
-
-               $i++;
+               my @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid");
+               fvwm_cmd("AddToMenu", $menu,
+                        $thumb . fvwm_label_escape($entry->{Album}),
+                        @submenu);
        }
-} # end use locale workaround
 } elsif ($mode eq "track") {
        # Create a title menu.
        my @titles;
@@ -613,32 +670,6 @@ sub sanitise
        return $_;
 }
 
-sub addalbumentry
-{
-       my ($playlist, $entry) = @_;
-
-       push(@$playlist, $entry);
-
-       if (defined $artist && $artist eq $entry->{Artist}) {
-               my $albumdir = albumdir($entry->{file});
-               $accept{$albumdir} = 1;
-       }
-}
-
-sub albumdir
-{
-       my $file = $_[0];
-
-       $file =~ s:(/Disk [0-9]+[^/]*)?/[^/]*$::;
-       return $file
-}
-
-sub albumsort
-{
-       return ($a->{Disc} <=> $b->{Disc}) if ($a->{Disc} != $b->{Disc});
-       return ($a->{Track} <=> $b->{Track});
-}
-
 sub titlesort
 {
        return ($a->{Album}  cmp $b->{Album})  if($a->{Album}  ne $b->{Album});
@@ -646,17 +677,6 @@ sub titlesort
        return ($a->{Title}  cmp $b->{Title});
 }
 
-sub shellify
-{
-       my ($str, $quoted) = @_;
-       $str =~ s/'/'\\''/g;
-       if ($quoted) {
-               $str =~ s/\\/\\\\/g;
-               $str =~ s/"/\\"/g;
-       }
-       return "'$str'";
-}
-
 sub cmd
 {
        print "$_[0]\n";