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"
41 #define VERSION "0.37"
42 #define MP3_BASE 1024
43 #define OGG_BASE 1024*10
44 #define MAX 1024*200            /* 200ko for ID3 with JPEG images in it */
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];
63 int counter = 0;
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
77 char *magic[] = { NULL,
78     "audio/mpeg", "audio/mpeg",
79     "audio/mpeg", "audio/mpeg",
80     "audio/ogg-vorbis", "audio/x-wav",
81     NULL
82 };
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 };
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 */
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 '+' */
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 };
218 void usage()
220     fprintf(stderr,
221             "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla] [-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);
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)
230     putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
231     putchar('\0');
234 void mywebputstr(const char *c)
236     while(*c != 0) {
237         mywebputchar(*c);
238         c++;
239     }
242 void myplaputstr(const char *c)
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     }
256 void myputstr(const char *c)
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     }
270 /* remove spaces at beginning and end of string */
271 void trim(char *c)
273     char *p;
274     /* remove spaces at beginning ... */
275     while(*c == ' ') {
276         p = c;
277         while(*p != '\0') {
278             *p = *(p + 1);
279             p++;
280         }
281     }
282     /* ... and end of string */
283     p = c + strlen(c);
284     while(--p > c && *p == ' ')
285         *p = '\0';
288 void print_webpath(const char *path)
290     const char *c = path;
292     printf(prefix);             /* we must not modify this part */
293     if(*c == '.' && c[1] == '/') {      /* remove leading "./" when parsing current directory */
294         c += 2;
295         /* maybe there follow many slashes */
296         while(*c == '/')
297             c++;
298     }
299     for(; *c != '\0'; c++) {
300         mywebputchar(*c);
301         /* remove multiple "//" when parsing a directory ending with a "/" */
302         while(*c == '/' && c[1] == '/')
303             c++;
304     }
307 void print_path(const char *path)
309     const char *c = path;
310     printf(prefix);
311     /* skip leading "./" when parsing current directory */
312     if(*c == '.' && *(c + 1) == '/') {
313         c += 2;
314         /* maybe there follow more slashes */
315         while(*c == '/')
316             c++;
317     }
318     myputstr(c);
321 void print_pathtail(const char *path)
323     const char *c;
324     c = strrchr(path, separator);
325     if(c != NULL)
326         c++;
327     else
328         c = path;
329     myputstr(c);
332 void noreferal(const char *path, const char *artist, const char *title)
334     printf("\t\t<description><![CDATA[<h4>");
335     myputstr(artist);
336     printf("</h4><h5>");
337     myputstr(title);
338     printf("</h5><a href=\"");
339     print_webpath(path);
340     printf
341         ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
342          eol);
345 void reference(const char *title)
347     FILE *pipe = NULL;
348     static char command[2048], buffer[1024];
349     int buflen = 8192;
351     buflen = strlen(title) + strlen(referal) + 3;
352     assert((buflen < 2046));
353     strcpy(command, referal);
354     buflen = strlen(command);
355     command[buflen] = ' ';
356     command[buflen + 1] = '"';
357     command[buflen + 2] = 0;
358     strcat(command, title);
359     buflen = strlen(command);
360     command[buflen] = '"';
361     command[buflen + 1] = 0;
362     if(debug)
363         fprintf(stderr, "Debug >> processing command: %s\n", command);
364     pipe = popen(command, "r");
365     if(pipe == NULL) {
366         fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
367         free(command);
368         return;
369     }
370     fgets(buffer, 1020, pipe);
371     while(!feof(pipe)) {
372         fputs(buffer, stdout);
373         fgets(buffer, 1020, pipe);
374     }
375     pclose(pipe);
376     return;
379 void parse_options(int argc, char **argv)
381     static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
382     static struct option long_options[] = {
383         {"backslash", no_argument, NULL, 'b'},
384         {"command", required_argument, NULL, 'c'},
385         {"debug", no_argument, NULL, 'd'},
386         {"format", required_argument, NULL, 'f'},
387         {"genre", required_argument, NULL, 'g'},
388         {"nohardlink", no_argument, NULL, 'n'},
389         {"output", required_argument, NULL, 'o'},
390         {"prefix", required_argument, NULL, 'p'},
391         {"recursive", no_argument, NULL, 'r'},
392         {"stdin", no_argument, NULL, 's'},
393         {"windows", no_argument, NULL, 'w'},
394         {"exclude", required_argument, NULL, 'x'}
395     };
396     int c;
397     int option_index = 0;
398     while((c =
399            getopt_long(argc, argv, short_options, long_options,
400                        &option_index)) != -1) {
401         switch (c) {
402         case 'b':
403             separator = '\\';
404             noand['/'] = '\\';
405             break;
406         case 'c':
407             if(strncmp(optarg, "intern", 6) == 0)
408                 referal = NULL;
409             else
410                 referal = strdup(optarg);
411             break;
412         case 'd':
413             debug = 1 - debug;
414             break;
415         case 'f':
416             if(strcmp(optarg, "m3u") == 0)
417                 format = 0;
418             else if(strcmp(optarg, "pls") == 0)
419                 format = 1;
420             else if(strcmp(optarg, "html") == 0)
421                 format = 2;
422             else if(strcmp(optarg, "rss") == 0)
423                 format = 3;
424             else if(strcmp(optarg, "pla") == 0)
425                 format = 4;
426             else
427                 usage();
428             break;
429         case 'g':
430             if(genrelist == NULL)
431                 genrelist = calloc(257, sizeof(char));  /* allow multiple includes/excludes */
432             if(genrelist == NULL) {
433                 fprintf(stderr,
434                         "Error >> unable to allocate cleared memory\n");
435                 exit(2);
436             } else {
437                 unsigned int n = 0;
438                 while(n < strlen(optarg)) {
439                     if(debug)
440                         fprintf(stderr,
441                                 "Debug >> genrelist entry activting : %d\n",
442                                 atoi(&optarg[n]));
443                     genrelist[atoi(&optarg[n])] = 1;
444                     while(isdigit(optarg[n++]));
445                 }
446             }
447             break;
448         case 'n':
449             avoidhlinked = 1;
450             break;
451         case 'o':
452             close(1);
453             if(fopen(optarg, "w") == NULL) {
454                 fprintf(stderr,
455                         "Error >> unable to open output file : %s\n",
456                         optarg);
457                 exit(2);
458             }
459             break;
460         case 'p':
461             prefix = malloc(strlen(optarg) + 1);
462             strcpy(prefix, optarg);
463             base = malloc(strlen(prefix) + 1);
464             strcpy(base, prefix);
465             dir = strchr(base, '/');
466             if((dir != NULL) && (dir[1] == '/'))
467                 dir = strchr(dir + 2, '/');
468             if(dir != NULL)
469                 *dir++ = 0;
470             else
471                 dir = "";
472             /* if prefix is a weblink, base is the baselink, dir is the path */
473             break;
474         case 'r':
475             recursive = 1;
476             break;
477         case 'u':
478             winorunix = one2one;
479             eol = "\n";
480             break;
481         case 'w':
482             winorunix = unix2dos;
483             eol = "\r\n";
484             break;
485         case 'x':
486             if(genrelist == NULL) {     /* allow multiple includes/excludes - not recommended (confusing) but possible */
487                 int n = 0;
488                 genrelist = calloc(257, sizeof(char));
489                 while(n < 256)
490                     genrelist[n++] = 1;
491             }
492             if(genrelist == NULL) {
493                 fprintf(stderr,
494                         "Error >> unable to allocate cleared memory\n");
495                 exit(2);
496             } else {
497                 unsigned int n = 0;
498                 while(n < strlen(optarg)) {
499                     if(debug)
500                         fprintf(stderr,
501                                 "Debug >> genrelist entry activting : %d\n",
502                                 atoi(&optarg[n]));
503                     genrelist[atoi(&optarg[n])] = 0;
504                     while(isdigit(optarg[n++]));
505                 }
506             }
507             break;
508         case 's':
509             fromstdin = 1;
510             break;
511         default:
512             usage();
513         }
514     }
515     /* hostname = getenv("HOSTNAME"); */
516     if(genrelist == NULL) {
517         genrelist = calloc(257, sizeof(char));
518         if(genrelist == NULL) {
519             fprintf(stderr,
520                     "Error >> unable to allocate cleared memory\n");
521             exit(2);
522         } else {
523             int n = 0;
524             while(n < 256)
525                 genrelist[n++] = 1;
526         }
527     }
530 void parse_mp3(unsigned char *file)
532     int bitrates[2][3][15] =
533         { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
534             416, 448},
535            {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
536             384},
537            {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
538             320}},
539     {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
540      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
541      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
542     };
543     FILE *fic;
544     unsigned char *c;
545     int lus;
547     genre = 0;
548     genrebuf[0] = 0;
549     if(debug)
550         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
552     /* read header */
553     if((fic = fopen(file, "r")) == NULL) {
554         fprintf(stderr, "Warning >> can't open file : %s\n", file);
555         return;
556     }
557     lus = fread(buffer, 1, MP3_BASE, fic);
558     c = buffer;
560     /* try ID3v2 */
561     if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
562         int size;
563         int version;
564         version = *(buffer + 3);
565         if(version < 2 || version > 4)
566             fprintf(stderr,
567                     "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
568                     version, file);
569         if(*(buffer + 5) != 0)
570             fprintf(stderr,
571                     "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
572                     *(buffer + 5), file);
573         c = buffer + 6;
574         size =
575             (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
576         /* read more header */
577         if(size + lus > MAX) {
578             lus += fread(buffer + lus, 1, MAX - lus, fic);
579             fprintf(stderr,
580                     "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
581                     size, file);
582         } else
583             lus += fread(buffer + lus, 1, size, fic);
584         if(size > lus)
585             size = lus;
586         c += 4;
587         if(version == 2)
588             while(c < buffer + size) {
589                 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
590                 if(*c == 0)
591                     break;
592                 if(strncmp(c, "TT2", 3) == 0) {
593                     strncpy(title, c + 7, size - 1);
594                     title[size - 1] = '\0';
595                 }
596                 if(strncmp(c, "TP1", 3) == 0) {
597                     strncpy(artist, c + 7, size - 1);
598                     artist[size - 1] = '\0';
599                 }
600                 if(strncmp(c, "TCO", 3) == 0) {
601                     /* strncpy(genrebuf,c+7,size-1); */
602                     /* genrebuf[size-1]='\0'; */
603                     /* genre=atoi(&genrebuf[1]); */
604                     genre = atoi(c + 8);
605                 }
606                 c += size + 6;
607             }
608         if(version == 3 || version == 4)
609             while(c < buffer + size) {
610                 int size =
611                     (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
612                     (*(c + 7));
613                 if(*c == 0)
614                     break;
615                 if(strncmp(c, "TIT2", 4) == 0) {
616                     strncpy(title, c + 11, size - 1);
617                     title[size - 1] = '\0';
618                 }
619                 if(strncmp(c, "TPE1", 4) == 0) {
620                     strncpy(artist, c + 11, size - 1);
621                     artist[size - 1] = '\0';
622                 }
623                 if(strncmp(c, "TCON", 4) == 0) {
624                     /* strncpy(genrebuf,c+11,size-1); */
625                     /* genrebuf[size-1]='\0'; */
626                     /* genre=atoi(&genrebuf[1]); */
627                     genre = atoi(c + 12);
628                 }
629                 c += size + 10;
630             }
631     }
633     while(c < buffer + lus - 10) {
634         if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
635             int version;
636             int lay;
637             int bitrate_index;
638             int bitrate;
639             version = 2 - (*(c + 1) >> 3 & 1);
640             lay = 4 - (*(c + 1) >> 1 & 3);
641             bitrate_index = *(c + 2) >> 4 & 0xF;
642             if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
643                && bitrate_index >= 0 && bitrate_index <= 14)
644                 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
645             else
646                 bitrate = 0;
647             if(bitrate != 0) {
648                 fseek(fic, 0, SEEK_END);
649                 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
650             } else
651                 duration = 0;
652             break;
653         }
654         c++;
655     }
657     /* try ID3v1 */
658     if(strlen(artist) == 0 && strlen(title) == 0) {
659         fseek(fic, -128, SEEK_END);
660         lus = fread(buffer, 1, 128, fic);
661         if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
662            && buffer[2] == 'G') {
663             strncpy(title, buffer + 3, 30);
664             title[30] = '\0';
665             c = title + 29;
666             while(c > title && *c == ' ')
667                 *(c--) = '\0';
668             strncpy(artist, buffer + 33, 30);
669             artist[30] = '\0';
670             c = artist + 29;
671             while(c > artist && *c == ' ')
672                 *(c--) = '\0';
673             /* strncpy(album,buffer+65,30); */
674             /* strncpy(year,buffer+97,4); */
675             /* strncpy(comment,buffer+101,30); */
676             /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
677             genre = buffer[127];
678         }
679     }
681     fclose(fic);
684 void parse_ogg(unsigned char *file)
686     FILE *fic;
687     unsigned char *c;
688     int lus;
689     int sample_rate;
690     int samples;
692     if(debug)
693         fprintf(stderr, "Debug >> parsing ogg : %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, OGG_BASE, fic);
702     /* try Ogg */
703     if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
704         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
705         return;
706     }
708     c = buffer + 0x28;
709     sample_rate =
710         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
712     while(c < buffer + lus - 10) {
713         int size;
714         if(strncasecmp(c, "TITLE=", 6) == 0) {
715             size =
716                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
717                 (*(c - 1) << 24);
718             strncpy(title, c + 6, size - 6);
719             title[size - 6] = '\0';
720             c += size;
721         }
722         if(strncasecmp(c, "ARTIST=", 7) == 0) {
723             size =
724                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
725                 (*(c - 1) << 24);
726             strncpy(artist, c + 7, size - 7);
727             artist[size - 7] = '\0';
728             c += size;
729         }
730         if(strncasecmp(c, "GENRE=", 6) == 0) {
731             static int i = 0;
732             size =
733                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
734                 (*(c - 1) << 24);
735             strncpy(genrebuf, c + 6, size - 6);
736             genrebuf[size - 6] = '\0';
737             c += size;
738             for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
739                 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
740                     genre = i;
741                     break;
742                 }
743                 if(i == ID3_NR_OF_V1_GENRES)
744                     genre = 0;
745             }
746         }
747         c++;
748     }
750     fseek(fic, -OGG_BASE, SEEK_END);
751     lus = fread(buffer, 1, OGG_BASE, fic);
752     c = buffer + lus - 1;
753     while(strncmp(c, "OggS", 4) != 0 && c > buffer)
754         c--;
755     if(c != buffer) {
756         c += 6;
757         samples =
758             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
759         duration = samples / sample_rate;
760     }
762     fclose(fic);
766 void parse_mpc(unsigned char *file)
768     FILE *fic;
769     unsigned char *c;
770     int lus;
771     int sample_rates[4] = { 44100, 48000, 37800, 32000 };
772     int frame_count;
773     int size, items;
774     int i;
776     if(debug)
777         fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
779     /* read header */
780     if((fic = fopen(file, "r")) == NULL) {
781         fprintf(stderr, "Warning >> can't open file : %s\n", file);
782         return;
783     }
784     lus = fread(buffer, 1, 12, fic);
786     /* try Musepack */
787     if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
788         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
789         return;
790     }
792     /* only version 7 */
793     if(buffer[3] != 7) {
794         fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
795                 file);
796         return;
797     }
799     /* duration */
800     c = buffer + 4;
801     frame_count =
802         (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
803     c += 5;
804     duration = frame_count * 1152 / sample_rates[*c & 3];
806     /* try APETAGEX footer */
807     fseek(fic, -32, SEEK_END);
808     lus = fread(buffer, 1, 32, fic);
809     if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
810         c = buffer + 12;
811         size =
812             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
813         size += 32;
814         c += 4;
815         items =
816             (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
817         fseek(fic, -size, SEEK_END);
818         lus = fread(buffer, 1, size, fic);
819         if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
820             c = buffer + 32;
821             while(items--) {
822                 size =
823                     (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
824                     (*(c + 3) << 24);
825                 c += 8;
826                 if(strcasecmp(c, "TITLE") == 0) {
827                     strncpy(title, c + 6, size);
828                     title[size] = '\0';
829                 }
830                 if(strcasecmp(c, "ARTIST") == 0) {
831                     strncpy(artist, c + 7, size);
832                     artist[size] = '\0';
833                 }
834                 if(strcasecmp(c, "GENRE") == 0) {
835                     for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
836                         strncpy(genrebuf, c + 6, size);
837                         genrebuf[size] = '\0';
838                         if(strcasecmp
839                            (ID3_v1_genre_description[i], genrebuf) == 0) {
840                             genre = i;
841                             break;
842                         }
843                         if(i == ID3_NR_OF_V1_GENRES)
844                             genre = 0;
845                     }
846                 }
847                 c += strlen(c) + 1 + size;
848             }
849         }
850     }
852     fclose(fic);
855 #define FSN  32
856 #define MAXINO  (1<<24)
857 #define INOTYP  unsigned long
858 #define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
859 #define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
861 int hlink_check(struct stat *info)
863     /*
864      * for speed this subroutine should only be called
865      * - if the file has more than one hardlink
866      * - if the file is a resolved softlink
867      */
868     /* the persistent variables */
869     static INOTYP *list[FSN];
870     static dev_t name[FSN];
871     /* some temporary variables */
872     int fsn, is_registered = 0;
874     /* assertions - in case parameters are lowered for less memory usage */
875     assert(fsn < FSN);
876     assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
878     /* search which internal registration number is used for this filesystem */
879     for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
881     /* if file system is not registered yet, do it and leave */
882     if(name[fsn] == 0) {
883         name[fsn] = (info->st_dev);
884         /* provide space for the bitmap that maps the inodes of this file system */
885         list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
886         /* no comparison is needed in empty lists ... return */
887         if(debug)
888             fprintf(stderr,
889                     "Debug >> Linked >> Init List %04x @mem %04lx\n",
890                     (int)name[fsn], (long)&list[fsn]);
891     } else {
892         /* this looks more complicated than it really is */
893         /* the idea is very simple: 
894          *  provide a bitmap that maps all inodes of a file system
895          *  to mark all files that have already been visited.
896          *  If it is already visited, do not add it to the playlist
897          */
898         /*
899          * The difficulty is as follows:
900          *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
901          * would be byte-aligned and would allocate at least eight times the needed space.
902          * Feel free to change the definitions that are involved here, if you know better.
903          */
904         if(regbit_qry(list[fsn], (info->st_ino)))
905             is_registered = 1;
906         else
907             regbit_set(list[fsn], (info->st_ino));
908         /*
909          * the debug expression is more complicated then the working stuff
910          */
911         if(debug)
912             fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
913                     "list[%02x][%04x] = %04x & %04x --> %s registered\n",
914                     (int)info->st_dev, (int)info->st_ino, fsn,
915                     (int)((info->st_ino) / sizeof(INOTYP)),
916                     (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
917                     1 << ((info->st_ino) % sizeof(INOTYP)),
918                     is_registered ? "Already" : "Not");
919     }
920     return is_registered;
924 void parse_file(unsigned char *newpath)
926     unsigned char ext[5];
927     int j, encoding = 0;
929     for(j = 0; j < 5; j++)
930         ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
931     artist[0] = '\0';
932     title[0] = '\0';
933     duration = -2;
934     if(strcmp(".mp2", ext) == 0) {
935         duration = -1;
936         parse_mp3(newpath);
937         encoding = MP2ENC;
938     }
939     if(strcmp(".mp3", ext) == 0) {
940         duration = -1;
941         parse_mp3(newpath);
942         encoding = MP3ENC;
943     }
944     if(strcmp(".mpc", ext) == 0) {
945         duration = -1;
946         parse_mpc(newpath);
947         encoding = MPCENC;
948     }
949     if(strcmp(".mp+", ext) == 0) {
950         duration = -1;
951         parse_mpc(newpath);
952         encoding = MPPENC;
953     }
954     if(strcmp(".ogg", ext) == 0) {
955         duration = -1;
956         parse_ogg(newpath);
957         encoding = OGGENC;
958     }
959     if(strcmp(".wav", ext) == 0) {
960         duration = -1;          /* parse_wav(newpath); */
961         encoding = WAVENC;
962     }
963     /* guesstitle() */
964     if((strlen(artist) == 0) && (strlen(title) == 0)) {
965         // there are no tag infos read
966         // use file name to state substitute it
967         char *c = strrchr(newpath, separator);
968         if(c == NULL)
969             c = newpath;
970         strcpy(artist, ++c);
971         // arbitrarily use the first '-'
972         // to separate artist and title
973         c = strchr(artist, '-');
974         if(c != NULL) {         // if trenner found, divide file name 
975             *c = '\0';
976             strcpy(title, ++c);
977             c = strrchr(title, '.');
978             if(c != NULL)
979                 *c = '\0';
980         } else {                // no trenner found, assume
981             // no artist, only title
982             strcpy(title, artist);
983             artist[0] = '\0';
984         }
985         // replace underscores by spaces
986         for(c = artist; (c = strchr(c, '_')) != NULL; c++)
987             *c = ' ';
988         for(c = title; (c = strchr(c, '_')) != NULL; c++)
989             *c = ' ';
990         // trim spaces
991         trim(artist);
992         trim(title);
993     }
994     /* guesstitle() end */
996     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
997         counter++;
998         switch (format) {
999         case 0:
1000             if(duration != -1) {
1001                 printf("#EXTINF:%d,", duration);
1002                 if(strlen(artist) != 0)
1003                     printf("%s - ", artist);
1004                 printf("%s%s", title, eol);
1005             }
1006             print_path(newpath);
1007             printf("%s", eol);
1008             break;
1009         case 1:
1010             printf("File%d=", counter);
1011             print_path(newpath);
1012             printf("%sTitle%d=", eol, counter);
1013             if(strlen(artist) != 0)
1014                 printf("%s - ", artist);
1015             printf("%s%s", title, eol);
1016             if(duration != -1)
1017                 printf("Length%d=%d%s", counter, duration, eol);
1018             break;
1019         case 2:
1020             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1021                    artist, title);
1022             if(duration == -1)
1023                 printf("?</td></tr>%s", eol);
1024             else
1025                 printf("%d:%s%d</td></tr>%s", duration / 60,
1026                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
1027             break;
1028         case 3:
1029             if(duration != -1) {
1030                 struct stat infos;
1031                 char timebuffer[256];
1033                 if(stat(newpath, &infos) != 0) {
1034                     fprintf(stderr, "Warning >> can't stat entry : %s\n",
1035                             newpath);
1036                     return;
1037                 }
1038                 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime)));   /* ctime() had a trailing CR */
1039                 printf("\t<item>%s", eol);
1040                 printf("\t\t<author>");
1041                 myputstr(artist);
1042                 printf("</author>%s\t\t<title>", eol);
1043                 myputstr(title);
1044                 printf("</title>%s", eol);
1046                 if(referal == NULL) {
1047                     noreferal(newpath, artist, title);
1048                 } else
1049                     reference(newpath);
1050                 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1051                        timebuffer, eol);
1052                 print_webpath(newpath);
1053                 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1054                        (int)infos.st_size, magic[encoding], eol);
1055                 print_pathtail(newpath);
1056                 printf("</guid>%s", eol);
1057                 if(duration > 3599)
1058                     printf
1059                         ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1060                          duration / 3600, (duration / 60) % 60,
1061                          duration % 60, eol);
1062                 else
1063                     printf
1064                         ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1065                          duration / 60, duration % 60, eol);
1066                 if(strlen(artist) != 0) {
1067                     printf("\t\t<itunes:author>");
1068                     myputstr(artist);
1069                     printf("</itunes:author>%s", eol);
1070                 }
1071                 printf("\t</item>%s", eol);
1072             }
1073             break;
1074         case 4:                // printing output for Sansa players
1075             myplaputstr("HARP, ");
1076             myplaputstr(newpath);
1077             myplaputstr(eol);
1078             break;
1079         }
1080     }
1083 void parse_directory(unsigned char *path)
1085     int i, n;
1086     struct dirent **namelist;
1087     unsigned char newpath[PATH_MAX];
1088     struct stat infos;
1090     if(debug)
1091         fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1092     if(stat(path, &infos) != 0) {
1093         fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1094         return;
1095     }
1096     /* check if it is a filename */
1097     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1098         parse_file(path);
1099         return;
1100     }
1101     /* must be a directory - or something unusable like pipe, socket, etc */
1102     if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1103         fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1104         return;
1105     }
1106     for(i = 0; i < n; i++) {
1107         sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1109         if(stat(newpath, &infos) != 0) {
1110             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1111             continue;
1112         }
1113         if(recursive && S_ISDIR(infos.st_mode)
1114            && strcmp(namelist[i]->d_name, ".") != 0
1115            && strcmp(namelist[i]->d_name, "..") != 0)
1116             parse_directory(newpath);
1117         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1118         if(S_ISREG(infos.st_mode)
1119            && !(avoidhlinked && hlink_check(&infos))) {
1120             parse_file(newpath);
1121         }
1122         free(namelist[i]);
1123     }
1124     free(namelist);
1127 int main(int argc, char **argv)
1129     winorunix = one2one;
1130     basemap = one2one;
1131     parse_options(argc, argv);
1132     if(optind == argc && !fromstdin)
1133         usage();
1134     switch (format) {
1135     case 0:
1136         printf("#EXTM3U%s", eol);
1137         break;
1138     case 1:
1139         printf("[playlist]%s", eol);
1140         break;
1141     case 2:
1142         printf
1143             ("<!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 "
1144              VERSION
1145              "</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",
1146              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1147              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1148              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1149              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1150              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1151              eol, eol, eol);
1152         break;
1153     case 3:
1154         {
1155             time_t zeit;
1156             char timebuffer[256];
1157             time(&zeit);
1158             strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1159                      localtime(&zeit));
1160             printf
1161                 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1162                  VERSION
1163                  " -->%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 "
1164                  VERSION
1165                  "</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",
1166                  eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1167                  prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1168                  eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1169                  hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1170                  eol, getenv("LANG"), eol, eol, eol);
1171             unix2dos[38] = 43;  // I never made an rss feed work with '&' in it
1172             basemap = noand;
1173         }
1174         break;
1175     case 4:
1176         {
1177             eol = "\r\n";
1178             myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1179         }
1180     }
1181     if(fromstdin) {
1182         unsigned char path[PATH_MAX];
1183         int i;
1184         while(fgets(path, PATH_MAX, stdin)) {
1185             for(i = 0; i < PATH_MAX; i++)
1186                 if(path[i] == '\r' || path[i] == '\n')
1187                     path[i] = '\0';
1188             parse_directory(path);
1189         }
1190     } else
1191         for(; optind < argc; optind++) {
1192             parse_directory(argv[optind]);
1193         }
1194     switch (format) {
1195     case 1:
1196         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1197         break;
1198     case 2:
1199         printf
1200             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1201              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1202              eol, eol);
1203         break;
1204     case 3:
1205         printf("    </channel>%s</rss>%s", eol, eol);
1206         break;
1207     }
1208     if(genrelist)
1209         free(genrelist);
1210     exit(0);