]> git.draconx.ca Git - mpdhacks.git/blobdiff - mpdreload.pl
mpdthumb: Fix failure when readpicture/albumart both return data.
[mpdhacks.git] / mpdreload.pl
index c852a2bd4b85e693ab9eb396d0451816d7193a85..8cf010dc250a0f9f8a2f144be2a055bb0c253b04 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/env perl
 #
-# Copyright © 2019-2020 Nick Bowler
+# Copyright © 2019-2021 Nick Bowler
 #
 # Replace the current MPD play queue with a saved playlist, by rearranging
 # existing queue entries when possible.  This avoids losing the current
@@ -26,11 +26,13 @@ use MPDHacks;
 
 my $sock;
 
-# Returns a hash reference containing all tracks in the current play queue.
-# The hash keys are filenames.
+my $pl_current_length;
+
+# Returns a hash reference mapping filenames to an array reference listing
+# the queue IDs for that file in the current play queue.
 sub get_tracks_in_play_queue {
-       my %matches;
-       my $entry;
+       my (%matches, %idmap, $entry);
+       my $pos = -1;
 
        MPD::exec("playlistinfo");
        while (<$sock>) {
@@ -39,23 +41,18 @@ sub get_tracks_in_play_queue {
 
                if (/^(\w+): (.*)$/) {
                        if ($1 eq "file") {
-                               if (exists($matches{$2})) {
-                                       $entry = $matches{$2};
-                               } else {
-                                       $entry = {};
-                                       $matches{$2} = $entry;
-                               }
-                       }
-
-                       if (exists($entry->{$1})) {
-                               $entry->{$1}->{$2} = 1;
-                       } else {
-                               $entry->{$1} = { $2 => 1 }
+                               $entry = $matches{$2} //= [];
+                               $pos++;
+                       } elsif ($1 eq "Id") {
+                               push @$entry, $2;
+                               $idmap{$2} = $pos;
                        }
                }
        }
 
-       return \%matches;
+       $pl_current_length = $pos+1;
+
+       return (\%matches, \%idmap);
 }
 
 # Given an MPD playlist name, returns a reference to an array containing
@@ -129,37 +126,78 @@ if (@ARGV != 1) {
 $sock = MPD::connect();
 
 # Retrieve the current play queue and target play queue.
-my $current = get_tracks_in_play_queue();
+MPD::run("tagtypes", "clear");
+my ($current, $idmap) = get_tracks_in_play_queue();
 my $target = get_playlist_files($ARGV[0]);
 
+my $add_start;
+
+sub load_tracks($$) {
+       my ($seq, $dst) = @_;
+       my ($newlen, $count);
+
+       my $start = $add_start // $seq;
+       my $add_position = $pl_current_length;
+       my $end = $seq+1;
+
+       $dst //= $start;
+
+       MPD::exec("load", $ARGV[0], "$start:$end");
+       MPD::exec("status");
+       MPD::exec("command_list_end");
+
+       while (<$sock>) {
+               last if (/^OK/);
+               die($_) if (/^ACK/);
+
+               if (/^(\w+): (.*)$/) {
+                       if ($1 eq "playlistlength") {
+                               $newlen = int($2);
+                       }
+               }
+       }
+
+       $count = $newlen - $pl_current_length;
+
+       MPD::exec("command_list_begin");
+       if ($newlen > $pl_current_length) {
+               MPD::exec("move", "$add_position:$newlen", $dst)
+                       if ($add_position != $dst);
+       }
+
+       $pl_current_length = $newlen;
+       undef $add_start;
+
+       return $count;
+}
+
 MPD::exec("command_list_begin");
+my ($num_added, $num_failed) = (0, 0);
 for (my $i = 0; $i < @$target; $i++) {
        my $f = $target->[$i];
-       my $ids = $current->{$f}->{Id};
+       my $id = shift @{ $current->{$f} };
 
-       my $id = (keys %$ids)[0];
-       delete $ids->{$id};
+       if (defined $id and defined $add_start) {
+               my $n = $i - $add_start;
+               my $m = load_tracks($i-1, $add_start - $num_failed);
 
-       # Remove tracks with no unused queue IDs
-       delete $current->{$f} unless (keys %$ids > 0);
+               $num_added += $m;
+               $num_failed += $n - $m;
+       }
 
        if (defined $id) {
-               MPD::exec("moveid", $id, $i);
+               # Try not to move tracks already in the right place.
+               MPD::exec("moveid", $id, $i - $num_failed)
+                       if ($i - $num_failed != $idmap->{$id} + $num_added);
        } else {
-               MPD::exec("addid", $f, $i);
+               $add_start //= $i;
        }
 }
 
-# Remove any tracks left from the old play queue.
-foreach (keys %$current) {
-       my $ids = $current->{$_}->{Id};
-       foreach (keys %$ids) {
-               MPD::exec("deleteid", $_);
-       }
-}
+# Now all unwanted tracks from the original playqueue have been moved to the
+# end and can be deleted all at once.
 
-MPD::exec("command_list_end");
-while (<$sock>) {
-       last if /^OK$/;
-       die($_) if /^ACK/;
-}
+my $pos = ($add_start // @$target) - $num_failed;
+MPD::exec("delete", $pos . ":") if map { @$_ } values %$current;
+MPD::exec("load", $ARGV[0], "$add_start:") if defined $add_start;
+MPD::run("command_list_end");