increase buffer size for ID3 with images
[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(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(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         free(command);
504         return;
505     }
506     fgets(buffer, 1020, pipe);
507     while(!feof(pipe)) {
508         fputs(buffer, stdout);
509         fgets(buffer, 1020, pipe);
510     }
511     pclose(pipe);
512     return;
513 }
514
515 void parse_options(int argc, char **argv)
516 {
517     static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
518     static struct option long_options[] = {
519         {"backslash", no_argument, NULL, 'b'},
520         {"command", required_argument, NULL, 'c'},
521         {"debug", no_argument, NULL, 'd'},
522         {"format", required_argument, NULL, 'f'},
523         {"genre", required_argument, NULL, 'g'},
524         {"nohardlink", no_argument, NULL, 'n'},
525         {"output", required_argument, NULL, 'o'},
526         {"prefix", required_argument, NULL, 'p'},
527         {"recursive", no_argument, NULL, 'r'},
528         {"stdin", no_argument, NULL, 's'},
529         {"windows", no_argument, NULL, 'w'},
530         {"exclude", required_argument, NULL, 'x'},
531         {NULL, 0, NULL, 0}
532     };
533     int c;
534     int option_index = 0;
535     while((c =
536            getopt_long(argc, argv, short_options, long_options,
537                        &option_index)) != -1) {
538         switch (c) {
539         case 'b':
540             separator = '\\';
541             noand['/'] = '\\';
542             break;
543         case 'c':
544             if(strncmp(optarg, "intern", 6) == 0)
545                 referal = NULL;
546             else
547                 referal = strdup(optarg);
548             break;
549         case 'd':
550             debug = 1 - debug;
551             break;
552         case 'f':
553             if(strcmp(optarg, "m3u") == 0)
554                 format = FORMAT_M3U;
555             else if(strcmp(optarg, "pls") == 0)
556                 format = FORMAT_PLS;
557             else if(strcmp(optarg, "html") == 0)
558                 format = FORMAT_HTML;
559             else if(strcmp(optarg, "rss") == 0)
560                 format = FORMAT_RSS;
561             else if(strcmp(optarg, "pla") == 0)
562                 format = FORMAT_PLP;
563             else if(strcmp(optarg, "txx") == 0)
564                 format = FORMAT_UMS;
565 #ifdef HAVE_LIBURIPARSER
566             else if(strcmp(optarg, "xspf") == 0)
567                 format = FORMAT_XSPF;
568 #endif
569             else
570                 usage();
571             break;
572         case 'g':
573             if(genrelist == NULL)
574                 genrelist = calloc(257, sizeof(char));  /* allow multiple includes/excludes */
575             if(genrelist == NULL) {
576                 fprintf(stderr,
577                         "Error >> unable to allocate cleared memory\n");
578                 exit(2);
579             } else {
580                 unsigned int n = 0;
581                 while(n < strlen(optarg)) {
582                     if(debug)
583                         fprintf(stderr,
584                                 "Debug >> genrelist entry activting : %d\n",
585                                 atoi(&optarg[n]));
586                     genrelist[atoi(&optarg[n])] = 1;
587                     while(isdigit(optarg[n++]));
588                 }
589             }
590             break;
591         case 'n':
592             avoidhlinked = 1;
593             break;
594         case 'o':
595             close(1);
596             if(fopen(optarg, "w") == NULL) {
597                 fprintf(stderr,
598                         "Error >> unable to open output file : %s\n",
599                         optarg);
600                 exit(2);
601             }
602             break;
603         case 'p':
604             prefix = malloc(strlen(optarg) + 1);
605             strcpy(prefix, optarg);
606             base = malloc(strlen(prefix) + 1);
607             strcpy(base, prefix);
608             dir = strchr(base, '/');
609             if((dir != NULL) && (dir[1] == '/'))
610                 dir = strchr(dir + 2, '/');
611             if(dir != NULL)
612                 *dir++ = 0;
613             else
614                 dir = "";
615             /* if prefix is a weblink, base is the baselink, dir is the path */
616             break;
617         case 'r':
618             recursive = 1;
619             break;
620         case 'u':
621             winorunix = one2one;
622             eol = "\n";
623             break;
624         case 'w':
625             winorunix = unix2dos;
626             eol = "\r\n";
627             break;
628         case 'x':
629             if(genrelist == NULL) {     /* allow multiple includes/excludes - not recommended (confusing) but possible */
630                 int n = 0;
631                 genrelist = calloc(257, sizeof(char));
632                 while(n < 256)
633                     genrelist[n++] = 1;
634             }
635             if(genrelist == NULL) {
636                 fprintf(stderr,
637                         "Error >> unable to allocate cleared memory\n");
638                 exit(2);
639             } else {
640                 unsigned int n = 0;
641                 while(n < strlen(optarg)) {
642                     if(debug)
643                         fprintf(stderr,
644                                 "Debug >> genrelist entry activting : %d\n",
645                                 atoi(&optarg[n]));
646                     genrelist[atoi(&optarg[n])] = 0;
647                     while(isdigit(optarg[n++]));
648                 }
649             }
650             break;
651         case 's':
652             fromstdin = 1;
653             break;
654         default:
655             usage();
656         }
657     }
658     /* hostname = getenv("HOSTNAME"); */
659     if(genrelist == NULL) {
660         genrelist = calloc(257, sizeof(char));
661         if(genrelist == NULL) {
662             fprintf(stderr,
663                     "Error >> unable to allocate cleared memory\n");
664             exit(2);
665         } else {
666             int n = 0;
667             while(n < 256)
668                 genrelist[n++] = 1;
669         }
670     }
671 }
672
673 void parse_mp3(unsigned char *file)
674 {
675     int bitrates[2][3][15] =
676         { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
677             416, 448},
678            {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
679             384},
680            {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
681             320}},
682     {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
683      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
684      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
685     };
686     FILE *fic;
687     unsigned char *c;
688     int lus;
689
690     genre = 0;
691     genrebuf[0] = 0;
692     if(debug)
693         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
694
695     /* read header */
696     if((fic = fopen(file, "r")) == NULL) {
697         fprintf(stderr, "Warning >> can't open file : %s\n", file);
698         return;
699     }
700     lus = fread(buffer, 1, MP3_BASE, fic);
701     c = buffer;
702
703     /* try ID3v2 */
704     if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
705         int size;
706         int version;
707         version = *(buffer + 3);
708         if(version < 2 || version > 4)
709             fprintf(stderr,
710                     "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
711                     version, file);
712         if(*(buffer + 5) != 0)
713             fprintf(stderr,
714                     "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
715                     *(buffer + 5), file);
716         c = buffer + 6;
717         size =
718             (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
719         /* read more header */
720         if(size + lus > MAX) {
721             lus += fread(buffer + lus, 1, MAX - lus, fic);
722             fprintf(stderr,
723                     "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
724                     size, file);
725         } else
726             lus += fread(buffer + lus, 1, size, fic);
727         if(size > lus)
728             size = lus;
729         c += 4;
730         if(version == 2)
731             while(c < buffer + size) {
732                 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
733                 if(*c == 0)
734                     break;
735                 if(strncmp(c, "TT2", 3) == 0) {
736                     strncpy(title, c + 7, size - 1);
737                     title[size - 1] = '\0';
738                 }
739                 if(strncmp(c, "TP1", 3) == 0) {
740                     strncpy(artist, c + 7, size - 1);
741                     artist[size - 1] = '\0';
742                 }
743                 if(strncmp(c, "TCO", 3) == 0) {
744                     /* strncpy(genrebuf,c+7,size-1); */
745                     /* genrebuf[size-1]='\0'; */
746                     /* genre=atoi(&genrebuf[1]); */
747                     genre = atoi(c + 8);
748                 }
749                 c += size + 6;
750             }
751         if(version == 3 || version == 4)
752             while(c < buffer + size) {
753                 int size =
754                     (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
755                     (*(c + 7));
756                 if(*c == 0)
757                     break;
758                 if(strncmp(c, "TIT2", 4) == 0) {
759                     strncpy(title, c + 11, size - 1);
760                     title[size - 1] = '\0';
761                 }
762                 if(strncmp(c, "TPE1", 4) == 0) {
763                     strncpy(artist, c + 11, size - 1);
764                     artist[size - 1] = '\0';
765                 }
766                 if(strncmp(c, "TCON", 4) == 0) {
767                     /* strncpy(genrebuf,c+11,size-1); */
768                     /* genrebuf[size-1]='\0'; */
769                     /* genre=atoi(&genrebuf[1]); */
770                     genre = atoi(c + 12);
771                 }
772                 c += size + 10;
773             }
774     }
775
776     while(c < buffer + lus - 10) {
777         if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
778             int version;
779             int lay;
780             int bitrate_index;
781             int bitrate;
782             version = 2 - (*(c + 1) >> 3 & 1);
783             lay = 4 - (*(c + 1) >> 1 & 3);
784             bitrate_index = *(c + 2) >> 4 & 0xF;
785             if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
786                && bitrate_index >= 0 && bitrate_index <= 14)
787                 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
788             else
789                 bitrate = 0;
790             if(bitrate != 0) {
791                 fseek(fic, 0, SEEK_END);
792                 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
793             } else
794                 duration = 0;
795             break;
796         }
797         c++;
798     }
799
800     /* try ID3v1 */
801     if(strlen(artist) == 0 && strlen(title) == 0) {
802         fseek(fic, -128, SEEK_END);
803         lus = fread(buffer, 1, 128, fic);
804         if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
805            && buffer[2] == 'G') {
806             strncpy(title, buffer + 3, 30);
807             title[30] = '\0';
808             c = title + 29;
809             while(c > title && *c == ' ')
810                 *(c--) = '\0';
811             strncpy(artist, buffer + 33, 30);
812             artist[30] = '\0';
813             c = artist + 29;
814             while(c > artist && *c == ' ')
815                 *(c--) = '\0';
816             /* strncpy(album,buffer+65,30); */
817             /* strncpy(year,buffer+97,4); */
818             /* strncpy(comment,buffer+101,30); */
819             /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
820             genre = buffer[127];
821         }
822     }
823
824     fclose(fic);
825 }
826
827 void parse_ogg(unsigned char *file)
828 {
829     FILE *fic;
830     unsigned char *c;
831     int lus;
832     int sample_rate;
833     int samples;
834
835     if(debug)
836         fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
837
838     /* read header */
839     if((fic = fopen(file, "r")) == NULL) {
840         fprintf(stderr, "Warning >> can't open file : %s\n", file);
841         return;
842     }
843     lus = fread(buffer, 1, OGG_BASE, fic);
844
845     /* try Ogg */
846     if(strncmp(buffer, "Ogg", 3) != 0) {
847         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
848         return;
849     }
850
851     c = buffer + 0x28;
852     sample_rate =
853         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
854
855     while(c < buffer + lus - 10) {
856         int size;
857         if(strncasecmp(c, "TITLE=", 6) == 0) {
858             size =
859                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
860                 (*(c - 1) << 24);
861             strncpy(title, c + 6, size - 6);
862             title[size - 6] = '\0';
863             c += size;
864         }
865         if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
866             // ignore tag
867             size =
868                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
869                 (*(c - 1) << 24);
870             c += size;
871         }
872         if(strncasecmp(c, "ARTIST=", 7) == 0) {
873             size =
874                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
875                 (*(c - 1) << 24);
876             strncpy(artist, c + 7, size - 7);
877             artist[size - 7] = '\0';
878             c += size;
879         }
880         if(strncasecmp(c, "GENRE=", 6) == 0) {
881             static int i = 0;
882             size =
883                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
884                 (*(c - 1) << 24);
885             strncpy(genrebuf, c + 6, size - 6);
886             genrebuf[size - 6] = '\0';
887             c += size;
888             for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
889                 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
890                     genre = i;
891                     break;
892                 }
893                 if(i == ID3_NR_OF_V1_GENRES)
894                     genre = 0;
895             }
896         }
897         c++;
898     }
899
900     fseek(fic, -OGG_BASE, SEEK_END);
901     lus = fread(buffer, 1, OGG_BASE, fic);
902     c = buffer + lus - 1;
903     while(strncmp(c, "OggS", 4) != 0 && c > buffer)
904         c--;
905     if(c != buffer) {
906         c += 6;
907         samples =
908             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
909         duration = samples / sample_rate;
910     }
911
912     fclose(fic);
913 }
914
915 void parse_mpc(unsigned char *file)
916 {
917     FILE *fic;
918     unsigned char *c;
919     int lus;
920     int sample_rates[4] = { 44100, 48000, 37800, 32000 };
921     int frame_count;
922     int size, items;
923     int i;
924
925     if(debug)
926         fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
927
928     /* read header */
929     if((fic = fopen(file, "r")) == NULL) {
930         fprintf(stderr, "Warning >> can't open file : %s\n", file);
931         return;
932     }
933     lus = fread(buffer, 1, 12, fic);
934
935     /* try Musepack */
936     if (strncmp(buffer, "MP+", 3) != 0) {
937         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
938         return;
939     }
940
941     /* only version 7 */
942     if(buffer[3] != 7) {
943         fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
944                 file);
945         return;
946     }
947
948     /* duration */
949     c = buffer + 4;
950     frame_count =
951         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
952     c += 5;
953     duration = frame_count * 1152 / sample_rates[*c & 3];
954
955     /* try APETAGEX footer */
956     fseek(fic, -32, SEEK_END);
957     lus = fread(buffer, 1, 32, fic);
958     if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
959         c = buffer + 12;
960         size =
961             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
962         size += 32;
963         c += 4;
964         items =
965             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
966         fseek(fic, -size, SEEK_END);
967         lus = fread(buffer, 1, size, fic);
968         if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
969             c = buffer + 32;
970             while(items--) {
971                 size =
972                     (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
973                     (*(c + 3) << 24);
974                 c += 8;
975                 if(strcasecmp(c, "TITLE") == 0) {
976                     strncpy(title, c + 6, size);
977                     title[size] = '\0';
978                 }
979                 if(strcasecmp(c, "ARTIST") == 0) {
980                     strncpy(artist, c + 7, size);
981                     artist[size] = '\0';
982                 }
983                 if(strcasecmp(c, "GENRE") == 0) {
984                     for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
985                         strncpy(genrebuf, c + 6, size);
986                         genrebuf[size] = '\0';
987                         if(strcasecmp
988                            (ID3_v1_genre_description[i], genrebuf) == 0) {
989                             genre = i;
990                             break;
991                         }
992                         if(i == ID3_NR_OF_V1_GENRES)
993                             genre = 0;
994                     }
995                 }
996                 c += strlen(c) + 1 + size;
997             }
998         }
999     }
1000
1001     fclose(fic);
1002 }
1003
1004 #define FSN  32
1005 #define MAXINO  (1<<24)
1006 #define INOTYP  unsigned long
1007 #define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
1008 #define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
1009
1010 int hlink_check(struct stat *info)
1011 {
1012     /*
1013      * for speed this subroutine should only be called
1014      * - if the file has more than one hardlink
1015      * - if the file is a resolved softlink
1016      */
1017     /* the persistent variables */
1018     static INOTYP *list[FSN];
1019     static dev_t name[FSN];
1020     /* some temporary variables */
1021     int fsn, is_registered = 0;
1022
1023     /* assertions - in case parameters are lowered for less memory usage */
1024     assert(fsn < FSN);
1025     assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1026
1027     /* search which internal registration number is used for this filesystem */
1028     for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1029
1030     /* if file system is not registered yet, do it and leave */
1031     if(name[fsn] == 0) {
1032         name[fsn] = (info->st_dev);
1033         /* provide space for the bitmap that maps the inodes of this file system */
1034         list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1035         /* no comparison is needed in empty lists ... return */
1036         if(debug)
1037             fprintf(stderr,
1038                     "Debug >> Linked >> Init List %04x @mem %04lx\n",
1039                     (int)name[fsn], (long)&list[fsn]);
1040     } else {
1041         /* this looks more complicated than it really is */
1042         /* the idea is very simple: 
1043          *  provide a bitmap that maps all inodes of a file system
1044          *  to mark all files that have already been visited.
1045          *  If it is already visited, do not add it to the playlist
1046          */
1047         /*
1048          * The difficulty is as follows:
1049          *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1050          * would be byte-aligned and would allocate at least eight times the needed space.
1051          * Feel free to change the definitions that are involved here, if you know better.
1052          */
1053         if(regbit_qry(list[fsn], (info->st_ino)))
1054             is_registered = 1;
1055         else
1056             regbit_set(list[fsn], (info->st_ino));
1057         /*
1058          * the debug expression is more complicated then the working stuff
1059          */
1060         if(debug)
1061             fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1062                     "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1063                     (int)info->st_dev, (int)info->st_ino, fsn,
1064                     (int)((info->st_ino) / sizeof(INOTYP)),
1065                     (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1066                     1 << ((info->st_ino) % sizeof(INOTYP)),
1067                     is_registered ? "Already" : "Not");
1068     }
1069     return is_registered;
1070 }
1071
1072 #ifdef HAVE_LIBURIPARSER
1073 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1074 {
1075     char * absSourceFile;
1076     size_t absSourceLen;
1077     char * sourceUriString;
1078     char * baseUriString;
1079     UriParserStateA state;
1080     UriUriA sourceUri;
1081     UriUriA baseUri;
1082     UriUriA relativeUri;
1083     int charsRequired;
1084     char * output;
1085   
1086     /* checks */
1087     if ((unixFilename == NULL) || (baseDir == NULL)) {
1088         return NULL;
1089     }
1090
1091     /* base URI */
1092     baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1093     if (baseUriString == NULL) {
1094         return NULL;
1095     }
1096     if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1097         free(baseUriString);
1098         return NULL;
1099     }
1100     state.uri = &baseUri;
1101     if (uriParseUriA(&state, baseUriString) != 0) {
1102         free(baseUriString);
1103         uriFreeUriMembersA(&baseUri);
1104         return NULL;
1105     }
1106
1107     /* source URI */
1108     if (unixFilename[0] != '/') {
1109         const int baseDirLen = strlen(baseDir);
1110         const int sourceFileLen = strlen(unixFilename);
1111         absSourceLen = baseDirLen + sourceFileLen;
1112         absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1113         sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1114     } else {
1115         absSourceLen = strlen(unixFilename);
1116         absSourceFile = (char *)unixFilename;
1117     }
1118     sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1119     if (sourceUriString == NULL) {
1120         free(baseUriString);
1121         if (unixFilename[0] != '/') {
1122             free(absSourceFile);
1123         }
1124         uriFreeUriMembersA(&baseUri);
1125         return NULL;
1126     }
1127     if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1128         free(baseUriString);
1129         free(sourceUriString);
1130         if (unixFilename[0] != '/') {
1131             free(absSourceFile);
1132         }
1133         uriFreeUriMembersA(&baseUri);
1134         return NULL;
1135     }
1136     state.uri = &sourceUri;
1137     if (uriParseUriA(&state, sourceUriString) != 0) {
1138         free(baseUriString);
1139         free(sourceUriString);
1140         uriFreeUriMembersA(&baseUri);
1141         uriFreeUriMembersA(&sourceUri);
1142         return NULL;
1143     }
1144     if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1145         free(baseUriString);
1146         free(sourceUriString);
1147         if (unixFilename[0] != '/') {
1148             free(absSourceFile);
1149         }
1150         uriFreeUriMembersA(&baseUri);
1151         uriFreeUriMembersA(&sourceUri);
1152         return NULL;
1153     }
1154
1155     /* make relative (or keep absolute if necessary) */
1156     if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1157         free(baseUriString);
1158         free(sourceUriString);
1159         if (unixFilename[0] != '/') {
1160             free(absSourceFile);
1161         }
1162         uriFreeUriMembersA(&baseUri);
1163         uriFreeUriMembersA(&sourceUri);
1164         uriFreeUriMembersA(&relativeUri);
1165         return NULL;
1166     }
1167     
1168     /* back to string */
1169     if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1170         free(baseUriString);
1171         free(sourceUriString);
1172         if (unixFilename[0] != '/') {
1173             free(absSourceFile);
1174         }
1175         uriFreeUriMembersA(&baseUri);
1176         uriFreeUriMembersA(&sourceUri);
1177         uriFreeUriMembersA(&relativeUri);
1178         return NULL;
1179     }
1180     output = malloc((charsRequired + 1) * sizeof(char));
1181     if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1182         free(baseUriString);
1183         free(sourceUriString);
1184         if (unixFilename[0] != '/') {
1185             free(absSourceFile);
1186         }
1187         free(output);
1188         uriFreeUriMembersA(&baseUri);
1189         uriFreeUriMembersA(&sourceUri);
1190         uriFreeUriMembersA(&relativeUri);
1191         return NULL;
1192     }
1193
1194     free(baseUriString);
1195     free(sourceUriString);
1196     if (unixFilename[0] != '/') {
1197         free(absSourceFile);
1198     }
1199     uriFreeUriMembersA(&baseUri);
1200     uriFreeUriMembersA(&sourceUri);
1201     uriFreeUriMembersA(&relativeUri);
1202
1203     return output;
1204 }
1205
1206 char * xml_escape_malloc(const char * input)
1207 {
1208     const char * read = input;
1209     char * output;
1210     char * write;
1211     
1212     if (input == NULL) {
1213         return NULL;
1214     }
1215     
1216     output = malloc((6 * strlen(input) + 1) * sizeof(char));
1217     if (output == NULL) {
1218         return NULL;
1219     }
1220     write = output;
1221     
1222     for (;;) {
1223         if (*read == '\0') {
1224             *write = '\0';
1225             return output;
1226         }
1227     
1228         switch ((unsigned char)*read) {
1229         case '&':
1230             strcpy(write, "&amp;");
1231             write += 5;
1232             break;
1233         case '<':
1234             strcpy(write, "&lt;");
1235             write += 4;
1236             break;
1237         case '>':
1238             strcpy(write, "&gt;");
1239             write += 4;
1240             break;
1241         case '\'':
1242             strcpy(write, "&apos;");
1243             write += 6;
1244             break;
1245         case '"':
1246             strcpy(write, "&quot;");
1247             write += 6;
1248             break;
1249         default:
1250             *(write++) = *read;
1251         }
1252         read++;
1253     }
1254 }
1255 #endif
1256
1257 void parse_file(unsigned char *newpath, unsigned char * original_path)
1258 {
1259     unsigned char ext[5];
1260     int j, encoding = 0;
1261
1262     for(j = 0; j < 5; j++)
1263         ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1264     artist[0] = '\0';
1265     title[0] = '\0';
1266     duration = -2;
1267     if(strcmp(".mp2", ext) == 0) {
1268         duration = -1;
1269         parse_mp3(newpath);
1270         encoding = MP2ENC;
1271     }
1272     if(strcmp(".mp3", ext) == 0) {
1273         duration = -1;
1274         parse_mp3(newpath);
1275         encoding = MP3ENC;
1276     }
1277     if(strcmp(".mpc", ext) == 0) {
1278         duration = -1;
1279         parse_mpc(newpath);
1280         encoding = MPCENC;
1281     }
1282     if(strcmp(".mp+", ext) == 0) {
1283         duration = -1;
1284         parse_mpc(newpath);
1285         encoding = MPPENC;
1286     }
1287     if(strcmp(".ogg", ext) == 0) {
1288         duration = -1;
1289         parse_ogg(newpath);
1290         encoding = OGGENC;
1291     }
1292     if(strcmp(".wav", ext) == 0) {
1293         duration = -1;
1294         /* parse_wav(newpath); */
1295         encoding = WAVENC;
1296     }
1297     if(strcmp(".wma", ext) == 0) {
1298         duration = -1;
1299         /* parse_wma(newpath); */
1300         encoding = WMAENC;
1301     }
1302     /* guesstitle() */
1303     if((strlen(artist) == 0) && (strlen(title) == 0)) {
1304         // there are no tag infos read
1305         // use file name to state substitute it
1306         char *c = strrchr(newpath, separator);
1307         if(c == NULL)
1308             c = newpath;
1309         strcpy(artist, ++c);
1310         // arbitrarily use the first '-'
1311         // to separate artist and title
1312         c = strchr(artist, '-');
1313         if(c != NULL) {         // if trenner found, divide file name 
1314             *c = '\0';
1315             strcpy(title, ++c);
1316             c = strrchr(title, '.');
1317             if(c != NULL)
1318                 *c = '\0';
1319         } else {                // no trenner found, assume
1320             // no artist, only title
1321             strcpy(title, artist);
1322             artist[0] = '\0';
1323         }
1324         // replace underscores by spaces
1325         for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1326             *c = ' ';
1327         for(c = title; (c = strchr(c, '_')) != NULL; c++)
1328             *c = ' ';
1329         // trim spaces
1330         trim(artist);
1331         trim(title);
1332     }
1333     /* guesstitle() end */
1334
1335     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
1336         counter++;
1337         switch (format) {
1338         case FORMAT_M3U:
1339             if(duration != -1) {
1340                 printf("#EXTINF:%d,", duration);
1341                 if(strlen(artist) != 0)
1342                     printf("%s - ", artist);
1343                 printf("%s%s", title, eol);
1344             }
1345             print_path(newpath);
1346             printf("%s", eol);
1347             break;
1348         case FORMAT_PLS:
1349             printf("File%d=", counter);
1350             print_path(newpath);
1351             printf("%sTitle%d=", eol, counter);
1352             if(strlen(artist) != 0)
1353                 printf("%s - ", artist);
1354             printf("%s%s", title, eol);
1355             if(duration != -1)
1356                 printf("Length%d=%d%s", counter, duration, eol);
1357             break;
1358         case FORMAT_HTML:
1359             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1360                    artist, title);
1361             if(duration == -1)
1362                 printf("?</td></tr>%s", eol);
1363             else
1364                 printf("%d:%s%d</td></tr>%s", duration / 60,
1365                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
1366             break;
1367         case FORMAT_RSS:
1368             if(duration != -1) {
1369                 struct stat infos;
1370                 char timebuffer[256];
1371
1372                 if(stat(newpath, &infos) != 0) {
1373                     fprintf(stderr, "Warning >> can't stat entry : %s\n",
1374                             newpath);
1375                     return;
1376                 }
1377                 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime)));   /* ctime() had a trailing CR */
1378                 printf("\t<item>%s", eol);
1379                 printf("\t\t<author>");
1380                 myputstr(artist);
1381                 printf("</author>%s\t\t<title>", eol);
1382                 myputstr(title);
1383                 printf("</title>%s", eol);
1384
1385                 if(referal == NULL) {
1386                     noreferal(newpath, artist, title);
1387                 } else
1388                     reference(newpath);
1389                 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1390                        timebuffer, eol);
1391                 print_webpath(newpath);
1392                 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1393                        (int)infos.st_size, magic[encoding], eol);
1394                 print_pathtail(newpath);
1395                 printf("</guid>%s", eol);
1396                 if(duration > 3599)
1397                     printf
1398                         ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1399                          duration / 3600, (duration / 60) % 60,
1400                          duration % 60, eol);
1401                 else
1402                     printf
1403                         ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1404                          duration / 60, duration % 60, eol);
1405                 if(strlen(artist) != 0) {
1406                     printf("\t\t<itunes:author>");
1407                     myputstr(artist);
1408                     printf("</itunes:author>%s", eol);
1409                 }
1410                 printf("\t</item>%s", eol);
1411             }
1412             break;
1413         case FORMAT_PLP:
1414             myplaputstr("HARP, ");
1415             myplaputstr(newpath);
1416             myplaputstr(eol);
1417             break;
1418         case FORMAT_UMS:
1419             txxputstr(newpath);
1420             break;
1421 #ifdef HAVE_LIBURIPARSER
1422         case FORMAT_XSPF:
1423             printf("<track>\n");
1424             if (strlen(title) > 0) {
1425                 char * escaped_title = xml_escape_malloc(title);
1426                 if (escaped_title != NULL) {
1427                     printf("    <title>%s</title>\n", escaped_title);
1428                     free(escaped_title);
1429                 }
1430             }
1431             if (strlen(artist) > 0) {
1432                 char * escaped_artist = xml_escape_malloc(artist);
1433                 if (escaped_artist != NULL) {
1434                     printf("    <creator>%s</creator>\n", escaped_artist);
1435                     free(escaped_artist);
1436                 }
1437             }
1438             if (duration > 0) {
1439                 printf("    <duration>%d</duration>\n", duration);
1440             }
1441             {
1442                 char * relative_location;
1443                 char * escaped_location;
1444                 relative_location = relative_uri_malloc(newpath, original_path);
1445                 if (relative_location != NULL) {
1446                     escaped_location = xml_escape_malloc(relative_location);
1447                     if (escaped_location != NULL) {
1448                         printf("    <location>%s</location>\n", escaped_location);
1449                         free(escaped_location);
1450                     }
1451                     free(relative_location);
1452                 }
1453             }
1454             printf("</track>\n");
1455             break;
1456 #endif
1457         }
1458     }
1459 }
1460
1461 void parse_directory(unsigned char *path, unsigned char * original_path)
1462 {
1463     int i, n;
1464     struct dirent **namelist;
1465     unsigned char newpath[PATH_MAX];
1466     struct stat infos;
1467
1468     if(debug)
1469         fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1470     if(stat(path, &infos) != 0) {
1471         fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1472         return;
1473     }
1474     /* check if it is a filename */
1475     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1476         parse_file(path, original_path);
1477         return;
1478     }
1479     /* must be a directory - or something unusable like pipe, socket, etc */
1480     if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1481         fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1482         return;
1483     }
1484     for(i = 0; i < n; i++) {
1485         snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1486
1487         if(stat(newpath, &infos) != 0) {
1488             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1489             continue;
1490         }
1491         if(recursive && S_ISDIR(infos.st_mode)
1492            && strcmp(namelist[i]->d_name, ".") != 0
1493            && strcmp(namelist[i]->d_name, "..") != 0)
1494             parse_directory(newpath, original_path);
1495         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1496         if(S_ISREG(infos.st_mode)
1497            && !(avoidhlinked && hlink_check(&infos))) {
1498             parse_file(newpath, original_path);
1499         }
1500         free(namelist[i]);
1501     }
1502     free(namelist);
1503 }
1504
1505 int main(int argc, char **argv)
1506 {
1507     winorunix = one2one;
1508     basemap = one2one;
1509     parse_options(argc, argv);
1510
1511     if(optind == argc && !fromstdin)
1512         usage();
1513
1514     /* print header */
1515     switch (format) {
1516     case FORMAT_M3U:
1517         printf("#EXTM3U%s", eol);
1518         break;
1519     case FORMAT_PLS:
1520         printf("[playlist]%s", eol);
1521         break;
1522     case FORMAT_HTML:
1523         printf
1524             ("<!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 "
1525              VERSION
1526              "</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",
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, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1530              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1531              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1532              eol, eol, eol);
1533         break;
1534     case FORMAT_RSS:
1535         {
1536             time_t zeit;
1537             char timebuffer[256];
1538             time(&zeit);
1539             strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1540                      localtime(&zeit));
1541             printf
1542                 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1543                  VERSION
1544                  " -->%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 "
1545                  VERSION
1546                  "</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",
1547                  eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1548                  prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1549                  eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1550                  hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1551                  eol, getenv("LANG"), eol, eol, eol);
1552             unix2dos[38] = 43;  // I never made an rss feed work with '&' in it
1553             basemap = noand;
1554         }
1555         break;
1556     case FORMAT_PLP:
1557         {
1558             eol = "\r\n";
1559             myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1560         }
1561         break;
1562     case FORMAT_UMS:
1563         {
1564             txxputheader("    iriver UMS PLA");
1565         }
1566         break;
1567 #ifdef HAVE_LIBURIPARSER
1568     case FORMAT_XSPF:
1569         printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1570                 "<!-- generator=\"FAPG " VERSION " -->\n"
1571                 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1572                 "<trackList>\n");
1573         break;
1574 #endif
1575     }
1576     
1577     /* iterate through files */
1578     {
1579         const char * const pwd_source = getenv("PWD");
1580         const int pwdlen = strlen(pwd_source);
1581         char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1582         sprintf(pwd, "%s/", pwd_source);
1583         
1584         if(fromstdin) {
1585             unsigned char path[PATH_MAX];
1586             int i;
1587             while(fgets(path, PATH_MAX, stdin)) {
1588                 for(i = 0; i < PATH_MAX; i++)
1589                     if(path[i] == '\r' || path[i] == '\n')
1590                         path[i] = '\0';
1591                 if (i <= 0) {
1592                     continue;
1593                 }
1594
1595                 /* strip trailing slash */
1596                 if (path[i - 1] == '/') {
1597                     path[i - 1] = '\0';
1598                 }
1599
1600                 parse_directory(path, pwd);
1601             }
1602         } else
1603             for(; optind < argc; optind++) {
1604                 /* strip trailing slash */
1605                 char * dup = strdup(argv[optind]);
1606                 const int len = strlen(dup);
1607                 if ((len > 0) && (dup[len - 1] == '/')) {
1608                     dup[len - 1] = '\0';
1609                 }
1610
1611                 parse_directory(dup, pwd);
1612             }
1613             
1614         free(pwd);
1615     }
1616
1617     /* print footer */
1618     switch (format) {
1619     case FORMAT_PLS:
1620         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1621         break;
1622     case FORMAT_HTML:
1623         printf
1624             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1625              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1626              eol, eol);
1627         break;
1628     case FORMAT_RSS:
1629         printf("    </channel>%s</rss>%s", eol, eol);
1630         break;
1631     case FORMAT_UMS:
1632         txxputcounter(counter);
1633         break;
1634 #ifdef HAVE_LIBURIPARSER
1635     case FORMAT_XSPF:
1636         printf("</trackList>\n"
1637                 "</playlist>\n");
1638         break;
1639 #endif
1640     }
1641
1642     if(genrelist)
1643         free(genrelist);
1644
1645     exit(0);
1646 }
1647