X-Git-Url: https://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/1e41d7997a92ba1a2a181335358068f883446eea..b0f5f5741b234174958ade58a6e884df8b6b00e4:/mpdexec.pl diff --git a/mpdexec.pl b/mpdexec.pl index 962656f..496c558 100755 --- a/mpdexec.pl +++ b/mpdexec.pl @@ -1,66 +1,167 @@ #!/usr/bin/env perl # -# Copyright © 2012,2019 Nick Bowler +# Copyright © 2012,2019-2020 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, $binary, $ignore_errors, $download); + +sub print_version { + print < 0); +} + +sub print_help { + print_usage(*STDOUT); + print <. +EOF +} + +GetOptions( + 'host|h=s' => \$MPD::host, + 'port|p=s' => \$MPD::port, + + 'quiet|q' => \$quiet, + 'no-quiet' => sub { $quiet = 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; }, + + 'V|version' => sub { print_version(); exit }, + 'H|help' => sub { print_help(); exit }, +) or do { print_usage; exit 1 }; + +my $binfile = *STDOUT; +if ($binary) { + if ($binary ne "-") { + open(my $fh, ">", $binary) or die "failed to open $binary: $!"; + $binfile = $fh; + } } +$quiet = 1 if (defined($binary) && $binary eq ""); + +my $sock = MPD::connect(binmode => ":raw"); -sub mpd_escape { - ($_) = @_; +sub read_binary { + my ($count) = @_; + my $buf; - # 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; + binmode($binfile); - if (/[ \t\\"]/) { - s/[\\"]/\\$&/g; - return "\"$_\""; + return 0 unless ($count); + my $rc = $sock->read($buf, $count) or die "$!"; + if (defined($binary)) { + $binfile->write($buf) or die "$!"; } - return $_; + + return $rc; } sub mpd_exec { - print $sock join(' ', @_), "\n"; + my $downloadseq; + + # special case for "albumart"; if no offset is specified + # (invalid command) we synthesize a sequence of albumart + # commands to retrieve the entire file. + if ($download && $_[0] eq "albumart" && @_ == 2) { + $_[2] = 0; + $downloadseq = 2; + } + + print $sock encode('UTF-8', join(' ', @_), Encode::FB_QUIET), $/; 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) } else { while (<>) { chomp;