]> git.draconx.ca Git - mpdhacks.git/blob - mpdexec.pl
mpdthumb: Fix failure when readpicture/albumart both return data.
[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, $verbose, $binary, $ignore_errors, $download);
31
32 sub print_version {
33         print <<EOF
34 mpdexec.pl 0.9
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   -v, --verbose     Print commands as they are submitted.  If both --quiet
60                     and --verbose are specified, --verbose has no effect.
61   -b, --binary[=FILE]
62                     Output raw binary response data, which is normally not
63                     written.  If FILE is specified, the data is written there.
64                     Otherwise, --quiet is automatically enabled and the data
65                     goes to standard output.
66   --download        Enable automatic sequencing of albumart and readpicture
67                     commands; if this option is specified, such commands
68                     without offsets will be expanded into multiple commands
69                     in order to download the entire file.
70   --ignore-errors   In batch mode, continue submitting commands after errors.
71   -V, --version     Print a version message and then exit.
72   -H, --help        Print this message and then exit.
73
74 Report bugs to <nbowler\@draconx.ca>.
75 EOF
76 }
77
78 GetOptions(
79         'host|h=s'         => \$MPD::host,
80         'port|p=s'         => \$MPD::port,
81
82         'quiet|q'          => \$quiet,
83         'no-quiet'         => sub { $quiet = 0; },
84         'verbose|v'        => \$verbose,
85         'no-verbose'       => sub { $verbose = 0 },
86         'binary|b:s'       => \$binary,
87         'no-binary'        => sub { $binary = undef; },
88
89         'download'         => \$download,
90         'no-download'      => sub { $download = 0; },
91
92         'ignore-errors'    => \$ignore_errors,
93         'no-ignore-errors' => sub { $ignore_errors = 0; },
94
95         'V|version'        => sub { print_version(); exit },
96         'H|help'           => sub { print_help(); exit },
97 ) or do { print_usage; exit 1 };
98
99 my $binfile = *STDOUT;
100 if ($binary) {
101         if ($binary ne "-") {
102                 open(my $fh, ">", $binary) or die "failed to open $binary: $!";
103                 $binfile = $fh;
104         }
105 }
106 $quiet = 1 if (defined($binary) && $binary eq "");
107 $verbose = 0 if ($quiet);
108
109 my $sock = MPD::connect(binmode => ":raw");
110
111 sub read_binary {
112         my ($count) = @_;
113         my $buf;
114
115         binmode($binfile);
116
117         return 0 unless ($count);
118         my $rc = $sock->read($buf, $count) or die "$!";
119         if (defined($binary)) {
120                 $binfile->write($buf) or die "$!";
121         }
122
123         return $rc;
124 }
125
126 sub mpd_send {
127         my $cmd = encode('UTF-8', join(' ', @_), Encode::FB_QUIET);
128         print "$cmd\n" if ($verbose);
129         print $sock $cmd, $/;
130 }
131
132 my %downloadcmds = map { $_ => 1 } ( "albumart", "readpicture" );
133 sub mpd_exec {
134         my $downloadseq;
135
136         # special case for "albumart" and "readpicture"; if no offset is
137         # specified (invalid command) we synthesize a sequence of albumart
138         # commands to retrieve the entire file.
139         if ($download && $downloadcmds{$_[0]} && @_ == 2) {
140                 $_[2] = 0;
141                 $downloadseq = 2;
142         }
143
144         mpd_send(@_);
145         while (<$sock>) {
146                 $_ = decode('UTF_8', $_, Encode::FB_QUIET);
147
148                 if (/^OK/) {
149                         last unless ($downloadseq);
150                         print $sock encode('UTF-8',
151                                            join(' ', @_),
152                                            Encode::FB_QUIET), $/;
153                         next;
154                 }
155
156                 if (/^binary: ([0-9]+)$/) {
157                         print unless ($quiet);
158                         read_binary($1);
159
160                         if ($downloadseq) {
161                                 $downloadseq = 0 unless ($1);
162                                 $_[$downloadseq] += $1;
163                         }
164                 } elsif (/^ACK/) {
165                         *STDOUT->flush;
166                         print STDERR;
167                         last if ($ignore_errors);
168                         exit 1;
169                 } else {
170                         print unless ($quiet);
171                 }
172         }
173 }
174
175 if (@ARGV) {
176         mpd_exec(map { MPD::escape } @ARGV)
177 } elsif ($ignore_errors) {
178         while (<>) { chomp; mpd_exec($_); }
179 } else {
180         mpd_send("command_list_begin");
181         while (<>) { chomp; mpd_send($_); }
182         mpd_exec("command_list_end");
183 }
184
185 print $sock "close\n";
186 close $sock;