Index: Makefile.in
===================================================================
--- Makefile.in	(revision 72)
+++ Makefile.in	(revision 220)
@@ -68,7 +68,7 @@
 
 bin_PROGRAMS = mpg321
 
-mpg321_SOURCES =  	mpg321.c 	mad.c 	playlist.c 	network.c 	mpg321.h 	getopt.c 	getopt1.c 	getopt.h 	remote.c 	ao.c 	options.c
+mpg321_SOURCES =  	mpg321.c 	mad.c 	playlist.c 	network.c 	mpg321.h 	getopt.c 	getopt1.c 	getopt.h 	remote.c 	ao.c 	options.c 	scrobbler.c
 
 
 SUBDIRS = m4
@@ -87,7 +87,7 @@
 LDFLAGS = @LDFLAGS@
 LIBS = @LIBS@
 mpg321_OBJECTS =  mpg321.o mad.o playlist.o network.o getopt.o getopt1.o \
-remote.o ao.o options.o
+remote.o ao.o options.o scrobbler.o
 mpg321_LDADD = $(LDADD)
 mpg321_DEPENDENCIES = 
 mpg321_LDFLAGS = 
Index: mad.c
===================================================================
--- mad.c	(revision 72)
+++ mad.c	(revision 220)
@@ -199,6 +199,12 @@
 
     mad_timer_add(&current_time, header->duration);
 
+    if (options.opt & MPG321_USE_SCROBBLER &&
+        scrobbler_time > 0 && scrobbler_time < current_time.seconds) {
+        scrobbler_time = -1;
+        scrobbler_report();
+    }
+
     if(options.opt & (MPG321_VERBOSE_PLAY | MPG321_REMOTE_PLAY))
     {
         mad_timer_string(current_time, long_currenttime_str, "%.2u:%.2u.%.2u", MAD_UNITS_MINUTES,
Index: mpg321.c
===================================================================
--- mpg321.c	(revision 72)
+++ mpg321.c	(revision 220)
@@ -40,6 +40,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <sys/mman.h>
+#include <sys/wait.h>
 
 #include "getopt.h" /* GNU getopt is needed, so I included it */
 #include "mpg321.h"
@@ -134,7 +135,49 @@
    }
 }
 
