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.37"
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;
60 unsigned char *eol = "\n";
61 unsigned char buffer[MAX];
65 unsigned char artist[1024];
66 unsigned char title[1024];
67 unsigned char genrebuf[1024];
68 unsigned char genre = 0;
77 char *magic[] = { NULL,
78 "audio/mpeg", "audio/mpeg",
79 "audio/mpeg", "audio/mpeg",
80 "audio/ogg-vorbis", "audio/x-wav",
84 unsigned char unix2dos[] =
85 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
86 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
87 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
88 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
89 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
90 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
91 96, 97, 98, 99, 100, 101, 102, 103,
92 104, 105, 106, 107, 108, 109, 110, 111,
93 112, 113, 114, 115, 116, 117, 118, 119,
94 120, 121, 122, 123, 36, 125, 126, 127,
95 199, 252, 233, 226, 228, 224, 229, 231,
96 234, 235, 232, 239, 238, 236, 196, 197,
97 201, 230, 198, 244, 246, 242, 251, 249,
98 255, 214, 220, 248, 163, 216, 215, 131,
99 225, 237, 243, 250, 241, 209, 170, 186,
100 191, 174, 172, 189, 188, 161, 171, 187,
101 166, 166, 166, 166, 166, 193, 194, 192,
102 169, 166, 166, 43, 43, 162, 165, 43,
103 43, 45, 45, 43, 45, 43, 227, 195,
104 43, 43, 45, 45, 166, 45, 43, 164,
105 240, 208, 202, 203, 200, 105, 205, 206,
106 207, 43, 43, 166, 220, 166, 204, 175,
107 211, 223, 212, 210, 245, 213, 181, 254,
108 222, 218, 219, 217, 253, 221, 175, 180,
109 173, 177, 61, 190, 182, 167, 247, 184,
110 176, 168, 183, 185, 179, 178, 166, 160
113 unsigned char *basemap;
114 unsigned char *winorunix;
115 unsigned char one2one[] =
116 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
117 16, 17, 18, 19, 20, 21, 22, 23,
118 24, 25, 26, 27, 28, 29, 30, 31,
119 32, 33, 34, 35, 36, 37, 38, 39,
120 40, 41, 42, 43, 44, 45, 46, 47,
121 48, 49, 50, 51, 52, 53, 54, 55,
122 56, 57, 58, 59, 60, 61, 62, 63,
123 64, 65, 66, 67, 68, 69, 70, 71,
124 72, 73, 74, 75, 76, 77, 78, 79,
125 80, 81, 82, 83, 84, 85, 86, 87,
126 88, 89, 90, 91, 92, 93, 94, 95,
127 96, 97, 98, 99, 100, 101, 102, 103,
128 104, 105, 106, 107, 108, 109, 110, 111,
129 112, 113, 114, 115, 116, 117, 118, 119,
130 120, 121, 122, 123, 124, 125, 126, 127,
131 128, 129, 130, 131, 132, 133, 134, 135,
132 136, 137, 138, 139, 140, 141, 142, 143,
133 144, 145, 146, 147, 148, 149, 150, 151,
134 152, 153, 154, 155, 156, 157, 158, 159,
135 160, 161, 162, 163, 164, 165, 166, 167,
136 168, 169, 170, 171, 172, 173, 174, 175,
137 176, 177, 178, 179, 180, 181, 182, 183,
138 184, 185, 186, 187, 188, 189, 190, 191,
139 192, 193, 194, 195, 196, 197, 198, 199,
140 200, 201, 202, 203, 204, 205, 206, 207,
141 208, 209, 210, 211, 212, 213, 214, 215,
142 216, 217, 218, 219, 220, 221, 222, 223,
143 224, 225, 226, 227, 228, 229, 230, 231,
144 232, 233, 234, 235, 236, 237, 238, 239,
145 240, 241, 242, 243, 244, 245, 246, 247,
146 248, 249, 250, 251, 252, 253, 254, 255
147 }; /* identical mapping */
149 unsigned char noand[256] =
150 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
151 16, 17, 18, 19, 20, 21, 22, 23,
152 24, 25, 26, 27, 28, 29, 30, 31,
153 32, 33, 34, 35, 36, 37, 43, 39,
154 40, 41, 42, 43, 44, 45, 46, 47,
155 48, 49, 50, 51, 52, 53, 54, 55,
156 56, 57, 58, 59, 60, 61, 62, 63,
157 64, 65, 66, 67, 68, 69, 70, 71,
158 72, 73, 74, 75, 76, 77, 78, 79,
159 80, 81, 82, 83, 84, 85, 86, 87,
160 88, 89, 90, 91, 92, 93, 94, 95,
161 96, 97, 98, 99, 100, 101, 102, 103,
162 104, 105, 106, 107, 108, 109, 110, 111,
163 112, 113, 114, 115, 116, 117, 118, 119,
164 120, 121, 122, 123, 124, 125, 126, 127,
165 128, 129, 130, 131, 132, 133, 134, 135,
166 136, 137, 138, 139, 140, 141, 142, 143,
167 144, 145, 146, 147, 148, 149, 150, 151,
168 152, 153, 154, 155, 156, 157, 158, 159,
169 160, 161, 162, 163, 164, 165, 166, 167,
170 168, 169, 170, 171, 172, 173, 174, 175,
171 176, 177, 178, 179, 180, 181, 182, 183,
172 184, 185, 186, 187, 188, 189, 190, 191,
173 192, 193, 194, 195, 196, 197, 198, 199,
174 200, 201, 202, 203, 204, 205, 206, 207,
175 208, 209, 210, 211, 212, 213, 214, 215,
176 216, 217, 218, 219, 220, 221, 222, 223,
177 224, 225, 226, 227, 228, 229, 230, 231,
178 232, 233, 234, 235, 236, 237, 238, 239,
179 240, 241, 242, 243, 244, 245, 246, 247,
180 248, 249, 250, 251, 252, 253, 254, 255
181 }; /* only '&' is mapped to '+' */
183 unsigned char *iso2web[256] = {
184 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
185 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
186 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
187 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
188 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
189 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
190 "0", "1", "2", "3", "4", "5", "6", "7",
191 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
192 "@", "A", "B", "C", "D", "E", "F", "G",
193 "H", "I", "J", "K", "L", "M", "N", "O",
194 "P", "Q", "R", "S", "T", "U", "V", "W",
195 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
196 "`", "a", "b", "c", "d", "e", "f", "g",
197 "h", "i", "j", "k", "l", "m", "n", "o",
198 "p", "q", "r", "s", "t", "u", "v", "w",
199 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
200 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
201 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
202 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
203 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
204 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
205 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
206 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
207 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
208 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
209 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
210 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
211 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
212 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
213 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
214 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
215 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
221 "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla] [-g|--genre=#:#:...] [-n|--nohardlink] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-c|--command=<intern|...>] [-x|--exclude=#:#:...] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");
225 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
226 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
227 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
228 void myplaputchar(const char x)
230 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
234 void mywebputstr(const char *c)
242 void myplaputstr(const char *c)
246 myplaputchar('\\'); /* translate slash to backslash */
250 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
251 while(*c == '/' && c[1] == '/')
256 void myputstr(const char *c)
264 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
265 while(*c == '/' && c[1] == '/')
270 /* remove spaces at beginning and end of string */
274 /* remove spaces at beginning ... */
282 /* ... and end of string */
284 while(--p > c && *p == ' ')
288 void print_webpath(const char *path)
290 const char *c = path;
292 printf(prefix); /* we must not modify this part */
293 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
295 /* maybe there follow many slashes */
299 for(; *c != '\0'; c++) {
301 /* remove multiple "//" when parsing a directory ending with a "/" */
302 while(*c == '/' && c[1] == '/')
307 void print_path(const char *path)
309 const char *c = path;
311 /* skip leading "./" when parsing current directory */
312 if(*c == '.' && *(c + 1) == '/') {
314 /* maybe there follow more slashes */
321 void print_pathtail(const char *path)
324 c = strrchr(path, separator);
332 void noreferal(const char *path, const char *artist, const char *title)
334 printf("\t\t<description><![CDATA[<h4>");
338 printf("</h5><a href=\"");
341 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
345 void reference(const char *title)
348 static char command[2048], buffer[1024];
351 buflen = strlen(title) + strlen(referal) + 3;
352 assert((buflen < 2046));
353 strcpy(command, referal);
354 buflen = strlen(command);
355 command[buflen] = ' ';
356 command[buflen + 1] = '"';
357 command[buflen + 2] = 0;
358 strcat(command, title);
359 buflen = strlen(command);
360 command[buflen] = '"';
361 command[buflen + 1] = 0;
363 fprintf(stderr, "Debug >> processing command: %s\n", command);
364 pipe = popen(command, "r");
366 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
370 fgets(buffer, 1020, pipe);
372 fputs(buffer, stdout);
373 fgets(buffer, 1020, pipe);
379 void parse_options(int argc, char **argv)
381 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
382 static struct option long_options[] = {
383 {"backslash", no_argument, NULL, 'b'},
384 {"command", required_argument, NULL, 'c'},
385 {"debug", no_argument, NULL, 'd'},
386 {"format", required_argument, NULL, 'f'},
387 {"genre", required_argument, NULL, 'g'},
388 {"nohardlink", no_argument, NULL, 'n'},
389 {"output", required_argument, NULL, 'o'},
390 {"prefix", required_argument, NULL, 'p'},
391 {"recursive", no_argument, NULL, 'r'},
392 {"stdin", no_argument, NULL, 's'},
393 {"windows", no_argument, NULL, 'w'},
394 {"exclude", required_argument, NULL, 'x'}
397 int option_index = 0;
399 getopt_long(argc, argv, short_options, long_options,
400 &option_index)) != -1) {
407 if(strncmp(optarg, "intern", 6) == 0)
410 referal = strdup(optarg);
416 if(strcmp(optarg, "m3u") == 0)
418 else if(strcmp(optarg, "pls") == 0)
420 else if(strcmp(optarg, "html") == 0)
422 else if(strcmp(optarg, "rss") == 0)
424 else if(strcmp(optarg, "pla") == 0)
430 if(genrelist == NULL)
431 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
432 if(genrelist == NULL) {
434 "Error >> unable to allocate cleared memory\n");
438 while(n < strlen(optarg)) {
441 "Debug >> genrelist entry activting : %d\n",
443 genrelist[atoi(&optarg[n])] = 1;
444 while(isdigit(optarg[n++]));
453 if(fopen(optarg, "w") == NULL) {
455 "Error >> unable to open output file : %s\n",
461 prefix = malloc(strlen(optarg) + 1);
462 strcpy(prefix, optarg);
463 base = malloc(strlen(prefix) + 1);
464 strcpy(base, prefix);
465 dir = strchr(base, '/');
466 if((dir != NULL) && (dir[1] == '/'))
467 dir = strchr(dir + 2, '/');
472 /* if prefix is a weblink, base is the baselink, dir is the path */
482 winorunix = unix2dos;
486 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
488 genrelist = calloc(257, sizeof(char));
492 if(genrelist == NULL) {
494 "Error >> unable to allocate cleared memory\n");
498 while(n < strlen(optarg)) {
501 "Debug >> genrelist entry activting : %d\n",
503 genrelist[atoi(&optarg[n])] = 0;
504 while(isdigit(optarg[n++]));
515 /* hostname = getenv("HOSTNAME"); */
516 if(genrelist == NULL) {
517 genrelist = calloc(257, sizeof(char));
518 if(genrelist == NULL) {
520 "Error >> unable to allocate cleared memory\n");
530 void parse_mp3(unsigned char *file)
532 int bitrates[2][3][15] =
533 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
535 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
537 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
539 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
540 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
541 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
550 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
553 if((fic = fopen(file, "r")) == NULL) {
554 fprintf(stderr, "Warning >> can't open file : %s\n", file);
557 lus = fread(buffer, 1, MP3_BASE, fic);
561 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
564 version = *(buffer + 3);
565 if(version < 2 || version > 4)
567 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
569 if(*(buffer + 5) != 0)
571 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
572 *(buffer + 5), file);
575 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
576 /* read more header */
577 if(size + lus > MAX) {
578 lus += fread(buffer + lus, 1, MAX - lus, fic);
580 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
583 lus += fread(buffer + lus, 1, size, fic);
588 while(c < buffer + size) {
589 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
592 if(strncmp(c, "TT2", 3) == 0) {
593 strncpy(title, c + 7, size - 1);
594 title[size - 1] = '\0';
596 if(strncmp(c, "TP1", 3) == 0) {
597 strncpy(artist, c + 7, size - 1);
598 artist[size - 1] = '\0';
600 if(strncmp(c, "TCO", 3) == 0) {
601 /* strncpy(genrebuf,c+7,size-1); */
602 /* genrebuf[size-1]='\0'; */
603 /* genre=atoi(&genrebuf[1]); */
608 if(version == 3 || version == 4)
609 while(c < buffer + size) {
611 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
615 if(strncmp(c, "TIT2", 4) == 0) {
616 strncpy(title, c + 11, size - 1);
617 title[size - 1] = '\0';
619 if(strncmp(c, "TPE1", 4) == 0) {
620 strncpy(artist, c + 11, size - 1);
621 artist[size - 1] = '\0';
623 if(strncmp(c, "TCON", 4) == 0) {
624 /* strncpy(genrebuf,c+11,size-1); */
625 /* genrebuf[size-1]='\0'; */
626 /* genre=atoi(&genrebuf[1]); */
627 genre = atoi(c + 12);
633 while(c < buffer + lus - 10) {
634 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
639 version = 2 - (*(c + 1) >> 3 & 1);
640 lay = 4 - (*(c + 1) >> 1 & 3);
641 bitrate_index = *(c + 2) >> 4 & 0xF;
642 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
643 && bitrate_index >= 0 && bitrate_index <= 14)
644 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
648 fseek(fic, 0, SEEK_END);
649 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
658 if(strlen(artist) == 0 && strlen(title) == 0) {
659 fseek(fic, -128, SEEK_END);
660 lus = fread(buffer, 1, 128, fic);
661 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
662 && buffer[2] == 'G') {
663 strncpy(title, buffer + 3, 30);
666 while(c > title && *c == ' ')
668 strncpy(artist, buffer + 33, 30);
671 while(c > artist && *c == ' ')
673 /* strncpy(album,buffer+65,30); */
674 /* strncpy(year,buffer+97,4); */
675 /* strncpy(comment,buffer+101,30); */
676 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
684 void parse_ogg(unsigned char *file)
693 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
696 if((fic = fopen(file, "r")) == NULL) {
697 fprintf(stderr, "Warning >> can't open file : %s\n", file);
700 lus = fread(buffer, 1, OGG_BASE, fic);
703 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
704 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
710 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
712 while(c < buffer + lus - 10) {
714 if(strncasecmp(c, "TITLE=", 6) == 0) {
716 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
718 strncpy(title, c + 6, size - 6);
719 title[size - 6] = '\0';
722 if(strncasecmp(c, "ARTIST=", 7) == 0) {
724 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
726 strncpy(artist, c + 7, size - 7);
727 artist[size - 7] = '\0';
730 if(strncasecmp(c, "GENRE=", 6) == 0) {
733 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
735 strncpy(genrebuf, c + 6, size - 6);
736 genrebuf[size - 6] = '\0';
738 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
739 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
743 if(i == ID3_NR_OF_V1_GENRES)
750 fseek(fic, -OGG_BASE, SEEK_END);
751 lus = fread(buffer, 1, OGG_BASE, fic);
752 c = buffer + lus - 1;
753 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
758 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
759 duration = samples / sample_rate;
766 void parse_mpc(unsigned char *file)
771 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
777 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
780 if((fic = fopen(file, "r")) == NULL) {
781 fprintf(stderr, "Warning >> can't open file : %s\n", file);
784 lus = fread(buffer, 1, 12, fic);
787 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
788 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
794 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
802 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
804 duration = frame_count * 1152 / sample_rates[*c & 3];
806 /* try APETAGEX footer */
807 fseek(fic, -32, SEEK_END);
808 lus = fread(buffer, 1, 32, fic);
809 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
812 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
816 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
817 fseek(fic, -size, SEEK_END);
818 lus = fread(buffer, 1, size, fic);
819 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
823 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
826 if(strcasecmp(c, "TITLE") == 0) {
827 strncpy(title, c + 6, size);
830 if(strcasecmp(c, "ARTIST") == 0) {
831 strncpy(artist, c + 7, size);
834 if(strcasecmp(c, "GENRE") == 0) {
835 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
836 strncpy(genrebuf, c + 6, size);
837 genrebuf[size] = '\0';
839 (ID3_v1_genre_description[i], genrebuf) == 0) {
843 if(i == ID3_NR_OF_V1_GENRES)
847 c += strlen(c) + 1 + size;
856 #define MAXINO (1<<24)
857 #define INOTYP unsigned long
858 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
859 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
861 int hlink_check(struct stat *info)
864 * for speed this subroutine should only be called
865 * - if the file has more than one hardlink
866 * - if the file is a resolved softlink
868 /* the persistent variables */
869 static INOTYP *list[FSN];
870 static dev_t name[FSN];
871 /* some temporary variables */
872 int fsn, is_registered = 0;
874 /* assertions - in case parameters are lowered for less memory usage */
876 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
878 /* search which internal registration number is used for this filesystem */
879 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
881 /* if file system is not registered yet, do it and leave */
883 name[fsn] = (info->st_dev);
884 /* provide space for the bitmap that maps the inodes of this file system */
885 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
886 /* no comparison is needed in empty lists ... return */
889 "Debug >> Linked >> Init List %04x @mem %04lx\n",
890 (int)name[fsn], (long)&list[fsn]);
892 /* this looks more complicated than it really is */
893 /* the idea is very simple:
894 * provide a bitmap that maps all inodes of a file system
895 * to mark all files that have already been visited.
896 * If it is already visited, do not add it to the playlist
899 * The difficulty is as follows:
900 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
901 * would be byte-aligned and would allocate at least eight times the needed space.
902 * Feel free to change the definitions that are involved here, if you know better.
904 if(regbit_qry(list[fsn], (info->st_ino)))
907 regbit_set(list[fsn], (info->st_ino));
909 * the debug expression is more complicated then the working stuff
912 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
913 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
914 (int)info->st_dev, (int)info->st_ino, fsn,
915 (int)((info->st_ino) / sizeof(INOTYP)),
916 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
917 1 << ((info->st_ino) % sizeof(INOTYP)),
918 is_registered ? "Already" : "Not");
920 return is_registered;
924 void parse_file(unsigned char *newpath)
926 unsigned char ext[5];
929 for(j = 0; j < 5; j++)
930 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
934 if(strcmp(".mp2", ext) == 0) {
939 if(strcmp(".mp3", ext) == 0) {
944 if(strcmp(".mpc", ext) == 0) {
949 if(strcmp(".mp+", ext) == 0) {
954 if(strcmp(".ogg", ext) == 0) {
959 if(strcmp(".wav", ext) == 0) {
960 duration = -1; /* parse_wav(newpath); */
964 if((strlen(artist) == 0) && (strlen(title) == 0)) {
965 // there are no tag infos read
966 // use file name to state substitute it
967 char *c = strrchr(newpath, separator);
971 // arbitrarily use the first '-'
972 // to separate artist and title
973 c = strchr(artist, '-');
974 if(c != NULL) { // if trenner found, divide file name
977 c = strrchr(title, '.');
980 } else { // no trenner found, assume
981 // no artist, only title
982 strcpy(title, artist);
985 // replace underscores by spaces
986 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
988 for(c = title; (c = strchr(c, '_')) != NULL; c++)
994 /* guesstitle() end */
996 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1000 if(duration != -1) {
1001 printf("#EXTINF:%d,", duration);
1002 if(strlen(artist) != 0)
1003 printf("%s - ", artist);
1004 printf("%s%s", title, eol);
1006 print_path(newpath);
1010 printf("File%d=", counter);
1011 print_path(newpath);
1012 printf("%sTitle%d=", eol, counter);
1013 if(strlen(artist) != 0)
1014 printf("%s - ", artist);
1015 printf("%s%s", title, eol);
1017 printf("Length%d=%d%s", counter, duration, eol);
1020 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1023 printf("?</td></tr>%s", eol);
1025 printf("%d:%s%d</td></tr>%s", duration / 60,
1026 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1029 if(duration != -1) {
1031 char timebuffer[256];
1033 if(stat(newpath, &infos) != 0) {
1034 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1038 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1039 printf("\t<item>%s", eol);
1040 printf("\t\t<author>");
1042 printf("</author>%s\t\t<title>", eol);
1044 printf("</title>%s", eol);
1046 if(referal == NULL) {
1047 noreferal(newpath, artist, title);
1050 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1052 print_webpath(newpath);
1053 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1054 (int)infos.st_size, magic[encoding], eol);
1055 print_pathtail(newpath);
1056 printf("</guid>%s", eol);
1059 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1060 duration / 3600, (duration / 60) % 60,
1061 duration % 60, eol);
1064 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1065 duration / 60, duration % 60, eol);
1066 if(strlen(artist) != 0) {
1067 printf("\t\t<itunes:author>");
1069 printf("</itunes:author>%s", eol);
1071 printf("\t</item>%s", eol);
1074 case 4: // printing output for Sansa players
1075 myplaputstr("HARP, ");
1076 myplaputstr(newpath);
1083 void parse_directory(unsigned char *path)
1086 struct dirent **namelist;
1087 unsigned char newpath[PATH_MAX];
1091 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1092 if(stat(path, &infos) != 0) {
1093 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1096 /* check if it is a filename */
1097 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1101 /* must be a directory - or something unusable like pipe, socket, etc */
1102 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1103 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1106 for(i = 0; i < n; i++) {
1107 sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1109 if(stat(newpath, &infos) != 0) {
1110 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1113 if(recursive && S_ISDIR(infos.st_mode)
1114 && strcmp(namelist[i]->d_name, ".") != 0
1115 && strcmp(namelist[i]->d_name, "..") != 0)
1116 parse_directory(newpath);
1117 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1118 if(S_ISREG(infos.st_mode)
1119 && !(avoidhlinked && hlink_check(&infos))) {
1120 parse_file(newpath);
1127 int main(int argc, char **argv)
1129 winorunix = one2one;
1131 parse_options(argc, argv);
1132 if(optind == argc && !fromstdin)
1136 printf("#EXTM3U%s", eol);
1139 printf("[playlist]%s", eol);
1143 ("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">%s%s<html>%s%s<head>%s<title>Playlist generated by FAPG "
1145 "</title>%s<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />%s<style type=\"text/css\">%s<!--%s%sbody,td,tr {%s font-family: Verdana, Arial, Helvetica, sans-serif;%s font-size: 12px;%s color: #000000;%s}%s%sbody {%s background: #ffffff;%s}%s%sth {%s text-align: center;%s background: #ffcccc;%s padding-left: 15px;%s padding-right: 15px;%s border: 1px #dd8888 solid;%s}%s%std {%s text-align: center;%s background: #eeeeee;%s padding-left: 15px;%s padding-right: 15px;%s border: 1px #cccccc solid;%s}%s%sh1 {%s font-size: 25px;%s}%s%sp {%s font-size: 10px;%s}%s%sa {%s color: #993333;%s text-decoration: none;%s}%s%sa:hover {%s text-decoration: underline;%s}%s%s-->%s</style>%s</head>%s%s<body>%s%s<h1>Playlist</h1>%s%s<table>%s<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>%s",
1146 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1147 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1148 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1149 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1150 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1156 char timebuffer[256];
1158 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1161 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1163 " -->%s<rss xmlns:itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\" version=\"2.0\">%s <channel>%s\t<title>%s - %s - %s</title>%s\t<description>Directory Tree %s</description>%s\t<link>%s</link>%s\t<itunes:image href=\"%s/xml/podcast.jpg\"/>%s\t<lastBuildDate>%s</lastBuildDate>%s\t<generator>FAPG "
1165 "</generator>%s\t<image>%s\t\t<url>%s/podcast.jpg</url>%s\t\t<title>Server Logo</title>%s\t\t<link>%s</link>%s\t\t<description>Feed provided by FAPG. Click to visit.</description>%s\t</image>%s\t<itunes:owner>%s\t\t<itunes:name>Admin %s</itunes:name>%s\t\t<itunes:email>podcast@%s</itunes:email>%s\t</itunes:owner>%s\t<category>Various</category>%s\t<itunes:subtitle>Directory Tree %s</itunes:subtitle>%s\t<itunes:author>%s</itunes:author>%s\t<copyright>unknown</copyright>%s\t<language>%s</language>%s\t<itunes:explicit>No</itunes:explicit>%s\t<ttl>1800</ttl>%s",
1166 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1167 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1168 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1169 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1170 eol, getenv("LANG"), eol, eol, eol);
1171 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1178 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1182 unsigned char path[PATH_MAX];
1184 while(fgets(path, PATH_MAX, stdin)) {
1185 for(i = 0; i < PATH_MAX; i++)
1186 if(path[i] == '\r' || path[i] == '\n')
1188 parse_directory(path);
1191 for(; optind < argc; optind++) {
1192 parse_directory(argv[optind]);
1196 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1200 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1201 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1205 printf(" </channel>%s</rss>%s", eol, eol);