3 # Copyright © 2019-2021 Nick Bowler
5 # Replace the current MPD play queue with a saved playlist, by rearranging
6 # existing queue entries when possible. This avoids losing the current
7 # player state when the loaded playlist is similar to the current queue.
9 # License GPLv3+: GNU General Public License version 3 or any later version.
10 # This is free software: you are free to change and redistribute it.
11 # There is NO WARRANTY, to the extent permitted by law.
16 use Encode::Locale qw(decode_argv);
17 decode_argv(Encode::FB_CROAK);
19 binmode(STDOUT, ":utf8");
21 use Getopt::Long qw(:config gnu_getopt);
24 use lib "$FindBin::Bin";
29 # Returns a hash reference mapping filenames to an array reference listing
30 # the queue IDs for that file in the current play queue.
31 sub get_tracks_in_play_queue {
32 my (%matches, %idmap, $entry);
35 MPD::exec("playlistinfo");
40 if (/^(\w+): (.*)$/) {
42 $entry = $matches{$2} //= [];
44 } elsif ($1 eq "Id") {
51 return (\%matches, \%idmap);
54 # Given an MPD playlist name, returns a reference to an array containing
55 # (in order) the files in the playlist.
56 sub get_playlist_files {
60 MPD::exec("listplaylist", $plname);
65 if (/^(\w+): (.*)$/) {
78 Copyright © 2019 Nick Bowler
79 License GPLv3+: GNU General Public License version 3 or any later version.
80 This is free software: you are free to change and redistribute it.
81 There is NO WARRANTY, to the extent permitted by law.
86 my ($fh) = (@_, *STDERR);
88 print $fh "Usage: $0 [options] playlist\n";
89 print $fh "Try $0 --help for more information.\n" unless (@_ > 0);
95 This is "mpdreload": a tool to reload a stored playlist into the MPD queue.
98 -h, --host=HOST Connect to the MPD server on HOST, overriding defaults.
99 -p, --port=PORT Connect to the MPD server on PORT, overriding defaults.
100 -V, --version Print a version message and then exit.
101 -H, --help Print this message and then exit.
103 Report bugs to <nbowler\@draconx.ca>
108 'host|h=s' => \$MPD::host,
109 'port|p=s' => \$MPD::port,
111 'V|version' => sub { print_version(); exit },
112 'H|help' => sub { print_help(); exit },
113 ) or do { print_usage(); exit 1};
116 print STDERR "Playlist name is required\n" unless @ARGV;
117 print STDERR "Excess command-line arguments\n" if @ARGV;
119 print_usage(); exit 1
122 $sock = MPD::connect();
124 # Retrieve the current play queue and target play queue.
125 MPD::run("tagtypes", "clear");
126 my ($current, $idmap) = get_tracks_in_play_queue();
127 my $target = get_playlist_files($ARGV[0]);
129 my $end_position = (keys %$current);
136 my $start = $add_start // $seq;
139 my $add_position = $end_position;
140 MPD::exec("load", $ARGV[0], "$start:$end");
141 $end_position += $end - $start;
142 MPD::exec("move", "$add_position:$end_position", "$start")
143 if ($add_position != $start);
148 MPD::exec("command_list_begin");
149 for (my $i = 0; $i < @$target; $i++) {
150 my $f = $target->[$i];
151 my $id = shift @{ $current->{$f} };
153 load_tracks($i - 1) if (defined $id and defined $add_start);
156 # Try not to move tracks already in the right place.
157 MPD::exec("moveid", $id, $i)
158 if ($i != $idmap->{$id} + $num_added);
165 # Now all unwanted tracks from the original playqueue have been moved to the
166 # end and can be deleted all at once.
168 my $pos = $add_start // @$target;
169 MPD::exec("delete", $pos . ":") if map { @$_ } values %$current;
170 MPD::exec("load", $ARGV[0], "$add_start:") if defined $add_start;
171 MPD::run("command_list_end");