]> git.draconx.ca Git - mpdhacks.git/blobdiff - mpdmenu.pl
mpdmenu: Factor out thumbnail generation.
[mpdhacks.git] / mpdmenu.pl
index d4120f671638732d7129f2a1409280d4de817b6b..9a7c77efe9f4f185dede74aab9cb4e202b3cfb7d 100755 (executable)
@@ -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 = <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");
 
@@ -67,27 +148,27 @@ if (defined $album) {
        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 = {};
                        }
-       
+
                        $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) = (
@@ -97,16 +178,17 @@ if (defined $album) {
                        $_->{Title},
                        $_->{Id},
                );
-       
+
                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);
        }
 } elsif (defined $artist) {
@@ -122,7 +204,7 @@ if (defined $album) {
        while (<$sock>) {
                last if (/^OK/);
                die($_) if (/^ACK/);
-       
+
                if (/^(\w+): (.*)$/) {
                        $file       = $2    if ($1 eq "file");
                        $albums{$2} = $file if ($1 eq "Album");
@@ -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 = <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;
 
@@ -186,19 +264,23 @@ if (defined $album) {
        s/ -Practice Track-//;                        # Fair Heaven
        s/〜世界で一番アナタが好き〜//;               # Pure Heart
        s/〜彼方への哀歌//;                           # 十二幻夢
+       s/ sora no uta ver.//;                       # 美しい星
 
        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((e\.)?piano|english|japanese|inst|tv|without|w\/o|off|back|short|karaoke|game).*//i;
+       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)(\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;
 
@@ -207,13 +289,13 @@ if (defined $album) {
        while (<$sock>) {
                last if (/^OK/);
                die($_) if (/^ACK/);
-       
+
                if (/^(\w+): (.*)$/) {
                        if ($1 eq "file") {
                                push @titles, $entry if (keys(%$entry) > 0);
                                $entry = {};
                        }
-       
+
                        $entry->{$1} = $2;
                }
        }
@@ -221,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 = <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 {
@@ -281,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 = <THUMB>;
-               my $scan  = <THUMB>;
-               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 \"<Song info unavailable>\"");
@@ -309,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");
@@ -339,22 +417,22 @@ 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)."\"");
-       
+
        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)."\"");
 
@@ -368,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 $_;
 }