X-Git-Url: https://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/e49c1f846176562de0978ba1cd445b50b1094a2d..HEAD:/mpdreload.pl diff --git a/mpdreload.pl b/mpdreload.pl index fcbbcc3..8cf010d 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 @@ -17,8 +17,6 @@ use Encode::Locale qw(decode_argv); decode_argv(Encode::FB_CROAK); binmode(STDOUT, ":utf8"); -use IO::Socket::INET6; -use IO::Socket::UNIX; use Getopt::Long qw(:config gnu_getopt); @@ -26,48 +24,35 @@ use FindBin; use lib "$FindBin::Bin"; use MPDHacks; -my $host = $ENV{MPD_HOST} // "localhost"; -my $port = $ENV{MPD_PORT} // 6600; my $sock; -# Submit a command to the MPD server; each argument to this function -# is quoted and sent as a single argument to MPD. -sub mpd_exec { - my $cmd = join(' ', map { MPD::escape } @_); +my $pl_current_length; - print $sock "$cmd\n"; -} - -# 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"); + MPD::exec("playlistinfo"); 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; - } - } - - 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 @@ -76,7 +61,7 @@ sub get_playlist_files { my ($plname) = @_; my @files; - mpd_exec("listplaylist", $plname); + MPD::exec("listplaylist", $plname); while (<$sock>) { last if /^OK/; die($_) if /^ACK/; @@ -102,10 +87,10 @@ EOF } sub print_usage { - my $fh = $_[1] // *STDERR; + my ($fh) = (@_, *STDERR); print $fh "Usage: $0 [options] playlist\n"; - print "Try $0 --help for more information.\n" unless (@_ > 0); + print $fh "Try $0 --help for more information.\n" unless (@_ > 0); } sub print_help { @@ -124,8 +109,8 @@ EOF } GetOptions( - 'host|h=s' => \$host, - 'port|p=s' => \$port, + 'host|h=s' => \$MPD::host, + 'port|p=s' => \$MPD::port, 'V|version' => sub { print_version(); exit }, 'H|help' => sub { print_help(); exit }, @@ -138,54 +123,81 @@ if (@ARGV != 1) { print_usage(); exit 1 }; -# Connect to MPD. -if ($host =~ /^[@\/]/) { - $host =~ s/^@/\0/; - $sock = new IO::Socket::UNIX(Type => SOCK_STREAM(), Peer => $host); -} else { - $sock = new IO::Socket::INET6(PeerAddr => $host, - PeerPort => $port, - Proto => 'tcp'); -} -$sock or die "failed to connect to MPD: $!"; -binmode($sock, ":utf8"); - -if (!(<$sock> =~ /^OK MPD ([0-9]+)\.([0-9]+)\.([0-9]+)$/)) { - die "MPD failed to announce version: $!"; -} +$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]); -mpd_exec("command_list_begin"); +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");