23 # Global hash for tracking what is to be "accepted".
26 my $FVWM = (defined $ENV{FVWM_USERDIR}) ? $ENV{FVWM_USERDIR}
27 : $ENV{HOME}."/.fvwm";
28 my $icons = "$FVWM/icons";
30 # Default values for stuff.
31 my ($album, $artist, $title, $menu) = (undef, undef, undef, undef);
32 my $host = (defined $ENV{MPD_HOST}) ? $ENV{MPD_HOST} : "localhost";
33 my $port = (defined $ENV{MPD_PORT}) ? $ENV{MPD_PORT} : "6600";
36 'host|h=s' => \&host, # Host that MPD is running on.
37 'port|p=s' => \&port, # Port that MPD is listening on.
38 'menu|m=s' => \$menu, # Name of the menu to create.
39 'album=s' => \$album, # Album to get tracks from
40 'artist=s' => \$artist, # Artist to limit results to
41 'title=s' => \$title, # Title to create menu for
44 $album = decode_utf8($album) if defined($album);
45 $artist = decode_utf8($artist) if defined($artist);
46 $title = decode_utf8($title) if defined($title);;
49 my $sock = new IO::Socket::INET6(
53 ) or die("could not open socket: $!.\n");
54 binmode($sock, ":utf8");
56 die("could not connect to MPD: $!.\n")
57 if (!(<$sock> =~ /^OK MPD ([0-9]+)\.([0-9]+)\.([0-9]+)$/));
59 die("MPD version $1.$2.$3 insufficient.\n")
60 if ( ($1 < MPD_MJR_MIN)
61 || ($1 == MPD_MJR_MIN && $2 < MPD_MNR_MIN)
62 || ($1 == MPD_MJR_MIN && $2 == MPD_MNR_MIN && $3 < MPD_REV_MIN));
65 # Create an album menu.
69 $menu = "MenuMPDAlbum" unless defined $menu;
72 print $sock "playlistfind album \"$album\"\n";
77 if (/^(\w+): (.*)$/) {
79 if (keys(%$entry) > 0) {
80 addalbumentry(\@playlist, $entry)
89 addalbumentry(\@playlist, $entry) if (keys(%$entry) > 0);
91 die("No tracks found.\n") if (!@playlist);
92 foreach (sort albumsort @playlist) {
93 my ($t_file, $t_trackno, $t_artist, $t_title, $t_id) = (
101 next if (defined $artist && !$accept{albumdir($t_file)});
103 $t_artist = sanitise($t_artist, 0);
104 $t_title = sanitise($t_title, 0);
106 my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\""
107 ." Exec exec $FVWM/scripts/mpdexec.pl"
109 $t_trackno, $t_artist, $t_title, $t_id;
113 } elsif (defined $artist) {
114 # Create an artist menu.
117 my $quoteartist = $artist;
119 $menu = "MenuMPDArtist" unless defined $menu;
121 $quoteartist =~ s/"/\\"/g;
122 print $sock "playlistfind artist \"$quoteartist\"\n";
127 if (/^(\w+): (.*)$/) {
128 $file = $2 if ($1 eq "file");
129 $albums{$2} = $file if ($1 eq "Album");
133 die("No albums found.\n") if (!keys(%albums));
135 { # work around 'use locale' breaking s///i
138 foreach (sort keys(%albums)) {
140 my $a_album = sanitise($key, 1);
142 open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
143 "--small", "--music", $albums{$key};
146 die("Incompetent use of thumbnail.zsh") if ($?);
149 $thumb = "%$thumb%" if (-f $thumb);
151 cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i");
153 cmd("AddToMenu MenuMPDArt_$i DynamicPopUpAction MakeMenuMPDArt_$i");
155 cmd("DestroyFunc MakeMenuMPDArt_$i");
156 cmd("AddToFunc MakeMenuMPDArt_$i
157 + I DestroyMenu MenuMPDArt_$i
158 + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
159 ."--menu MenuMPDArt_$i "
160 ."--album ".shellify($key, 1)." "
161 ."--artist ".shellify($artist, 1)."\"");
163 cmd("AddToFunc KillMenuMPD I DestroyMenu MenuMPDArt_$i");
164 cmd("AddToFunc KillMenuMPD I DestroyFunc MakeMenuMPDArt_$i");
168 } # end use locale workaround
169 } elsif (defined $title) {
170 # Create a title menu.
174 $menu = "MenuMPDTitle" unless defined $menu;
176 # Open and close brackets.
177 my ($ob, $cb) = ("[\[~〜<〈(ー−-]", "[\]~〜>〉)ー−-]");
181 # Deal with specific cases.
182 s/ちいさな(?=ヘミソフィア)//; # ヘミソフィア
183 s/ "mix on air flavor" dear EIKO SHIMAMIYA//; # Spiral wind
184 s/ "So,you need me" Style//; # I need you
185 s/ ::Symphony Second movement:://; # Disintegration
186 s/-\[instrumental\]//; # 青い果実
187 s/ -Practice Track-//; # Fair Heaven
188 s/〜世界で一番アナタが好き〜//; # Pure Heart
190 s/ sora no uta ver.//; # 美しい星
192 s/\s*-remix-$//; # Otherwise "D-THREAD -remix-" doesn't work right.
194 # Deal with titles like "blah (ABC version)".
195 s/\s*$ob.*(style|mix|edit|edition|ver\.?|version|melody|カラオケ)$cb?$//i;
197 # Deal with titles like "blah (without XYZ)".
198 s/\s*$ob\s*((e\.)?piano|english|japanese|inst|tv|without|w\/o|off|back|short|karaoke|game).*//i;
200 # Deal with titles like "blah instrumental".
201 s/\s+(instrumental|off vocal|short|tv)([\s-]+(mix|size|version))?$//i;
202 s/\s+without\s+\w+$//i;
204 # Deal with separate movements in classical pieces.
208 my $_basetitle = $basetitle;
210 $_basetitle =~ s/"/\\"/g;
211 print $sock "playlistsearch title \"$_basetitle\"\n";
216 if (/^(\w+): (.*)$/) {
218 push @titles, $entry if (keys(%$entry) > 0);
225 push @titles, $entry if (keys(%$entry) > 0);
227 { # work around 'use locale' breaking s///i
229 foreach (sort titlesort @titles) {
230 my ($t_file, $t_artist, $t_title, $t_id) = (
237 # MPD searches are case-insensitive.
238 next if (!($t_title =~ m/(\P{Latin}|^)\Q$basetitle\E(\P{Latin}|$)/ || $t_title =~ m/\Q$basetitle\E/i));
240 $t_artist = sanitise($t_artist, 1);
241 $t_title = sanitise($t_title, 1);
243 open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
244 "--small", "--music", $t_file;
247 die("Incompetent use of thumbnail.zsh") if ($?);
250 $thumb = "%$thumb%" if (-f $thumb);
252 cmd("AddToMenu $menu \"$thumb$t_artist - $t_title\""
253 ." Exec exec $FVWM/scripts/mpdexec.pl"
256 } # end use locale workaround
259 my ($state, $songid) = (undef, undef);
262 $menu = "MenuMPD" unless defined $menu;
264 print $sock "status\n";
269 if (/^(\w+): (.*)$/) {
270 $state = $2 if ($1 eq "state");
271 $songid = $2 if ($1 eq "songid");
274 die("Failed status query\n") unless (defined $state);
276 cmd("AddToMenu $menu Playing Title") if ($state eq "play");
277 cmd("AddToMenu $menu Paused Title") if ($state eq "pause");
278 cmd("AddToMenu $menu Stopped Title") if ($state eq "stop");
280 if (defined $songid) {
281 print $sock "playlistid $songid\n";
286 if (/^(\w+): (.*)$/) {
290 die("Failed data query\n") unless (keys(%entry) > 0);
292 open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh",
293 "--image", "--music", $entry{file};
297 die("Incompetent use of thumbnail.sh") if ($?);
303 cmd("AddToMenu $menu \"*$thumb*\" "
304 ."Exec exec geeqie ".shellify($scan, 0));
306 cmd("AddToMenu $menu \"Title: ".sanitise($entry{Title}, 0)
307 ."\" Popup MenuMPDTitle");
308 cmd("AddToMenu $menu \"Artist: ".sanitise($entry{Artist}, 0)
309 ."\" Popup MenuMPDArtist");
310 cmd("AddToMenu $menu \"Album: ".sanitise($entry{Album}, 0)
311 ."\" Popup MenuMPDAlbum");
312 cmd("AddToMenu $menu \"\" Nop");
314 cmd("AddToMenu $menu \"<Song info unavailable>\"");
315 cmd("AddToMenu $menu \"\" Nop");
318 if ($state eq "play" || $state eq "pause") {
319 cmd("AddToMenu $menu \"\t\tNext%$icons/next.svg:16x16%\" "
320 ."Exec exec mpc next");
321 cmd("AddToMenu $menu \"\t\tPause%$icons/pause.svg:16x16%\" "
322 ."Exec exec mpc pause") if ($state eq "play");
323 cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
324 ."Exec exec mpc play") if ($state eq "pause");
325 cmd("AddToMenu $menu \"\t\tStop%$icons/stop.svg:16x16%\" "
326 ."Exec exec mpc stop");
327 cmd("AddToMenu $menu \"\t\tPrev%$icons/prev.svg:16x16%\" "
328 ."Exec exec mpc prev");
329 } elsif ($state eq "stop") {
330 cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" "
331 ."Exec exec mpc play");
333 die("Unknown MPD state!\n");
336 cmd("AddToMenu $menu \"\" Nop");
337 cmd("AddToMenu $menu \"\t\tShuffle%$icons/shuffle.svg:16x16%\" "
338 ."Exec exec mpc shuffle");
340 cmd("DestroyMenu MenuMPDTitle");
341 cmd("AddToMenu MenuMPDTitle DynamicPopUpAction MakeMenuMPDTitle");
342 cmd("DestroyMenu MenuMPDArtist");
343 cmd("AddToMenu MenuMPDArtist DynamicPopUpAction MakeMenuMPDArtist");
344 cmd("DestroyMenu MenuMPDAlbum");
345 cmd("AddToMenu MenuMPDAlbum DynamicPopUpAction MakeMenuMPDAlbum");
347 cmd("DestroyFunc MakeMenuMPDTitle");
348 cmd("AddToFunc MakeMenuMPDTitle
349 + I DestroyMenu MenuMPDTitle
350 + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
351 ."--menu MenuMPDTitle "
352 ."--title ".shellify($entry{Title}, 1)."\"");
354 cmd("DestroyFunc MakeMenuMPDAlbum");
355 cmd("AddToFunc MakeMenuMPDAlbum
356 + I DestroyMenu MenuMPDAlbum
357 + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
358 ."--menu MenuMPDAlbum "
359 ."--album ".shellify($entry{Album}, 1)." "
360 ."--artist ".shellify($entry{Artist}, 1)."\"");
362 cmd("DestroyFunc MakeMenuMPDArtist");
363 cmd("AddToFunc MakeMenuMPDArtist
364 + I DestroyMenu MenuMPDArtist
365 + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl "
366 ."--menu MenuMPDArtist "
367 ."--artist ".shellify($entry{Artist}, 1)."\"");
369 cmd("DestroyFunc KillMenuMPD");
370 cmd("AddToFunc KillMenuMPD I Nop");
374 print $sock "close\n";
387 my ($playlist, $entry) = @_;
389 push(@$playlist, $entry);
391 if (defined $artist && $artist eq $entry->{Artist}) {
392 my $albumdir = albumdir($entry->{file});
393 $accept{$albumdir} = 1;
401 $file =~ s:(/Disk [0-9]+[^/]*)?/[^/]*$::;
407 return ($a->{Disc} <=> $b->{Disc}) if ($a->{Disc} != $b->{Disc});
408 return ($a->{Track} <=> $b->{Track});
413 return ($a->{Album} cmp $b->{Album}) if($a->{Album} ne $b->{Album});
414 return ($a->{Artist} cmp $b->{Artist}) if($a->{Artist} ne $b->{Artist});
415 return ($a->{Title} cmp $b->{Title});
420 my ($str, $quoted) = @_;