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