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.
8 * Copyright (C) 2003-2004 Antoine Jacquet <royale@zerezo.com>
9 * http://royale.zerezo.com/fapg/
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.
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.
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
31 #include <sys/types.h>
41 #define VERSION "0.36"
43 #define OGG_BASE 1024*10
44 #define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */
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;
59 unsigned char *eol = "\n";
60 unsigned char buffer[MAX];
64 unsigned char artist[1024];
65 unsigned char title[1024];
66 unsigned char genrebuf[1024];
67 unsigned char genre = 0;
76 char *magic[] = { NULL,
77 "audio/mpeg", "audio/mpeg",
78 "audio/mpeg", "audio/mpeg",
79 "audio/ogg-vorbis", "audio/x-wav",
83 unsigned char unix2dos[] =
84 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
85 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
86 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
87 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
88 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
89 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
90 96, 97, 98, 99, 100, 101, 102, 103,
91 104, 105, 106, 107, 108, 109, 110, 111,
92 112, 113, 114, 115, 116, 117, 118, 119,
93 120, 121, 122, 123, 36, 125, 126, 127,
94 199, 252, 233, 226, 228, 224, 229, 231,
95 234, 235, 232, 239, 238, 236, 196, 197,
96 201, 230, 198, 244, 246, 242, 251, 249,
97 255, 214, 220, 248, 163, 216, 215, 131,
98 225, 237, 243, 250, 241, 209, 170, 186,
99 191, 174, 172, 189, 188, 161, 171, 187,
100 166, 166, 166, 166, 166, 193, 194, 192,
101 169, 166, 166, 43, 43, 162, 165, 43,
102 43, 45, 45, 43, 45, 43, 227, 195,
103 43, 43, 45, 45, 166, 45, 43, 164,
104 240, 208, 202, 203, 200, 105, 205, 206,
105 207, 43, 43, 166, 220, 166, 204, 175,
106 211, 223, 212, 210, 245, 213, 181, 254,
107 222, 218, 219, 217, 253, 221, 175, 180,
108 173, 177, 61, 190, 182, 167, 247, 184,
109 176, 168, 183, 185, 179, 178, 166, 160
112 unsigned char *basemap;
113 unsigned char *winorunix;
114 unsigned char one2one[] =
115 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
116 16, 17, 18, 19, 20, 21, 22, 23,
117 24, 25, 26, 27, 28, 29, 30, 31,
118 32, 33, 34, 35, 36, 37, 38, 39,
119 40, 41, 42, 43, 44, 45, 46, 47,
120 48, 49, 50, 51, 52, 53, 54, 55,
121 56, 57, 58, 59, 60, 61, 62, 63,
122 64, 65, 66, 67, 68, 69, 70, 71,
123 72, 73, 74, 75, 76, 77, 78, 79,
124 80, 81, 82, 83, 84, 85, 86, 87,
125 88, 89, 90, 91, 92, 93, 94, 95,
126 96, 97, 98, 99, 100, 101, 102, 103,
127 104, 105, 106, 107, 108, 109, 110, 111,
128 112, 113, 114, 115, 116, 117, 118, 119,
129 120, 121, 122, 123, 124, 125, 126, 127,
130 128, 129, 130, 131, 132, 133, 134, 135,
131 136, 137, 138, 139, 140, 141, 142, 143,
132 144, 145, 146, 147, 148, 149, 150, 151,
133 152, 153, 154, 155, 156, 157, 158, 159,
134 160, 161, 162, 163, 164, 165, 166, 167,
135 168, 169, 170, 171, 172, 173, 174, 175,
136 176, 177, 178, 179, 180, 181, 182, 183,
137 184, 185, 186, 187, 188, 189, 190, 191,
138 192, 193, 194, 195, 196, 197, 198, 199,
139 200, 201, 202, 203, 204, 205, 206, 207,
140 208, 209, 210, 211, 212, 213, 214, 215,
141 216, 217, 218, 219, 220, 221, 222, 223,
142 224, 225, 226, 227, 228, 229, 230, 231,
143 232, 233, 234, 235, 236, 237, 238, 239,
144 240, 241, 242, 243, 244, 245, 246, 247,
145 248, 249, 250, 251, 252, 253, 254, 255
146 }; /* identical mapping */
148 unsigned char noand[256] =
149 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
150 16, 17, 18, 19, 20, 21, 22, 23,
151 24, 25, 26, 27, 28, 29, 30, 31,
152 32, 33, 34, 35, 36, 37, 43, 39,
153 40, 41, 42, 43, 44, 45, 46, 47,
154 48, 49, 50, 51, 52, 53, 54, 55,
155 56, 57, 58, 59, 60, 61, 62, 63,
156 64, 65, 66, 67, 68, 69, 70, 71,
157 72, 73, 74, 75, 76, 77, 78, 79,
158 80, 81, 82, 83, 84, 85, 86, 87,
159 88, 89, 90, 91, 92, 93, 94, 95,
160 96, 97, 98, 99, 100, 101, 102, 103,
161 104, 105, 106, 107, 108, 109, 110, 111,
162 112, 113, 114, 115, 116, 117, 118, 119,
163 120, 121, 122, 123, 124, 125, 126, 127,
164 128, 129, 130, 131, 132, 133, 134, 135,
165 136, 137, 138, 139, 140, 141, 142, 143,
166 144, 145, 146, 147, 148, 149, 150, 151,
167 152, 153, 154, 155, 156, 157, 158, 159,
168 160, 161, 162, 163, 164, 165, 166, 167,
169 168, 169, 170, 171, 172, 173, 174, 175,
170 176, 177, 178, 179, 180, 181, 182, 183,
171 184, 185, 186, 187, 188, 189, 190, 191,
172 192, 193, 194, 195, 196, 197, 198, 199,
173 200, 201, 202, 203, 204, 205, 206, 207,
174 208, 209, 210, 211, 212, 213, 214, 215,
175 216, 217, 218, 219, 220, 221, 222, 223,
176 224, 225, 226, 227, 228, 229, 230, 231,
177 232, 233, 234, 235, 236, 237, 238, 239,
178 240, 241, 242, 243, 244, 245, 246, 247,
179 248, 249, 250, 251, 252, 253, 254, 255
180 }; /* only '&' is mapped to '+' */
182 unsigned char *iso2web[256] = {
183 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
184 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
185 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
186 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
187 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
188 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
189 "0", "1", "2", "3", "4", "5", "6", "7",
190 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
191 "@", "A", "B", "C", "D", "E", "F", "G",
192 "H", "I", "J", "K", "L", "M", "N", "O",
193 "P", "Q", "R", "S", "T", "U", "V", "W",
194 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
195 "`", "a", "b", "c", "d", "e", "f", "g",
196 "h", "i", "j", "k", "l", "m", "n", "o",
197 "p", "q", "r", "s", "t", "u", "v", "w",
198 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
199 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
200 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
201 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
202 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
203 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
204 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
205 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
206 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
207 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
208 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
209 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
210 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
211 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
212 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
213 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
214 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
220 "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=#:#:...] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");
224 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
225 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
226 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
227 void myplaputchar(const char x)
229 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
233 void mywebputstr(const char *c)
241 void myplaputstr(const char *c)
245 myplaputchar('\\'); /* translate slash to backslash */
249 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
250 while(*c == '/' && c[1] == '/')
255 void myputstr(const char *c)
260 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
261 while(*c == '/' && c[1] == '/')
266 void print_webpath(const char *path)
268 const char *c = path;
270 printf(prefix); /* we must not modify this part */
271 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
273 /* maybe there follow many slashes */
277 for(; *c != '\0'; c++) {
279 /* remove multiple "//" when parsing a directory ending with a "/" */
280 while(*c == '/' && c[1] == '/')
285 void print_path(const char *path)
287 const char *c = path;
289 /* skip leading "./" when parsing current directory */
290 if(*c == '.' && *(c + 1) == '/') {
292 /* maybe there follow more slashes */
299 void print_pathtail(const char *path)
302 c = strrchr(path, separator);
310 void noreferal(const char *path, const char *artist, const char *title)
312 printf("\t\t<description><![CDATA[<h4>");
316 printf("</h5><a href=\"");
319 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
323 void reference(const char *title)
326 static char command[2048], buffer[1024];
329 buflen = strlen(title) + strlen(referal) + 3;
330 assert((buflen < 2046));
331 strcpy(command, referal);
332 buflen = strlen(command);
333 command[buflen] = ' ';
334 command[buflen + 1] = '"';
335 command[buflen + 2] = 0;
336 strcat(command, title);
337 buflen = strlen(command);
338 command[buflen] = '"';
339 command[buflen + 1] = 0;
341 fprintf(stderr, "Debug >> processing command: %s\n", command);
342 pipe = popen(command, "r");
344 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
348 fgets(buffer, 1020, pipe);
350 fputs(buffer, stdout);
351 fgets(buffer, 1020, pipe);
357 void parse_options(int argc, char **argv)
359 static char const short_options[] = "c:bdf:g:lo:np:ruwx:";
360 static struct option long_options[] = {
361 {"backslash", no_argument, NULL, 'b'},
362 {"command", required_argument, NULL, 'b'},
363 {"debug", no_argument, NULL, 'd'},
364 {"format", required_argument, NULL, 'f'},
365 {"genre", required_argument, NULL, 'g'},
366 {"nohardlink", no_argument, NULL, 'n'},
367 {"output", required_argument, NULL, 'o'},
368 {"prefix", required_argument, NULL, 'p'},
369 {"recursive", no_argument, NULL, 'r'},
370 {"windows", no_argument, NULL, 'w'},
371 {"exclude", required_argument, NULL, 'x'}
374 int option_index = 0;
376 getopt_long(argc, argv, short_options, long_options,
377 &option_index)) != -1) {
384 if(strncmp(optarg, "intern", 6) == 0)
387 referal = strdup(optarg);
393 if(strcmp(optarg, "m3u") == 0)
395 else if(strcmp(optarg, "pls") == 0)
397 else if(strcmp(optarg, "html") == 0)
399 else if(strcmp(optarg, "rss") == 0)
401 else if(strcmp(optarg, "pla") == 0)
407 if(genrelist == NULL)
408 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
409 if(genrelist == NULL) {
411 "Error >> unable to allocate cleared memory\n");
415 while(n < strlen(optarg)) {
418 "Debug >> genrelist entry activting : %d\n",
420 genrelist[atoi(&optarg[n])] = 1;
421 while(isdigit(optarg[n++]));
430 if(fopen(optarg, "w") == NULL) {
432 "Error >> unable to open output file : %s\n",
438 prefix = malloc(strlen(optarg) + 1);
439 strcpy(prefix, optarg);
440 base = malloc(strlen(prefix) + 1);
441 strcpy(base, prefix);
442 dir = strchr(base, '/');
443 if((dir != NULL) && (dir[1] == '/'))
444 dir = strchr(dir + 2, '/');
449 /* if prefix is a weblink, base is the baselink, dir is the path */
459 winorunix = unix2dos;
463 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
465 genrelist = calloc(257, sizeof(char));
469 if(genrelist == NULL) {
471 "Error >> unable to allocate cleared memory\n");
475 while(n < strlen(optarg)) {
478 "Debug >> genrelist entry activting : %d\n",
480 genrelist[atoi(&optarg[n])] = 0;
481 while(isdigit(optarg[n++]));
489 /* hostname = getenv("HOSTNAME"); */
490 if(genrelist == NULL) {
491 genrelist = calloc(257, sizeof(char));
492 if(genrelist == NULL) {
494 "Error >> unable to allocate cleared memory\n");
504 void parse_mp3(unsigned char *file)
506 int bitrates[2][3][15] =
507 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
509 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
511 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
513 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
514 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
515 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
524 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
527 if((fic = fopen(file, "r")) == NULL) {
528 fprintf(stderr, "Warning >> can't open file : %s\n", file);
531 lus = fread(buffer, 1, MP3_BASE, fic);
535 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
538 version = *(buffer + 3);
539 if(version < 2 || version > 4)
541 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
543 if(*(buffer + 5) != 0)
545 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
546 *(buffer + 5), file);
549 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
550 /* read more header */
551 if(size + lus > MAX) {
552 lus += fread(buffer + lus, 1, MAX - lus, fic);
554 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
557 lus += fread(buffer + lus, 1, size, fic);
562 while(c < buffer + size) {
563 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
566 if(strncmp(c, "TT2", 3) == 0) {
567 strncpy(title, c + 7, size - 1);
568 title[size - 1] = '\0';
570 if(strncmp(c, "TP1", 3) == 0) {
571 strncpy(artist, c + 7, size - 1);
572 artist[size - 1] = '\0';
574 if(strncmp(c, "TCO", 3) == 0) {
575 /* strncpy(genrebuf,c+7,size-1); */
576 /* genrebuf[size-1]='\0'; */
577 /* genre=atoi(&genrebuf[1]); */
582 if(version == 3 || version == 4)
583 while(c < buffer + size) {
585 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
589 if(strncmp(c, "TIT2", 4) == 0) {
590 strncpy(title, c + 11, size - 1);
591 title[size - 1] = '\0';
593 if(strncmp(c, "TPE1", 4) == 0) {
594 strncpy(artist, c + 11, size - 1);
595 artist[size - 1] = '\0';
597 if(strncmp(c, "TCON", 4) == 0) {
598 /* strncpy(genrebuf,c+11,size-1); */
599 /* genrebuf[size-1]='\0'; */
600 /* genre=atoi(&genrebuf[1]); */
601 genre = atoi(c + 12);
607 while(c < buffer + lus - 10) {
608 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
613 version = 2 - (*(c + 1) >> 3 & 1);
614 lay = 4 - (*(c + 1) >> 1 & 3);
615 bitrate_index = *(c + 2) >> 4 & 0xF;
616 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
617 && bitrate_index >= 0 && bitrate_index <= 14)
618 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
622 fseek(fic, 0, SEEK_END);
623 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
632 if(strlen(artist) == 0 && strlen(title) == 0) {
633 fseek(fic, -128, SEEK_END);
634 lus = fread(buffer, 1, 128, fic);
635 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
636 && buffer[2] == 'G') {
637 strncpy(title, buffer + 3, 30);
640 while(c > title && *c == ' ')
642 strncpy(artist, buffer + 33, 30);
645 while(c > artist && *c == ' ')
647 /* strncpy(album,buffer+65,30); */
648 /* strncpy(year,buffer+97,4); */
649 /* strncpy(comment,buffer+101,30); */
650 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
658 void parse_ogg(unsigned char *file)
667 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
670 if((fic = fopen(file, "r")) == NULL) {
671 fprintf(stderr, "Warning >> can't open file : %s\n", file);
674 lus = fread(buffer, 1, OGG_BASE, fic);
677 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
678 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
684 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
686 while(c < buffer + lus - 10) {
688 if(strncasecmp(c, "TITLE=", 6) == 0) {
690 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
692 strncpy(title, c + 6, size - 6);
693 title[size - 6] = '\0';
696 if(strncasecmp(c, "ARTIST=", 7) == 0) {
698 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
700 strncpy(artist, c + 7, size - 7);
701 artist[size - 7] = '\0';
704 if(strncasecmp(c, "GENRE=", 6) == 0) {
707 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
709 strncpy(genrebuf, c + 6, size - 6);
710 genrebuf[size - 6] = '\0';
712 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
713 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
717 if(i == ID3_NR_OF_V1_GENRES)
724 fseek(fic, -OGG_BASE, SEEK_END);
725 lus = fread(buffer, 1, OGG_BASE, fic);
726 c = buffer + lus - 1;
727 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
732 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
733 duration = samples / sample_rate;
740 void parse_mpc(unsigned char *file)
745 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
751 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
754 if((fic = fopen(file, "r")) == NULL) {
755 fprintf(stderr, "Warning >> can't open file : %s\n", file);
758 lus = fread(buffer, 1, 12, fic);
761 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
762 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
768 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
776 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
778 duration = frame_count * 1152 / sample_rates[*c & 3];
780 /* try APETAGEX footer */
781 fseek(fic, -32, SEEK_END);
782 lus = fread(buffer, 1, 32, fic);
783 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
786 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
790 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
791 fseek(fic, -size, SEEK_END);
792 lus = fread(buffer, 1, size, fic);
793 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
797 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
800 if(strcasecmp(c, "TITLE") == 0) {
801 strncpy(title, c + 6, size);
804 if(strcasecmp(c, "ARTIST") == 0) {
805 strncpy(artist, c + 7, size);
808 if(strcasecmp(c, "GENRE") == 0) {
809 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
810 strncpy(genrebuf, c + 6, size);
811 genrebuf[size] = '\0';
813 (ID3_v1_genre_description[i], genrebuf) == 0) {
817 if(i == ID3_NR_OF_V1_GENRES)
821 c += strlen(c) + 1 + size;
830 #define MAXINO (1<<24)
831 #define INOTYP unsigned long
832 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
833 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
835 int hlink_check(struct stat *info)
838 * for speed this subroutine should only be called
839 * - if the file has more than one hardlink
840 * - if the file is a resolved softlink
842 /* the persistent variables */
843 static INOTYP *list[FSN];
844 static dev_t name[FSN];
845 /* some temporary variables */
846 int fsn, is_registered = 0;
848 /* assertions - in case parameters are lowered for less memory usage */
850 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
852 /* search which internal registration number is used for this filesystem */
853 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
855 /* if file system is not registered yet, do it and leave */
857 name[fsn] = (info->st_dev);
858 /* provide space for the bitmap that maps the inodes of this file system */
859 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
860 /* no comparison is needed in empty lists ... return */
863 "Debug >> Linked >> Init List %04x @mem %04lx\n",
864 (int)name[fsn], (long)&list[fsn]);
866 /* this looks more complicated than it really is */
867 /* the idea is very simple:
868 * provide a bitmap that maps all inodes of a file system
869 * to mark all files that have already been visited.
870 * If it is already visited, do not add it to the playlist
873 * The difficulty is as follows:
874 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
875 * would be byte-aligned and would allocate at least eight times the needed space.
876 * Feel free to change the definitions that are involved here, if you know better.
878 if(regbit_qry(list[fsn], (info->st_ino)))
881 regbit_set(list[fsn], (info->st_ino));
883 * the debug expression is more complicated then the working stuff
886 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
887 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
888 (int)info->st_dev, (int)info->st_ino, fsn,
889 (int)((info->st_ino) / sizeof(INOTYP)),
890 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
891 1 << ((info->st_ino) % sizeof(INOTYP)),
892 is_registered ? "Already" : "Not");
894 return is_registered;
898 void parse_file(unsigned char *newpath)
900 unsigned char ext[5];
903 for(j = 0; j < 5; j++)
904 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
908 if(strcmp(".mp2", ext) == 0) {
913 if(strcmp(".mp3", ext) == 0) {
918 if(strcmp(".mpc", ext) == 0) {
923 if(strcmp(".mp+", ext) == 0) {
928 if(strcmp(".ogg", ext) == 0) {
933 if(strcmp(".wav", ext) == 0) {
934 duration = -1; /* parse_wav(newpath); */
938 if((strlen(artist) == 0) && (strlen(title) == 0)) {
939 // there are no tag infos read
940 // use file name to state substitute it
941 char *c = strrchr(newpath, separator);
942 if (c == NULL) c = newpath;
944 // arbitrarily use the first '-'
945 // to separate artist and title
946 c = strchr(artist, '-');
947 if(c != NULL) { // if trenner found, divide file name
950 c = strrchr(title, '.');
953 } else { // no trenner found, assume
954 // no artist, only title
955 strcpy(title, artist);
958 // replace underscores by spaces
959 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
961 for(c = title; (c = strchr(c, '_')) != NULL; c++)
964 /* faketitle() end */
966 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
971 printf("#EXTINF:%d,%s - %s%s", duration, artist, title,
978 printf("File%d=", counter);
980 printf("%sTitle%d=%s - %s%s", eol, counter, artist, title,
983 printf("Length%d=%d%s", counter, duration, eol);
986 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
989 printf("?</td></tr>%s", eol);
991 printf("%d:%s%d</td></tr>%s", duration / 60,
992 duration % 60 < 10 ? "0" : "", duration % 60, eol);
997 char timebuffer[256];
999 if(stat(newpath, &infos) != 0) {
1000 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1004 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1005 printf("\t<item>%s", eol);
1006 printf("\t\t<author>");
1008 printf("</author>%s\t\t<title>", eol);
1010 printf("</title>%s", eol);
1012 if(referal == NULL) {
1013 noreferal(newpath, artist, title);
1016 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1018 print_webpath(newpath);
1019 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1020 (int)infos.st_size, magic[encoding], eol);
1021 print_pathtail(newpath);
1022 printf("</guid>%s", eol);
1025 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1026 duration / 3600, (duration / 60) % 60,
1027 duration % 60, eol);
1030 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1031 duration / 60, duration % 60, eol);
1032 if(strlen(artist) != 0) {
1033 printf("\t\t<itunes:author>");
1035 printf("</itunes:author>%s", eol);
1037 printf("\t</item>%s", eol);
1040 case 4: // printing output for Sansa players
1041 myplaputstr("HARP, ");
1042 myplaputstr(newpath);
1049 void parse_directory(unsigned char *path)
1052 struct dirent **namelist;
1053 unsigned char newpath[PATH_MAX];
1057 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1058 if(stat(path, &infos) != 0) {
1059 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1062 /* check if it is a filename */
1063 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1067 /* must be a directory - or something unusable like pipe, socket, etc */
1068 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1069 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1072 for(i = 0; i < n; i++) {
1073 sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1075 if(stat(newpath, &infos) != 0) {
1076 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1079 if(recursive && S_ISDIR(infos.st_mode)
1080 && strcmp(namelist[i]->d_name, ".") != 0
1081 && strcmp(namelist[i]->d_name, "..") != 0)
1082 parse_directory(newpath);
1083 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1084 if(S_ISREG(infos.st_mode)
1085 && !(avoidhlinked && hlink_check(&infos))) {
1086 parse_file(newpath);
1093 int main(int argc, char **argv)
1095 winorunix = one2one;
1097 parse_options(argc, argv);
1102 printf("#EXTM3U%s", eol);
1105 printf("[playlist]%s", eol);
1109 ("<!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 "
1111 "</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",
1112 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1113 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1114 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1115 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1116 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1122 char timebuffer[256];
1124 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1127 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1129 " -->%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 "
1131 "</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",
1132 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1133 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1134 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1135 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1136 eol, getenv("LANG"), eol, eol, eol);
1137 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1144 myplaputstr( "PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n" );
1147 for(; optind < argc; optind++) {
1148 parse_directory(argv[optind]);
1152 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1156 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1157 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1161 printf(" </channel>%s</rss>%s", eol, eol);