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