]> git.draconx.ca Git - mpdhacks.git/commitdiff
mpdmenu: Use MBIDs for track matching instead of name matching.
authorNick Bowler <nbowler@draconx.ca>
Sun, 30 Jun 2019 14:17:05 +0000 (10:17 -0400)
committerNick Bowler <nbowler@draconx.ca>
Sun, 30 Jun 2019 14:34:19 +0000 (10:34 -0400)
Instead of guessing based on titles, match tracks by their work MBID.

This makes the title menu significantly simpler as we remove all the
title-specific special cases.  This approach gives results pretty close
to what we got originally (much better in some cases), but currently
things like different symphony movements which used to show up in the
menu are not handled.

mpdmenu.pl

index 5968843a0bfb5c8092dc71bafccd08eb06b6ca08..8e597e7f9c704c3b585ecd29043aace22fe61b35 100755 (executable)
@@ -37,7 +37,7 @@ my $host  = $ENV{MPD_HOST} // "localhost";
 my $port  = $ENV{MPD_PORT} // "6600";
 my $sock;
 
-my ($albumid, $title);
+my ($albumid, $trackid);
 my %artistids;
 my $menu;
 my $mode = "top";
@@ -252,9 +252,10 @@ sub top_track_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");
+       @submenu = make_submenu("$menu-$mbid", "--track-id=$mbid") if $mbid;
 
        fvwm_cmd("AddToMenu", $menu,
                 fvwm_label_escape("Title:\t$entry->{Title}"),
@@ -292,6 +293,72 @@ sub top_track_album {
                 @submenu);
 }
 
+# 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) = @_;
+       my %source;
+       my %matches;
+       my $entry;
+
+       return \%matches unless ($mbid);
+       mpd_exec("search", "(MUSICBRAINZ_RELEASETRACKID == \"$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;
+}
+
 # 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 {
@@ -430,6 +497,7 @@ Options:
   -m, --menu=NAME   Set the name of the generated menu.
   --album-id=MBID   Generate a menu for the given release MBID.
   --artist-id=MBID  Generate a menu for the given artist MBID.
+  --track-id=MBID   Generate a menu for the given track MBID.
   -V, --version     Print a version message and then exit.
   -H, --help        Print this message and then exit.
 EOF
@@ -442,7 +510,7 @@ GetOptions(
 
        'artist-id=s' => sub { $artistids{$_[1]} = 1; $mode = "artist"; },
        'album-id=s'  => sub { $albumid = $_[1]; $mode = "album"; },
-       'title=s'     => sub { $title = $_[1]; $mode = "track"; },
+       'track-id=s'  => sub { $trackid = $_[1]; $mode = "track"; },
 
        'V|version'   => sub { print_version(); exit },
        'H|help'      => sub { print_help(); exit },
@@ -596,114 +664,40 @@ if ($mode eq "top") {
                         @submenu);
        }
 } elsif ($mode eq "track") {
-       # Create a title menu.
-       my @titles;
-       my $entry;
-
-       $menu = "MenuMPDTitle" unless defined $menu;
-
-       # Open and close brackets.
-       my ($ob, $cb) = ("[\[~〜<〈(ー−-]", "[\]~〜>〉)ー−-]");
-
-       $_ = $title;
-
-       # 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.//;                       # 美しい星
-
-       s/\s*-remix-$//; # Otherwise "D-THREAD -remix-" doesn't work right.
+       my $matches = get_tracks_by_track_mbid($trackid);
+       my @notqueued;
 
-       # Deal with titles like "blah (ABC version)".
-       s/\s*$ob.*(style|mix|edit|edition|ver\.?|version|melody|カラオケ)$cb?$//i;
+       $menu //= "MenuMPDTrack";
 
-       # 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-]+(mix|size|version))?$//i;
-       s/\s+without\s+\w+$//i;
-
-       # Deal with separate movements in classical pieces.
-       s/: [IVX]+\..*//;
-
-       my $basetitle  = $_;
-       my $_basetitle = $basetitle;
+       my @files = sort { datesort($matches, $a, $b) } keys %$matches;
+       my @thumbs = get_item_thumbnails({ small => 1 }, @files);
 
-       $_basetitle =~ s/"/\\"/g;
-       print $sock "playlistsearch title \"$_basetitle\"\n";
-       while (<$sock>) {
-               last if (/^OK/);
-               die($_) if (/^ACK/);
+       fvwm_cmd("AddToMenu", $menu);
+       fvwm_cmd("+", "No tracks found", "Title") unless @files;
+       foreach my $file (@files) {
+               my $entry = $matches->{$file};
+               $entry->{thumb} = shift @thumbs;
 
-               if (/^(\w+): (.*)$/) {
-                       if ($1 eq "file") {
-                               push @titles, $entry if (keys(%$entry) > 0);
-                               $entry = {};
+               unless (exists $entry->{Id}) {
+                       my ($id) = get_ids_by_filename($file);
+                       if (defined $id) {
+                               $entry->{Id} = $id;
+                       } else {
+                               push @notqueued, $entry;
+                               next;
                        }
-
-                       $entry->{$1} = $2;
                }
-       }
-       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";
-}