X-Git-Url: https://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/e690203f7feff453cc72a0a1cf032ec135992e08..a93fdf47f9567f0542ef75207e3b5b26362b5620:/mpdmenu.pl diff --git a/mpdmenu.pl b/mpdmenu.pl index b7072cf..9a7c77e 100755 --- a/mpdmenu.pl +++ b/mpdmenu.pl @@ -1,9 +1,20 @@ #!/usr/bin/perl +# +# Copyright © 2008,2010,2012,2019 Nick Bowler +# +# Silly little script to generate an FVWM menu with various bits of MPD +# status information and controls. +# +# 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. use strict; use Getopt::Long; use IO::Socket::INET6; +use Scalar::Util qw(reftype); +use FindBin; use constant { MPD_MJR_MIN => 0, @@ -12,9 +23,78 @@ use constant { }; use utf8; -use encoding 'utf8'; +use open qw(:std :utf8); +binmode(STDOUT, ":utf8"); use Encode; +my $SELF = "$FindBin::Bin/$FindBin::Script"; +my $MUSIC = $ENV{MUSIC} // "/srv/music"; + +# get_item_thumbnails({ options }, file, ...) +# get_item_thumbnails(file, ...) +# +# For each music file listed, obtain a thumbnail (if any) for the +# cover art. +# +# The first argument is a hash reference to control the mode of +# operation; it may be omitted for default options. +# +# get_item_thumbnails({ small => 1 }, ...) - smaller thumbnails +# +# The returned list consists of strings (in the same order as the filename +# arguments) suitable for use directly in FVWM menus; by default the filename +# is bracketed by asterisks (e.g., "*thumbnail.png*"); in small mode it is +# surrounded by % (e.g., "%thumbnail.png%"). If no cover art was found, the +# empty string is returned for that file. +sub get_item_thumbnails { + my @results = (); + my $flags = {}; + my @opts = (); + + $flags = shift if (reftype($_[0]) eq "HASH"); + return @results unless @_; + + my $c = "*"; + if ($flags->{small}) { + push @opts, "--small"; + $c = "%"; + } + + foreach (@_) { + open THUMB, "-|", "$FindBin::Bin/thumbnail.zsh", "--music", + @opts, $_; + my $thumb = ; + chomp $thumb; + + $thumb = "$c$thumb$c" if (-f $thumb); + push @results, $thumb; + close THUMB; + die("thumbnail.zsh failed") if ($?); + } + + return @results; +} + +# 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; +} + sub cmd { print "$_[0]\n"; @@ -49,7 +129,8 @@ $title = decode_utf8($title) if defined($title);; my $sock = new IO::Socket::INET6( PeerAddr => $host, PeerPort => $port, - Proto => 'tcp' + Proto => 'tcp', + Timeout => 2 ) or die("could not open socket: $!.\n"); binmode($sock, ":utf8"); @@ -100,11 +181,12 @@ if (defined $album) { next if (defined $artist && !$accept{albumdir($t_file)}); - $t_artist = sanitise($t_artist); - $t_title = sanitise($t_title); + $t_artist = sanitise($t_artist, 0); + $t_title = sanitise($t_title, 0); my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\"" - ." Exec mpc playid %d", + ." Exec exec $FindBin::Bin/mpdexec.pl" + ." playid %d", $t_trackno, $t_artist, $t_title, $t_id; cmd($cmd); @@ -134,18 +216,14 @@ if (defined $album) { { # work around 'use locale' breaking s///i my $i = 0; use locale; - foreach (sort keys(%albums)) { - my $key = $_; - my $a_album = sanitise($key); - open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh", - "--small", "--music", $albums{$key}; - my $thumb = ; - close THUMB; - die("Incompetent use of thumbnail.zsh") if ($?); + my @keys = sort keys %albums; + my @thumbs = get_item_thumbnails({ small => 1 }, + map { $albums{$_} } @keys); - $thumb =~ s/\n//sg; - $thumb = "%$thumb%" if (-f $thumb); + foreach my $key (@keys) { + my $a_album = sanitise($key, 1); + my $thumb = shift @thumbs; cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i"); @@ -154,7 +232,7 @@ if (defined $album) { cmd("DestroyFunc MakeMenuMPDArt_$i"); cmd("AddToFunc MakeMenuMPDArt_$i + I DestroyMenu MenuMPDArt_$i - + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl " + + I -PipeRead \"exec $SELF " ."--menu MenuMPDArt_$i " ."--album ".shellify($key, 1)." " ."--artist ".shellify($artist, 1)."\""); @@ -173,7 +251,7 @@ if (defined $album) { $menu = "MenuMPDTitle" unless defined $menu; # Open and close brackets. - my ($ob, $cb) = ("[\[~〜<(ー−-]", "[\]~〜>)ー−-]"); + my ($ob, $cb) = ("[\[~〜<〈(ー−-]", "[\]~〜>〉)ー−-]"); $_ = $title; @@ -191,15 +269,18 @@ if (defined $album) { s/\s*-remix-$//; # Otherwise "D-THREAD -remix-" doesn't work right. # Deal with titles like "blah (ABC version)". - s/\s*$ob.*(style|mix|edit|edition|ver\.?|version|カラオケ)$cb?$//i; + s/\s*$ob.*(style|mix|edit|edition|ver\.?|version|melody|カラオケ)$cb?$//i; # 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; # Deal with titles like "blah instrumental". - s/\s+(instrumental|off vocal|short|tv)([\s-]+(size|version|s))?$//i; + s/\s+(instrumental|off vocal|short|tv)([\s-]+(mix|size|version))?$//i; s/\s+without\s+\w+$//i; + # Deal with separate movements in classical pieces. + s/: [IVX]+\..*//; + my $basetitle = $_; my $_basetitle = $basetitle; @@ -222,29 +303,31 @@ if (defined $album) { { # 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]; + } + foreach (sort titlesort @titles) { - my ($t_file, $t_artist, $t_title, $t_id) = ( + 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_artist = sanitise($t_artist); - $t_title = sanitise($t_title); - - open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh", - "--small", "--music", $t_file; - my $thumb = ; - close(THUMB); - die("Incompetent use of thumbnail.zsh") if ($?); + next if (!($t_title =~ m/(\P{Latin}|^)\Q$basetitle\E(\P{Latin}|$)/ || $t_title =~ m/\Q$basetitle\E/i)); - $thumb =~ s/\n//sg; - $thumb = "%$thumb%" if (-f $thumb); + $t_artist = sanitise($t_artist, 1); + $t_title = sanitise($t_title, 1); - cmd("AddToMenu $menu \"$thumb$t_artist - $t_title\" Exec mpc playid $t_id"); + cmd("AddToMenu $menu \"$thumb$t_artist - $t_title\"" + ." Exec exec $FindBin::Bin/mpdexec.pl" + ." playid $t_id"); } } # end use locale workaround } else { @@ -282,26 +365,20 @@ if (defined $album) { } die("Failed data query\n") unless (keys(%entry) > 0); - open THUMB, "-|", "$FVWM/scripts/thumbnail.zsh", - "--image", "--music", $entry{file}; - my $thumb = ; - my $scan = ; - close(THUMB); - die("Incompetent use of thumbnail.zsh") if ($?); - - $thumb =~ s/\n//sg; - $scan =~ s/\n//sg; + my ($thumb) = get_item_thumbnails($entry{file}); + if ($thumb) { + my $cover = mpd_cover_filename("$MUSIC/$entry{file}"); - if (-f $thumb) { - cmd("AddToMenu $menu \"*$thumb*\" " - ."Exec exec gqview ".shellify($scan, 0)); + cmd("AddToMenu $menu \"$thumb\" " + ."Exec exec geeqie ".shellify($cover, 0)); } - cmd("AddToMenu $menu \"Title: ".sanitise($entry{Title})."\" " - ."Popup MenuMPDTitle"); - cmd("AddToMenu $menu \"Artist: ".sanitise($entry{Artist})."\" " - ."Popup MenuMPDArtist"); - cmd("AddToMenu $menu \"Album: ".sanitise($entry{Album})."\" " - ."Popup MenuMPDAlbum"); + + cmd("AddToMenu $menu \"Title: ".sanitise($entry{Title}, 0) + ."\" Popup MenuMPDTitle"); + cmd("AddToMenu $menu \"Artist: ".sanitise($entry{Artist}, 0) + ."\" Popup MenuMPDArtist"); + cmd("AddToMenu $menu \"Album: ".sanitise($entry{Album}, 0) + ."\" Popup MenuMPDAlbum"); cmd("AddToMenu $menu \"\" Nop"); } else { cmd("AddToMenu $menu \"\""); @@ -310,25 +387,25 @@ if (defined $album) { if ($state eq "play" || $state eq "pause") { cmd("AddToMenu $menu \"\t\tNext%$icons/next.svg:16x16%\" " - ."Exec exec mpc next"); + ."Exec exec $FindBin::Bin/mpdexec.pl next"); cmd("AddToMenu $menu \"\t\tPause%$icons/pause.svg:16x16%\" " - ."Exec exec mpc pause") if ($state eq "play"); + ."Exec exec $FindBin::Bin/mpdexec.pl pause"); cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" " - ."Exec exec mpc play") if ($state eq "pause"); + ."Exec exec $FindBin::Bin/mpdexec.pl play"); cmd("AddToMenu $menu \"\t\tStop%$icons/stop.svg:16x16%\" " - ."Exec exec mpc stop"); + ."Exec exec $FindBin::Bin/mpdexec.pl stop"); cmd("AddToMenu $menu \"\t\tPrev%$icons/prev.svg:16x16%\" " - ."Exec exec mpc prev"); + ."Exec exec $FindBin::Bin/mpdexec.pl previous"); } elsif ($state eq "stop") { cmd("AddToMenu $menu \"\t\tPlay%$icons/play.svg:16x16%\" " - ."Exec exec mpc play"); + ."Exec exec $FindBin::Bin/mpdexec.pl play"); } else { die("Unknown MPD state!\n"); } cmd("AddToMenu $menu \"\" Nop"); cmd("AddToMenu $menu \"\t\tShuffle%$icons/shuffle.svg:16x16%\" " - ."Exec exec mpc shuffle"); + ."Exec exec $FindBin::Bin/mpdexec.pl shuffle"); cmd("DestroyMenu MenuMPDTitle"); cmd("AddToMenu MenuMPDTitle DynamicPopUpAction MakeMenuMPDTitle"); @@ -340,14 +417,14 @@ if (defined $album) { cmd("DestroyFunc MakeMenuMPDTitle"); cmd("AddToFunc MakeMenuMPDTitle + I DestroyMenu MenuMPDTitle - + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl " + + I -PipeRead \"exec $SELF " ."--menu MenuMPDTitle " ."--title ".shellify($entry{Title}, 1)."\""); cmd("DestroyFunc MakeMenuMPDAlbum"); cmd("AddToFunc MakeMenuMPDAlbum + I DestroyMenu MenuMPDAlbum - + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl " + + I -PipeRead \"exec $SELF " ."--menu MenuMPDAlbum " ."--album ".shellify($entry{Album}, 1)." " ."--artist ".shellify($entry{Artist}, 1)."\""); @@ -355,7 +432,7 @@ if (defined $album) { cmd("DestroyFunc MakeMenuMPDArtist"); cmd("AddToFunc MakeMenuMPDArtist + I DestroyMenu MenuMPDArtist - + I -PipeRead \"exec $FVWM/scripts/mpdmenu.pl " + + I -PipeRead \"exec $SELF " ."--menu MenuMPDArtist " ."--artist ".shellify($entry{Artist}, 1)."\""); @@ -369,7 +446,8 @@ print $sock "close\n"; sub sanitise { $_ = $_[0]; - s/([\$&@%^*])/\1\1/g; + s/&/&&/g if ($_[1]); + s/([\$@%^*])/\1\1/g; s/"/\\"/g; return $_; }