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.
12 * Copyright (C) 2003-2004 Antoine Jacquet <royale@zerezo.com>
13 * http://royale.zerezo.com/fapg/
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.
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.
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
35 #include <sys/types.h>
46 #define OGG_BASE 1024*10
47 #define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */
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;
62 unsigned char *eol = "\n";
63 unsigned char buffer[MAX];
67 unsigned char artist[1024];
68 unsigned char title[1024];
69 unsigned char genrebuf[1024];
70 unsigned char genre = 0;
79 char *magic[] = { NULL,
80 "audio/mpeg", "audio/mpeg",
81 "audio/mpeg", "audio/mpeg",
82 "audio/ogg-vorbis", "audio/x-wav",
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
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 */
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 '+' */
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"
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");
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]]); }
230 void mywebputstr(const char *c)
238 void myputstr(const char *c)
243 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
244 while(*c == '/' && c[1] == '/')
249 void print_webpath(const char *path)
251 const char *c = path;
253 printf(prefix); /* we must not modify this part */
254 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
256 /* maybe there follow many slashes */
260 for(; *c != '\0'; c++) {
262 /* remove multiple "//" when parsing a directory ending with a "/" */
263 while(*c == '/' && c[1] == '/')
268 void print_path(const char *path)
270 const char *c = path;
272 /* skip leading "./" when parsing current directory */
273 if(*c == '.' && *(c + 1) == '/') {
275 /* maybe there follow more slashes */
282 void print_pathtail(const char *path)
285 c = strrchr(path, separator);
293 void noreferal(const char *path, const char *artist, const char *title)
295 printf("\t\t<description><![CDATA[<h4>");
299 printf("</h5><a href=\"");
302 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
306 void reference(const char *title)
309 static char command[2048], buffer[1024];
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;
324 fprintf(stderr, "Debug >> processing command: %s\n", command);
325 pipe = popen(command, "r");
327 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
331 fgets(buffer, 1020, pipe);
333 fputs(buffer, stdout);
334 fgets(buffer, 1020, pipe);
340 void parse_options(int argc, char **argv)
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'}
357 int option_index = 0;
359 getopt_long(argc, argv, short_options, long_options,
360 &option_index)) != -1) {
367 if(strncmp(optarg, "intern", 6) == 0)
370 referal = strdup(optarg);
376 if(strcmp(optarg, "m3u") == 0)
378 else if(strcmp(optarg, "pls") == 0)
380 else if(strcmp(optarg, "html") == 0)
382 else if(strcmp(optarg, "rss") == 0)
388 if(genrelist == NULL)
389 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
390 if(genrelist == NULL) {
392 "Error >> unable to allocate cleared memory\n");
396 while(n < strlen(optarg)) {
399 "Debug >> genrelist entry activting : %d\n",
401 genrelist[atoi(&optarg[n])] = 1;
402 while(isdigit(optarg[n++]));
411 if(fopen(optarg, "w") == NULL) {
413 "Error >> unable to open output file : %s\n",
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, '/');
430 /* if prefix is a weblink, base is the baselink, dir is the path */
440 winorunix = unix2dos;
444 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
446 genrelist = calloc(257, sizeof(char));
450 if(genrelist == NULL) {
452 "Error >> unable to allocate cleared memory\n");
456 while(n < strlen(optarg)) {
459 "Debug >> genrelist entry activting : %d\n",
461 genrelist[atoi(&optarg[n])] = 0;
462 while(isdigit(optarg[n++]));
470 /* hostname = getenv("HOSTNAME"); */
471 if(genrelist == NULL) {
472 genrelist = calloc(257, sizeof(char));
473 if(genrelist == NULL) {
475 "Error >> unable to allocate cleared memory\n");
485 void parse_mp3(unsigned char *file)
487 int bitrates[2][3][15] =
488 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
490 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
492 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
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}}
505 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
508 if((fic = fopen(file, "r")) == NULL) {
509 fprintf(stderr, "Warning >> can't open file : %s\n", file);
512 lus = fread(buffer, 1, MP3_BASE, fic);
516 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
519 version = *(buffer + 3);
520 if(version < 2 || version > 4)
522 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
524 if(*(buffer + 5) != 0)
526 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
527 *(buffer + 5), file);
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);
535 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
538 lus += fread(buffer + lus, 1, size, fic);
543 while(c < buffer + size) {
544 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
547 if(strncmp(c, "TT2", 3) == 0) {
548 strncpy(title, c + 7, size - 1);
549 title[size - 1] = '\0';
551 if(strncmp(c, "TP1", 3) == 0) {
552 strncpy(artist, c + 7, size - 1);
553 artist[size - 1] = '\0';
555 if(strncmp(c, "TCO", 3) == 0) {
556 /* strncpy(genrebuf,c+7,size-1); */
557 /* genrebuf[size-1]='\0'; */
558 /* genre=atoi(&genrebuf[1]); */
563 if(version == 3 || version == 4)
564 while(c < buffer + size) {
566 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
570 if(strncmp(c, "TIT2", 4) == 0) {
571 strncpy(title, c + 11, size - 1);
572 title[size - 1] = '\0';
574 if(strncmp(c, "TPE1", 4) == 0) {
575 strncpy(artist, c + 11, size - 1);
576 artist[size - 1] = '\0';
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);
588 while(c < buffer + lus - 10) {
589 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
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];
603 fseek(fic, 0, SEEK_END);
604 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
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);
621 while(c > title && *c == ' ')
623 strncpy(artist, buffer + 33, 30);
626 while(c > artist && *c == ' ')
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; */
639 void parse_ogg(unsigned char *file)
648 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
651 if((fic = fopen(file, "r")) == NULL) {
652 fprintf(stderr, "Warning >> can't open file : %s\n", file);
655 lus = fread(buffer, 1, OGG_BASE, fic);
658 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
659 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
665 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
667 while(c < buffer + lus - 10) {
669 if(strncasecmp(c, "TITLE=", 6) == 0) {
671 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
673 strncpy(title, c + 6, size - 6);
674 title[size - 6] = '\0';
677 if(strncasecmp(c, "ARTIST=", 7) == 0) {
679 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
681 strncpy(artist, c + 7, size - 7);
682 artist[size - 7] = '\0';
685 if(strncasecmp(c, "GENRE=", 6) == 0) {
688 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
690 strncpy(genrebuf, c + 6, size - 6);
691 genrebuf[size - 6] = '\0';
693 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
694 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
698 if(i == ID3_NR_OF_V1_GENRES)
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)
713 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
714 duration = samples / sample_rate;
721 void parse_mpc(unsigned char *file)
726 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
732 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
735 if((fic = fopen(file, "r")) == NULL) {
736 fprintf(stderr, "Warning >> can't open file : %s\n", file);
739 lus = fread(buffer, 1, 12, fic);
742 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
743 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
749 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
757 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
759 duration = frame_count * 1152 / sample_rates[*c & 3];
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) {
767 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
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) {
778 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
781 if(strcasecmp(c, "TITLE") == 0) {
782 strncpy(title, c + 6, size);
785 if(strcasecmp(c, "ARTIST") == 0) {
786 strncpy(artist, c + 7, size);
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';
794 (ID3_v1_genre_description[i], genrebuf) == 0) {
798 if(i == ID3_NR_OF_V1_GENRES)
802 c += strlen(c) + 1 + size;
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) ) )
816 int hlink_check(struct stat *info)
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
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;
829 /* assertions - in case parameters are lowered for less memory usage */
831 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
833 /* search which internal registration number is used for this filesystem */
834 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
836 /* if file system is not registered yet, do it and leave */
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 */
844 "Debug >> Linked >> Init List %04x @mem %04lx\n",
845 (int)name[fsn], (long)&list[fsn]);
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
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.
859 if(regbit_qry(list[fsn], (info->st_ino)))
862 regbit_set(list[fsn], (info->st_ino));
864 * the debug expression is more complicated then the working stuff
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");
875 return is_registered;
879 void parse_file(unsigned char *newpath)
881 unsigned char ext[5];
884 for(j = 0; j < 5; j++)
885 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
889 if(strcmp(".mp2", ext) == 0) {
894 if(strcmp(".mp3", ext) == 0) {
899 if(strcmp(".mpc", ext) == 0) {
904 if(strcmp(".mp+", ext) == 0) {
909 if(strcmp(".ogg", ext) == 0) {
914 if(strcmp(".wav", ext) == 0) {
915 duration = -1; /* parse_wav(newpath); */
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);
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
930 c = strrchr(title, '.');
933 } else { // no trenner found, assume
934 // no artist, only title
935 strcpy(title, artist);
938 // replace underscores by spaces
939 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
941 for(c = title; (c = strchr(c, '_')) != NULL; c++)
944 /* faketitle() end */
946 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
951 printf("#EXTINF:%d,%s - %s%s", duration, artist, title,
958 printf("File%d=", counter);
960 printf("%sTitle%d=%s - %s%s", eol, counter, artist, title,
963 printf("Length%d=%d%s", counter, duration, eol);
966 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
969 printf("?</td></tr>%s", eol);
971 printf("%d:%s%d</td></tr>%s", duration / 60,
972 duration % 60 < 10 ? "0" : "", duration % 60, eol);
977 char timebuffer[256];
979 if(stat(newpath, &infos) != 0) {
980 fprintf(stderr, "Warning >> can't stat entry : %s\n",
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>");
988 printf("</author>%s\t\t<title>", eol);
990 printf("</title>%s", eol);
992 if(referal == NULL) {
993 noreferal(newpath, artist, title);
996 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
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);
1005 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1006 duration / 3600, (duration / 60) % 60,
1007 duration % 60, eol);
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>");
1015 printf("</itunes:author>%s", eol);
1017 printf("\t</item>%s", eol);
1024 void parse_directory(unsigned char *path)
1027 struct dirent **namelist;
1028 unsigned char newpath[PATH_MAX];
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);
1037 /* check if it is a filename */
1038 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
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);
1047 for(i = 0; i < n; i++) {
1048 sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1050 if(stat(newpath, &infos) != 0) {
1051 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
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);
1068 int main(int argc, char **argv)
1070 winorunix = one2one;
1072 parse_options(argc, argv);
1077 printf("#EXTM3U%s", eol);
1080 printf("[playlist]%s", eol);
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 "
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,
1097 char timebuffer[256];
1099 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1102 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
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 "
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
1117 for(; optind < argc; optind++) {
1118 parse_directory(argv[optind]);
1122 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
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,
1131 printf(" </channel>%s</rss>%s", eol, eol);