X-Git-Url: https://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/1e41d7997a92ba1a2a181335358068f883446eea..HEAD:/mpdexec.pl diff --git a/mpdexec.pl b/mpdexec.pl index 962656f..e83146b 100755 --- a/mpdexec.pl +++ b/mpdexec.pl @@ -1,71 +1,185 @@ #!/usr/bin/env perl # -# Copyright © 2012,2019 Nick Bowler +# Copyright © 2012,2019-2021 Nick Bowler # -# Simple program to send a command to MPD. Each command-line argument is -# quoted as necessary so it appears as a single argument at the protocol -# level. The result is printed to standard output. +# Send commands to MPD. Each command-line argument is quoted as necessary +# so it appears as a single argument at the protocol level. The result is +# printed to standard output. # -# License WTFPL2: Do What The Fuck You Want To Public License, version 2. -# This is free software: you are free to do what the fuck you want to. +# License GPLv3+: GNU General Public License version 3 or any later version. +# This is free software: you are free to change and redistribute it. # There is NO WARRANTY, to the extent permitted by law. use strict; use utf8; +use Encode qw(decode encode); use Encode::Locale qw(decode_argv); decode_argv(Encode::FB_CROAK); binmode(STDOUT, ":utf8"); binmode(STDIN, ":utf8"); -use IO::Socket::INET6; -my $host = $ENV{MPD_HOST} // "localhost"; -my $port = $ENV{MPD_PORT} // 6600; +use Getopt::Long qw(:config gnu_getopt); -my $sock = new IO::Socket::INET6( - PeerAddr => $host, - PeerPort => $port, - Proto => 'tcp', -) or die "failed to connect to MPD: $!"; -binmode($sock, ":utf8"); +use FindBin; +use lib "$FindBin::Bin"; +use MPDHacks; -if (!(<$sock> =~ /^OK MPD ([0-9]+)\.([0-9]+)\.([0-9]+)$/)) { - die "MPD failed to announce version: $!"; +my ($quiet, $verbose, $binary, $ignore_errors, $download); + +sub print_version { + print < 0); +} + +sub print_help { + print_usage(*STDOUT); + print <. +EOF } -sub mpd_escape { - ($_) = @_; +GetOptions( + 'host|h=s' => \$MPD::host, + 'port|p=s' => \$MPD::port, + + 'quiet|q' => \$quiet, + 'no-quiet' => sub { $quiet = 0; }, + 'verbose|v' => \$verbose, + 'no-verbose' => sub { $verbose = 0 }, + 'binary|b:s' => \$binary, + 'no-binary' => sub { $binary = undef; }, + + 'download' => \$download, + 'no-download' => sub { $download = 0; }, + + 'ignore-errors' => \$ignore_errors, + 'no-ignore-errors' => sub { $ignore_errors = 0; }, - # No way to encode literal newlines in the protocol, so we convert - # any newlines in the arguments into a space, which can help with - # shell quoting. - s/\n/ /g; + 'V|version' => sub { print_version(); exit }, + 'H|help' => sub { print_help(); exit }, +) or do { print_usage; exit 1 }; - if (/[ \t\\"]/) { - s/[\\"]/\\$&/g; - return "\"$_\""; +my $binfile = *STDOUT; +if ($binary) { + if ($binary ne "-") { + open(my $fh, ">", $binary) or die "failed to open $binary: $!"; + $binfile = $fh; } - return $_; } +$quiet = 1 if (defined($binary) && $binary eq ""); +$verbose = 0 if ($quiet); + +my $sock = MPD::connect(binmode => ":raw"); + +sub read_binary { + my ($count) = @_; + my $buf; + + binmode($binfile); + return 0 unless ($count); + my $rc = $sock->read($buf, $count) or die "$!"; + if (defined($binary)) { + $binfile->write($buf) or die "$!"; + } + + return $rc; +} + +sub mpd_send { + my $cmd = encode('UTF-8', join(' ', @_), Encode::FB_QUIET); + print "$cmd\n" if ($verbose); + print $sock $cmd, $/; +} + +my %downloadcmds = map { $_ => 1 } ( "albumart", "readpicture" ); sub mpd_exec { - print $sock join(' ', @_), "\n"; + my $downloadseq; + + # special case for "albumart" and "readpicture"; if no offset is + # specified (invalid command) we synthesize a sequence of albumart + # commands to retrieve the entire file. + if ($download && $downloadcmds{$_[0]} && @_ == 2) { + $_[2] = 0; + $downloadseq = 2; + } + + mpd_send(@_); while (<$sock>) { - last if (/^OK/); - print; - exit 1 if (/^ACK/); + $_ = decode('UTF_8', $_, Encode::FB_QUIET); + + if (/^OK/) { + last unless ($downloadseq); + print $sock encode('UTF-8', + join(' ', @_), + Encode::FB_QUIET), $/; + next; + } + + if (/^binary: ([0-9]+)$/) { + print unless ($quiet); + read_binary($1); + + if ($downloadseq) { + $downloadseq = 0 unless ($1); + $_[$downloadseq] += $1; + } + } elsif (/^ACK/) { + *STDOUT->flush; + print STDERR; + last if ($ignore_errors); + exit 1; + } else { + print unless ($quiet); + } } } if (@ARGV) { - mpd_exec(map { mpd_escape($_) } @ARGV) + mpd_exec(map { MPD::escape } @ARGV) +} elsif ($ignore_errors) { + while (<>) { chomp; mpd_exec($_); } } else { - while (<>) { - chomp; - mpd_exec($_); - } + mpd_send("command_list_begin"); + while (<>) { chomp; mpd_send($_); } + mpd_exec("command_list_end"); } print $sock "close\n";