#!/usr/bin/perl
#
-# Copyright © 2008,2010,2012,2019 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.
decode_argv(Encode::FB_CROAK);
binmode(STDOUT, ":utf8");
-use IO::Socket::INET6;
use Getopt::Long qw(:config gnu_getopt);
use Scalar::Util qw(reftype);
use List::Util qw(any max);
use FindBin;
+use lib "$FindBin::Bin";
+use MPDHacks;
+
use constant {
MPD_MJR_MIN => 0,
MPD_MNR_MIN => 21,
my $SELF = "$FindBin::Bin/$FindBin::Script";
my $MUSIC = $ENV{MUSIC} // "/srv/music";
-my $host = $ENV{MPD_HOST} // "localhost";
-my $port = $ENV{MPD_PORT} // "6600";
-my $sock;
+my ($sock, $mpd_have_binarylimit);
-my ($albumid, $artist, $title);
-my $menu;
+my ($albumid, $albumname, $trackid, $recordingid);
+my ($topmenu, $menu);
my $mode = "top";
-
-# Quotes the argument so that it is presented as a single argument to MPD
-# at the protocol level. This also works OK for most FVWM arguments.
-sub escape {
- my $s = @_[0] // $_;
-
- # No way to encode literal newlines in the protocol, so we
- # convert any newlines in the arguments into a space, which
- # can help with quoting.
- $s =~ s/\n/ /g;
-
- if (/[ \t\\"]/) {
- $s =~ s/[\\"]/\\$&/g;
- return "\"$s\"";
- }
-
- $s =~ s/^\s*$/"$&"/;
- return $s;
-}
-
-# Submit a command to the MPD server; each argument to this function
-# is quoted and sent as a single argument to MPD.
-sub mpd_exec {
- my $cmd = join(' ', map { escape } @_);
-
- print $sock "$cmd\n";
-}
+my %artistids;
sub fvwm_cmd_unquoted {
print join(' ', @_), "\n";
}
sub fvwm_cmd {
- fvwm_cmd_unquoted(map { escape } @_);
+ fvwm_cmd_unquoted(map { MPD::escape } @_);
}
# Quotes the argument in such a way that it is passed unadulterated by
sub make_submenu {
my $name = shift;
$name =~ s/-/_/g;
- unshift @_, ("exec", $SELF, "--menu=$name");
+ unshift @_, ("exec", $SELF, "--topmenu=$topmenu", "--menu=$name");
fvwm_cmd("DestroyFunc", "Make$name");
fvwm_cmd("AddToFunc", "Make$name");
fvwm_cmd("DestroyMenu", $name);
fvwm_cmd("AddToMenu", $name, "DynamicPopupAction", "Make$name");
- fvwm_cmd("AddToFunc", "KillMenuMPD", "I", "DestroyMenu", $name);
+ fvwm_cmd("AddToFunc", "Kill$topmenu", "I", "DestroyMenu", $name);
fvwm_cmd("DestroyFunc", "Make$name");
fvwm_cmd("AddToFunc", "Make$name");
fvwm_cmd("+", "I", "DestroyMenu", $name);
fvwm_cmd("+", "I", "-PipeRead",
join(' ', map { fvwm_shell_literal } @_));
- fvwm_cmd("AddToFunc", "KillMenuMPD", "I", "DestroyFunc", "Make$name");
+ fvwm_cmd("AddToFunc", "Kill$topmenu", "I", "DestroyFunc", "Make$name");
return ("Popup", $name);
}
$c = "%";
}
+ if ($mpd_have_binarylimit) {
+ # --embedded implies and requires binarylimit support
+ push @opts, "--embedded";
+ } else {
+ push @opts, "--no-binarylimit";
+ }
+
open THUMB, "-|", "$FindBin::Bin/mpdthumb.sh", @opts, "--", @_;
foreach (@_) {
my $thumb = <THUMB>;
my $cover = mpd_cover_filename($file);
$cover = fvwm_shell_literal($cover // $file);
- fvwm_cmd_unquoted("AddToMenu", escape($menu),
- escape($entry->{thumb}),
+ fvwm_cmd_unquoted("AddToMenu", MPD::escape($menu),
+ MPD::escape($entry->{thumb}),
"Exec", "exec", "geeqie", $cover);
}
}
# Generate the "Title:" entry in the top menu.
sub top_track_title {
my ($entry) = @_;
+ my @submenu;
- my @submenu = make_submenu("$menu-TopTrack",
- "--title=$entry->{Title}");
+ my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_RELEASETRACKID");
+ 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}"),
# Generate the "Artist:" entry in the top menu.
sub top_track_artist {
my ($entry) = @_;
+ my @submenu;
- my @submenu = make_submenu("$menu-TopArtist",
- "--artist=$entry->{Artist}");
+ # TODO: multi-artist tracks should get multiple artist menus; for now
+ # just combine the releases from all artists.
+ my @mbids = get_track_metadata($entry, "MUSICBRAINZ_ARTISTID");
+ if (@mbids) {
+ @submenu = make_submenu("$menu-TopArtist",
+ map { "--artist-id=$_" } @mbids);
+ }
fvwm_cmd("AddToMenu", $menu,
fvwm_label_escape("Artist:\t$entry->{Artist}"),
sub top_track_album {
my ($entry) = @_;
my @submenu;
+ my $mbid;
- my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID");
- @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid") if $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");
+
+ @submenu = make_submenu("$menu-$mbid", "--album-name=$album",
+ map { "--artist-id=$_" } @a);
+
+ }
fvwm_cmd("AddToMenu", $menu,
fvwm_label_escape("Album:\t$entry->{Album}"),
@submenu);
}
+# Generate the "MusicBrainz:" entry in the top menu.
+sub top_track_musicbrainz {
+ my ($entry) = @_;
+ my ($track_mbid, $recording_mbid, $release_mbid);
+ my @artist_mbids;
+ my $label = "MB:";
+ my %idmap;
+
+ ($track_mbid) = get_track_metadata($entry, "MUSICBRAINZ_RELEASETRACKID");
+ ($recording_mbid) = get_track_metadata($entry, "MUSICBRAINZ_TRACKID");
+ ($release_mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID");
+ @artist_mbids = get_track_metadata($entry, "MUSICBRAINZ_ARTISTID");
+ return unless $track_mbid // $recording_mbid
+ // $release_mbid // @artist_mbids;
+
+ foreach (get_track_metadata($entry, "Comment")) {
+ $idmap{$1} = $2 if /^([^=]*)=(.*) \(idmap\)$/;
+ }
+
+ fvwm_cmd("AddToMenu", $menu, "", "Nop");
+ if ($track_mbid) {
+ fvwm_cmd("AddToMenu", $menu, "$label\tShow track",
+ "Exec", "exec", "xdg-open",
+ "https://musicbrainz.org/track/$track_mbid");
+ $label = "";
+ } elsif ($recording_mbid) {
+ fvwm_cmd("AddToMenu", $menu, "$label\tShow recording",
+ "Exec", "exec", "xdg-open",
+ "https://musicbrainz.org/recording/$recording_mbid");
+ $label = "";
+ } elsif ($release_mbid) {
+ fvwm_cmd("AddToMenu", $menu, "$label\tShow",
+ "Exec", "exec", "xdg-open",
+ "https://musicbrainz.org/release/$release_mbid");
+ $label = "";
+ }
+
+ foreach my $mbid (@artist_mbids) {
+ my $name = " $idmap{$mbid}" if $idmap{$mbid};
+
+ fvwm_cmd("AddToMenu", $menu, "$label\tShow artist$name",
+ "Exec", "exec", "xdg-open",
+ "https://musicbrainz.org/artist/$mbid");
+ $label = "";
+ }
+}
+
+# Given a work MBID, return a hash reference containing all tracks
+# linked to that work. The hash keys are filenames.
+sub get_tracks_by_work_mbid {
+ my %matches;
+ my $entry;
+
+ foreach my $mbid (@_) {
+ MPD::exec("search", "(MUSICBRAINZ_WORKID == \"$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 track MBID, return a hash reference containing all "related"
+# tracks in the MPD database. The hash keys are filenames.
+#
+# Currently tracks are considered "related" if their associated recordings
+# have at least one work in common.
+sub get_tracks_by_track_mbid {
+ my ($mbid, $tagname) = (@_, "MUSICBRAINZ_RELEASETRACKID");
+ my %source;
+ my %matches;
+ my $entry;
+
+ return \%matches unless ($mbid);
+ MPD::exec("search", "($tagname == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ add_track_metadata(\%source, $1, $2);
+ }
+ }
+
+ # Always include the current track
+ $matches{$source{file}} = \%source;
+
+ # Find all tracks related by work
+ foreach my $mbid (get_track_metadata(\%source, "MUSICBRAINZ_WORKID")) {
+ my $related = get_tracks_by_work_mbid($mbid);
+ foreach (keys %$related) {
+ $matches{$_} //= $related->{$_};
+ }
+ }
+
+ 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 {
my $entry;
return \%matches unless ($mbid);
- mpd_exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")");
+ MPD::exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")");
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
return \%matches;
}
+# 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, %standalones);
+ my $entry;
+
+ foreach my $mbid (@_) {
+ MPD::exec("search", "(MUSICBRAINZ_ARTISTID == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ if ($1 eq "file") {
+ check_standalone(\%standalones, $entry);
+ $entry = {};
+ } elsif ($1 eq "MUSICBRAINZ_ALBUMID") {
+ $releases{$2} //= $entry;
+ }
+
+ add_track_metadata($entry, $1, $2);
+ }
+ }
+ check_standalone(\%standalones, $entry);
+ }
+
+ return wantarray ? (\%releases, values %standalones) : \%releases;
+}
+
# 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);
+ MPD::exec("playlistfind", "file", $file);
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
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.
sub print_version {
print <<EOF
-mpdmenu.pl 0.8
-Copyright © 2019 Nick Bowler
+mpdmenu.pl 0.9
+Copyright © 2021 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.
}
sub print_usage {
- my $fh = $_[1] // *STDERR;
+ my ($fh) = (@_, *STDERR);
print $fh "Usage: $0 [options]\n";
- print "Try $0 --help for more information.\n" unless (@_ > 0);
+ print $fh "Try $0 --help for more information.\n" unless (@_ > 0);
}
sub print_help {
-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.
+ --album-name=NAME
+ Generate a menu for standalone tracks with the given
+ "album" NAME. An artist MBID must be supplied.
+ --artist-id=MBID Generate a menu for the given artist MBID.
+ --track-id=MBID Generate a menu for the given track MBID.
+ --recording-id=MBID
+ Generate a menu for the given recording MBID.
-V, --version Print a version message and then exit.
-H, --help Print this message and then exit.
EOF
}
GetOptions(
- 'host|h=s' => \$host,
- 'port|p=s' => \$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"; },
+ 'album-name=s' => sub { $albumname = $_[1]; $mode = "albumname"; },
+ 'track-id=s' => sub { $trackid = $_[1]; $mode = "track"; },
+ 'recording-id=s' => sub { $recordingid = $_[1]; $mode = "recording"; },
- '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 },
+ '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)
) or do { print_usage; exit 1 };
+$mode = "albumname" if ($albumname && $mode eq "artist");
+
+unless (defined $menu) {
+ $topmenu //= "MenuMPD";
+ $menu = $topmenu . ($mode ne "top" ? $mode : "");
+}
+$topmenu //= $menu;
+
# Connect to MPD.
-$sock = new IO::Socket::INET6(
- PeerAddr => $host,
- PeerPort => $port,
- Proto => 'tcp',
- Timeout => 2
-) or die("could not open socket: $!.\n");
-binmode($sock, ":utf8");
-
-die("could not connect to MPD: $!.\n")
- if (!(<$sock> =~ /^OK MPD ([0-9]+)\.([0-9]+)\.([0-9]+)$/));
-
-die("MPD version $1.$2.$3 insufficient.\n")
- if ( ($1 < MPD_MJR_MIN)
- || ($1 == MPD_MJR_MIN && $2 < MPD_MNR_MIN)
- || ($1 == MPD_MJR_MIN && $2 == MPD_MNR_MIN && $3 < MPD_REV_MIN));
+$sock = MPD::connect();
+die("MPD version $MPD::major.$MPD::minor.$MPD::revision insufficient.")
+ unless MPD::min_version(MPD_MJR_MIN, MPD_MNR_MIN, MPD_REV_MIN);
+
+MPD::exec("binarylimit", 64);
+while (<$sock>) {
+ $mpd_have_binarylimit = 1 if /^OK/;
+ last if /^OK/ or /^ACK/;
+}
if ($mode eq "top") {
my %current;
$menu //= "MenuMPD";
- mpd_exec("status");
+ MPD::exec("status");
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
}
}
- mpd_exec("currentsong");
+ MPD::exec("currentsong");
while (<$sock>) {
last if (/^OK/);
die($_) if (/^ACK/);
top_track_title(\%current);
top_track_artist(\%current);
top_track_album(\%current);
+ top_track_musicbrainz(\%current);
} else {
fvwm_cmd("AddToMenu", $menu, "[current track unavailable]");
}
}
} elsif ($mode eq "artist") {
# Create an artist menu.
- my %matches;
- my $entry;
+ my ($matches, @recs) = get_releases_by_artist_mbid(keys %artistids);
$menu //= "MenuMPDArtist";
- mpd_exec("playlistfind", "artist", $artist);
- while (<$sock>) {
- last if (/^OK/);
- die($_) if (/^ACK/);
+ my @mbids = sort { datesort($matches, $a, $b) } keys %$matches;
+ my @files = map { $matches->{$_}->{file} } @mbids;
+ my @thumbs = get_item_thumbnails({ small => 1 }, @files);
- if (/^(\w+): (.*)$/) {
- $entry = {} if ($1 eq "file");
- $matches{$2} //= $entry if ($1 eq "MUSICBRAINZ_ALBUMID");
- add_track_metadata($entry, $1, $2);
- }
+ unless (@mbids) {
+ fvwm_cmd("AddToMenu", $menu, "No releases found", "Title")
}
- 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 $mbid (@mbids) {
- my $entry = $matches{$mbid};
+ my $entry = $matches->{$mbid};
my $thumb = shift @thumbs;
- my @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid");
+ my @submenu = make_submenu("$topmenu-$mbid",
+ "--album-id=$mbid");
fvwm_cmd("AddToMenu", $menu,
$thumb . fvwm_label_escape($entry->{Album}),
- @submenu);
+ @submenu);
}
-} elsif ($mode eq "track") {
- # Create a title menu.
- my @titles;
- my $entry;
- $menu = "MenuMPDTitle" unless defined $menu;
+ 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);
- # Open and close brackets.
- my ($ob, $cb) = ("[\[~〜<〈(ー−-]", "[\]~〜>〉)ー−-]");
+ $menu //= "MenuMPDRecordings";
+ my @tracks = sort { $a->{Title} cmp $b->{Title} }
+ grep { $_->{Album} eq $albumname } @recs;
- $_ = $title;
+ # Show thumbnails for standalone recordings
+ my @thumbs = get_item_thumbnails({ small => 1 },
+ map { $_->{file} } @tracks);
+ foreach my $entry (@tracks) {
+ $entry->{thumb} = shift @thumbs;
+ }
- # Deal with specific cases.
- s/ちいさな(?=ヘミソフィア)//; # ヘミソフィア
- s/ "mix on air flavor" dear EIKO SHIMAMIYA//; # Spiral wind
- s/ "So,you need me" Style//; # I need you
- s/ ::Symphony Second movement:://; # Disintegration
- s/-\[instrumental\]//; # 青い果実
- s/ -Practice Track-//; # Fair Heaven
- s/〜世界で一番アナタが好き〜//; # Pure Heart
- s/〜彼方への哀歌//; # 十二幻夢
- s/ sora no uta ver.//; # 美しい星
+ my @notqueued = update_entry_ids(@tracks);
- s/\s*-remix-$//; # Otherwise "D-THREAD -remix-" doesn't work right.
+ fvwm_cmd("AddToMenu", $menu);
+ fvwm_cmd("+", "No tracks found", "Title") unless @tracks;
- # Deal with titles like "blah (ABC version)".
- s/\s*$ob.*(style|mix|edit|edition|ver\.?|version|melody|カラオケ)$cb?$//i;
+ foreach my $entry (@tracks) {
+ next unless exists $entry->{Id};
- # Deal with titles like "blah (without XYZ)".
- s/\s*$ob\s*((e\.)?piano|english|japanese|inst|tv|without|w\/o|off|back|short|karaoke|game).*//i;
+ fvwm_cmd("+", menu_trackname($entry), "Exec",
+ "exec", "$FindBin::Bin/mpdexec.pl",
+ "playid", $entry->{Id});
+ }
- # Deal with titles like "blah instrumental".
- s/\s+(instrumental|off vocal|short|tv)([\s-]+(mix|size|version))?$//i;
- s/\s+without\s+\w+$//i;
+ fvwm_cmd("+", "Not in play queue", "Title") if @notqueued;
+ foreach my $entry (@notqueued) {
+ fvwm_cmd("+", menu_trackname($entry));
+ }
+} elsif ($mode eq "track" || $mode eq "recording") {
+ my ($matches, $mbid);
+ my @notqueued;
- # Deal with separate movements in classical pieces.
- s/: [IVX]+\..*//;
+ if ($mode eq "track") {
+ $matches = get_tracks_by_track_mbid($trackid)
+ } else {
+ $matches = get_tracks_by_recording_mbid($recordingid)
+ }
- my $basetitle = $_;
- my $_basetitle = $basetitle;
+ $menu //= "MenuMPDTrack";
+ fvwm_cmd("DestroyMenu", $menu);
- $_basetitle =~ s/"/\\"/g;
- print $sock "playlistsearch title \"$_basetitle\"\n";
- while (<$sock>) {
- last if (/^OK/);
- die($_) if (/^ACK/);
+ my @files = sort { datesort($matches, $a, $b) } keys %$matches;
+ my @thumbs = get_item_thumbnails({ small => 1 }, @files);
- if (/^(\w+): (.*)$/) {
- if ($1 eq "file") {
- push @titles, $entry if (keys(%$entry) > 0);
- $entry = {};
- }
+ fvwm_cmd("AddToMenu", $menu);
+ fvwm_cmd("+", "No tracks found", "Title") unless @files;
+ foreach my $file (@files) {
+ my $entry = $matches->{$file};
+ $entry->{thumb} = shift @thumbs;
- $entry->{$1} = $2;
+ unless (exists $entry->{Id}) {
+ my ($id) = get_ids_by_filename($file);
+ if (defined $id) {
+ $entry->{Id} = $id;
+ } else {
+ push @notqueued, $entry;
+ next;
+ }
}
- }
- push @titles, $entry if (keys(%$entry) > 0);
-{ # work around 'use locale' breaking s///i
- use locale;
-
- my @thumbs = get_item_thumbnails({ small => 1 },
- map { $_->{file} } @titles);
- for (my $i = 0; $i < @titles; $i++) {
- $titles[$i]->{thumb} = $thumbs[$i];
+ fvwm_cmd("+", menu_trackname($entry), "Exec",
+ "exec", "$FindBin::Bin/mpdexec.pl",
+ "playid", $entry->{Id});
}
- foreach (sort titlesort @titles) {
- my ($t_file, $t_artist, $t_title, $t_id, $thumb) = (
- $_->{file},
- $_->{Artist},
- $_->{Title},
- $_->{Id},
- $_->{thumb}
- );
-
- # MPD searches are case-insensitive.
- next if (!($t_title =~ m/(\P{Latin}|^)\Q$basetitle\E(\P{Latin}|$)/ || $t_title =~ m/\Q$basetitle\E/i));
-
- $t_artist = sanitise($t_artist, 1);
- $t_title = sanitise($t_title, 1);
-
- cmd("AddToMenu $menu \"$thumb$t_artist - $t_title\""
- ." Exec exec $FindBin::Bin/mpdexec.pl"
- ." playid $t_id");
+ fvwm_cmd("+", "Not in play queue", "Title") if @notqueued;
+ foreach my $entry (@notqueued) {
+ fvwm_cmd("+", menu_trackname($entry));
}
-} # end use locale workaround
}
# Finished.
print $sock "close\n";
-
-sub sanitise
-{
- $_ = $_[0];
- s/&/&&/g if ($_[1]);
- s/([\$@%^*])/\1\1/g;
- s/"/\\"/g;
- return $_;
-}
-
-sub titlesort
-{
- return ($a->{Album} cmp $b->{Album}) if($a->{Album} ne $b->{Album});
- return ($a->{Artist} cmp $b->{Artist}) if($a->{Artist} ne $b->{Artist});
- return ($a->{Title} cmp $b->{Title});
-}
-
-sub cmd
-{
- print "$_[0]\n";
-}