rendered paste body#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <id3tag.h>
#include <mad.h>
#include "qxmms.h"
#define INPUT_BUFFER_SIZE 16*1024
#define GENRE_MAX 0x94
#define MAD_AVERAGE_FRAMES 10
#define DO_FAST_CALC 1
#define XING_MAGIC ( ('X' << 24) | ('i' << 16) | ('n' << 8) | 'g' )
#define EF_VBR (1 << 0)
struct xing {
long flags; /* valid fields (see below) */
unsigned long frames; /* total number of frames */
unsigned long bytes; /* total number of bytes */
unsigned char toc[100]; /* 100-point seek table */
long scale; /* ?? */
};
enum {
XING_FRAMES = 0x00000001L,
XING_BYTES = 0x00000002L,
XING_TOC = 0x00000004L,
XING_SCALE = 0x00000008L
};
enum { FT_MP3, FT_OGG, FT_FLAC, FT_MP4, FT_MAX };
static short xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned short bitlen)
{
if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC)
goto fail;
xing->flags = mad_bit_read(&ptr, 32);
bitlen -= 64;
if (xing->flags & XING_FRAMES) {
if (bitlen < 32)
goto fail;
xing->frames = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_BYTES) {
if (bitlen < 32)
goto fail;
xing->bytes = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (xing->flags & XING_TOC) {
register unsigned short i;
if (bitlen < 800)
goto fail;
for (i = 0; i < 100; ++i)
xing->toc[i] = mad_bit_read(&ptr, 8);
bitlen -= 800;
}
if (xing->flags & XING_SCALE) {
if (bitlen < 32)
goto fail;
xing->scale = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
return 0;
fail:
xing->flags = 0;
return -1;
}
char *strrev( char *s )
{
char *p1, *p2;
char c;
if( s != NULL ) {
p1 = s;
p2 = s + strlen(s) - 1;
while( p1 < p2 ) {
c = *p1;
*p1 = *p2;
*p2 = c;
p1++; p2--;
}
}
return s;
}
void _splitpath(const char* path, char* drv, char* dir, char* name, char* ext)
{
const char* end; /* end of processed string */
const char* p; /* search pointer */
const char* s; /* copy pointer */
/* extract drive name */
if (path[0] && path[1]==':') {
if (drv) {
*drv++ = *path++;
*drv++ = *path++;
*drv = '\0';
}
} else if (drv)
*drv = '\0';
/* search for end of string or stream separator */
for(end=path; *end && *end!=':'; )
end++;
/* search for begin of file extension */
for(p=end; p>path && *--p!='\\' && *p!='/'; )
if (*p == '.') {
end = p;
break;
}
if (ext)
for(s=end; (*ext=*s++); )
ext++;
/* search for end of directory name */
for(p=end; p>path; )
if (*--p=='\\' || *p=='/') {
p++;
break;
}
if (name) {
for(s=p; s<end; )
*name++ = *s++;
*name = '\0';
}
if (dir) {
for(s=path; s<p; )
*dir++ = *s++;
*dir = '\0';
}
}
id3_utf8_t *utf8_id3tag_findframe(struct id3_tag *tag, const char *const frameid,
unsigned short index)
{
const struct id3_frame *frame = NULL;
const union id3_field *field = NULL;
const id3_ucs4_t *ucs4 = NULL;
unsigned int nstrings, i, mp3gid;
if ((frame = id3_tag_findframe (tag, frameid, index))) {
field = id3_frame_field(frame, 1);
nstrings = id3_field_getnstrings(field);
for (i = 0; i < nstrings; ++i) {
if ((ucs4 = id3_field_getstrings(field, i))) {
if (strcmp(frameid, ID3_FRAME_GENRE) == 0) {
/* Some encoders encaps id nb in brackets and the
lib doesn't handle this properly. This is ugly */
if ((ucs4[0] == '(') && (isdigit(ucs4[1]))) {
mp3gid = id3_ucs4_getnumber(ucs4+1);
ucs4 = id3_genre_index(mp3gid);
}
else
ucs4 = id3_genre_name(ucs4);
}
return(id3_ucs4_utf8duplicate(ucs4));
/* id3_ucs4_utf8duplicate() allocates memory.
Don't forget to free() after the function call. */
}
}
}
return NULL;
}
int qxmms_test() {
if(xmms_remote_get_version(XMMS_SESSION)) {
return 1;
} else {
return 0;
}
}
void go_mad(const char *filename) {
FILE *in = fopen(filename, "r");
if(in) {
struct stat filestat;
struct mad_stream madstream;
struct mad_header madheader;
struct mad_frame madframe;
struct xing xing = { .frames = 0, .bytes = 0 };
unsigned char madinput_buffer[INPUT_BUFFER_SIZE];
size_t madread_size, remaining;
mad_timer_t madduration = mad_timer_zero; /* total play time */
unsigned short has_xing = 0;
unsigned long tagsize = 0, data_used = 0, frames = 0;
signed char filetype = 0;
unsigned char flags = 0;
unsigned long size = 0, mtime = 0, bitrate = 0;
unsigned short freq = 0, length = 0;
fstat(fileno(in), &filestat);
filetype = FT_MP3;
flags &= ~EF_VBR;
size = filestat.st_size;
mtime = filestat.st_mtime;
/* Now it's time for mad operations to find out the bitrate, length & samplerate! */
/* initialisation of the MAD subsystem */
mad_stream_init(&madstream);
mad_header_init(&madheader);
mad_frame_init(&madframe);
while (1) {
/* find out how much we have to read from the file */
remaining = madstream.bufend - madstream.next_frame;
memcpy(madinput_buffer, madstream.this_frame, remaining);
madread_size = fread(madinput_buffer + remaining, 1, INPUT_BUFFER_SIZE - remaining, in);
if (madread_size <= 0) {
printf("DBG: maderror madread_size <= 0 on %s", filename);
break;
/* TODO traitement eventuel erreur lecture */
}
mad_stream_buffer(&madstream, madinput_buffer, madread_size + remaining);
while (1) {
if (mad_header_decode(&madheader, &madstream) == -1) {
if (madstream.error == MAD_ERROR_BUFLEN)
break;
if (!MAD_RECOVERABLE(madstream.error)) {
/* TODO traitement eventuel erreur recoverable */
printf("DBG: maderror unrecoverable header decode on %s", filename);
break;
}
if (madstream.error == MAD_ERROR_LOSTSYNC) {
/* ignore LOSTSYNC due to ID3 tags */
tagsize = id3_tag_query(madstream.this_frame, madstream.bufend - madstream.this_frame);
if (tagsize > 0) {
mad_stream_skip(&madstream, tagsize);
continue;
}
}
continue;
}
frames++; /* count the number of frames for average length calculation */
mad_timer_add(&madduration, madheader.duration); /* sum frame duration */
data_used += madstream.next_frame - madstream.this_frame;
if (frames == 1) {
/* the first frame should give us pretty much everything we need, unless... */
freq = madheader.samplerate;
bitrate = madheader.bitrate;
/* ...we have a VBR file. See if it has XING headers first */
madframe.header = madheader;
if (mad_frame_decode(&madframe, &madstream) == -1) {
if (!MAD_RECOVERABLE(madstream.error)) {
/* TODO traitement eventuel erreur recoverable */
printf("DBG: maderror unrecoverable frame decode on %s", filename);
break;
}
}
if (xing_parse(&xing, madstream.anc_ptr, madstream.anc_bitlen) == 0) {
/* found xing header */
has_xing = 1;
flags |= EF_VBR;
/* Get the total number of frames and find out the average bitrate */
frames = xing.frames;
mad_timer_multiply(&madduration, frames);
bitrate = 8 * xing.bytes / mad_timer_count(madduration, MAD_UNITS_SECONDS);
break;
}
}
else {
/* Maybe we have a VBR file without xing header */
if (bitrate != madheader.bitrate)
flags |= EF_VBR;
if ((flags & EF_VBR))
bitrate += madheader.bitrate; /* we'll do an average, XXX overflow when no FAST_CALC? */
}
/* We can either scan the whole file to get precise duration & bitrate,
or just check a few frames and guess the whole story, if DO_FAST_CALC is set */
if (DO_FAST_CALC && frames >= MAD_AVERAGE_FRAMES) {
float frame_size = ((double)data_used) / MAD_AVERAGE_FRAMES;
frames = (size - tagsize) / frame_size;
madduration.seconds /= MAD_AVERAGE_FRAMES;
madduration.fraction /= MAD_AVERAGE_FRAMES;
mad_timer_multiply(&madduration, frames);
break;
}
}
if (madstream.error != MAD_ERROR_BUFLEN)
break;
}
if ((flags & EF_VBR) && !has_xing)
bitrate = bitrate / frames * 1000;
if (!length) /* remember, length can sometimes be encoded in ID3 tags */
length = mad_timer_count(madduration, MAD_UNITS_SECONDS);
/* MAD subsystem exit */
mad_frame_finish(&madframe);
mad_header_finish(&madheader);
mad_stream_finish(&madstream);
/* convert the bitrate to bytes */
// bitrate *= 1.024;
printf("@[");
printf( (flags & EF_VBR) ? "VBR~%ld" : "CBR=%ld", bitrate / 1000);
printf("kb/s]");
fclose(in);
}
}
int qxmms_process(int argc, char **args) {
if(!qxmms_test()) {
printf("XMMS does not seem to be running.");
}
//. query xmms.
qxmms(&q);
char dir[FILENAME_MAX];
_splitpath(q.file, NULL, dir, NULL, NULL);
int n = 0;
char *p;
if((p = strchr(dir, '/')) != NULL) {
n++;
while( (p = strchr(++p, '/')) != NULL)
n++;
}
dir[strlen(dir) - 1] = '\0'; // remove trailing '/'
if(n > 1) {
strrev(dir);
*strchr(dir, '/') = '\0';
strrev(dir);
}
id3_utf8_t *utf8;
struct id3_tag *tag;
struct id3_file *file = id3_file_open(q.file, ID3_FILE_MODE_READONLY);
if(file) {
if( (tag = id3_file_tag(file)) && tag->frames) {
struct id3_frame *frame;
if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_GENRE, 0))) {
char *p = (char *)utf8;
printf("[%s]", p);
free(utf8);
}
printf ("[%s]", dir);
if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_ARTIST, 0))) {
char *p = (char *)utf8;
printf("[%s -", p);
free(utf8);
}
if ((utf8 = utf8_id3tag_findframe(tag, ID3_FRAME_TITLE, 0))) {
char *p = (char *)utf8;
printf(" %s]", p);
free(utf8);
}
}
id3_file_close(file);
go_mad(q.file);
qxmms_print_time();
}
}
void qxmms_play() {
xmms_remote_play(XMMS_SESSION);
}
void qxmms_stop() {
xmms_remote_stop(XMMS_SESSION);
}
void qxmms_pause() {
xmms_remote_pause(XMMS_SESSION);
}
void qxmms_next() {
xmms_remote_playlist_next(XMMS_SESSION);
}
void qxmms_prev() {
xmms_remote_playlist_prev(XMMS_SESSION);
}
void qxmms_addfile(char *string) {
xmms_remote_playlist_add_url_string(XMMS_SESSION,string);
}
void qxmms_set_volume(char *value) {
int vol,intval;
xmms_remote_get_volume(XMMS_SESSION, &vol, &vol);
if(!strcmp("down",value)) {
intval = vol - 5;
} else if(!strcmp("up",value)) {
intval = vol + 5;
} else {
intval = atoi(value);
}
xmms_remote_set_volume(0, intval, intval);
}
void qxmms_jump_pos(char *charvalue) {
int value = atoi(charvalue);
xmms_remote_set_playlist_pos(XMMS_SESSION,(value - 1));
}
void qxmms_seek(char *input) {
char *subcmd;
int cmdlen = strlen(input);
int curtime = xmms_remote_get_output_time(XMMS_SESSION);
int inc = 5;
int min,sec,newtime;
if(input) {
if(!strncmp(input,"back",4)) {
subcmd = strstr(input," ");
if(subcmd) {
subcmd++;
inc = atoi(subcmd);
}
xmms_remote_jump_to_time(XMMS_SESSION,curtime - (inc*1000));
} else if(!strncmp(input,"forward",7)) {
subcmd = strstr(input," ");
if(subcmd) {
subcmd++;
inc = atoi(subcmd);
}
xmms_remote_jump_to_time(XMMS_SESSION,curtime + (inc*1000));
} else {
strtok(input,":");
subcmd = &input[strlen(input) + 1];
xmms_remote_jump_to_time(XMMS_SESSION,(((atoi(input) * 60) + atoi(subcmd)) * 1000));
}
}
}
void qxmms_skin(char *input) {
xmms_remote_set_skin(XMMS_SESSION, input);
}