+ static char const short_options[] = "c:bdf:g:lo:np:ruwx:";
+ static struct option long_options[] = {
+ {"backslash", no_argument, NULL, 'b'},
+ {"command", required_argument, NULL, 'b'},
+ {"debug", no_argument, NULL, 'd'},
+ {"format", required_argument, NULL, 'f'},
+ {"genre", required_argument, NULL, 'g'},
+ {"nohardlink", no_argument, NULL, 'n'},
+ {"output", required_argument, NULL, 'o'},
+ {"prefix", required_argument, NULL, 'p'},
+ {"recursive", no_argument, NULL, 'r'},
+ {"windows", no_argument, NULL, 'w'},
+ {"exclude", required_argument, NULL, 'x'}
+ };
+ int c;
+ int option_index = 0;
+ while((c =
+ getopt_long(argc, argv, short_options, long_options,
+ &option_index)) != -1) {
+ switch (c) {
+ case 'b':
+ separator = '\\';
+ noand['/'] = '\\';
+ break;
+ case 'c':
+ if(strncmp(optarg, "intern", 6) == 0)
+ referal = NULL;
+ else
+ referal = strdup(optarg);
+ break;
+ case 'd':
+ debug = 1 - debug;
+ break;
+ case 'f':
+ if(strcmp(optarg, "m3u") == 0)
+ format = 0;
+ else if(strcmp(optarg, "pls") == 0)
+ format = 1;
+ else if(strcmp(optarg, "html") == 0)
+ format = 2;
+ else if(strcmp(optarg, "rss") == 0)
+ format = 3;
+ else
+ usage();
+ break;
+ case 'g':
+ if(genrelist == NULL)
+ genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
+ if(genrelist == NULL) {
+ fprintf(stderr,
+ "Error >> unable to allocate cleared memory\n");
+ exit(2);
+ } else {
+ unsigned int n = 0;
+ while(n < strlen(optarg)) {
+ if(debug)
+ fprintf(stderr,
+ "Debug >> genrelist entry activting : %d\n",
+ atoi(&optarg[n]));
+ genrelist[atoi(&optarg[n])] = 1;
+ while(isdigit(optarg[n++]));
+ }
+ }
+ break;
+ case 'n':
+ avoidhlinked = 1;
+ break;
+ case 'o':
+ close(1);
+ if(fopen(optarg, "w") == NULL) {
+ fprintf(stderr,
+ "Error >> unable to open output file : %s\n",
+ optarg);
+ exit(2);
+ }
+ break;
+ case 'p':
+ prefix = malloc(strlen(optarg) + 1);
+ strcpy(prefix, optarg);
+ base = malloc(strlen(prefix) + 1);
+ strcpy(base, prefix);
+ dir = strchr(base, '/');
+ if((dir != NULL) && (dir[1] == '/'))
+ dir = strchr(dir + 2, '/');
+ if(dir != NULL)
+ *dir++ = 0;
+ else
+ dir = "";
+ /* if prefix is a weblink, base is the baselink, dir is the path */
+ break;
+ case 'r':
+ recursive = 1;
+ break;
+ case 'u':
+ winorunix = one2one;
+ eol = "\n";
+ break;
+ case 'w':
+ winorunix = unix2dos;
+ eol = "\r\n";
+ break;
+ case 'x':
+ if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
+ int n = 0;
+ genrelist = calloc(257, sizeof(char));
+ while(n < 256)
+ genrelist[n++] = 1;
+ }
+ if(genrelist == NULL) {
+ fprintf(stderr,
+ "Error >> unable to allocate cleared memory\n");
+ exit(2);
+ } else {
+ unsigned int n = 0;
+ while(n < strlen(optarg)) {
+ if(debug)
+ fprintf(stderr,
+ "Debug >> genrelist entry activting : %d\n",
+ atoi(&optarg[n]));
+ genrelist[atoi(&optarg[n])] = 0;
+ while(isdigit(optarg[n++]));
+ }
+ }
+ break;
+ default:
+ usage();
+ }
+ }
+ /* hostname = getenv("HOSTNAME"); */
+ if(genrelist == NULL) {
+ genrelist = calloc(257, sizeof(char));
+ if(genrelist == NULL) {
+ fprintf(stderr,
+ "Error >> unable to allocate cleared memory\n");
+ exit(2);
+ } else {
+ int n = 0;
+ while(n < 256)
+ genrelist[n++] = 1;
+ }
+ }
+}
+
+void parse_mp3(unsigned char *file)
+{
+ int bitrates[2][3][15] =
+ { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
+ 416, 448},
+ {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
+ 384},
+ {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
+ 320}},
+ {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
+ {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
+ {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
+ };
+ FILE *fic;
+ unsigned char *c;
+ int lus;
+
+ genre = 0;
+ genrebuf[0] = 0;
+ if(debug)
+ fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
+
+ /* read header */
+ if((fic = fopen(file, "r")) == NULL) {
+ fprintf(stderr, "Warning >> can't open file : %s\n", file);
+ return;
+ }
+ lus = fread(buffer, 1, MP3_BASE, fic);
+ c = buffer;
+
+ /* try ID3v2 */
+ if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
+ int size;
+ int version;
+ version = *(buffer + 3);
+ if(version < 2 || version > 4)
+ fprintf(stderr,
+ "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
+ version, file);
+ if(*(buffer + 5) != 0)
+ fprintf(stderr,
+ "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
+ *(buffer + 5), file);
+ c = buffer + 6;
+ size =
+ (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
+ /* read more header */
+ if(size + lus > MAX) {
+ lus += fread(buffer + lus, 1, MAX - lus, fic);
+ fprintf(stderr,
+ "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
+ size, file);
+ } else
+ lus += fread(buffer + lus, 1, size, fic);
+ if(size > lus)
+ size = lus;
+ c += 4;
+ if(version == 2)
+ while(c < buffer + size) {
+ int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
+ if(*c == 0)
+ break;
+ if(strncmp(c, "TT2", 3) == 0) {
+ strncpy(title, c + 7, size - 1);
+ title[size - 1] = '\0';
+ }
+ if(strncmp(c, "TP1", 3) == 0) {
+ strncpy(artist, c + 7, size - 1);
+ artist[size - 1] = '\0';
+ }
+ if(strncmp(c, "TCO", 3) == 0) {
+ /* strncpy(genrebuf,c+7,size-1); */
+ /* genrebuf[size-1]='\0'; */
+ /* genre=atoi(&genrebuf[1]); */
+ genre = atoi(c + 8);
+ }
+ c += size + 6;
+ }
+ if(version == 3 || version == 4)
+ while(c < buffer + size) {
+ int size =
+ (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
+ (*(c + 7));
+ if(*c == 0)
+ break;
+ if(strncmp(c, "TIT2", 4) == 0) {
+ strncpy(title, c + 11, size - 1);
+ title[size - 1] = '\0';
+ }
+ if(strncmp(c, "TPE1", 4) == 0) {
+ strncpy(artist, c + 11, size - 1);
+ artist[size - 1] = '\0';
+ }
+ if(strncmp(c, "TCON", 4) == 0) {
+ /* strncpy(genrebuf,c+11,size-1); */
+ /* genrebuf[size-1]='\0'; */
+ /* genre=atoi(&genrebuf[1]); */
+ genre = atoi(c + 12);
+ }
+ c += size + 10;
+ }
+ }
+
+ while(c < buffer + lus - 10) {
+ if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
+ int version;
+ int lay;
+ int bitrate_index;
+ int bitrate;
+ version = 2 - (*(c + 1) >> 3 & 1);
+ lay = 4 - (*(c + 1) >> 1 & 3);
+ bitrate_index = *(c + 2) >> 4 & 0xF;
+ if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
+ && bitrate_index >= 0 && bitrate_index <= 14)
+ bitrate = bitrates[version - 1][lay - 1][bitrate_index];
+ else
+ bitrate = 0;
+ if(bitrate != 0) {
+ fseek(fic, 0, SEEK_END);
+ duration = (ftell(fic) + buffer - c) / 125 / bitrate;
+ } else
+ duration = 0;
+ break;
+ }
+ c++;
+ }
+
+ /* try ID3v1 */
+ if(strlen(artist) == 0 && strlen(title) == 0) {
+ fseek(fic, -128, SEEK_END);
+ lus = fread(buffer, 1, 128, fic);
+ if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
+ && buffer[2] == 'G') {
+ strncpy(title, buffer + 3, 30);
+ title[30] = '\0';
+ c = title + 29;
+ while(c > title && *c == ' ')
+ *(c--) = '\0';
+ strncpy(artist, buffer + 33, 30);
+ artist[30] = '\0';
+ c = artist + 29;
+ while(c > artist && *c == ' ')
+ *(c--) = '\0';
+ /* strncpy(album,buffer+65,30); */
+ /* strncpy(year,buffer+97,4); */
+ /* strncpy(comment,buffer+101,30); */
+ /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
+ genre = buffer[127];
+ }
+ }
+
+ fclose(fic);
+}
+
+void parse_ogg(unsigned char *file)
+{
+ FILE *fic;
+ unsigned char *c;
+ int lus;
+ int sample_rate;
+ int samples;
+
+ if(debug)
+ fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
+
+ /* read header */
+ if((fic = fopen(file, "r")) == NULL) {
+ fprintf(stderr, "Warning >> can't open file : %s\n", file);
+ return;
+ }
+ lus = fread(buffer, 1, OGG_BASE, fic);
+
+ /* try Ogg */
+ if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
+ fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
+ return;
+ }
+
+ c = buffer + 0x28;
+ sample_rate =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+
+ while(c < buffer + lus - 10) {
+ int size;
+ if(strncasecmp(c, "TITLE=", 6) == 0) {
+ size =
+ *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+ (*(c - 1) << 24);
+ strncpy(title, c + 6, size - 6);
+ title[size - 6] = '\0';
+ c += size;
+ }
+ if(strncasecmp(c, "ARTIST=", 7) == 0) {
+ size =
+ *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+ (*(c - 1) << 24);
+ strncpy(artist, c + 7, size - 7);
+ artist[size - 7] = '\0';
+ c += size;
+ }
+ if(strncasecmp(c, "GENRE=", 6) == 0) {
+ static int i = 0;
+ size =
+ *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+ (*(c - 1) << 24);
+ strncpy(genrebuf, c + 6, size - 6);
+ genrebuf[size - 6] = '\0';
+ c += size;
+ for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
+ if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
+ genre = i;
+ break;
+ }
+ if(i == ID3_NR_OF_V1_GENRES)
+ genre = 0;
+ }
+ }
+ c++;
+ }
+
+ fseek(fic, -OGG_BASE, SEEK_END);
+ lus = fread(buffer, 1, OGG_BASE, fic);
+ c = buffer + lus - 1;
+ while(strncmp(c, "OggS", 4) != 0 && c > buffer)
+ c--;
+ if(c != buffer) {
+ c += 6;
+ samples =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+ duration = samples / sample_rate;
+ }
+
+ fclose(fic);
+}
+
+
+void parse_mpc(unsigned char *file)
+{
+ FILE *fic;
+ unsigned char *c;
+ int lus;
+ int sample_rates[4] = { 44100, 48000, 37800, 32000 };
+ int frame_count;
+ int size, items;
+ int i;
+
+ if(debug)
+ fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
+
+ /* read header */
+ if((fic = fopen(file, "r")) == NULL) {
+ fprintf(stderr, "Warning >> can't open file : %s\n", file);
+ return;
+ }
+ lus = fread(buffer, 1, 12, fic);
+
+ /* try Musepack */
+ if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
+ fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
+ return;
+ }
+
+ /* only version 7 */
+ if(buffer[3] != 7) {
+ fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
+ file);
+ return;
+ }
+
+ /* duration */
+ c = buffer + 4;
+ frame_count =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+ c += 5;
+ duration = frame_count * 1152 / sample_rates[*c & 3];
+
+ /* try APETAGEX footer */
+ fseek(fic, -32, SEEK_END);
+ lus = fread(buffer, 1, 32, fic);
+ if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
+ c = buffer + 12;
+ size =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+ size += 32;
+ c += 4;
+ items =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+ fseek(fic, -size, SEEK_END);
+ lus = fread(buffer, 1, size, fic);
+ if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
+ c = buffer + 32;
+ while(items--) {
+ size =
+ (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
+ (*(c + 3) << 24);
+ c += 8;
+ if(strcasecmp(c, "TITLE") == 0) {
+ strncpy(title, c + 6, size);
+ title[size] = '\0';
+ }
+ if(strcasecmp(c, "ARTIST") == 0) {
+ strncpy(artist, c + 7, size);
+ artist[size] = '\0';
+ }
+ if(strcasecmp(c, "GENRE") == 0) {
+ for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
+ strncpy(genrebuf, c + 6, size);
+ genrebuf[size] = '\0';
+ if(strcasecmp
+ (ID3_v1_genre_description[i], genrebuf) == 0) {
+ genre = i;
+ break;
+ }
+ if(i == ID3_NR_OF_V1_GENRES)
+ genre = 0;
+ }
+ }
+ c += strlen(c) + 1 + size;
+ }
+ }
+ }
+
+ fclose(fic);
+}
+
+#define FSN 32
+#define MAXINO (1<<24)
+#define INOTYP unsigned long
+#define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
+#define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
+
+int hlink_check(struct stat *info)
+{
+ /*
+ * for speed this subroutine should only be called
+ * - if the file has more than one hardlink
+ * - if the file is a resolved softlink
+ */
+ /* the persistent variables */
+ static INOTYP *list[FSN];
+ static dev_t name[FSN];
+ /* some temporary variables */
+ int fsn, is_registered = 0;
+
+ /* assertions - in case parameters are lowered for less memory usage */
+ assert(fsn < FSN);
+ assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
+
+ /* search which internal registration number is used for this filesystem */
+ for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
+
+ /* if file system is not registered yet, do it and leave */
+ if(name[fsn] == 0) {
+ name[fsn] = (info->st_dev);
+ /* provide space for the bitmap that maps the inodes of this file system */
+ list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
+ /* no comparison is needed in empty lists ... return */
+ if(debug)
+ fprintf(stderr,
+ "Debug >> Linked >> Init List %04x @mem %04lx\n",
+ (int)name[fsn], (long)&list[fsn]);
+ } else {
+ /* this looks more complicated than it really is */
+ /* the idea is very simple:
+ * provide a bitmap that maps all inodes of a file system
+ * to mark all files that have already been visited.
+ * If it is already visited, do not add it to the playlist
+ */
+ /*
+ * The difficulty is as follows:
+ * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
+ * would be byte-aligned and would allocate at least eight times the needed space.
+ * Feel free to change the definitions that are involved here, if you know better.
+ */
+ if(regbit_qry(list[fsn], (info->st_ino)))
+ is_registered = 1;
+ else
+ regbit_set(list[fsn], (info->st_ino));
+ /*
+ * the debug expression is more complicated then the working stuff
+ */
+ if(debug)
+ fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
+ "list[%02x][%04x] = %04x & %04x --> %s registered\n",
+ (int)info->st_dev, (int)info->st_ino, fsn,
+ (int)((info->st_ino) / sizeof(INOTYP)),
+ (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
+ 1 << ((info->st_ino) % sizeof(INOTYP)),
+ is_registered ? "Already" : "Not");
+ }
+ return is_registered;
+}
+
+
+void parse_file(unsigned char *newpath)
+{
+ unsigned char ext[5];
+ int j, encoding = 0;
+
+ for(j = 0; j < 5; j++)
+ ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
+ artist[0] = '\0';
+ title[0] = '\0';
+ duration = -2;
+ if(strcmp(".mp2", ext) == 0) {
+ duration = -1;
+ parse_mp3(newpath);
+ encoding = MP2ENC;
+ }
+ if(strcmp(".mp3", ext) == 0) {
+ duration = -1;
+ parse_mp3(newpath);
+ encoding = MP3ENC;
+ }
+ if(strcmp(".mpc", ext) == 0) {
+ duration = -1;
+ parse_mpc(newpath);
+ encoding = MPCENC;
+ }
+ if(strcmp(".mp+", ext) == 0) {
+ duration = -1;
+ parse_mpc(newpath);
+ encoding = MPPENC;
+ }
+ if(strcmp(".ogg", ext) == 0) {
+ duration = -1;
+ parse_ogg(newpath);
+ encoding = OGGENC;
+ }
+ if(strcmp(".wav", ext) == 0) {
+ duration = -1; /* parse_wav(newpath); */
+ encoding = WAVENC;
+ }
+ /* faketitle() */
+ if((strlen(artist) == 0) && (strlen(title) == 0)) {
+ // there are no tag infos read
+ // use file name to state substitute it
+ char *c = strrchr(newpath, separator);
+ strcpy(artist, ++c);
+ // arbitrarily use the first '-'
+ // to separate artist and title
+ c = strchr(artist, '-');
+ if(c != NULL) { // if trenner found, divide file name
+ *c = '\0';
+ strcpy(title, ++c);
+ c = strrchr(title, '.');
+ if(c != NULL)
+ *c = '\0';
+ } else { // no trenner found, assume
+ // no artist, only title
+ strcpy(title, artist);
+ artist[0] = '\0';
+ }
+ // replace underscores by spaces
+ for(c = artist; (c = strchr(c, '_')) != NULL; c++)
+ *c = ' ';
+ for(c = title; (c = strchr(c, '_')) != NULL; c++)
+ *c = ' ';
+ }
+ /* faketitle() end */
+
+ if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */