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