+ push(@$ref, $value) unless (any {$_ eq $value} @$ref);
+ } else {
+ $entry->{$key} = $value;
+ }
+}
+
+# get_track_metadata(hashref, key)
+#
+# Return the values associated with the given metadata key as a list.
+sub get_track_metadata {
+ my ($entry, $key) = @_;
+
+ return () unless (exists($entry->{$key}));
+
+ my $ref = $entry->{$key};
+ return @$ref if (reftype($ref) eq "ARRAY");
+ return $ref
+}
+
+# Given a music filename, search for the cover art in the same directory.
+sub mpd_cover_filename {
+ my ($dir) = @_;
+ my $file;
+
+ $dir =~ s/\/[^\/]*$//;
+ foreach ("cover.png", "cover.jpg", "cover.tiff", "cover.bmp") {
+ if (-f "$dir/$_") {
+ $file = "$dir/$_";
+ last;
+ }
+ }
+ return unless defined $file;
+
+ # Follow one level of symbolic link to get to the scans directory.
+ $file = readlink($file) // $file;
+ $file = "$dir/$file" unless ($file =~ /^\//);
+ return $file;
+}
+
+# Generate the cover art entry in the top menu.
+sub top_track_cover {
+ my ($entry) = @_;
+
+ ($entry->{thumb}) = get_item_thumbnails($entry->{file});
+ print "$entry->{thumb}\n";
+ if ($entry->{thumb}) {
+ my $file = "$MUSIC/$entry->{file}";
+ my $cover = mpd_cover_filename($file);
+
+ $cover = fvwm_shell_literal($cover // $file);
+ fvwm_cmd_unquoted("AddToMenu", escape($menu),
+ escape($entry->{thumb}),
+ "Exec", "exec", "geeqie", $cover);
+ }
+}
+
+# Generate the "Title:" entry in the top menu.
+sub top_track_title {
+ my ($entry) = @_;
+
+ my @submenu = make_submenu("$menu-TopTrack",
+ "--title=$entry->{Title}");
+
+ fvwm_cmd("AddToMenu", $menu,
+ fvwm_label_escape("Title:\t$entry->{Title}"),
+ @submenu);
+}
+
+# Generate the "Artist:" entry in the top menu.
+sub top_track_artist {
+ my ($entry) = @_;
+
+ my @submenu = make_submenu("$menu-TopArtist",
+ "--artist=$entry->{Artist}");
+
+ fvwm_cmd("AddToMenu", $menu,
+ fvwm_label_escape("Artist:\t$entry->{Artist}"),
+ @submenu);
+}
+
+# Generate the "Album:" entry in the top menu.
+sub top_track_album {
+ my ($entry) = @_;
+ my @submenu;
+
+ 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);
+}
+
+# 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;
+}
+
+# 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
+mpdmenu.pl 0.8
+Copyright © 2019 Nick Bowler
+License GPLv3+: GNU General Public License version 3 or any later version.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+EOF
+}
+
+sub print_usage {
+ my $fh = $_[1] // *STDERR;
+
+ print $fh "Usage: $0 [options]\n";
+ print "Try $0 --help for more information.\n" unless (@_ > 0);
+}
+
+sub print_help {
+ print_usage(*STDOUT);
+ print <<EOF
+This is "mpdmenu": a menu-based MPD client for FVWM.
+
+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
+}