+/* Ignore child processes from the AudioScrobbler helper */
+RETSIGTYPE handle_sigchld(int sig) 
+{
+    int stat;
+
+    while (waitpid(-1, &stat, WNOHANG) > 0)
+        ;
+}
+
+static struct {
+    int index;
+    char const *id;
+    char const *name;
+} const info_id3[] = {
+    { 0,    ID3_FRAME_TITLE,  "Title  : "   },
+    { 1,    ID3_FRAME_ARTIST, "  Artist: "  },
+    { 2,    ID3_FRAME_ALBUM,  "Album  : "   },
+    { 3,    ID3_FRAME_YEAR,   "  Year  : "  },
+    { 4,    ID3_FRAME_COMMENT,"Comment: "   },
+    { 5,    ID3_FRAME_GENRE,  "  Genre : "  }
+};
+
 /*
+    Parse an ID3 tag into
+    TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE
+    and return true if at least one of those is present.
+*/
+static int parse_id3(char *names[], struct id3_tag const *tag)
+{
+    int found, i;
+
+    found = 0;
+    /*  Get ID3 tag if available, 30 chars except 4 for year  */
+    for (i=0; i<=5; i++)    {
+        names[i] = NULL;
+        names[i] = id3_get_tag(tag, info_id3[i].id, (i==3) ? 4 : 30);
+        if (names[i] != NULL && names[i][0] != '\0')
+            found = 1;
+    }
+    return (found);
+}
+
+/*
     Only shows ID3 tags if at least one of
     TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE
     is present.
@@ -142,36 +185,12 @@
 static int show_id3(struct id3_tag const *tag)
 {
     unsigned int i;
-    int print = 0;
     char emptystring[31];
     char *names[6];
-    struct {
-        int index;
-        char const *id;
-        char const *name;
-    } const info[] = {
-        { 0,    ID3_FRAME_TITLE,  "Title  : "   },
-        { 1,    ID3_FRAME_ARTIST, "  Artist: "  },
-        { 2,    ID3_FRAME_ALBUM,  "Album  : "   },
-        { 3,    ID3_FRAME_YEAR,   "  Year  : "  },
-        { 4,    ID3_FRAME_COMMENT,"Comment: "   },
-        { 5,    ID3_FRAME_GENRE,  "  Genre : "  }
-    };
 
     memset (emptystring, ' ', 30);
     emptystring[30] = '\0';
-    /*  Get ID3 tag if available, 30 chars except 4 for year  */
-    for (i=0; i<=5; i++)    {
-        names[i] = NULL;
-        names[i] = id3_get_tag(tag, info[i].id, (i==3) ? 4 : 30);
-    }
-    for (i=0; i<=5; i++)    {
-        if (names[i])   {
-            print = 1;
-            break;
-        }
-    }
-    if (!print) {
+    if (!parse_id3(names, tag)) {
         return 0;
     }
 
@@ -199,7 +218,7 @@
     {
         /* Emulate mpg123 original behaviour  */
         for (i=0; i<=5; i++)    {
-            fprintf (stderr, "%s", info[i].name);
+            fprintf (stderr, "%s", info_id3[i].name);
             if (!names[i])  {
                 fprintf (stderr, emptystring);
             }   else    {
@@ -213,6 +232,26 @@
     return 1;
 }
 
+static int
+get_id3_info(const char *fname, struct id3_file **id3struct,
+        struct id3_tag **id3tag)
+{
+    struct id3_file *s;
+    struct id3_tag *t;
+
+    s = id3_file_open (fname, ID3_FILE_MODE_READONLY);
+    if (s == NULL)
+        return (0);
+    t = id3_file_tag (s);
+    if (t == NULL) {
+        id3_file_close(s);
+        return (0);
+    }
+    *id3struct = s;
+    *id3tag = t;
+    return (1);
+}
+
 int main(int argc, char *argv[])
 {
     int fd = 0;
@@ -300,61 +339,68 @@
         
         mad_timer_reset(&current_time);
 
+        id3struct = NULL;
+
         if (!(options.opt & MPG321_QUIET_PLAY) && file_change)
         {
-            id3struct = id3_file_open (currentfile, ID3_FILE_MODE_READONLY);
+            if (id3struct == NULL)
+                get_id3_info(currentfile, &id3struct, &id3tag);
+            if (id3tag)
+                   show_id3 (id3tag);
+        }
 
-            if (id3struct)
+        scrobbler_time = -1;
+        if (options.opt & MPG321_USE_SCROBBLER)
+        {
+
+            if (id3struct == NULL)
+                get_id3_info(currentfile, &id3struct, &id3tag);
+            if (id3tag)
             {
-                id3tag = id3_file_tag (id3struct);
-            
-                if (id3tag)
-                {
-                    show_id3 (id3tag);
+                char emptystring[31], emptyyear[5] = "    ";
+                int i;
+
+                if (parse_id3(scrobbler_args, id3tag)) {
+                    memset(emptystring, ' ', 30);
+                    emptystring[30] = '\0';
+                    if (options.opt & MPG321_VERBOSE_PLAY) {
+                        fprintf(stderr, "Preparing for the AudioScrobbler:\n");
+                        for (i = 0; i < 6; i++) {
+                            if (scrobbler_args[i] == NULL)
+                                scrobbler_args[i] =
+                                (i == 3? emptyyear: emptystring);
+                            fprintf(stderr, "- %s\n", scrobbler_args[i]);
+                        }
+                    }
                 }
-
-                id3_file_close (id3struct);
             }
         }
 
         if (options.opt & MPG321_REMOTE_PLAY && file_change)
         {
-            id3struct = id3_file_open (currentfile, ID3_FILE_MODE_READONLY);
-
-            if (id3struct)
-            {
-                id3tag = id3_file_tag (id3struct);
-            
-                if (id3tag)
+            if (id3struct == NULL)
+                get_id3_info(currentfile, &id3struct, &id3tag);
+                   if (id3tag)
+              {
+                if (!show_id3(id3tag))
                 {
-                    if (!show_id3(id3tag))
-                    {
-                        /* This shouldn't be necessary, but it appears that
-                           libid3tag doesn't necessarily know if there are no
-                           id3 tags on a given mp3 */
-                        char * basec = strdup(currentfile);
-                        char * basen = basename(basec);
-                        
-                        char * dot = strrchr(basen, '.');
-                        
-                        if (dot)
-                            *dot = '\0';
-                        
-                        printf("@I %s\n", basen);
-
-                        free(basec);
+                    /* This shouldn't be necessary, but it appears that
+                       libid3tag doesn't necessarily know if there are no
+                       id3 tags on a given mp3 */
+                    char * basec = strdup(currentfile);
+                    char * basen = basename(basec);
+                    
+                    char * dot = strrchr(basen, '.');
+                    
+                    if (dot)
+                        *dot = '\0';
+                    
+                    printf("@I %s\n", basen);
+                    
+                    free(basec);
                     }
-                }
+            }
                 
-                else
-                {
-                    fprintf(stderr, "Allocation error");
-                    exit(1);
-                }
-
-                id3_file_close (id3struct);
-            }
-            
             else
             {
                 char * basec = strdup(currentfile);
@@ -371,6 +417,9 @@
             }
         }
 
+        if (id3struct != NULL)
+                id3_file_close(id3struct);
+
         /* Create the MPEG stream */
         /* Check if source is on the network */
         if((fd = raw_open(currentfile)) != 0 || (fd = http_open(currentfile)) != 0
@@ -422,6 +471,10 @@
             }
             
             calc_length(currentfile, &playbuf);
+            if (options.opt & MPG321_VERBOSE_PLAY)
+                fprintf(stderr, "Track duration: %ld seconds\n",
+                    playbuf.duration.seconds);
+            scrobbler_set_time(playbuf.duration.seconds);
 
             if ((options.maxframes != -1) && (options.maxframes <= playbuf.num_frames))
             { 
@@ -480,6 +533,7 @@
         }    
 
         signal(SIGINT, handle_signals);
+        signal(SIGCHLD, handle_sigchld);
 
         /* Every time the user gets us to rewind, we exit decoding,
            reinitialize it, and re-start it */
Index: scrobbler.c
===================================================================
--- scrobbler.c	(revision 0)
+++ scrobbler.c	(revision 220)
@@ -0,0 +1,118 @@
+/*
+    mpg321 - a fully free clone of mpg123.
+    scrobbler.c: Copyright (C) 2005-2006 Peter Pentchev
+    
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "mpg321.h"
+
+#define SCROBBLER_PLUGIN_NAME   "321"
+#define SCROBBLER_PLUGIN_VER    "0.1"
+
+#define SCROBBLER_HELPER_PATH   "scrobbler-helper"
+#define SCROBBLER_CUTOFF        240
+#define SCROBBLER_LOW_CUTOFF    30
+
+char *scrobbler_args[6] = {
+    "", "", "", "", "", "",
+};
+int scrobbler_time = -1;
+
+static int scrobbler_track_length = -1;
+static int scrobbler_verbose = 0;
+
+void
+scrobbler_set_time(long seconds)
+{
+    if (seconds < SCROBBLER_LOW_CUTOFF)
+        scrobbler_time = -1;
+    else if (seconds < 2 * SCROBBLER_CUTOFF)
+        scrobbler_time = seconds / 2;
+    else
+        scrobbler_time = SCROBBLER_CUTOFF;
+    scrobbler_track_length = seconds;
+    if (scrobbler_verbose)
+        fprintf(stderr, "AudioScrobbler report at %d sec\n", scrobbler_time);
+}
+
+void
+scrobbler_report(void)
+{
+    char *args[] = {
+        SCROBBLER_HELPER_PATH,
+        "-P",
+        SCROBBLER_PLUGIN_NAME,
+        "-V",
+        SCROBBLER_PLUGIN_VER,
+        "--",
+        "", "", "", "", "", "",
+        "",
+        NULL
+    };
+#define SARGS_SIZE      (sizeof(args) / sizeof(args[0]))
+#define SARGS_POS       (SARGS_SIZE - (6 + 2))
+    char lengthbuf[20];
+
+    if (scrobbler_verbose) {
+        printf("Reporting to AudioScrobbler!\n");
+        args[1] = "-vP";
+    }
+    switch(fork()) {
+        case -1:
+            /* Error */
+            mpg321_error("forking for the scrobbler helper");
+            break;
+            
+        case 0:
+            /* Child: execute the scrobbler plug-in */
+            
+            /* Close the AO fd first */
+            if(playdevice)
+                ao_close(playdevice);
+            ao_shutdown();
+            
+            /* Merge the ID3 info with the scrobbler helper execv() args */
+            memcpy(args + SARGS_POS, scrobbler_args,
+                sizeof(scrobbler_args));
+            snprintf(lengthbuf, sizeof(lengthbuf),
+                "%d", scrobbler_track_length);
+            args[SARGS_SIZE - 2] = lengthbuf;
+            execvp(args[0], args);
+            mpg321_error("scrobbler helper " SCROBBLER_HELPER_PATH);
+            exit(1);
+            
+        default:
+            /* Parent: nothing to do */
+            break;
+    }
+}
+
+void
+scrobbler_set_verbose(int v)
+{
+
+    scrobbler_verbose = v;
+}
Index: mpg321.h
===================================================================
--- mpg321.h	(revision 72)
+++ mpg321.h	(revision 220)
@@ -112,6 +112,9 @@
 
 extern int status;
 
+extern int scrobbler_time;
+extern char *scrobbler_args[6];
+
 enum
 {
     MPG321_STOPPED       = 0x0001,
@@ -140,7 +143,8 @@
     MPG321_USE_USERDEF   = 0x00004000,
     MPG321_USE_ALSA09    = 0x00008000,
     
-    MPG321_FORCE_STEREO  = 0x00010000
+    MPG321_FORCE_STEREO  = 0x00010000,
+    MPG321_USE_SCROBBLER = 0x000200000,
 };
 
 #define DEFAULT_PLAYLIST_SIZE 1024
@@ -187,6 +191,11 @@
 void remote_get_input_wait(buffer *buf);
 enum mad_flow remote_get_input_nowait(buffer *buf);
 
+/* AudioScrobbler functions */
+void scrobbler_report(void);
+void scrobbler_set_time(long);
+void scrobbler_set_verbose(int);
+
 /* options */
 void parse_options(int argc, char *argv[], playlist *pl);
 
Index: options.c
===================================================================
--- options.c	(revision 72)
+++ options.c	(revision 220)
@@ -89,6 +89,7 @@
         { "random", 0, 0, 'Z' },
         { "remote", 0, 0, 'R' },
         { "stereo", 0, 0, 'T' },
+        { "scrobbler", 0, 0, 'S' },
             
         /* takes parameters */
         { "frames", 1, 0, 'n' },
@@ -107,7 +108,7 @@
 
     while ((c = getopt_long(argc, argv, 
                                 "OPLTNEI824cy01mCu:d:h:f:b:p:r:G:" /* unimplemented */
-                                "A:D:vqtsVHzZRo:n:@:k:w:a:g:",     /* implemented */
+                                "A:D:SvqtsVHzZRo:n:@:k:w:a:g:",     /* implemented */
                         long_options, &option_index)) != -1)
     {            
         switch(c)
@@ -137,6 +138,7 @@
 
             case 'v':
                 options.opt |= MPG321_VERBOSE_PLAY;
+                scrobbler_set_verbose(1);
                 setvbuf(stdout, NULL, _IONBF, 0);
                 break;
             
@@ -188,6 +190,10 @@
             case 's':
                 options.opt |= MPG321_USE_STDOUT;
                 break;
+
+            case 'S':
+                options.opt |= MPG321_USE_SCROBBLER;
+                break;
                 
             case 'o':
                 if (strcmp(optarg, "alsa") == 0)
