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