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>
40 #ifdef HAVE_LIBURIPARSER
41 # include <uriparser/Uri.h>
45 #define OGG_BASE 1024*10
46 #define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */
54 #ifdef HAVE_LIBURIPARSER
55 # define FORMAT_XSPF 6
59 int format = FORMAT_M3U;
60 char *genrelist = NULL;
61 unsigned char *prefix = "";
62 unsigned char *base = "";
63 unsigned char *dir = "";
64 unsigned char *hostname = "fritzserver.de";
65 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
66 unsigned char *referal = NULL;
72 unsigned char *eol = "\n";
73 unsigned char buffer[MAX];
77 unsigned char artist[1024];
78 unsigned char title[1024];
79 unsigned char genrebuf[1024];
80 unsigned char genre = 0;
89 char *magic[] = { NULL,
90 "audio/mpeg", "audio/mpeg",
91 "audio/mpeg", "audio/mpeg",
92 "audio/ogg-vorbis", "audio/x-wav",
96 unsigned char unix2dos[] =
97 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
98 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
99 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
100 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
101 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
102 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
103 96, 97, 98, 99, 100, 101, 102, 103,
104 104, 105, 106, 107, 108, 109, 110, 111,
105 112, 113, 114, 115, 116, 117, 118, 119,
106 120, 121, 122, 123, 36, 125, 126, 127,
107 199, 252, 233, 226, 228, 224, 229, 231,
108 234, 235, 232, 239, 238, 236, 196, 197,
109 201, 230, 198, 244, 246, 242, 251, 249,
110 255, 214, 220, 248, 163, 216, 215, 131,
111 225, 237, 243, 250, 241, 209, 170, 186,
112 191, 174, 172, 189, 188, 161, 171, 187,
113 166, 166, 166, 166, 166, 193, 194, 192,
114 169, 166, 166, 43, 43, 162, 165, 43,
115 43, 45, 45, 43, 45, 43, 227, 195,
116 43, 43, 45, 45, 166, 45, 43, 164,
117 240, 208, 202, 203, 200, 105, 205, 206,
118 207, 43, 43, 166, 220, 166, 204, 175,
119 211, 223, 212, 210, 245, 213, 181, 254,
120 222, 218, 219, 217, 253, 221, 175, 180,
121 173, 177, 61, 190, 182, 167, 247, 184,
122 176, 168, 183, 185, 179, 178, 166, 160
125 unsigned char *basemap;
126 unsigned char *winorunix;
127 unsigned char one2one[] =
128 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
129 16, 17, 18, 19, 20, 21, 22, 23,
130 24, 25, 26, 27, 28, 29, 30, 31,
131 32, 33, 34, 35, 36, 37, 38, 39,
132 40, 41, 42, 43, 44, 45, 46, 47,
133 48, 49, 50, 51, 52, 53, 54, 55,
134 56, 57, 58, 59, 60, 61, 62, 63,
135 64, 65, 66, 67, 68, 69, 70, 71,
136 72, 73, 74, 75, 76, 77, 78, 79,
137 80, 81, 82, 83, 84, 85, 86, 87,
138 88, 89, 90, 91, 92, 93, 94, 95,
139 96, 97, 98, 99, 100, 101, 102, 103,
140 104, 105, 106, 107, 108, 109, 110, 111,
141 112, 113, 114, 115, 116, 117, 118, 119,
142 120, 121, 122, 123, 124, 125, 126, 127,
143 128, 129, 130, 131, 132, 133, 134, 135,
144 136, 137, 138, 139, 140, 141, 142, 143,
145 144, 145, 146, 147, 148, 149, 150, 151,
146 152, 153, 154, 155, 156, 157, 158, 159,
147 160, 161, 162, 163, 164, 165, 166, 167,
148 168, 169, 170, 171, 172, 173, 174, 175,
149 176, 177, 178, 179, 180, 181, 182, 183,
150 184, 185, 186, 187, 188, 189, 190, 191,
151 192, 193, 194, 195, 196, 197, 198, 199,
152 200, 201, 202, 203, 204, 205, 206, 207,
153 208, 209, 210, 211, 212, 213, 214, 215,
154 216, 217, 218, 219, 220, 221, 222, 223,
155 224, 225, 226, 227, 228, 229, 230, 231,
156 232, 233, 234, 235, 236, 237, 238, 239,
157 240, 241, 242, 243, 244, 245, 246, 247,
158 248, 249, 250, 251, 252, 253, 254, 255
159 }; /* identical mapping */
161 unsigned char noand[256] =
162 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
163 16, 17, 18, 19, 20, 21, 22, 23,
164 24, 25, 26, 27, 28, 29, 30, 31,
165 32, 33, 34, 35, 36, 37, 43, 39,
166 40, 41, 42, 43, 44, 45, 46, 47,
167 48, 49, 50, 51, 52, 53, 54, 55,
168 56, 57, 58, 59, 60, 61, 62, 63,
169 64, 65, 66, 67, 68, 69, 70, 71,
170 72, 73, 74, 75, 76, 77, 78, 79,
171 80, 81, 82, 83, 84, 85, 86, 87,
172 88, 89, 90, 91, 92, 93, 94, 95,
173 96, 97, 98, 99, 100, 101, 102, 103,
174 104, 105, 106, 107, 108, 109, 110, 111,
175 112, 113, 114, 115, 116, 117, 118, 119,
176 120, 121, 122, 123, 124, 125, 126, 127,
177 128, 129, 130, 131, 132, 133, 134, 135,
178 136, 137, 138, 139, 140, 141, 142, 143,
179 144, 145, 146, 147, 148, 149, 150, 151,
180 152, 153, 154, 155, 156, 157, 158, 159,
181 160, 161, 162, 163, 164, 165, 166, 167,
182 168, 169, 170, 171, 172, 173, 174, 175,
183 176, 177, 178, 179, 180, 181, 182, 183,
184 184, 185, 186, 187, 188, 189, 190, 191,
185 192, 193, 194, 195, 196, 197, 198, 199,
186 200, 201, 202, 203, 204, 205, 206, 207,
187 208, 209, 210, 211, 212, 213, 214, 215,
188 216, 217, 218, 219, 220, 221, 222, 223,
189 224, 225, 226, 227, 228, 229, 230, 231,
190 232, 233, 234, 235, 236, 237, 238, 239,
191 240, 241, 242, 243, 244, 245, 246, 247,
192 248, 249, 250, 251, 252, 253, 254, 255
193 }; /* only '&' is mapped to '+' */
195 unsigned char *iso2web[256] = {
196 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
197 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
198 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
199 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
200 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
201 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
202 "0", "1", "2", "3", "4", "5", "6", "7",
203 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
204 "@", "A", "B", "C", "D", "E", "F", "G",
205 "H", "I", "J", "K", "L", "M", "N", "O",
206 "P", "Q", "R", "S", "T", "U", "V", "W",
207 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
208 "`", "a", "b", "c", "d", "e", "f", "g",
209 "h", "i", "j", "k", "l", "m", "n", "o",
210 "p", "q", "r", "s", "t", "u", "v", "w",
211 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
212 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
213 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
214 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
215 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
216 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
217 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
218 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
219 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
220 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
221 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
222 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
223 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
224 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
225 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
226 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
227 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
232 #ifdef HAVE_LIBURIPARSER
233 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
235 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
238 "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=" FAPG_FORMATS "] [-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");
243 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
244 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
245 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
246 void myplaputchar(const char x)
248 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
252 void mywebputstr(const char *c)
260 void myplaputstr(const char *c)
264 myplaputchar('\\'); /* translate slash to backslash */
268 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
269 while(*c == '/' && c[1] == '/')
274 void myputstr(const char *c)
282 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
283 while(*c == '/' && c[1] == '/')
288 void txxputheader(const char *c)
304 void txxputnameoffset(const char *c)
309 unsigned char *prefx;
322 cnt--; // skip the leading dot of the filepath
335 b = (pos & 0xFF00) >> 8;
341 void txxputstr(const char *c)
345 unsigned char *prefx;
350 fprintf(stderr, "prefix: '%s'\n", prefx);
366 c++; // skip the leading dot
388 void txxputcounter(int c)
394 b = (c & 0xFF000000) >> 24;
396 b = (c & 0x00FF0000) >> 16;
398 b = (c & 0x0000FF00) >> 8;
400 b = (c & 0x000000FF);
404 /* remove spaces at beginning and end of string */
408 /* remove spaces at beginning ... */
416 /* ... and end of string */
418 while(--p > c && *p == ' ')
422 void print_webpath(const char *path)
424 const char *c = path;
426 printf(prefix); /* we must not modify this part */
427 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
429 /* maybe there follow many slashes */
433 for(; *c != '\0'; c++) {
435 /* remove multiple "//" when parsing a directory ending with a "/" */
436 while(*c == '/' && c[1] == '/')
441 void print_path(const char *path)
443 const char *c = path;
445 /* skip leading "./" when parsing current directory */
446 if(*c == '.' && *(c + 1) == '/') {
448 /* maybe there follow more slashes */
455 void print_pathtail(const char *path)
458 c = strrchr(path, separator);
466 void noreferal(const char *path, const char *artist, const char *title)
468 printf("\t\t<description><![CDATA[<h4>");
472 printf("</h5><a href=\"");
475 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
479 void reference(const char *title)
482 static char command[2048], buffer[1024];
485 buflen = strlen(title) + strlen(referal) + 3;
486 assert((buflen < 2046));
487 strcpy(command, referal);
488 buflen = strlen(command);
489 command[buflen] = ' ';
490 command[buflen + 1] = '"';
491 command[buflen + 2] = 0;
492 strcat(command, title);
493 buflen = strlen(command);
494 command[buflen] = '"';
495 command[buflen + 1] = 0;
497 fprintf(stderr, "Debug >> processing command: %s\n", command);
498 pipe = popen(command, "r");
500 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
504 fgets(buffer, 1020, pipe);
506 fputs(buffer, stdout);
507 fgets(buffer, 1020, pipe);
513 void parse_options(int argc, char **argv)
515 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
516 static struct option long_options[] = {
517 {"backslash", no_argument, NULL, 'b'},
518 {"command", required_argument, NULL, 'c'},
519 {"debug", no_argument, NULL, 'd'},
520 {"format", required_argument, NULL, 'f'},
521 {"genre", required_argument, NULL, 'g'},
522 {"nohardlink", no_argument, NULL, 'n'},
523 {"output", required_argument, NULL, 'o'},
524 {"prefix", required_argument, NULL, 'p'},
525 {"recursive", no_argument, NULL, 'r'},
526 {"stdin", no_argument, NULL, 's'},
527 {"windows", no_argument, NULL, 'w'},
528 {"exclude", required_argument, NULL, 'x'}
531 int option_index = 0;
533 getopt_long(argc, argv, short_options, long_options,
534 &option_index)) != -1) {
541 if(strncmp(optarg, "intern", 6) == 0)
544 referal = strdup(optarg);
550 if(strcmp(optarg, "m3u") == 0)
552 else if(strcmp(optarg, "pls") == 0)
554 else if(strcmp(optarg, "html") == 0)
555 format = FORMAT_HTML;
556 else if(strcmp(optarg, "rss") == 0)
558 else if(strcmp(optarg, "pla") == 0)
560 else if(strcmp(optarg, "txx") == 0)
562 #ifdef HAVE_LIBURIPARSER
563 else if(strcmp(optarg, "xspf") == 0)
564 format = FORMAT_XSPF;
570 if(genrelist == NULL)
571 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
572 if(genrelist == NULL) {
574 "Error >> unable to allocate cleared memory\n");
578 while(n < strlen(optarg)) {
581 "Debug >> genrelist entry activting : %d\n",
583 genrelist[atoi(&optarg[n])] = 1;
584 while(isdigit(optarg[n++]));
593 if(fopen(optarg, "w") == NULL) {
595 "Error >> unable to open output file : %s\n",
601 prefix = malloc(strlen(optarg) + 1);
602 strcpy(prefix, optarg);
603 base = malloc(strlen(prefix) + 1);
604 strcpy(base, prefix);
605 dir = strchr(base, '/');
606 if((dir != NULL) && (dir[1] == '/'))
607 dir = strchr(dir + 2, '/');
612 /* if prefix is a weblink, base is the baselink, dir is the path */
622 winorunix = unix2dos;
626 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
628 genrelist = calloc(257, sizeof(char));
632 if(genrelist == NULL) {
634 "Error >> unable to allocate cleared memory\n");
638 while(n < strlen(optarg)) {
641 "Debug >> genrelist entry activting : %d\n",
643 genrelist[atoi(&optarg[n])] = 0;
644 while(isdigit(optarg[n++]));
655 /* hostname = getenv("HOSTNAME"); */
656 if(genrelist == NULL) {
657 genrelist = calloc(257, sizeof(char));
658 if(genrelist == NULL) {
660 "Error >> unable to allocate cleared memory\n");
670 void parse_mp3(unsigned char *file)
672 int bitrates[2][3][15] =
673 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
675 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
677 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
679 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
680 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
681 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
690 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
693 if((fic = fopen(file, "r")) == NULL) {
694 fprintf(stderr, "Warning >> can't open file : %s\n", file);
697 lus = fread(buffer, 1, MP3_BASE, fic);
701 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
704 version = *(buffer + 3);
705 if(version < 2 || version > 4)
707 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
709 if(*(buffer + 5) != 0)
711 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
712 *(buffer + 5), file);
715 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
716 /* read more header */
717 if(size + lus > MAX) {
718 lus += fread(buffer + lus, 1, MAX - lus, fic);
720 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
723 lus += fread(buffer + lus, 1, size, fic);
728 while(c < buffer + size) {
729 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
732 if(strncmp(c, "TT2", 3) == 0) {
733 strncpy(title, c + 7, size - 1);
734 title[size - 1] = '\0';
736 if(strncmp(c, "TP1", 3) == 0) {
737 strncpy(artist, c + 7, size - 1);
738 artist[size - 1] = '\0';
740 if(strncmp(c, "TCO", 3) == 0) {
741 /* strncpy(genrebuf,c+7,size-1); */
742 /* genrebuf[size-1]='\0'; */
743 /* genre=atoi(&genrebuf[1]); */
748 if(version == 3 || version == 4)
749 while(c < buffer + size) {
751 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
755 if(strncmp(c, "TIT2", 4) == 0) {
756 strncpy(title, c + 11, size - 1);
757 title[size - 1] = '\0';
759 if(strncmp(c, "TPE1", 4) == 0) {
760 strncpy(artist, c + 11, size - 1);
761 artist[size - 1] = '\0';
763 if(strncmp(c, "TCON", 4) == 0) {
764 /* strncpy(genrebuf,c+11,size-1); */
765 /* genrebuf[size-1]='\0'; */
766 /* genre=atoi(&genrebuf[1]); */
767 genre = atoi(c + 12);
773 while(c < buffer + lus - 10) {
774 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
779 version = 2 - (*(c + 1) >> 3 & 1);
780 lay = 4 - (*(c + 1) >> 1 & 3);
781 bitrate_index = *(c + 2) >> 4 & 0xF;
782 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
783 && bitrate_index >= 0 && bitrate_index <= 14)
784 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
788 fseek(fic, 0, SEEK_END);
789 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
798 if(strlen(artist) == 0 && strlen(title) == 0) {
799 fseek(fic, -128, SEEK_END);
800 lus = fread(buffer, 1, 128, fic);
801 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
802 && buffer[2] == 'G') {
803 strncpy(title, buffer + 3, 30);
806 while(c > title && *c == ' ')
808 strncpy(artist, buffer + 33, 30);
811 while(c > artist && *c == ' ')
813 /* strncpy(album,buffer+65,30); */
814 /* strncpy(year,buffer+97,4); */
815 /* strncpy(comment,buffer+101,30); */
816 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
824 void parse_ogg(unsigned char *file)
833 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
836 if((fic = fopen(file, "r")) == NULL) {
837 fprintf(stderr, "Warning >> can't open file : %s\n", file);
840 lus = fread(buffer, 1, OGG_BASE, fic);
843 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
844 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
850 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
852 while(c < buffer + lus - 10) {
854 if(strncasecmp(c, "TITLE=", 6) == 0) {
856 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
858 strncpy(title, c + 6, size - 6);
859 title[size - 6] = '\0';
862 if(strncasecmp(c, "ARTIST=", 7) == 0) {
864 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
866 strncpy(artist, c + 7, size - 7);
867 artist[size - 7] = '\0';
870 if(strncasecmp(c, "GENRE=", 6) == 0) {
873 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
875 strncpy(genrebuf, c + 6, size - 6);
876 genrebuf[size - 6] = '\0';
878 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
879 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
883 if(i == ID3_NR_OF_V1_GENRES)
890 fseek(fic, -OGG_BASE, SEEK_END);
891 lus = fread(buffer, 1, OGG_BASE, fic);
892 c = buffer + lus - 1;
893 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
898 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
899 duration = samples / sample_rate;
905 void parse_mpc(unsigned char *file)
910 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
916 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
919 if((fic = fopen(file, "r")) == NULL) {
920 fprintf(stderr, "Warning >> can't open file : %s\n", file);
923 lus = fread(buffer, 1, 12, fic);
926 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
927 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
933 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
941 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
943 duration = frame_count * 1152 / sample_rates[*c & 3];
945 /* try APETAGEX footer */
946 fseek(fic, -32, SEEK_END);
947 lus = fread(buffer, 1, 32, fic);
948 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
951 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
955 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
956 fseek(fic, -size, SEEK_END);
957 lus = fread(buffer, 1, size, fic);
958 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
962 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
965 if(strcasecmp(c, "TITLE") == 0) {
966 strncpy(title, c + 6, size);
969 if(strcasecmp(c, "ARTIST") == 0) {
970 strncpy(artist, c + 7, size);
973 if(strcasecmp(c, "GENRE") == 0) {
974 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
975 strncpy(genrebuf, c + 6, size);
976 genrebuf[size] = '\0';
978 (ID3_v1_genre_description[i], genrebuf) == 0) {
982 if(i == ID3_NR_OF_V1_GENRES)
986 c += strlen(c) + 1 + size;
995 #define MAXINO (1<<24)
996 #define INOTYP unsigned long
997 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
998 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1000 int hlink_check(struct stat *info)
1003 * for speed this subroutine should only be called
1004 * - if the file has more than one hardlink
1005 * - if the file is a resolved softlink
1007 /* the persistent variables */
1008 static INOTYP *list[FSN];
1009 static dev_t name[FSN];
1010 /* some temporary variables */
1011 int fsn, is_registered = 0;
1013 /* assertions - in case parameters are lowered for less memory usage */
1015 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1017 /* search which internal registration number is used for this filesystem */
1018 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1020 /* if file system is not registered yet, do it and leave */
1021 if(name[fsn] == 0) {
1022 name[fsn] = (info->st_dev);
1023 /* provide space for the bitmap that maps the inodes of this file system */
1024 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1025 /* no comparison is needed in empty lists ... return */
1028 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1029 (int)name[fsn], (long)&list[fsn]);
1031 /* this looks more complicated than it really is */
1032 /* the idea is very simple:
1033 * provide a bitmap that maps all inodes of a file system
1034 * to mark all files that have already been visited.
1035 * If it is already visited, do not add it to the playlist
1038 * The difficulty is as follows:
1039 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1040 * would be byte-aligned and would allocate at least eight times the needed space.
1041 * Feel free to change the definitions that are involved here, if you know better.
1043 if(regbit_qry(list[fsn], (info->st_ino)))
1046 regbit_set(list[fsn], (info->st_ino));
1048 * the debug expression is more complicated then the working stuff
1051 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1052 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1053 (int)info->st_dev, (int)info->st_ino, fsn,
1054 (int)((info->st_ino) / sizeof(INOTYP)),
1055 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1056 1 << ((info->st_ino) % sizeof(INOTYP)),
1057 is_registered ? "Already" : "Not");
1059 return is_registered;
1062 #ifdef HAVE_LIBURIPARSER
1063 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1065 char * absSourceFile;
1066 size_t absSourceLen;
1067 char * sourceUriString;
1068 char * baseUriString;
1069 UriParserStateA state;
1072 UriUriA relativeUri;
1077 if ((unixFilename == NULL) || (baseDir == NULL)) {
1082 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1083 if (baseUriString == NULL) {
1086 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1087 free(baseUriString);
1090 state.uri = &baseUri;
1091 if (uriParseUriA(&state, baseUriString) != 0) {
1092 free(baseUriString);
1093 uriFreeUriMembersA(&baseUri);
1098 if (unixFilename[0] != '/') {
1099 const int baseDirLen = strlen(baseDir);
1100 const int sourceFileLen = strlen(unixFilename);
1101 absSourceLen = baseDirLen + sourceFileLen;
1102 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1103 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1105 absSourceLen = strlen(unixFilename);
1106 absSourceFile = (char *)unixFilename;
1108 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1109 if (sourceUriString == NULL) {
1110 free(baseUriString);
1111 if (unixFilename[0] != '/') {
1112 free(absSourceFile);
1114 uriFreeUriMembersA(&baseUri);
1117 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1118 free(baseUriString);
1119 free(sourceUriString);
1120 if (unixFilename[0] != '/') {
1121 free(absSourceFile);
1123 uriFreeUriMembersA(&baseUri);
1126 state.uri = &sourceUri;
1127 if (uriParseUriA(&state, sourceUriString) != 0) {
1128 free(baseUriString);
1129 free(sourceUriString);
1130 uriFreeUriMembersA(&baseUri);
1131 uriFreeUriMembersA(&sourceUri);
1134 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1135 free(baseUriString);
1136 free(sourceUriString);
1137 if (unixFilename[0] != '/') {
1138 free(absSourceFile);
1140 uriFreeUriMembersA(&baseUri);
1141 uriFreeUriMembersA(&sourceUri);
1145 /* make relative (or keep absolute if necessary) */
1146 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1147 free(baseUriString);
1148 free(sourceUriString);
1149 if (unixFilename[0] != '/') {
1150 free(absSourceFile);
1152 uriFreeUriMembersA(&baseUri);
1153 uriFreeUriMembersA(&sourceUri);
1154 uriFreeUriMembersA(&relativeUri);
1158 /* back to string */
1159 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1160 free(baseUriString);
1161 free(sourceUriString);
1162 if (unixFilename[0] != '/') {
1163 free(absSourceFile);
1165 uriFreeUriMembersA(&baseUri);
1166 uriFreeUriMembersA(&sourceUri);
1167 uriFreeUriMembersA(&relativeUri);
1170 output = malloc((charsRequired + 1) * sizeof(char));
1171 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1172 free(baseUriString);
1173 free(sourceUriString);
1174 if (unixFilename[0] != '/') {
1175 free(absSourceFile);
1178 uriFreeUriMembersA(&baseUri);
1179 uriFreeUriMembersA(&sourceUri);
1180 uriFreeUriMembersA(&relativeUri);
1184 free(baseUriString);
1185 free(sourceUriString);
1186 if (unixFilename[0] != '/') {
1187 free(absSourceFile);
1189 uriFreeUriMembersA(&baseUri);
1190 uriFreeUriMembersA(&sourceUri);
1191 uriFreeUriMembersA(&relativeUri);
1196 char * xml_escape_malloc(const char * input)
1198 const char * read = input;
1202 if (input == NULL) {
1206 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1207 if (output == NULL) {
1213 if (*read == '\0') {
1218 switch ((unsigned char)*read) {
1220 strcpy(write, "&");
1224 strcpy(write, "<");
1228 strcpy(write, ">");
1232 strcpy(write, "'");
1236 strcpy(write, """);
1247 void parse_file(unsigned char *newpath, unsigned char * original_path)
1249 unsigned char ext[5];
1250 int j, encoding = 0;
1252 for(j = 0; j < 5; j++)
1253 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1257 if(strcmp(".mp2", ext) == 0) {
1262 if(strcmp(".mp3", ext) == 0) {
1267 if(strcmp(".mpc", ext) == 0) {
1272 if(strcmp(".mp+", ext) == 0) {
1277 if(strcmp(".ogg", ext) == 0) {
1282 if(strcmp(".wav", ext) == 0) {
1283 duration = -1; /* parse_wav(newpath); */
1287 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1288 // there are no tag infos read
1289 // use file name to state substitute it
1290 char *c = strrchr(newpath, separator);
1293 strcpy(artist, ++c);
1294 // arbitrarily use the first '-'
1295 // to separate artist and title
1296 c = strchr(artist, '-');
1297 if(c != NULL) { // if trenner found, divide file name
1300 c = strrchr(title, '.');
1303 } else { // no trenner found, assume
1304 // no artist, only title
1305 strcpy(title, artist);
1308 // replace underscores by spaces
1309 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1311 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1317 /* guesstitle() end */
1319 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1323 if(duration != -1) {
1324 printf("#EXTINF:%d,", duration);
1325 if(strlen(artist) != 0)
1326 printf("%s - ", artist);
1327 printf("%s%s", title, eol);
1329 print_path(newpath);
1333 printf("File%d=", counter);
1334 print_path(newpath);
1335 printf("%sTitle%d=", eol, counter);
1336 if(strlen(artist) != 0)
1337 printf("%s - ", artist);
1338 printf("%s%s", title, eol);
1340 printf("Length%d=%d%s", counter, duration, eol);
1343 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1346 printf("?</td></tr>%s", eol);
1348 printf("%d:%s%d</td></tr>%s", duration / 60,
1349 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1352 if(duration != -1) {
1354 char timebuffer[256];
1356 if(stat(newpath, &infos) != 0) {
1357 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1361 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1362 printf("\t<item>%s", eol);
1363 printf("\t\t<author>");
1365 printf("</author>%s\t\t<title>", eol);
1367 printf("</title>%s", eol);
1369 if(referal == NULL) {
1370 noreferal(newpath, artist, title);
1373 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1375 print_webpath(newpath);
1376 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1377 (int)infos.st_size, magic[encoding], eol);
1378 print_pathtail(newpath);
1379 printf("</guid>%s", eol);
1382 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1383 duration / 3600, (duration / 60) % 60,
1384 duration % 60, eol);
1387 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1388 duration / 60, duration % 60, eol);
1389 if(strlen(artist) != 0) {
1390 printf("\t\t<itunes:author>");
1392 printf("</itunes:author>%s", eol);
1394 printf("\t</item>%s", eol);
1398 myplaputstr("HARP, ");
1399 myplaputstr(newpath);
1405 #ifdef HAVE_LIBURIPARSER
1407 printf("<track>\n");
1408 if (strlen(title) > 0) {
1409 char * escaped_title = xml_escape_malloc(title);
1410 if (escaped_title != NULL) {
1411 printf(" <title>%s</title>\n", escaped_title);
1412 free(escaped_title);
1415 if (strlen(artist) > 0) {
1416 char * escaped_artist = xml_escape_malloc(artist);
1417 if (escaped_artist != NULL) {
1418 printf(" <creator>%s</creator>\n", escaped_artist);
1419 free(escaped_artist);
1423 printf(" <duration>%d</duration>\n", duration);
1426 char * relative_location;
1427 char * escaped_location;
1428 relative_location = relative_uri_malloc(newpath, original_path);
1429 if (relative_location != NULL) {
1430 escaped_location = xml_escape_malloc(relative_location);
1431 if (escaped_location != NULL) {
1432 printf(" <location>%s</location>\n", escaped_location);
1433 free(escaped_location);
1435 free(relative_location);
1438 printf("</track>\n");
1445 void parse_directory(unsigned char *path, unsigned char * original_path)
1448 struct dirent **namelist;
1449 unsigned char newpath[PATH_MAX];
1453 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1454 if(stat(path, &infos) != 0) {
1455 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1458 /* check if it is a filename */
1459 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1460 parse_file(path, original_path);
1463 /* must be a directory - or something unusable like pipe, socket, etc */
1464 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1465 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1468 for(i = 0; i < n; i++) {
1469 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1471 if(stat(newpath, &infos) != 0) {
1472 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1475 if(recursive && S_ISDIR(infos.st_mode)
1476 && strcmp(namelist[i]->d_name, ".") != 0
1477 && strcmp(namelist[i]->d_name, "..") != 0)
1478 parse_directory(newpath, original_path);
1479 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1480 if(S_ISREG(infos.st_mode)
1481 && !(avoidhlinked && hlink_check(&infos))) {
1482 parse_file(newpath, original_path);
1489 int main(int argc, char **argv)
1491 winorunix = one2one;
1493 parse_options(argc, argv);
1495 if(optind == argc && !fromstdin)
1501 printf("#EXTM3U%s", eol);
1504 printf("[playlist]%s", eol);
1508 ("<!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 "
1510 "</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",
1511 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1512 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1513 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1514 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1515 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1521 char timebuffer[256];
1523 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1526 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1528 " -->%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 "
1530 "</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",
1531 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1532 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1533 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1534 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1535 eol, getenv("LANG"), eol, eol, eol);
1536 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1543 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1548 txxputheader(" iriver UMS PLA");
1551 #ifdef HAVE_LIBURIPARSER
1553 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1554 "<!-- generator=\"FAPG " VERSION " -->\n"
1555 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1561 /* iterate through files */
1563 const char * const pwd_source = getenv("PWD");
1564 const int pwdlen = strlen(pwd_source);
1565 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1566 sprintf(pwd, "%s/", pwd_source);
1569 unsigned char path[PATH_MAX];
1571 while(fgets(path, PATH_MAX, stdin)) {
1572 for(i = 0; i < PATH_MAX; i++)
1573 if(path[i] == '\r' || path[i] == '\n')
1579 /* strip trailing slash */
1580 if (path[i - 1] == '/') {
1584 parse_directory(path, pwd);
1587 for(; optind < argc; optind++) {
1588 /* strip trailing slash */
1589 char * dup = strdup(argv[optind]);
1590 const int len = strlen(dup);
1591 if ((len > 0) && (dup[len - 1] == '/')) {
1592 dup[len - 1] = '\0';
1595 parse_directory(dup, pwd);
1604 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1608 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1609 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1613 printf(" </channel>%s</rss>%s", eol, eol);
1616 txxputcounter(counter);
1618 #ifdef HAVE_LIBURIPARSER
1620 printf("</trackList>\n"