X-Git-Url: https://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/8a39a0fd4ed8d545759e07937fa4c66f5d324987..f0e74c3a1fc44153fe80961ff7de3b4a0786e5ea:/mpdreload.pl diff --git a/mpdreload.pl b/mpdreload.pl index c852a2b..66bb39d 100755 --- a/mpdreload.pl +++ b/mpdreload.pl @@ -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,11 @@ use MPDHacks; my $sock; -# Returns a hash reference containing all tracks in the current play queue. -# The hash keys are filenames. +# 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 +39,16 @@ 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; + return (\%matches, \%idmap); } # Given an MPD playlist name, returns a reference to an array containing @@ -129,37 +122,50 @@ 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 $end_position = (keys %$current); +my $num_added = 0; +my $add_start; + +sub load_tracks($) { + my ($seq) = @_; + + my $start = $add_start // $seq; + my $end = $seq+1; + + my $add_position = $end_position; + MPD::exec("load", $ARGV[0], "$start:$end"); + $end_position += $end - $start; + MPD::exec("move", "$add_position:$end_position", "$start") + if ($add_position != $start); + + undef $add_start; +} + MPD::exec("command_list_begin"); for (my $i = 0; $i < @$target; $i++) { my $f = $target->[$i]; - my $ids = $current->{$f}->{Id}; - - my $id = (keys %$ids)[0]; - delete $ids->{$id}; + my $id = shift @{ $current->{$f} }; - # Remove tracks with no unused queue IDs - delete $current->{$f} unless (keys %$ids > 0); + load_tracks($i - 1) if (defined $id and defined $add_start); if (defined $id) { - MPD::exec("moveid", $id, $i); + # Try not to move tracks already in the right place. + MPD::exec("moveid", $id, $i) + if ($i != $idmap->{$id} + $num_added); } else { - MPD::exec("addid", $f, $i); + $add_start //= $i; + $num_added++; } } -# 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; +MPD::exec("delete", $pos . ":") if map { @$_ } values %$current; +MPD::exec("load", $ARGV[0], "$add_start:") if defined $add_start; +MPD::run("command_list_end");