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(¤t_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 #include #include +#include #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(¤t_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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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)