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