Git

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  */
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
44 #define MP3_BASE 1024
45 #define OGG_BASE 1024*10
46 #define MAX 1024*200            /* 200ko for ID3 with JPEG images in it */
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
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];
75 int counter = 0;
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
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 };
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 };
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 */
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 '+' */
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 };
232 void usage()
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);
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)
250     putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
251     putchar('\0');
254 void mywebputstr(const char *c)
256     while(*c != 0) {
257         mywebputchar(*c);
258         c++;
259     }
262 void myplaputstr(const char *c)
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     }
276 void myputstr(const char *c)
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     }
290 void txxputheader(const char *c)
292     int cnt = 0;
294     while(*c != 0) {
295         myputchar(*c);
296         cnt++;
297         c++;
298     }
300     while(cnt < 512) {
301         putchar('\0');
302         cnt++;
303     }
306 void txxputnameoffset(const char *c)
308     int pos = 0;
309     int cnt = 0;
310     char b;
311     unsigned char *prefx;
313     prefx = prefix;
315     if(*prefx != 0) {
316         while(*prefx != 0) {
317             if(*prefx == '/') {
318                 pos = cnt;
319             }
320             cnt++;
321             prefx++;
322         }
324         cnt--;                  // skip the leading dot of the filepath
325     }
327     while(*c != 0) {
328         if(*c == '/') {
329             pos = cnt;
330         }
331         cnt++;
332         c++;
333     }
335     pos += 2;
337     b = (pos & 0xFF00) >> 8;
338     putchar(b);
339     b = (pos & 0x00FF);
340     putchar(b);
343 void txxputstr(const char *c)
345     int cnt = 0;
346     int pos;
347     unsigned char *prefx;
349     txxputnameoffset(c);
351     prefx = prefix;
352     fprintf(stderr, "prefix: '%s'\n", prefx);
354     if(*prefx != 0) {
355         while(*prefx != 0) {
356             myputchar('\0');
357             cnt++;
359             if(*prefx == '/')
360                 putchar(separator);
361             else
362                 myputchar(*prefx);
363             cnt++;
365             prefx++;
366         }
368         c++;                    // skip the leading dot
369     }
371     while(*c != 0) {
372         myputchar('\0');
373         cnt++;
375         if(*c == '/')
376             putchar(separator);
377         else
378             myputchar(*c);
379         cnt++;
381         c++;
382     }
384     while(cnt < 510) {
385         myputchar('\0');
386         cnt++;
387     }
390 void txxputcounter(int c)
392     int b;
394     rewind(stdout);
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);
406 /* remove spaces at beginning and end of string */
407 void trim(char *c)
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';
424 void print_webpath(const char *path)
426     const char *c = path;
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     }
443 void print_path(const char *path)
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);
457 void print_pathtail(const char *path)
459     const char *c;
460     c = strrchr(path, separator);
461     if(c != NULL)
462         c++;
463     else
464         c = path;
465     myputstr(c);
468 void noreferal(const char *path, const char *artist, const char *title)
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);
481 void reference(const char *title)
483     FILE *pipe = NULL;
484     static char command[2048], buffer[1024];
485     int buflen = 8192;
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;
515 void parse_options(int argc, char **argv)
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     }
673 void parse_mp3(unsigned char *file)
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;
690     genre = 0;
691     genrebuf[0] = 0;
692     if(debug)
693         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
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;
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     }
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     }
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     }
824     fclose(fic);
827 void parse_ogg(unsigned char *file)
829     FILE *fic;
830     unsigned char *c;
831     int lus;
832     int sample_rate;
833     int samples;
835     if(debug)
836         fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
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);
845     /* try Ogg */
846     if(strncmp(buffer, "Ogg", 3) != 0) {
847         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
848         return;
849     }
851     c = buffer + 0x28;
852     sample_rate =
853         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
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, "ALBUM ARTIST=", 13) == 0) {
866             // ignore tag
867             size =
868                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
869                 (*(c - 1) << 24);
870             c += size;
871         }
872         if(strncasecmp(c, "ARTIST=", 7) == 0) {
873             size =
874                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
875                 (*(c - 1) << 24);
876             strncpy(artist, c + 7, size - 7);
877             artist[size - 7] = '\0';
878             c += size;
879         }
880         if(strncasecmp(c, "GENRE=", 6) == 0) {
881             static int i = 0;
882             size =
883                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
884                 (*(c - 1) << 24);
885             strncpy(genrebuf, c + 6, size - 6);
886             genrebuf[size - 6] = '\0';
887             c += size;
888             for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
889                 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
890                     genre = i;
891                     break;
892                 }
893                 if(i == ID3_NR_OF_V1_GENRES)
894                     genre = 0;
895             }
896         }
897         c++;
898     }
900     fseek(fic, -OGG_BASE, SEEK_END);
901     lus = fread(buffer, 1, OGG_BASE, fic);
902     c = buffer + lus - 1;
903     while(strncmp(c, "OggS", 4) != 0 && c > buffer)
904         c--;
905     if(c != buffer) {
906         c += 6;
907         samples =
908             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
909         duration = samples / sample_rate;
910     }
912     fclose(fic);
915 void parse_mpc(unsigned char *file)
917     FILE *fic;
918     unsigned char *c;
919     int lus;
920     int sample_rates[4] = { 44100, 48000, 37800, 32000 };
921     int frame_count;
922     int size, items;
923     int i;
925     if(debug)
926         fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
928     /* read header */
929     if((fic = fopen(file, "r")) == NULL) {
930         fprintf(stderr, "Warning >> can't open file : %s\n", file);
931         return;
932     }
933     lus = fread(buffer, 1, 12, fic);
935     /* try Musepack */
936     if (strncmp(buffer, "MP+", 3) != 0) {
937         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
938         return;
939     }
941     /* only version 7 */
942     if(buffer[3] != 7) {
943         fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
944                 file);
945         return;
946     }
948     /* duration */
949     c = buffer + 4;
950     frame_count =
951         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
952     c += 5;
953     duration = frame_count * 1152 / sample_rates[*c & 3];
955     /* try APETAGEX footer */
956     fseek(fic, -32, SEEK_END);
957     lus = fread(buffer, 1, 32, fic);
958     if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
959         c = buffer + 12;
960         size =
961             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
962         size += 32;
963         c += 4;
964         items =
965             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
966         fseek(fic, -size, SEEK_END);
967         lus = fread(buffer, 1, size, fic);
968         if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
969             c = buffer + 32;
970             while(items--) {
971                 size =
972                     (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
973                     (*(c + 3) << 24);
974                 c += 8;
975                 if(strcasecmp(c, "TITLE") == 0) {
976                     strncpy(title, c + 6, size);
977                     title[size] = '\0';
978                 }
979                 if(strcasecmp(c, "ARTIST") == 0) {
980                     strncpy(artist, c + 7, size);
981                     artist[size] = '\0';
982                 }
983                 if(strcasecmp(c, "GENRE") == 0) {
984                     for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
985                         strncpy(genrebuf, c + 6, size);
986                         genrebuf[size] = '\0';
987                         if(strcasecmp
988                            (ID3_v1_genre_description[i], genrebuf) == 0) {
989                             genre = i;
990                             break;
991                         }
992                         if(i == ID3_NR_OF_V1_GENRES)
993                             genre = 0;
994                     }
995                 }
996                 c += strlen(c) + 1 + size;
997             }
998         }
999     }
1001     fclose(fic);
1004 #define FSN  32
1005 #define MAXINO  (1<<24)
1006 #define INOTYP  unsigned long
1007 #define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
1008 #define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
1010 int hlink_check(struct stat *info)
1012     /*
1013      * for speed this subroutine should only be called
1014      * - if the file has more than one hardlink
1015      * - if the file is a resolved softlink
1016      */
1017     /* the persistent variables */
1018     static INOTYP *list[FSN];
1019     static dev_t name[FSN];
1020     /* some temporary variables */
1021     int fsn, is_registered = 0;
1023     /* assertions - in case parameters are lowered for less memory usage */
1024     assert(fsn < FSN);
1025     assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1027     /* search which internal registration number is used for this filesystem */
1028     for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1030     /* if file system is not registered yet, do it and leave */
1031     if(name[fsn] == 0) {
1032         name[fsn] = (info->st_dev);
1033         /* provide space for the bitmap that maps the inodes of this file system */
1034         list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1035         /* no comparison is needed in empty lists ... return */
1036         if(debug)
1037             fprintf(stderr,
1038                     "Debug >> Linked >> Init List %04x @mem %04lx\n",
1039                     (int)name[fsn], (long)&list[fsn]);
1040     } else {
1041         /* this looks more complicated than it really is */
1042         /* the idea is very simple: 
1043          *  provide a bitmap that maps all inodes of a file system
1044          *  to mark all files that have already been visited.
1045          *  If it is already visited, do not add it to the playlist
1046          */
1047         /*
1048          * The difficulty is as follows:
1049          *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1050          * would be byte-aligned and would allocate at least eight times the needed space.
1051          * Feel free to change the definitions that are involved here, if you know better.
1052          */
1053         if(regbit_qry(list[fsn], (info->st_ino)))
1054             is_registered = 1;
1055         else
1056             regbit_set(list[fsn], (info->st_ino));
1057         /*
1058          * the debug expression is more complicated then the working stuff
1059          */
1060         if(debug)
1061             fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1062                     "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1063                     (int)info->st_dev, (int)info->st_ino, fsn,
1064                     (int)((info->st_ino) / sizeof(INOTYP)),
1065                     (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1066                     1 << ((info->st_ino) % sizeof(INOTYP)),
1067                     is_registered ? "Already" : "Not");
1068     }
1069     return is_registered;
1072 #ifdef HAVE_LIBURIPARSER
1073 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1075     char * absSourceFile;
1076     size_t absSourceLen;
1077     char * sourceUriString;
1078     char * baseUriString;
1079     UriParserStateA state;
1080     UriUriA sourceUri;
1081     UriUriA baseUri;
1082     UriUriA relativeUri;
1083     int charsRequired;
1084     char * output;
1085   
1086     /* checks */
1087     if ((unixFilename == NULL) || (baseDir == NULL)) {
1088         return NULL;
1089     }
1091     /* base URI */
1092     baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1093     if (baseUriString == NULL) {
1094         return NULL;
1095     }
1096     if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1097         free(baseUriString);
1098         return NULL;
1099     }
1100     state.uri = &baseUri;
1101     if (uriParseUriA(&state, baseUriString) != 0) {
1102         free(baseUriString);
1103         uriFreeUriMembersA(&baseUri);
1104         return NULL;
1105     }
1107     /* source URI */
1108     if (unixFilename[0] != '/') {
1109         const int baseDirLen = strlen(baseDir);
1110         const int sourceFileLen = strlen(unixFilename);
1111         absSourceLen = baseDirLen + sourceFileLen;
1112         absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1113         sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1114     } else {
1115         absSourceLen = strlen(unixFilename);
1116         absSourceFile = (char *)unixFilename;
1117     }
1118     sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1119     if (sourceUriString == NULL) {
1120         free(baseUriString);
1121         if (unixFilename[0] != '/') {
1122             free(absSourceFile);
1123         }
1124         uriFreeUriMembersA(&baseUri);
1125         return NULL;
1126     }
1127     if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1128         free(baseUriString);
1129         free(sourceUriString);
1130         if (unixFilename[0] != '/') {
1131             free(absSourceFile);
1132         }
1133         uriFreeUriMembersA(&baseUri);
1134         return NULL;
1135     }
1136     state.uri = &sourceUri;
1137     if (uriParseUriA(&state, sourceUriString) != 0) {
1138         free(baseUriString);
1139         free(sourceUriString);
1140         uriFreeUriMembersA(&baseUri);
1141         uriFreeUriMembersA(&sourceUri);
1142         return NULL;
1143     }
1144     if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1145         free(baseUriString);
1146         free(sourceUriString);
1147         if (unixFilename[0] != '/') {
1148             free(absSourceFile);
1149         }
1150         uriFreeUriMembersA(&baseUri);
1151         uriFreeUriMembersA(&sourceUri);
1152         return NULL;
1153     }
1155     /* make relative (or keep absolute if necessary) */
1156     if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1157         free(baseUriString);
1158         free(sourceUriString);
1159         if (unixFilename[0] != '/') {
1160             free(absSourceFile);
1161         }
1162         uriFreeUriMembersA(&baseUri);
1163         uriFreeUriMembersA(&sourceUri);
1164         uriFreeUriMembersA(&relativeUri);
1165         return NULL;
1166     }
1167     
1168     /* back to string */
1169     if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1170         free(baseUriString);
1171         free(sourceUriString);
1172         if (unixFilename[0] != '/') {
1173             free(absSourceFile);
1174         }
1175         uriFreeUriMembersA(&baseUri);
1176         uriFreeUriMembersA(&sourceUri);
1177         uriFreeUriMembersA(&relativeUri);
1178         return NULL;
1179     }
1180     output = malloc((charsRequired + 1) * sizeof(char));
1181     if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1182         free(baseUriString);
1183         free(sourceUriString);
1184         if (unixFilename[0] != '/') {
1185             free(absSourceFile);
1186         }
1187         free(output);
1188         uriFreeUriMembersA(&baseUri);
1189         uriFreeUriMembersA(&sourceUri);
1190         uriFreeUriMembersA(&relativeUri);
1191         return NULL;
1192     }
1194     free(baseUriString);
1195     free(sourceUriString);
1196     if (unixFilename[0] != '/') {
1197         free(absSourceFile);
1198     }
1199     uriFreeUriMembersA(&baseUri);
1200     uriFreeUriMembersA(&sourceUri);
1201     uriFreeUriMembersA(&relativeUri);
1203     return output;
1206 char * xml_escape_malloc(const char * input)
1208     const char * read = input;
1209     char * output;
1210     char * write;
1211     
1212     if (input == NULL) {
1213         return NULL;
1214     }
1215     
1216     output = malloc((6 * strlen(input) + 1) * sizeof(char));
1217     if (output == NULL) {
1218         return NULL;
1219     }
1220     write = output;
1221     
1222     for (;;) {
1223         if (*read == '\0') {
1224             *write = '\0';
1225             return output;
1226         }
1227     
1228         switch ((unsigned char)*read) {
1229         case '&':
1230             strcpy(write, "&amp;");
1231             write += 5;
1232             break;
1233         case '<':
1234             strcpy(write, "&lt;");
1235             write += 4;
1236             break;
1237         case '>':
1238             strcpy(write, "&gt;");
1239             write += 4;
1240             break;
1241         case '\'':
1242             strcpy(write, "&apos;");
1243             write += 6;
1244             break;
1245         case '"':
1246             strcpy(write, "&quot;");
1247             write += 6;
1248             break;
1249         default:
1250             *(write++) = *read;
1251         }
1252         read++;
1253     }
1255 #endif
1257 void parse_file(unsigned char *newpath, unsigned char * original_path)
1259     unsigned char ext[5];
1260     int j, encoding = 0;
1262     for(j = 0; j < 5; j++)
1263         ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1264     artist[0] = '\0';
1265     title[0] = '\0';
1266     duration = -2;
1267     if(strcmp(".mp2", ext) == 0) {
1268         duration = -1;
1269         parse_mp3(newpath);
1270         encoding = MP2ENC;
1271     }
1272     if(strcmp(".mp3", ext) == 0) {
1273         duration = -1;
1274         parse_mp3(newpath);
1275         encoding = MP3ENC;
1276     }
1277     if(strcmp(".mpc", ext) == 0) {
1278         duration = -1;
1279         parse_mpc(newpath);
1280         encoding = MPCENC;
1281     }
1282     if(strcmp(".mp+", ext) == 0) {
1283         duration = -1;
1284         parse_mpc(newpath);
1285         encoding = MPPENC;
1286     }
1287     if(strcmp(".ogg", ext) == 0) {
1288         duration = -1;
1289         parse_ogg(newpath);
1290         encoding = OGGENC;
1291     }
1292     if(strcmp(".wav", ext) == 0) {
1293         duration = -1;
1294         /* parse_wav(newpath); */
1295         encoding = WAVENC;
1296     }
1297     if(strcmp(".wma", ext) == 0) {
1298         duration = -1;
1299         /* parse_wma(newpath); */
1300         encoding = WMAENC;
1301     }
1302     /* guesstitle() */
1303     if((strlen(artist) == 0) && (strlen(title) == 0)) {
1304         // there are no tag infos read
1305         // use file name to state substitute it
1306         char *c = strrchr(newpath, separator);
1307         if(c == NULL)
1308             c = newpath;
1309         strcpy(artist, ++c);
1310         // arbitrarily use the first '-'
1311         // to separate artist and title
1312         c = strchr(artist, '-');
1313         if(c != NULL) {         // if trenner found, divide file name 
1314             *c = '\0';
1315             strcpy(title, ++c);
1316             c = strrchr(title, '.');
1317             if(c != NULL)
1318                 *c = '\0';
1319         } else {                // no trenner found, assume
1320             // no artist, only title
1321             strcpy(title, artist);
1322             artist[0] = '\0';
1323         }
1324         // replace underscores by spaces
1325         for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1326             *c = ' ';
1327         for(c = title; (c = strchr(c, '_')) != NULL; c++)
1328             *c = ' ';
1329         // trim spaces
1330         trim(artist);
1331         trim(title);
1332     }
1333     /* guesstitle() end */
1335     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
1336         counter++;
1337         switch (format) {
1338         case FORMAT_M3U:
1339             if(duration != -1) {
1340                 printf("#EXTINF:%d,", duration);
1341                 if(strlen(artist) != 0)
1342                     printf("%s - ", artist);
1343                 printf("%s%s", title, eol);
1344             }
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];
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);
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     }
1461 void parse_directory(unsigned char *path, unsigned char * original_path)
1463     int i, n;
1464     struct dirent **namelist;
1465     unsigned char newpath[PATH_MAX];
1466     struct stat infos;
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);
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);
1505 int main(int argc, char **argv)
1507     winorunix = one2one;
1508     basemap = one2one;
1509     parse_options(argc, argv);
1511     if(optind == argc && !fromstdin)
1512         usage();
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                 }
1595                 /* strip trailing slash */
1596                 if (path[i - 1] == '/') {
1597                     path[i - 1] = '\0';
1598                 }
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                 }
1611                 parse_directory(dup, pwd);
1612             }
1613             
1614         free(pwd);
1615     }
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     }
1642     if(genrelist)
1643         free(genrelist);
1645     exit(0);