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