1 #!/usr/bin/perl
2 # yargla - Yet Another Rapid Glue Last.fm App v0.03
3 # Glues together Audio::MPD and Audio::Scrobbler
4 # Reads current playing track from MPD daemon
5 # and submits it to your last.fm profile
6
7 # put yargla somewhere in your $PATH, chmod +x it
8 # install Audio::MPD, Audio::Scrobbler and Config::Tiny (and Readonly)
9 # with cpan -i or your distrib/os packages
10 # p5-Audio-MPD, p5-Audio-Scrobbler and p5-Config-Tiny are in OpenBSD ports
11
12 # i wrote this script because mpdscribble kept segfaulting on my boxes,
13 # scmpc has mad dependencies, and i like perl - the glue of the internet.
14
15 # the mandatory licence for each little piece of code - ISC licenced
16 #
17 # Copyright (c) 2008,2010 Jasper Lievisse Adriaanse <jasper@humppa.nl>
18 # Copyright (c) 2007 Landry Breuil <gaston@gcu.info>
19 #
20 # Permission to use, copy, modify, and distribute this software for any
21 # purpose with or without fee is hereby granted, provided that the above
22 # copyright notice and this permission notice appear in all copies.
23 #
24 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
25 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
26 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
27 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
28 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
29 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
30 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
31 #
32 # TODO: - log into file
33 # - cache tracks and submit by batches
34 #
35 # sample config file:
36 # ; MPD-host variables
37 # [mpd]
38 # host = localhost
39 # port = 6600
40 # ; Last.FM user variables
41 # [lastfm]
42 # user = foo
43 # password = secret
44 #
45
46 use Audio::MPD;
47 use Audio::Scrobbler;
48 use Config::Tiny;
49 use Getopt::Long;
50 use POSIX qw(setsid);
51 use strict;
52
53 use vars qw($VERSION);
54 $VERSION = "0.03";
55
56 sub usage;
57
58 # parse cmdline options
59 my ($as_user, $as_pass, $mpd_host, $mpd_port) = ('', '', 'localhost', 6600);
60 my ($help, $verbose, $config, $config_fh, $daemon);
61
62 GetOptions('daemon' => \$daemon,
63 'verbose' => \$verbose,
64 'help' => \$help,
65 'file:s' => \$config,
66 'user:s' => \$as_user,
67 'password:s' => \$as_pass,
68 'mpd:s' => \$mpd_host,
69 'nport:i' => \$mpd_port);
70
71 if ($as_user eq '' || $as_pass eq '' || $help) {
72 usage() if (!defined($config));
73 }
74
75 # parse the configuration file
76 unless (!defined($config)) {
77 if (! -e $config) {
78 print "Configuration file $config not found.\n";
79 usage();
80 }
81 $config_fh = Config::Tiny->read("$config");
82 }
83
84 $mpd_host = $config_fh->{mpd}->{host};
85 $mpd_port = $config_fh->{mpd}->{port};
86 $as_user = $config_fh->{lastfm}->{user};
87 $as_pass = $config_fh->{lastfm}->{password};
88
89 # validate the configuration values
90 if ((!defined($mpd_host)) or ($mpd_host eq '')) {
91 die "No valid host found in $config\n";
92 } elsif ((!defined($mpd_port)) or ($mpd_port eq '')) {
93 die "No valid port found in $config\n";
94 } elsif ((!defined($as_user)) or ($as_user eq '')) {
95 die "No valid user found in $config\n";
96 } elsif ((!defined($as_pass)) or ($as_pass eq '')) {
97 die "No valid password found in $config\n";
98 }
99
100 # initialize handlers on both sides
101 print "Connecting to MPD on $mpd_host:$mpd_port\n";
102 my $mpd = Audio::MPD->new($mpd_host, $mpd_port) || die "Failed to connect to MPD";
103
104 my $as = Audio::Scrobbler->new( cfg => {
105 verbose => $verbose,
106 progname => 'mdc', # use mpdscribble id atm
107 progver => '0.2.12',
108 username => $as_user,
109 password => $as_pass} );
110
111 $as->handshake() || die "Failed to handshake() with Last.fm";
112
113 my $status = $mpd->status();
114
115 # We've setup ourselves far enough, so daemonize if needed.
116 if ($daemon) {
117 # Flush the buffer first.
118 $| = 1;
119 daemonize();
120 }
121
122 while(1)
123 {
124 my $curid = $status->songid();
125 my $cursong = $mpd->current();
126
127 my ($artist,$title,$album,$length) = ($cursong->artist,$cursong->title,$cursong->album,$cursong->time);
128 my @t = localtime();
129 my $datestr = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $t[5] + 1900, $t[4] + 1, @t[3, 2, 1, 0]);
130
131 print "[$datestr] submitting $artist - $album - $title\n";
132
133 my $r = $as->submit( {title => "$title", artist => "$artist", album => "$album", 'length' => "$length"});
134
135 if (!$r)
136 {
137 print "error:".$as->err.", trying to re-handshake and re-submit in 5s...\n";
138 sleep 5;
139 $as->handshake();
140 $as->submit( {title => "$title", artist => "$artist", album => "$album", 'length' => "$length"});
141 }
142
143 while ($curid == $status->songid)
144 {
145 if ($status->state eq "play")
146 { sleep 10; }
147 else
148 { sleep 60; }
149 $status = $mpd->status();
150 }
151 }
152
153 # Setup a sane environment, then fork.
154 sub daemonize {
155 eval {
156 chdir '/';
157 open STDIN, '/dev/null';
158 open STDOUT, '>>/dev/null';
159 open STDERR, '>>/dev/null';
160 defined(my $pid = fork) or die "Can't fork: $!";
161 exit if $pid;
162 setsid; # Get new session ID.
163 umask 0;
164 };
165 if ($@) {
166 die "Failed to daemonize $0";
167 };
168 }
169
170 sub usage
171 {
172 print "$0 v. $VERSION\n";
173 print "usage: $0 -d|--daemon -v|--verbose -h|--help -f|--file [config] \n-u|--user [Last.fm user] -p|--password [Last.fm password] -m|--mpd [MPD host]\n-n|--nport [MPD port]\n\n";
174 print "-u and -p are mandatory, unless -f is specified\n";
175 print "-m and -n defaults to localhost:6600\n";
176 print "-h shows usage\n";
177 print "-d fork to the background\n\n";
178 print "values provided in the file provided with -f, overrule command line flags\n";
179 exit;
180 }
181
182 # (c) 2007 - G.C.U.