]> git.draconx.ca Git - mpdhacks.git/blob - mpdexec.pl
72f81fe3b6336cbfcfa4f88d4299f7369eeb683a
[mpdhacks.git] / mpdexec.pl
1 #!/usr/bin/env perl
2 #
3 # Copyright © 2012,2019-2021 Nick Bowler
4 #
5 # Send commands to MPD.  Each command-line argument is quoted as necessary
6 # so it appears as a single argument at the protocol level.  The result is
7 # printed to standard output.
8 #
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.
12
13 use strict;
14
15 use utf8;
16
17 use Encode qw(decode encode);
18 use Encode::Locale qw(decode_argv);
19 decode_argv(Encode::FB_CROAK);
20
21 binmode(STDOUT, ":utf8");
22 binmode(STDIN, ":utf8");
23
24 use Getopt::Long qw(:config gnu_getopt);
25
26 use FindBin;
27 use lib "$FindBin::Bin";
28 use MPDHacks;
29
30 my ($quiet, $binary, $ignore_errors, $download);
31
32 sub print_version {
33         print <<EOF
34 mpdexec.pl 0.8
35 Copyright © 2021 Nick Bowler
36 License GPLv3+: GNU General Public License version 3 or any later version.
37 This is free software: you are free to change and redistribute it.
38 There is NO WARRANTY, to the extent permitted by law.
39 EOF
40 }
41
42 sub print_usage {
43         my ($fh) = (@_, *STDERR);
44
45         print $fh "Usage: $0 [options] [command ...]\n";
46         print $fh "Try $0 --help for more information.\n" unless (@_ > 0);
47 }
48
49 sub print_help {
50         print_usage(*STDOUT);
51         print <<EOF
52 This is "mpdexec": a tool to send simple commands to MPD.
53
54 Options:
55   -h, --host=HOST   Connect to the MPD server on HOST, overriding defaults.
56   -p, --port=PORT   Connect to the MPD server on PORT, overriding defaults.
57   -q, --quiet       Do not output any response messages.  Only errors (on
58                     standard error) or binary data (if enabled) are output.
59   -b, --binary[=FILE]
60                     Output raw binary response data, which is normally not
61                     written.  If FILE is specified, the data is written there.
62                     Otherwise, --quiet is automatically enabled and the data
63                     goes to standard output.
64   --download        Enable automatic sequencing of albumart and readpicture
65                     commands; if this option is specified, such commands
66                     without offsets will be expanded into multiple commands
67                     in order to download the entire file.
68   --ignore-errors   In batch mode, continue submitting commands after errors.
69   -V, --version     Print a version message and then exit.
70   -H, --help        Print this message and then exit.
71
72 Report bugs to <nbowler\@draconx.ca>.
73 EOF
74 }
75
76 GetOptions(
77         'host|h=s'         => \$MPD::host,
78         'port|p=s'         => \$MPD::port,
79
80         'quiet|q'          => \$quiet,
81         'no-quiet'         => sub { $quiet = 0; },
82         'binary|b:s'       => \$binary,
83         'no-binary'        => sub { $binary = undef; },
84
85         'download'         => \$download,
86         'no-download'      => sub { $download = 0; },
87
88         'ignore-errors'    => \$ignore_errors,
89         'no-ignore-errors' => sub { $ignore_errors = 0; },
90
91         'V|version'        => sub { print_version(); exit },
92         'H|help'           => sub { print_help(); exit },
93 ) or do { print_usage; exit 1 };
94
95 my $binfile = *STDOUT;
96 if ($binary) {
97         if ($binary ne "-") {
98                 open(my $fh, ">", $binary) or die "failed to open $binary: $!";
99                 $binfile = $fh;
100         }
101 }
102 $quiet = 1 if (defined($binary) && $binary eq "");
103
104 my $sock = MPD::connect(binmode => ":raw");
105
106 sub read_binary {
107         my ($count) = @_;
108         my $buf;
109
110         binmode($binfile);
111
112         return 0 unless ($count);
113         my $rc = $sock->read($buf, $count) or die "$!";
114         if (defined($binary)) {
115                 $binfile->write($buf) or die "$!";
116         }
117
118         return $rc;
119 }
120
121 my %downloadcmds = map { $_ => 1 } ( "albumart", "readpicture" );
122 sub mpd_exec {
123         my $downloadseq;
124
125         # special case for "albumart" and "readpicture"; if no offset is
126         # specified (invalid command) we synthesize a sequence of albumart
127         # commands to retrieve the entire file.
128         if ($download && $downloadcmds{$_[0]} && @_ == 2) {
129                 $_[2] = 0;
130                 $downloadseq = 2;
131         }
132
133         print $sock encode('UTF-8', join(' ', @_), Encode::FB_QUIET), $/;
134         while (<$sock>) {
135                 $_ = decode('UTF_8', $_, Encode::FB_QUIET);
136
137                 if (/^OK/) {
138                         last unless ($downloadseq);
139                         print $sock encode('UTF-8',
140                                            join(' ', @_),
141                                            Encode::FB_QUIET), $/;
142                         next;
143                 }
144
145                 if (/^binary: ([0-9]+)$/) {
146                         print unless ($quiet);
147                         read_binary($1);
148
149                         if ($downloadseq) {
150                                 $downloadseq = 0 unless ($1);
151                                 $_[$downloadseq] += $1;
152                         }
153                 } elsif (/^ACK/) {
154                         *STDOUT->flush;
155                         print STDERR;
156                         last if ($ignore_errors);
157                         exit 1;
158                 } else {
159                         print unless ($quiet);
160                 }
161         }
162 }
163
164 if (@ARGV) {
165         mpd_exec(map { MPD::escape($_) } @ARGV)
166 } else {
167         while (<>) {
168                 chomp;
169                 mpd_exec($_);
170         }
171 }
172
173 print $sock "close\n";
174 close $sock;