745e6ae3387ac42c944c1fee7d2ce19125463de0
[FAPG] / fapg.c
1 /*
2  * FAPG means Fast Audio Playlist Generator.
3  * It is a tool to generate list of audio files (Wav, MP3, Ogg, etc)
4  * in various formats (M3U, PLS, HTML, etc).
5  * It is very usefull if you have a large amount of audio files
6  * and you want to quickly and frequently build a playlist.
7  *
8  * Copyright (C) 2003-2004  Antoine Jacquet <royale@zerezo.com>
9  * http://royale.zerezo.com/fapg/
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <getopt.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <limits.h>
35 #include <unistd.h>
36 #include <ctype.h>
37 #include <time.h>
38 #include <assert.h>
39 #include "genres.h"
40 #ifdef HAVE_LIBURIPARSER
41 # include <uriparser/Uri.h>
42 #endif
43
44 #define MP3_BASE 1024
45 #define OGG_BASE 1024*10
46 #define MAX 1024*250            /* 250ko for ID3 with JPEG images in it */
47
48 #define FORMAT_M3U 0
49 #define FORMAT_PLS 1
50 #define FORMAT_HTML 2
51 #define FORMAT_RSS 3
52 #define FORMAT_PLP 4
53 #define FORMAT_UMS 5
54 #ifdef HAVE_LIBURIPARSER
55 # define FORMAT_XSPF 6
56 #endif
57
58 int debug = 0;
59 int format = FORMAT_M3U;
60 char *genrelist = NULL;
61 unsigned char *prefix = "";
62 unsigned char *base = "";
63 unsigned char *dir = "";
64 unsigned char *hostname = "fritzserver.de";
65 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
66 unsigned char *referal = NULL;
67 //int windows=0;
68 int fromstdin = 0;
69 int recursive = 0;
70 int avoidhlinked = 0;
71 int separator = '/';
72 unsigned char *eol = "\n";
73 unsigned char buffer[MAX];
74
75 int counter = 0;
76
77 unsigned char artist[1024];
78 unsigned char title[1024];
79 unsigned char genrebuf[1024];
80 unsigned char genre = 0;
81 int duration;
82 #define MP2ENC 1
83 #define MP3ENC 2
84 #define MPCENC 3
85 #define MPPENC 4
86 #define OGGENC 5
87 #define WAVENC 6
88 #define WMAENC 7
89
90 char *magic[] = { NULL,
91     "audio/mpeg", "audio/mpeg",
92     "audio/mpeg", "audio/mpeg",
93     "audio/ogg-vorbis", "audio/x-wav",
94     "audio/x-ms-wma", 
95     NULL
96 };
97
98 unsigned char unix2dos[] =
99     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
100     16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
101     32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
102     48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
103     64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
104     80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
105     96, 97, 98, 99, 100, 101, 102, 103,
106     104, 105, 106, 107, 108, 109, 110, 111,
107     112, 113, 114, 115, 116, 117, 118, 119,
108     120, 121, 122, 123, 36, 125, 126, 127,
109     199, 252, 233, 226, 228, 224, 229, 231,
110     234, 235, 232, 239, 238, 236, 196, 197,
111     201, 230, 198, 244, 246, 242, 251, 249,
112     255, 214, 220, 248, 163, 216, 215, 131,
113     225, 237, 243, 250, 241, 209, 170, 186,
114     191, 174, 172, 189, 188, 161, 171, 187,
115     166, 166, 166, 166, 166, 193, 194, 192,
116     169, 166, 166, 43, 43, 162, 165, 43,
117     43, 45, 45, 43, 45, 43, 227, 195,
118     43, 43, 45, 45, 166, 45, 43, 164,
119     240, 208, 202, 203, 200, 105, 205, 206,
120     207, 43, 43, 166, 220, 166, 204, 175,
121     211, 223, 212, 210, 245, 213, 181, 254,
122     222, 218, 219, 217, 253, 221, 175, 180,
123     173, 177, 61, 190, 182, 167, 247, 184,
124     176, 168, 183, 185, 179, 178, 166, 160
125 };
126
127 unsigned char *basemap;
128 unsigned char *winorunix;
129 unsigned char one2one[] =
130     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
131     16, 17, 18, 19, 20, 21, 22, 23,
132     24, 25, 26, 27, 28, 29, 30, 31,
133     32, 33, 34, 35, 36, 37, 38, 39,
134     40, 41, 42, 43, 44, 45, 46, 47,
135     48, 49, 50, 51, 52, 53, 54, 55,
136     56, 57, 58, 59, 60, 61, 62, 63,
137     64, 65, 66, 67, 68, 69, 70, 71,
138     72, 73, 74, 75, 76, 77, 78, 79,
139     80, 81, 82, 83, 84, 85, 86, 87,
140     88, 89, 90, 91, 92, 93, 94, 95,
141     96, 97, 98, 99, 100, 101, 102, 103,
142     104, 105, 106, 107, 108, 109, 110, 111,
143     112, 113, 114, 115, 116, 117, 118, 119,
144     120, 121, 122, 123, 124, 125, 126, 127,
145     128, 129, 130, 131, 132, 133, 134, 135,
146     136, 137, 138, 139, 140, 141, 142, 143,
147     144, 145, 146, 147, 148, 149, 150, 151,
148     152, 153, 154, 155, 156, 157, 158, 159,
149     160, 161, 162, 163, 164, 165, 166, 167,
150     168, 169, 170, 171, 172, 173, 174, 175,
151     176, 177, 178, 179, 180, 181, 182, 183,
152     184, 185, 186, 187, 188, 189, 190, 191,
153     192, 193, 194, 195, 196, 197, 198, 199,
154     200, 201, 202, 203, 204, 205, 206, 207,
155     208, 209, 210, 211, 212, 213, 214, 215,
156     216, 217, 218, 219, 220, 221, 222, 223,
157     224, 225, 226, 227, 228, 229, 230, 231,
158     232, 233, 234, 235, 236, 237, 238, 239,
159     240, 241, 242, 243, 244, 245, 246, 247,
160     248, 249, 250, 251, 252, 253, 254, 255
161 };                              /* identical mapping */
162
163 unsigned char noand[256] =
164     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
165     16, 17, 18, 19, 20, 21, 22, 23,
166     24, 25, 26, 27, 28, 29, 30, 31,
167     32, 33, 34, 35, 36, 37, 43, 39,
168     40, 41, 42, 43, 44, 45, 46, 47,
169     48, 49, 50, 51, 52, 53, 54, 55,
170     56, 57, 58, 59, 60, 61, 62, 63,
171     64, 65, 66, 67, 68, 69, 70, 71,
172     72, 73, 74, 75, 76, 77, 78, 79,
173     80, 81, 82, 83, 84, 85, 86, 87,
174     88, 89, 90, 91, 92, 93, 94, 95,
175     96, 97, 98, 99, 100, 101, 102, 103,
176     104, 105, 106, 107, 108, 109, 110, 111,
177     112, 113, 114, 115, 116, 117, 118, 119,
178     120, 121, 122, 123, 124, 125, 126, 127,
179     128, 129, 130, 131, 132, 133, 134, 135,
180     136, 137, 138, 139, 140, 141, 142, 143,
181     144, 145, 146, 147, 148, 149, 150, 151,
182     152, 153, 154, 155, 156, 157, 158, 159,
183     160, 161, 162, 163, 164, 165, 166, 167,
184     168, 169, 170, 171, 172, 173, 174, 175,
185     176, 177, 178, 179, 180, 181, 182, 183,
186     184, 185, 186, 187, 188, 189, 190, 191,
187     192, 193, 194, 195, 196, 197, 198, 199,
188     200, 201, 202, 203, 204, 205, 206, 207,
189     208, 209, 210, 211, 212, 213, 214, 215,
190     216, 217, 218, 219, 220, 221, 222, 223,
191     224, 225, 226, 227, 228, 229, 230, 231,
192     232, 233, 234, 235, 236, 237, 238, 239,
193     240, 241, 242, 243, 244, 245, 246, 247,
194     248, 249, 250, 251, 252, 253, 254, 255
195 };                              /* only '&' is mapped to '+' */
196
197 unsigned char *iso2web[256] = {
198     "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
199     "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
200     "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
201     "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
202     "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
203     "%28", "%29", "%2a", "+", ",", "-", ".", "/",
204     "0", "1", "2", "3", "4", "5", "6", "7",
205     "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
206     "@", "A", "B", "C", "D", "E", "F", "G",
207     "H", "I", "J", "K", "L", "M", "N", "O",
208     "P", "Q", "R", "S", "T", "U", "V", "W",
209     "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
210     "`", "a", "b", "c", "d", "e", "f", "g",
211     "h", "i", "j", "k", "l", "m", "n", "o",
212     "p", "q", "r", "s", "t", "u", "v", "w",
213     "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
214     "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
215     "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
216     "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
217     "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
218     "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
219     "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
220     "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
221     "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
222     "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
223     "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
224     "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
225     "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
226     "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
227     "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
228     "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
229     "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
230 };
231
232 void usage()
233 {
234 #ifdef HAVE_LIBURIPARSER
235 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
236 #else
237 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
238 #endif
239     fprintf(stderr,
240             "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=" FAPG_FORMATS "] [-g|--genre=#:#:...] [-n|--nohardlink] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-c|--command=<intern|...>] [-x|--exclude=#:#:...] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");
241 #undef FAPG_FORMATS
242     exit(1);
243 }
244
245 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
246 #define    myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
247 /* #define    myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
248 void myplaputchar(const char x)
249 {
250     putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
251     putchar('\0');
252 }
253
254 void mywebputstr(const char *c)
255 {
256     while(*c != 0) {
257         mywebputchar(*c);
258         c++;
259     }
260 }
261
262 void myplaputstr(const char *c)
263 {
264     while(*c != 0) {
265         if(*c == '/')
266             myplaputchar('\\'); /* translate slash to backslash */
267         else
268             myplaputchar(*c);
269         c++;
270         /* remove multiple slashes "//" when parsing a directory ending with a "/" */
271         while(*c == '/' && c[1] == '/')
272             c++;
273     }
274 }
275
276 void myputstr(const char *c)
277 {
278     while(*c != 0) {
279         if(*c == '/')
280             putchar(separator);
281         else
282             myputchar(*c);
283         c++;
284         /* remove multiple slashes "//" when parsing a directory ending with a "/" */
285         while(*c == '/' && c[1] == '/')
286             c++;
287     }
288 }
289
290 void txxputheader(const char *c)
291 {
292     int cnt = 0;
293
294     while(*c != 0) {
295         myputchar(*c);
296         cnt++;
297         c++;
298     }
299
300     while(cnt < 512) {
301         putchar('\0');
302         cnt++;
303     }
304 }
305
306 void txxputnameoffset(const char *c)
307 {
308     int pos = 0;
309     int cnt = 0;
310     char b;
311     unsigned char *prefx;
312
313     prefx = prefix;
314
315     if(*prefx != 0) {
316         while(*prefx != 0) {
317             if(*prefx == '/') {
318                 pos = cnt;
319             }
320             cnt++;
321             prefx++;
322         }
323
324         cnt--;                  // skip the leading dot of the filepath
325     }
326
327     while(*c != 0) {
328         if(*c == '/') {
329             pos = cnt;
330         }
331         cnt++;
332         c++;
333     }
334
335     pos += 2;
336
337     b = (pos & 0xFF00) >> 8;
338     putchar(b);
339     b = (pos & 0x00FF);
340     putchar(b);
341 }
342
343 void txxputstr(const char *c)
344 {
345     int cnt = 0;
346     int pos;
347     unsigned char *prefx;
348
349     txxputnameoffset(c);
350
351     prefx = prefix;
352     fprintf(stderr, "prefix: '%s'\n", prefx);
353
354     if(*prefx != 0) {
355         while(*prefx != 0) {
356             myputchar('\0');
357             cnt++;
358
359             if(*prefx == '/')
360                 putchar(separator);
361             else
362                 myputchar(*prefx);
363             cnt++;
364
365             prefx++;
366         }
367
368         c++;                    // skip the leading dot
369     }
370
371     while(*c != 0) {
372         myputchar('\0');
373         cnt++;
374
375         if(*c == '/')
376             putchar(separator);
377         else
378             myputchar(*c);
379         cnt++;
380
381         c++;
382     }
383
384     while(cnt < 510) {
385         myputchar('\0');
386         cnt++;
387     }
388 }
389
390 void txxputcounter(int c)
391 {
392     int b;
393
394     rewind(stdout);
395
396     b = (c & 0xFF000000) >> 24;
397     putchar(b);
398     b = (c & 0x00FF0000) >> 16;
399     putchar(b);
400     b = (c & 0x0000FF00) >> 8;
401     putchar(b);
402     b = (c & 0x000000FF);
403     putchar(b);
404 }
405
406 /* remove spaces at beginning and end of string */
407 void trim(char *c)
408 {
409     char *p;
410     /* remove spaces at beginning ... */
411     while(*c == ' ') {
412         p = c;
413         while(*p != '\0') {
414             *p = *(p + 1);
415             p++;
416         }
417     }
418     /* ... and end of string */
419     p = c + strlen(c);
420     while(--p > c && *p == ' ')
421         *p = '\0';
422 }
423
424 void print_webpath(const char *path)
425 {
426     const char *c = path;
427
428     printf("%s", prefix);             /* we must not modify this part */
429     if(*c == '.' && c[1] == '/') {      /* remove leading "./" when parsing current directory */
430         c += 2;
431         /* maybe there follow many slashes */
432         while(*c == '/')
433             c++;
434     }
435     for(; *c != '\0'; c++) {
436         mywebputchar(*c);
437         /* remove multiple "//" when parsing a directory ending with a "/" */
438         while(*c == '/' && c[1] == '/')
439             c++;
440     }
441 }
442
443 void print_path(const char *path)
444 {
445     const char *c = path;
446     printf("%s", prefix);
447     /* skip leading "./" when parsing current directory */
448     if(*c == '.' && *(c + 1) == '/') {
449         c += 2;
450         /* maybe there follow more slashes */
451         while(*c == '/')
452             c++;
453     }
454     myputstr(c);
455 }
456
457 void print_pathtail(const char *path)
458 {
459     const char *c;
460     c = strrchr(path, separator);
461     if(c != NULL)
462         c++;
463     else
464         c = path;
465     myputstr(c);
466 }
467
468 void noreferal(const char *path, const char *artist, const char *title)
469 {
470     printf("\t\t<description><![CDATA[<h4>");
471     myputstr(artist);
472     printf("</h4><h5>");
473     myputstr(title);
474     printf("</h5><a href=\"");
475     print_webpath(path);
476     printf
477         ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
478          eol);
479 }
480
481 void reference(const char *title)
482 {
483     FILE *pipe = NULL;
484     static char command[2048], buffer[1024];
485     int buflen = 8192;
486
487     buflen = strlen(title) + strlen(referal) + 3;
488     assert((buflen < 2046));
489     strcpy(command, referal);
490     buflen = strlen(command);
491     command[buflen] = ' ';
492     command[buflen + 1] = '"';
493     command[buflen + 2] = 0;
494     strcat(command, title);
495     buflen = strlen(command);
496     command[buflen] = '"';
497     command[buflen + 1] = 0;
498     if(debug)
499         fprintf(stderr, "Debug >> processing command: %s\n", command);
500     pipe = popen(command, "r");
501     if(pipe == NULL) {
502         fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
503         return;
504     }
505     fgets(buffer, 1020, pipe);
506     while(!feof(pipe)) {
507         fputs(buffer, stdout);
508         fgets(buffer, 1020, pipe);
509     }
510     pclose(pipe);
511     return;
512 }
513
514 void parse_options(int argc, char **argv)
515 {
516     static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
517     static struct option long_options[] = {
518         {"backslash", no_argument, NULL, 'b'},
519         {"command", required_argument, NULL, 'c'},
520         {"debug", no_argument, NULL, 'd'},
521         {"format", required_argument, NULL, 'f'},
522         {"genre", required_argument, NULL, 'g'},
523         {"nohardlink", no_argument, NULL, 'n'},
524         {"output", required_argument, NULL, 'o'},
525         {"prefix", required_argument, NULL, 'p'},
526         {"recursive", no_argument, NULL, 'r'},
527         {"stdin", no_argument, NULL, 's'},
528         {"windows", no_argument, NULL, 'w'},
529         {"exclude", required_argument, NULL, 'x'},
530         {NULL, 0, NULL, 0}
531     };
532     int c;
533     int option_index = 0;
534     while((c =
535            getopt_long(argc, argv, short_options, long_options,
536                        &option_index)) != -1) {
537         switch (c) {
538         case 'b':
539             separator = '\\';
540             noand['/'] = '\\';
541             break;
542         case 'c':
543             if(strncmp(optarg, "intern", 6) == 0)
544                 referal = NULL;
545             else
546                 referal = strdup(optarg);
547             break;
548         case 'd':
549             debug = 1 - debug;
550             break;
551         case 'f':
552             if(strcmp(optarg, "m3u") == 0)
553                 format = FORMAT_M3U;
554             else if(strcmp(optarg, "pls") == 0)
555                 format = FORMAT_PLS;
556             else if(strcmp(optarg, "html") == 0)
557                 format = FORMAT_HTML;
558             else if(strcmp(optarg, "rss") == 0)
559                 format = FORMAT_RSS;
560             else if(strcmp(optarg, "pla") == 0)
561                 format = FORMAT_PLP;
562             else if(strcmp(optarg, "txx") == 0)
563                 format = FORMAT_UMS;
564 #ifdef HAVE_LIBURIPARSER
565             else if(strcmp(optarg, "xspf") == 0)
566                 format = FORMAT_XSPF;
567 #endif
568             else
569                 usage();
570             break;
571         case 'g':
572             if(genrelist == NULL)
573                 genrelist = calloc(257, sizeof(char));  /* allow multiple includes/excludes */
574             if(genrelist == NULL) {
575                 fprintf(stderr,
576                         "Error >> unable to allocate cleared memory\n");
577                 exit(2);
578             } else {
579                 unsigned int n = 0;
580                 while(n < strlen(optarg)) {
581                     if(debug)
582                         fprintf(stderr,
583                                 "Debug >> genrelist entry activting : %d\n",
584                                 atoi(&optarg[n]));
585                     genrelist[atoi(&optarg[n])] = 1;
586                     while(isdigit(optarg[n++]));
587                 }
588             }
589             break;
590         case 'n':
591             avoidhlinked = 1;
592             break;
593         case 'o':
594             close(1);
595             if(fopen(optarg, "w") == NULL) {
596                 fprintf(stderr,
597                         "Error >> unable to open output file : %s\n",
598                         optarg);
599                 exit(2);
600             }
601             break;
602         case 'p':
603             prefix = malloc(strlen(optarg) + 1);
604             strcpy(prefix, optarg);
605             base = malloc(strlen(prefix) + 1);
606             strcpy(base, prefix);
607             dir = strchr(base, '/');
608             if((dir != NULL) && (dir[1] == '/'))
609                 dir = strchr(dir + 2, '/');
610             if(dir != NULL)
611                 *dir++ = 0;
612             else
613                 dir = "";
614             /* if prefix is a weblink, base is the baselink, dir is the path */
615             break;
616         case 'r':
617             recursive = 1;
618             break;
619         case 'u':
620             winorunix = one2one;
621             eol = "\n";
622             break;
623         case 'w':
624             winorunix = unix2dos;
625             eol = "\r\n";
626             break;
627         case 'x':
628             if(genrelist == NULL) {     /* allow multiple includes/excludes - not recommended (confusing) but possible */
629                 int n = 0;
630                 genrelist = calloc(257, sizeof(char));
631                 while(n < 256)
632                     genrelist[n++] = 1;
633             }
634             if(genrelist == NULL) {
635                 fprintf(stderr,
636                         "Error >> unable to allocate cleared memory\n");
637                 exit(2);
638             } else {
639                 unsigned int n = 0;
640                 while(n < strlen(optarg)) {
641                     if(debug)
642                         fprintf(stderr,
643                                 "Debug >> genrelist entry activting : %d\n",
644                                 atoi(&optarg[n]));
645                     genrelist[atoi(&optarg[n])] = 0;
646                     while(isdigit(optarg[n++]));
647                 }
648             }
649             break;
650         case 's':
651             fromstdin = 1;
652             break;
653         default:
654             usage();
655         }
656     }
657     /* hostname = getenv("HOSTNAME"); */
658     if(genrelist == NULL) {
659         genrelist = calloc(257, sizeof(char));
660         if(genrelist == NULL) {
661             fprintf(stderr,
662                     "Error >> unable to allocate cleared memory\n");
663             exit(2);
664         } else {
665             int n = 0;
666             while(n < 256)
667                 genrelist[n++] = 1;
668         }
669     }
670 }
671
672 void parse_mp3(unsigned char *file)
673 {
674     int bitrates[2][3][15] =
675         { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
676             416, 448},
677            {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
678             384},
679            {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
680             320}},
681     {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
682      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
683      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
684     };
685     FILE *fic;
686     unsigned char *c;
687     int lus;
688
689     genre = 0;
690     genrebuf[0] = 0;
691     if(debug)
692         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
693
694     /* read header */
695     if((fic = fopen(file, "r")) == NULL) {
696         fprintf(stderr, "Warning >> can't open file : %s\n", file);
697         return;
698     }
699     lus = fread(buffer, 1, MP3_BASE, fic);
700     c = buffer;
701
702     /* try ID3v2 */
703     if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
704         int size;
705         int version;
706         version = *(buffer + 3);
707         if(version < 2 || version > 4)
708             fprintf(stderr,
709                     "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
710                     version, file);
711         if(*(buffer + 5) != 0)
712             fprintf(stderr,
713                     "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
714                     *(buffer + 5), file);
715         c = buffer + 6;
716         size =
717             (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
718         /* read more header */
719         if(size + lus > MAX) {
720             lus += fread(buffer + lus, 1, MAX - lus, fic);
721             fprintf(stderr,
722                     "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
723                     size, file);
724         } else
725             lus += fread(buffer + lus, 1, size, fic);
726         if(size > lus)
727             size = lus;
728         c += 4;
729         if(version == 2)
730             while(c < buffer + size) {
731                 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
732                 if(*c == 0)
733                     break;
734                 if(strncmp(c, "TT2", 3) == 0) {
735                     strncpy(title, c + 7, size - 1);
736                     title[size - 1] = '\0';
737                 }
738                 if(strncmp(c, "TP1", 3) == 0) {
739                     strncpy(artist, c + 7, size - 1);
740                     artist[size - 1] = '\0';
741                 }
742                 if(strncmp(c, "TCO", 3) == 0) {
743                     /* strncpy(genrebuf,c+7,size-1); */
744                     /* genrebuf[size-1]='\0'; */
745                     /* genre=atoi(&genrebuf[1]); */
746                     genre = atoi(c + 8);
747                 }
748                 c += size + 6;
749             }
750         if(version == 3 || version == 4)
751             while(c < buffer + size) {
752                 int size =
753                     (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
754                     (*(c + 7));
755                 if(*c == 0)
756                     break;
757                 if(strncmp(c, "TIT2", 4) == 0) {
758                     strncpy(title, c + 11, size - 1);
759                     title[size - 1] = '\0';
760                 }
761                 if(strncmp(c, "TPE1", 4) == 0) {
762                     strncpy(artist, c + 11, size - 1);
763                     artist[size - 1] = '\0';
764                 }
765                 if(strncmp(c, "TCON", 4) == 0) {
766                     /* strncpy(genrebuf,c+11,size-1); */
767                     /* genrebuf[size-1]='\0'; */
768                     /* genre=atoi(&genrebuf[1]); */
769                     genre = atoi(c + 12);
770                 }
771                 c += size + 10;
772             }
773     }
774
775     while(c < buffer + lus - 10) {
776         if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
777             int version;
778             int lay;
779             int bitrate_index;
780             int bitrate;
781             version = 2 - (*(c + 1) >> 3 & 1);
782             lay = 4 - (*(c + 1) >> 1 & 3);
783             bitrate_index = *(c + 2) >> 4 & 0xF;
784             if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
785                && bitrate_index >= 0 && bitrate_index <= 14)
786                 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
787             else
788                 bitrate = 0;
789             if(bitrate != 0) {
790                 fseek(fic, 0, SEEK_END);
791                 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
792             } else
793                 duration = 0;
794             break;
795         }
796         c++;
797     }
798
799     /* try ID3v1 */
800     if(strlen(artist) == 0 && strlen(title) == 0) {
801         fseek(fic, -128, SEEK_END);
802         lus = fread(buffer, 1, 128, fic);
803         if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
804            && buffer[2] == 'G') {
805             strncpy(title, buffer + 3, 30);
806             title[30] = '\0';
807             c = title + 29;
808             while(c > title && *c == ' ')
809                 *(c--) = '\0';
810             strncpy(artist, buffer + 33, 30);
811             artist[30] = '\0';
812             c = artist + 29;
813             while(c > artist && *c == ' ')
814                 *(c--) = '\0';
815             /* strncpy(album,buffer+65,30); */
816             /* strncpy(year,buffer+97,4); */
817             /* strncpy(comment,buffer+101,30); */
818             /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
819             genre = buffer[127];
820         }
821     }
822
823     fclose(fic);
824 }
825
826 void parse_ogg(unsigned char *file)
827 {
828     FILE *fic;
829     unsigned char *c;
830     int lus;
831     int sample_rate;
832     int samples;
833
834     if(debug)
835         fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
836
837     /* read header */
838     if((fic = fopen(file, "r")) == NULL) {
839         fprintf(stderr, "Warning >> can't open file : %s\n", file);
840         return;
841     }
842     lus = fread(buffer, 1, OGG_BASE, fic);
843
844     /* try Ogg */
845     if(strncmp(buffer, "Ogg", 3) != 0) {
846         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
847         return;
848     }
849
850     c = buffer + 0x28;
851     sample_rate =
852         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
853
854     while(c < buffer + lus - 10) {
855         int size;
856         if(strncasecmp(c, "TITLE=", 6) == 0) {
857             size =
858                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
859                 (*(c - 1) << 24);
860             strncpy(title, c + 6, size - 6);
861             title[size - 6] = '\0';
862             c += size;
863         }
864         if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
865             // ignore tag
866             size =
867                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
868                 (*(c - 1) << 24);
869             c += size;
870         }
871         if(strncasecmp(c, "ARTIST=", 7) == 0) {
872             size =
873                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
874                 (*(c - 1) << 24);
875             strncpy(artist, c + 7, size - 7);
876             artist[size - 7] = '\0';
877             c += size;
878         }
879         if(strncasecmp(c, "GENRE=", 6) == 0) {
880             static int i = 0;
881             size =
882                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
883                 (*(c - 1) << 24);
884             strncpy(genrebuf, c + 6, size - 6);
885             genrebuf[size - 6] = '\0';
886             c += size;
887             for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
888                 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
889                     genre = i;
890                     break;
891                 }
892                 if(i == ID3_NR_OF_V1_GENRES)
893                     genre = 0;
894             }
895         }
896         c++;
897     }
898
899     fseek(fic, -OGG_BASE, SEEK_END);
900     lus = fread(buffer, 1, OGG_BASE, fic);
901     c = buffer + lus - 1;
902     while(strncmp(c, "OggS", 4) != 0 && c > buffer)
903         c--;
904     if(c != buffer) {
905         c += 6;
906         samples =
907             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
908         duration = samples / sample_rate;
909     }
910
911     fclose(fic);
912 }
913
914 void parse_mpc(unsigned char *file)
915 {
916     FILE *fic;
917     unsigned char *c;
918     int lus;
919     int sample_rates[4] = { 44100, 48000, 37800, 32000 };
920     int frame_count;
921     int size, items;
922     int i;
923
924     if(debug)
925         fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
926
927     /* read header */
928     if((fic = fopen(file, "r")) == NULL) {
929         fprintf(stderr, "Warning >> can't open file : %s\n", file);
930         return;
931     }
932     lus = fread(buffer, 1, 12, fic);
933
934     /* try Musepack */
935     if (strncmp(buffer, "MP+", 3) != 0) {
936         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
937         return;
938     }
939
940     /* only version 7 */
941     if(buffer[3] != 7) {
942         fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
943                 file);
944         return;
945     }
946
947     /* duration */
948     c = buffer + 4;
949     frame_count =
950         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
951     c += 5;
952     duration = frame_count * 1152 / sample_rates[*c & 3];
953
954     /* try APETAGEX footer */
955     fseek(fic, -32, SEEK_END);
956     lus = fread(buffer, 1, 32, fic);
957     if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
958         c = buffer + 12;
959         size =
960             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
961         size += 32;
962         c += 4;
963         items =
964             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
965         fseek(fic, -size, SEEK_END);
966         lus = fread(buffer, 1, size, fic);
967         if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
968             c = buffer + 32;
969             while(items--) {
970                 size =
971                     (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
972                     (*(c + 3) << 24);
973                 c += 8;
974                 if(strcasecmp(c, "TITLE") == 0) {
975                     strncpy(title, c + 6, size);
976                     title[size] = '\0';
977                 }
978                 if(strcasecmp(c, "ARTIST") == 0) {
979                     strncpy(artist, c + 7, size);
980                     artist[size] = '\0';
981                 }
982                 if(strcasecmp(c, "GENRE") == 0) {
983                     for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
984                         strncpy(genrebuf, c + 6, size);
985                         genrebuf[size] = '\0';
986                         if(strcasecmp
987                            (ID3_v1_genre_description[i], genrebuf) == 0) {
988                             genre = i;
989                             break;
990                         }
991                         if(i == ID3_NR_OF_V1_GENRES)
992                             genre = 0;
993                     }
994                 }
995                 c += strlen(c) + 1 + size;
996             }
997         }
998     }
999
1000     fclose(fic);
1001 }
1002
1003 #define FSN  32
1004 #define MAXINO  (1<<24)
1005 #define INOTYP  unsigned long
1006 #define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
1007 #define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
1008
1009 int hlink_check(struct stat *info)
1010 {
1011     /*
1012      * for speed this subroutine should only be called
1013      * - if the file has more than one hardlink
1014      * - if the file is a resolved softlink
1015      */
1016     /* the persistent variables */
1017     static INOTYP *list[FSN];
1018     static dev_t name[FSN];
1019     /* some temporary variables */
1020     int fsn, is_registered = 0;
1021
1022     /* assertions - in case parameters are lowered for less memory usage */
1023     assert(fsn < FSN);
1024     assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1025
1026     /* search which internal registration number is used for this filesystem */
1027     for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1028
1029     /* if file system is not registered yet, do it and leave */
1030     if(name[fsn] == 0) {
1031         name[fsn] = (info->st_dev);
1032         /* provide space for the bitmap that maps the inodes of this file system */
1033         list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1034         /* no comparison is needed in empty lists ... return */
1035         if(debug)
1036             fprintf(stderr,
1037                     "Debug >> Linked >> Init List %04x @mem %04lx\n",
1038                     (int)name[fsn], (long)&list[fsn]);
1039     } else {
1040         /* this looks more complicated than it really is */
1041         /* the idea is very simple: 
1042          *  provide a bitmap that maps all inodes of a file system
1043          *  to mark all files that have already been visited.
1044          *  If it is already visited, do not add it to the playlist
1045          */
1046         /*
1047          * The difficulty is as follows:
1048          *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1049          * would be byte-aligned and would allocate at least eight times the needed space.
1050          * Feel free to change the definitions that are involved here, if you know better.
1051          */
1052         if(regbit_qry(list[fsn], (info->st_ino)))
1053             is_registered = 1;
1054         else
1055             regbit_set(list[fsn], (info->st_ino));
1056         /*
1057          * the debug expression is more complicated then the working stuff
1058          */
1059         if(debug)
1060             fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1061                     "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1062                     (int)info->st_dev, (int)info->st_ino, fsn,
1063                     (int)((info->st_ino) / sizeof(INOTYP)),
1064                     (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1065                     1 << ((info->st_ino) % sizeof(INOTYP)),
1066                     is_registered ? "Already" : "Not");
1067     }
1068     return is_registered;
1069 }
1070
1071 #ifdef HAVE_LIBURIPARSER
1072 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1073 {
1074     char * absSourceFile;
1075     size_t absSourceLen;
1076     char * sourceUriString;
1077     char * baseUriString;
1078     UriParserStateA state;
1079     UriUriA sourceUri;
1080     UriUriA baseUri;
1081     UriUriA relativeUri;
1082     int charsRequired;
1083     char * output;
1084   
1085     /* checks */
1086     if ((unixFilename == NULL) || (baseDir == NULL)) {
1087         return NULL;
1088     }
1089
1090     /* base URI */
1091     baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1092     if (baseUriString == NULL) {
1093         return NULL;
1094     }
1095     if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1096         free(baseUriString);
1097         return NULL;
1098     }
1099     state.uri = &baseUri;
1100     if (uriParseUriA(&state, baseUriString) != 0) {
1101         free(baseUriString);
1102         uriFreeUriMembersA(&baseUri);
1103         return NULL;
1104     }
1105
1106     /* source URI */
1107     if (unixFilename[0] != '/') {
1108         const int baseDirLen = strlen(baseDir);
1109         const int sourceFileLen = strlen(unixFilename);
1110         absSourceLen = baseDirLen + sourceFileLen;
1111         absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1112         sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1113     } else {
1114         absSourceLen = strlen(unixFilename);
1115         absSourceFile = (char *)unixFilename;
1116     }
1117     sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1118     if (sourceUriString == NULL) {
1119         free(baseUriString);
1120         if (unixFilename[0] != '/') {
1121             free(absSourceFile);
1122         }
1123         uriFreeUriMembersA(&baseUri);
1124         return NULL;
1125     }
1126     if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1127         free(baseUriString);
1128         free(sourceUriString);
1129         if (unixFilename[0] != '/') {
1130             free(absSourceFile);
1131         }
1132         uriFreeUriMembersA(&baseUri);
1133         return NULL;
1134     }
1135     state.uri = &sourceUri;
1136     if (uriParseUriA(&state, sourceUriString) != 0) {
1137         free(baseUriString);
1138         free(sourceUriString);
1139         uriFreeUriMembersA(&baseUri);
1140         uriFreeUriMembersA(&sourceUri);
1141         return NULL;
1142     }
1143     if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1144         free(baseUriString);
1145         free(sourceUriString);
1146         if (unixFilename[0] != '/') {
1147             free(absSourceFile);
1148         }
1149         uriFreeUriMembersA(&baseUri);
1150         uriFreeUriMembersA(&sourceUri);
1151         return NULL;
1152     }
1153
1154     /* make relative (or keep absolute if necessary) */
1155     if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1156         free(baseUriString);
1157         free(sourceUriString);
1158         if (unixFilename[0] != '/') {
1159             free(absSourceFile);
1160         }
1161         uriFreeUriMembersA(&baseUri);
1162         uriFreeUriMembersA(&sourceUri);
1163         uriFreeUriMembersA(&relativeUri);
1164         return NULL;
1165     }
1166     
1167     /* back to string */
1168     if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1169         free(baseUriString);
1170         free(sourceUriString);
1171         if (unixFilename[0] != '/') {
1172             free(absSourceFile);
1173         }
1174         uriFreeUriMembersA(&baseUri);
1175         uriFreeUriMembersA(&sourceUri);
1176         uriFreeUriMembersA(&relativeUri);
1177         return NULL;
1178     }
1179     output = malloc((charsRequired + 1) * sizeof(char));
1180     if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1181         free(baseUriString);
1182         free(sourceUriString);
1183         if (unixFilename[0] != '/') {
1184             free(absSourceFile);
1185         }
1186         free(output);
1187         uriFreeUriMembersA(&baseUri);
1188         uriFreeUriMembersA(&sourceUri);
1189         uriFreeUriMembersA(&relativeUri);
1190         return NULL;
1191     }
1192
1193     free(baseUriString);
1194     free(sourceUriString);
1195     if (unixFilename[0] != '/') {
1196         free(absSourceFile);
1197     }
1198     uriFreeUriMembersA(&baseUri);
1199     uriFreeUriMembersA(&sourceUri);
1200     uriFreeUriMembersA(&relativeUri);
1201
1202     return output;
1203 }
1204
1205 char * xml_escape_malloc(const char * input)
1206 {
1207     const char * read = input;
1208     char * output;
1209     char * write;
1210     
1211     if (input == NULL) {
1212         return NULL;
1213     }
1214     
1215     output = malloc((6 * strlen(input) + 1) * sizeof(char));
1216     if (output == NULL) {
1217         return NULL;
1218     }
1219     write = output;
1220     
1221     for (;;) {
1222         if (*read == '\0') {
1223             *write = '\0';
1224             return output;
1225         }
1226     
1227         switch ((unsigned char)*read) {
1228         case '&':
1229             strcpy(write, "&amp;");
1230             write += 5;
1231             break;
1232         case '<':
1233             strcpy(write, "&lt;");
1234             write += 4;
1235             break;
1236         case '>':
1237             strcpy(write, "&gt;");
1238             write += 4;
1239             break;
1240         case '\'':
1241             strcpy(write, "&apos;");
1242             write += 6;
1243             break;
1244         case '"':
1245             strcpy(write, "&quot;");
1246             write += 6;
1247             break;
1248         default:
1249             *(write++) = *read;
1250         }
1251         read++;
1252     }
1253 }
1254 #endif
1255
1256 void parse_file(unsigned char *newpath, unsigned char * original_path)
1257 {
1258     unsigned char ext[5];
1259     int j, encoding = 0;
1260
1261     for(j = 0; j < 5; j++)
1262         ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1263     artist[0] = '\0';
1264     title[0] = '\0';
1265     duration = -2;
1266     if(strcmp(".mp2", ext) == 0) {
1267         duration = -1;
1268         parse_mp3(newpath);
1269         encoding = MP2ENC;
1270     }
1271     if(strcmp(".mp3", ext) == 0) {
1272         duration = -1;
1273         parse_mp3(newpath);
1274         encoding = MP3ENC;
1275     }
1276     if(strcmp(".mpc", ext) == 0) {
1277         duration = -1;
1278         parse_mpc(newpath);
1279         encoding = MPCENC;
1280     }
1281     if(strcmp(".mp+", ext) == 0) {
1282         duration = -1;
1283         parse_mpc(newpath);
1284         encoding = MPPENC;
1285     }
1286     if(strcmp(".ogg", ext) == 0) {
1287         duration = -1;
1288         parse_ogg(newpath);
1289         encoding = OGGENC;
1290     }
1291     if(strcmp(".wav", ext) == 0) {
1292         duration = -1;
1293         /* parse_wav(newpath); */
1294         encoding = WAVENC;
1295     }
1296     if(strcmp(".wma", ext) == 0) {
1297         duration = -1;
1298         /* parse_wma(newpath); */
1299         encoding = WMAENC;
1300     }
1301     /* guesstitle() */
1302     if((strlen(artist) == 0) && (strlen(title) == 0)) {
1303         // there are no tag infos read
1304         // use file name to state substitute it
1305         char *c = strrchr(newpath, separator);
1306         if(c == NULL)
1307             c = newpath;
1308         strcpy(artist, ++c);
1309         // arbitrarily use the first '-'
1310         // to separate artist and title
1311         c = strchr(artist, '-');
1312         if(c != NULL) {         // if trenner found, divide file name 
1313             *c = '\0';
1314             strcpy(title, ++c);
1315             c = strrchr(title, '.');
1316             if(c != NULL)
1317                 *c = '\0';
1318         } else {                // no trenner found, assume
1319             // no artist, only title
1320             strcpy(title, artist);
1321             artist[0] = '\0';
1322         }
1323         // replace underscores by spaces
1324         for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1325             *c = ' ';
1326         for(c = title; (c = strchr(c, '_')) != NULL; c++)
1327             *c = ' ';
1328         // trim spaces
1329         trim(artist);
1330         trim(title);
1331     }
1332     /* guesstitle() end */
1333
1334     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
1335         counter++;
1336         switch (format) {
1337         case FORMAT_M3U:
1338             printf("#EXTINF:%d,", duration);
1339             if(strlen(artist) != 0)
1340                 printf("%s - ", artist);
1341             printf("%s%s", title, eol);
1342             print_path(newpath);
1343             printf("%s", eol);
1344             break;
1345         case FORMAT_PLS:
1346             printf("File%d=", counter);
1347             print_path(newpath);
1348             printf("%sTitle%d=", eol, counter);
1349             if(strlen(artist) != 0)
1350                 printf("%s - ", artist);
1351             printf("%s%s", title, eol);
1352             if(duration != -1)
1353                 printf("Length%d=%d%s", counter, duration, eol);
1354             break;
1355         case FORMAT_HTML:
1356             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1357                    artist, title);
1358             if(duration == -1)
1359                 printf("?</td></tr>%s", eol);
1360             else
1361                 printf("%d:%s%d</td></tr>%s", duration / 60,
1362                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
1363             break;
1364         case FORMAT_RSS:
1365             if(duration != -1) {
1366                 struct stat infos;
1367                 char timebuffer[256];
1368
1369                 if(stat(newpath, &infos) != 0) {
1370                     fprintf(stderr, "Warning >> can't stat entry : %s\n",
1371                             newpath);
1372                     return;
1373                 }
1374                 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime)));   /* ctime() had a trailing CR */
1375                 printf("\t<item>%s", eol);
1376                 printf("\t\t<author>");
1377                 myputstr(artist);
1378                 printf("</author>%s\t\t<title>", eol);
1379                 myputstr(title);
1380                 printf("</title>%s", eol);
1381
1382                 if(referal == NULL) {
1383                     noreferal(newpath, artist, title);
1384                 } else
1385                     reference(newpath);
1386                 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1387                        timebuffer, eol);
1388                 print_webpath(newpath);
1389                 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1390                        (int)infos.st_size, magic[encoding], eol);
1391                 print_pathtail(newpath);
1392                 printf("</guid>%s", eol);
1393                 if(duration > 3599)
1394                     printf
1395                         ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1396                          duration / 3600, (duration / 60) % 60,
1397                          duration % 60, eol);
1398                 else
1399                     printf
1400                         ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1401                          duration / 60, duration % 60, eol);
1402                 if(strlen(artist) != 0) {
1403                     printf("\t\t<itunes:author>");
1404                     myputstr(artist);
1405                     printf("</itunes:author>%s", eol);
1406                 }
1407                 printf("\t</item>%s", eol);
1408             }
1409             break;
1410         case FORMAT_PLP:
1411             myplaputstr("HARP, ");
1412             myplaputstr(newpath);
1413             myplaputstr(eol);
1414             break;
1415         case FORMAT_UMS:
1416             txxputstr(newpath);
1417             break;
1418 #ifdef HAVE_LIBURIPARSER
1419         case FORMAT_XSPF:
1420             printf("<track>\n");
1421             if (strlen(title) > 0) {
1422                 char * escaped_title = xml_escape_malloc(title);
1423                 if (escaped_title != NULL) {
1424                     printf("    <title>%s</title>\n", escaped_title);
1425                     free(escaped_title);
1426                 }
1427             }
1428             if (strlen(artist) > 0) {
1429                 char * escaped_artist = xml_escape_malloc(artist);
1430                 if (escaped_artist != NULL) {
1431                     printf("    <creator>%s</creator>\n", escaped_artist);
1432                     free(escaped_artist);
1433                 }
1434             }
1435             if (duration > 0) {
1436                 printf("    <duration>%d</duration>\n", duration);
1437             }
1438             {
1439                 char * relative_location;
1440                 char * escaped_location;
1441                 relative_location = relative_uri_malloc(newpath, original_path);
1442                 if (relative_location != NULL) {
1443                     escaped_location = xml_escape_malloc(relative_location);
1444                     if (escaped_location != NULL) {
1445                         printf("    <location>%s</location>\n", escaped_location);
1446                         free(escaped_location);
1447                     }
1448                     free(relative_location);
1449                 }
1450             }
1451             printf("</track>\n");
1452             break;
1453 #endif
1454         }
1455     }
1456 }
1457
1458 void parse_directory(unsigned char *path, unsigned char * original_path)
1459 {
1460     int i, n;
1461     struct dirent **namelist;
1462     unsigned char newpath[PATH_MAX];
1463     struct stat infos;
1464
1465     if(debug)
1466         fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1467     if(stat(path, &infos) != 0) {
1468         fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1469         return;
1470     }
1471     /* check if it is a filename */
1472     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1473         parse_file(path, original_path);
1474         return;
1475     }
1476     /* must be a directory - or something unusable like pipe, socket, etc */
1477     if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1478         fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1479         return;
1480     }
1481     for(i = 0; i < n; i++) {
1482         snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1483
1484         if(stat(newpath, &infos) != 0) {
1485             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1486             continue;
1487         }
1488         if(recursive && S_ISDIR(infos.st_mode)
1489            && strcmp(namelist[i]->d_name, ".") != 0
1490            && strcmp(namelist[i]->d_name, "..") != 0)
1491             parse_directory(newpath, original_path);
1492         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1493         if(S_ISREG(infos.st_mode)
1494            && !(avoidhlinked && hlink_check(&infos))) {
1495             parse_file(newpath, original_path);
1496         }
1497         free(namelist[i]);
1498     }
1499     free(namelist);
1500 }
1501
1502 int main(int argc, char **argv)
1503 {
1504     winorunix = one2one;
1505     basemap = one2one;
1506     parse_options(argc, argv);
1507
1508     if(optind == argc && !fromstdin)
1509         usage();
1510
1511     /* print header */
1512     switch (format) {
1513     case FORMAT_M3U:
1514         printf("#EXTM3U%s", eol);
1515         break;
1516     case FORMAT_PLS:
1517         printf("[playlist]%s", eol);
1518         break;
1519     case FORMAT_HTML:
1520         printf
1521             ("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">%s%s<html>%s%s<head>%s<title>Playlist generated by FAPG "
1522              VERSION
1523              "</title>%s<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />%s<style type=\"text/css\">%s<!--%s%sbody,td,tr {%s font-family: Verdana, Arial, Helvetica, sans-serif;%s  font-size: 12px;%s  color: #000000;%s}%s%sbody {%s  background: #ffffff;%s}%s%sth {%s  text-align: center;%s  background: #ffcccc;%s  padding-left: 15px;%s  padding-right: 15px;%s  border: 1px #dd8888 solid;%s}%s%std {%s  text-align: center;%s  background: #eeeeee;%s  padding-left: 15px;%s  padding-right: 15px;%s  border: 1px #cccccc solid;%s}%s%sh1 {%s  font-size: 25px;%s}%s%sp {%s  font-size: 10px;%s}%s%sa {%s  color: #993333;%s  text-decoration: none;%s}%s%sa:hover {%s text-decoration: underline;%s}%s%s-->%s</style>%s</head>%s%s<body>%s%s<h1>Playlist</h1>%s%s<table>%s<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>%s",
1524              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1525              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1526              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1527              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1528              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1529              eol, eol, eol);
1530         break;
1531     case FORMAT_RSS:
1532         {
1533             time_t zeit;
1534             char timebuffer[256];
1535             time(&zeit);
1536             strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1537                      localtime(&zeit));
1538             printf
1539                 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1540                  VERSION
1541                  " -->%s<rss xmlns:itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\" version=\"2.0\">%s    <channel>%s\t<title>%s - %s - %s</title>%s\t<description>Directory Tree %s</description>%s\t<link>%s</link>%s\t<itunes:image href=\"%s/xml/podcast.jpg\"/>%s\t<lastBuildDate>%s</lastBuildDate>%s\t<generator>FAPG "
1542                  VERSION
1543                  "</generator>%s\t<image>%s\t\t<url>%s/podcast.jpg</url>%s\t\t<title>Server Logo</title>%s\t\t<link>%s</link>%s\t\t<description>Feed provided by FAPG. Click to visit.</description>%s\t</image>%s\t<itunes:owner>%s\t\t<itunes:name>Admin %s</itunes:name>%s\t\t<itunes:email>podcast@%s</itunes:email>%s\t</itunes:owner>%s\t<category>Various</category>%s\t<itunes:subtitle>Directory Tree %s</itunes:subtitle>%s\t<itunes:author>%s</itunes:author>%s\t<copyright>unknown</copyright>%s\t<language>%s</language>%s\t<itunes:explicit>No</itunes:explicit>%s\t<ttl>1800</ttl>%s",
1544                  eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1545                  prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1546                  eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1547                  hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1548                  eol, getenv("LANG"), eol, eol, eol);
1549             unix2dos[38] = 43;  // I never made an rss feed work with '&' in it
1550             basemap = noand;
1551         }
1552         break;
1553     case FORMAT_PLP:
1554         {
1555             eol = "\r\n";
1556             myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1557         }
1558         break;
1559     case FORMAT_UMS:
1560         {
1561             txxputheader("    iriver UMS PLA");
1562         }
1563         break;
1564 #ifdef HAVE_LIBURIPARSER
1565     case FORMAT_XSPF:
1566         printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1567                 "<!-- generator=\"FAPG " VERSION " -->\n"
1568                 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1569                 "<trackList>\n");
1570         break;
1571 #endif
1572     }
1573     
1574     /* iterate through files */
1575     {
1576         const char * const pwd_source = getenv("PWD");
1577         const int pwdlen = strlen(pwd_source);
1578         char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1579         sprintf(pwd, "%s/", pwd_source);
1580         
1581         if(fromstdin) {
1582             unsigned char path[PATH_MAX];
1583             int i;
1584             while(fgets(path, PATH_MAX, stdin)) {
1585                 for(i = 0; i < PATH_MAX; i++)
1586                     if(path[i] == '\r' || path[i] == '\n')
1587                         path[i] = '\0';
1588                 if (i <= 0) {
1589                     continue;
1590                 }
1591
1592                 /* strip trailing slash */
1593                 if (path[i - 1] == '/') {
1594                     path[i - 1] = '\0';
1595                 }
1596
1597                 parse_directory(path, pwd);
1598             }
1599         } else
1600             for(; optind < argc; optind++) {
1601                 /* strip trailing slash */
1602                 char * dup = strdup(argv[optind]);
1603                 const int len = strlen(dup);
1604                 if ((len > 0) && (dup[len - 1] == '/')) {
1605                     dup[len - 1] = '\0';
1606                 }
1607
1608                 parse_directory(dup, pwd);
1609             }
1610             
1611         free(pwd);
1612     }
1613
1614     /* print footer */
1615     switch (format) {
1616     case FORMAT_PLS:
1617         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1618         break;
1619     case FORMAT_HTML:
1620         printf
1621             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1622              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1623              eol, eol);
1624         break;
1625     case FORMAT_RSS:
1626         printf("    </channel>%s</rss>%s", eol, eol);
1627         break;
1628     case FORMAT_UMS:
1629         txxputcounter(counter);
1630         break;
1631 #ifdef HAVE_LIBURIPARSER
1632     case FORMAT_XSPF:
1633         printf("</trackList>\n"
1634                 "</playlist>\n");
1635         break;
1636 #endif
1637     }
1638
1639     if(genrelist)
1640         free(genrelist);
1641
1642     exit(0);
1643 }
1644