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