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.