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*250 /* 250ko for ID3 with JPEG images in it */
49 #define FORMAT_M3U 0 /* "0" is not a good choice for debugging, but OK for a default */
55 #ifdef HAVE_LIBURIPARSER
56 # define FORMAT_XSPF 6
60 int format = FORMAT_M3U;
61 char *genrelist = NULL;
62 unsigned char *prefix = "";
63 unsigned char *base = "";
64 unsigned char *dir = "";
65 unsigned char *hostname = "fritzserver.de";
66 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
67 unsigned char *referal = NULL;
73 unsigned char *eol = "\n";
74 unsigned char buffer[MAX];
78 unsigned char artist[MAX_ITEM];
79 unsigned char title[MAX_ITEM];
80 unsigned char genrebuf[MAX_ITEM];
81 unsigned char genre = 0;
91 char *magic[] = { NULL,
92 "audio/mpeg", "audio/mpeg",
93 "audio/mpeg", "audio/mpeg",
94 "audio/ogg-vorbis", "audio/x-wav",
99 unsigned char unix2dos[] =
100 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
101 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
102 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
103 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
104 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
105 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
106 96, 97, 98, 99, 100, 101, 102, 103,
107 104, 105, 106, 107, 108, 109, 110, 111,
108 112, 113, 114, 115, 116, 117, 118, 119,
109 120, 121, 122, 123, 36, 125, 126, 127,
110 199, 252, 233, 226, 228, 224, 229, 231,
111 234, 235, 232, 239, 238, 236, 196, 197,
112 201, 230, 198, 244, 246, 242, 251, 249,
113 255, 214, 220, 248, 163, 216, 215, 131,
114 225, 237, 243, 250, 241, 209, 170, 186,
115 191, 174, 172, 189, 188, 161, 171, 187,
116 166, 166, 166, 166, 166, 193, 194, 192,
117 169, 166, 166, 43, 43, 162, 165, 43,
118 43, 45, 45, 43, 45, 43, 227, 195,
119 43, 43, 45, 45, 166, 45, 43, 164,
120 240, 208, 202, 203, 200, 105, 205, 206,
121 207, 43, 43, 166, 220, 166, 204, 175,
122 211, 223, 212, 210, 245, 213, 181, 254,
123 222, 218, 219, 217, 253, 221, 175, 180,
124 173, 177, 61, 190, 182, 167, 247, 184,
125 176, 168, 183, 185, 179, 178, 166, 160
128 unsigned char *basemap;
129 unsigned char *winorunix;
130 unsigned char one2one[] =
131 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
132 16, 17, 18, 19, 20, 21, 22, 23,
133 24, 25, 26, 27, 28, 29, 30, 31,
134 32, 33, 34, 35, 36, 37, 38, 39,
135 40, 41, 42, 43, 44, 45, 46, 47,
136 48, 49, 50, 51, 52, 53, 54, 55,
137 56, 57, 58, 59, 60, 61, 62, 63,
138 64, 65, 66, 67, 68, 69, 70, 71,
139 72, 73, 74, 75, 76, 77, 78, 79,
140 80, 81, 82, 83, 84, 85, 86, 87,
141 88, 89, 90, 91, 92, 93, 94, 95,
142 96, 97, 98, 99, 100, 101, 102, 103,
143 104, 105, 106, 107, 108, 109, 110, 111,
144 112, 113, 114, 115, 116, 117, 118, 119,
145 120, 121, 122, 123, 124, 125, 126, 127,
146 128, 129, 130, 131, 132, 133, 134, 135,
147 136, 137, 138, 139, 140, 141, 142, 143,
148 144, 145, 146, 147, 148, 149, 150, 151,
149 152, 153, 154, 155, 156, 157, 158, 159,
150 160, 161, 162, 163, 164, 165, 166, 167,
151 168, 169, 170, 171, 172, 173, 174, 175,
152 176, 177, 178, 179, 180, 181, 182, 183,
153 184, 185, 186, 187, 188, 189, 190, 191,
154 192, 193, 194, 195, 196, 197, 198, 199,
155 200, 201, 202, 203, 204, 205, 206, 207,
156 208, 209, 210, 211, 212, 213, 214, 215,
157 216, 217, 218, 219, 220, 221, 222, 223,
158 224, 225, 226, 227, 228, 229, 230, 231,
159 232, 233, 234, 235, 236, 237, 238, 239,
160 240, 241, 242, 243, 244, 245, 246, 247,
161 248, 249, 250, 251, 252, 253, 254, 255
162 }; /* identical mapping */
164 unsigned char noand[256] =
165 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
166 16, 17, 18, 19, 20, 21, 22, 23,
167 24, 25, 26, 27, 28, 29, 30, 31,
168 32, 33, 34, 35, 36, 37, 43, 39,
169 40, 41, 42, 43, 44, 45, 46, 47,
170 48, 49, 50, 51, 52, 53, 54, 55,
171 56, 57, 58, 59, 60, 61, 62, 63,
172 64, 65, 66, 67, 68, 69, 70, 71,
173 72, 73, 74, 75, 76, 77, 78, 79,
174 80, 81, 82, 83, 84, 85, 86, 87,
175 88, 89, 90, 91, 92, 93, 94, 95,
176 96, 97, 98, 99, 100, 101, 102, 103,
177 104, 105, 106, 107, 108, 109, 110, 111,
178 112, 113, 114, 115, 116, 117, 118, 119,
179 120, 121, 122, 123, 124, 125, 126, 127,
180 128, 129, 130, 131, 132, 133, 134, 135,
181 136, 137, 138, 139, 140, 141, 142, 143,
182 144, 145, 146, 147, 148, 149, 150, 151,
183 152, 153, 154, 155, 156, 157, 158, 159,
184 160, 161, 162, 163, 164, 165, 166, 167,
185 168, 169, 170, 171, 172, 173, 174, 175,
186 176, 177, 178, 179, 180, 181, 182, 183,
187 184, 185, 186, 187, 188, 189, 190, 191,
188 192, 193, 194, 195, 196, 197, 198, 199,
189 200, 201, 202, 203, 204, 205, 206, 207,
190 208, 209, 210, 211, 212, 213, 214, 215,
191 216, 217, 218, 219, 220, 221, 222, 223,
192 224, 225, 226, 227, 228, 229, 230, 231,
193 232, 233, 234, 235, 236, 237, 238, 239,
194 240, 241, 242, 243, 244, 245, 246, 247,
195 248, 249, 250, 251, 252, 253, 254, 255
196 }; /* only '&' is mapped to '+' */
198 unsigned char *iso2web[256] = {
199 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
200 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
201 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
202 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
203 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
204 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
205 "0", "1", "2", "3", "4", "5", "6", "7",
206 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
207 "@", "A", "B", "C", "D", "E", "F", "G",
208 "H", "I", "J", "K", "L", "M", "N", "O",
209 "P", "Q", "R", "S", "T", "U", "V", "W",
210 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
211 "`", "a", "b", "c", "d", "e", "f", "g",
212 "h", "i", "j", "k", "l", "m", "n", "o",
213 "p", "q", "r", "s", "t", "u", "v", "w",
214 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
215 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
216 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
217 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
218 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
219 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
220 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
221 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
222 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
223 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
224 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
225 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
226 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
227 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
228 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
229 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
230 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
235 #ifdef HAVE_LIBURIPARSER
236 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
238 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
241 "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");
246 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
247 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
248 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
249 void myplaputchar(const char x)
251 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
255 void mywebputstr(const char *c)
263 void utf16toutf8(char *c,int n)
265 /* check whether the we need to convert UTF-16 to UTF-8 strings */
266 if ( ( c[0] != '\377' ) || ( c[1] != '\376' ) ) { return; }
267 /* only continue here, if the first 2 letters are 0xfffe *
268 * c references an UTF-16 input, where latin letters are *
269 * separated by zero bytes, which we need to eliminate */
271 for(int j=2; (j<n) && (j<MAX_ITEM); j++ ) {
272 if( isprint(c[j]) ) { /* this is not perfect ! */
274 } } c[i+1]=c[i]='\0';
275 /* the index i follows the zero-terminated "string" *
276 * now the read buffer is modified, not the file */
280 void myplaputstr(const char *c)
284 myplaputchar('\\'); /* translate slash to backslash */
288 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
289 while(*c == '/' && c[1] == '/')
294 void myputstr(const char *c)
302 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
303 while(*c == '/' && c[1] == '/')
308 void txxputheader(const char *c)
324 void txxputnameoffset(const char *c)
329 unsigned char *prefx;
342 cnt--; // skip the leading dot of the filepath
355 b = (pos & 0xFF00) >> 8;
361 void txxputstr(const char *c)
365 unsigned char *prefx;
370 fprintf(stderr, "prefix: '%s'\n", prefx);
386 c++; // skip the leading dot
408 void txxputcounter(int c)
414 b = (c & 0xFF000000) >> 24;
416 b = (c & 0x00FF0000) >> 16;
418 b = (c & 0x0000FF00) >> 8;
420 b = (c & 0x000000FF);
424 /* remove spaces at beginning and end of string */
428 /* remove spaces at beginning ... */
436 /* ... and end of string */
438 while(--p > c && *p == ' ')
442 void print_webpath(const char *path)
444 const char *c = path;
446 printf("%s", prefix); /* we must not modify this part */
447 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
449 /* maybe there follow many slashes */
453 for(; *c != '\0'; c++) {
455 /* remove multiple "//" when parsing a directory ending with a "/" */
456 while(*c == '/' && c[1] == '/')
461 void print_path(const char *path)
463 const char *c = path;
464 printf("%s", prefix);
465 /* skip leading "./" when parsing current directory */
466 if(*c == '.' && *(c + 1) == '/') {
468 /* maybe there follow more slashes */
475 void print_pathtail(const char *path)
478 c = strrchr(path, separator);
486 void noreferal(const char *path, const char *artist, const char *title)
488 printf("\t\t<description><![CDATA[<h4>");
492 printf("</h5><a href=\"");
495 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
499 void reference(const char *title)
502 static char command[2048], buffer[1024];
505 buflen = strlen(title) + strlen(referal) + 3;
506 assert((buflen < 2046));
507 strcpy(command, referal);
508 buflen = strlen(command);
509 command[buflen] = ' ';
510 command[buflen + 1] = '"';
511 command[buflen + 2] = 0;
512 strcat(command, title);
513 buflen = strlen(command);
514 command[buflen] = '"';
515 command[buflen + 1] = 0;
517 fprintf(stderr, "Debug >> processing command: %s\n", command);
518 pipe = popen(command, "r");
520 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
523 fgets(buffer, 1020, pipe);
525 fputs(buffer, stdout);
526 fgets(buffer, 1020, pipe);
532 void parse_options(int argc, char **argv)
534 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
535 static struct option long_options[] = {
536 {"backslash", no_argument, NULL, 'b'},
537 {"command", required_argument, NULL, 'c'},
538 {"debug", no_argument, NULL, 'd'},
539 {"format", required_argument, NULL, 'f'},
540 {"genre", required_argument, NULL, 'g'},
541 {"nohardlink", no_argument, NULL, 'n'},
542 {"output", required_argument, NULL, 'o'},
543 {"prefix", required_argument, NULL, 'p'},
544 {"recursive", no_argument, NULL, 'r'},
545 {"stdin", no_argument, NULL, 's'},
546 {"windows", no_argument, NULL, 'w'},
547 {"exclude", required_argument, NULL, 'x'},
551 int option_index = 0;
553 getopt_long(argc, argv, short_options, long_options,
554 &option_index)) != -1) {
561 if(strncmp(optarg, "intern", 6) == 0)
564 referal = strdup(optarg);
570 if(strcmp(optarg, "m3u") == 0)
572 else if(strcmp(optarg, "pls") == 0)
574 else if(strcmp(optarg, "html") == 0)
575 format = FORMAT_HTML;
576 else if(strcmp(optarg, "rss") == 0)
578 else if(strcmp(optarg, "pla") == 0)
580 else if(strcmp(optarg, "txx") == 0)
582 #ifdef HAVE_LIBURIPARSER
583 else if(strcmp(optarg, "xspf") == 0)
584 format = FORMAT_XSPF;
590 if(genrelist == NULL)
591 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
592 if(genrelist == NULL) {
594 "Error >> unable to allocate cleared memory\n");
598 while(n < strlen(optarg)) {
601 "Debug >> genrelist entry activting : %d\n",
603 genrelist[atoi(&optarg[n])] = 1;
604 while(isdigit(optarg[n++]));
613 if(fopen(optarg, "w") == NULL) {
615 "Error >> unable to open output file : %s\n",
621 prefix = malloc(strlen(optarg) + 1);
622 strcpy(prefix, optarg);
623 base = malloc(strlen(prefix) + 1);
624 strcpy(base, prefix);
625 dir = strchr(base, '/');
626 if((dir != NULL) && (dir[1] == '/'))
627 dir = strchr(dir + 2, '/');
632 /* if prefix is a weblink, base is the baselink, dir is the path */
642 winorunix = unix2dos;
646 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
648 genrelist = calloc(257, sizeof(char));
652 if(genrelist == NULL) {
654 "Error >> unable to allocate cleared memory\n");
658 while(n < strlen(optarg)) {
661 "Debug >> genrelist entry activting : %d\n",
663 genrelist[atoi(&optarg[n])] = 0;
664 while(isdigit(optarg[n++]));
675 /* hostname = getenv("HOSTNAME"); */
676 if(genrelist == NULL) {
677 genrelist = calloc(257, sizeof(char));
678 if(genrelist == NULL) {
680 "Error >> unable to allocate cleared memory\n");
690 void parse_mp3(unsigned char *file)
692 int bitrates[2][3][15] =
693 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
695 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
697 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
699 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
700 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
701 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
710 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
713 if((fic = fopen(file, "rb")) == NULL) {
714 fprintf(stderr, "Warning >> can't open file : %s\n", file);
717 lus = fread(buffer, 1, MP3_BASE, fic);
721 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
724 version = *(buffer + 3);
725 if(version < 2 || version > 4)
727 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
729 if(*(buffer + 5) != 0)
731 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
732 *(buffer + 5), file);
735 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
736 /* read more header */
737 if(size + lus > MAX) {
738 lus += fread(buffer + lus, 1, MAX - lus, fic);
740 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
743 lus += fread(buffer + lus, 1, size, fic);
748 while(c < buffer + size) {
749 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
752 if(strncmp(c, "TT2", 3) == 0) {
753 utf16toutf8(c+7,size);
754 strncpy(title, c + 7, size - 1);
755 title[size - 1] = '\0';
757 if(strncmp(c, "TP1", 3) == 0) {
758 utf16toutf8(c+7,size);
759 strncpy(artist, c + 7, size - 1);
760 artist[size - 1] = '\0';
762 if(strncmp(c, "TCO", 3) == 0) {
763 /* strncpy(genrebuf,c+7,size-1); */
764 /* genrebuf[size-1]='\0'; */
765 /* genre=atoi(&genrebuf[1]); */
770 if(version == 3 || version == 4)
771 while(c < buffer + size) {
773 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
777 if(strncmp(c, "TIT2", 4) == 0) {
778 utf16toutf8(c+11,size);
779 strncpy(title, c + 11, size - 1);
780 title[size - 1] = '\0';
782 if(strncmp(c, "TPE1", 4) == 0) {
783 utf16toutf8(c+11,size);
784 strncpy(artist, c + 11, size - 1);
785 artist[size - 1] = '\0';
787 if(strncmp(c, "TCON", 4) == 0) {
788 /* strncpy(genrebuf,c+11,size-1); */
789 /* genrebuf[size-1]='\0'; */
790 /* genre=atoi(&genrebuf[1]); */
791 genre = atoi(c + 12);
793 if(strncmp(c, "TLEN", 4) == 0) {
794 duration = atoi(c + 11) / 1000;
800 while(c < buffer + lus - 10) {
801 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
806 version = 2 - (*(c + 1) >> 3 & 1);
807 lay = 4 - (*(c + 1) >> 1 & 3);
808 bitrate_index = *(c + 2) >> 4 & 0xF;
809 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
810 && bitrate_index >= 0 && bitrate_index <= 14)
811 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
815 fseek(fic, 0, SEEK_END);
816 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
825 if(strlen(artist) == 0 && strlen(title) == 0) {
826 fseek(fic, -128, SEEK_END);
827 lus = fread(buffer, 1, 128, fic);
828 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
829 && buffer[2] == 'G') {
830 strncpy(title, buffer + 3, 30);
833 while(c > title && *c == ' ')
835 strncpy(artist, buffer + 33, 30);
838 while(c > artist && *c == ' ')
840 /* strncpy(album,buffer+65,30); */
841 /* strncpy(year,buffer+97,4); */
842 /* strncpy(comment,buffer+101,30); */
843 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
851 void parse_ogg(unsigned char *file)
860 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
863 if((fic = fopen(file, "r")) == NULL) {
864 fprintf(stderr, "Warning >> can't open file : %s\n", file);
867 lus = fread(buffer, 1, OGG_BASE, fic);
870 if(strncmp(buffer, "Ogg", 3) != 0) {
871 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
877 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
879 while(c < buffer + lus - 10) {
881 if(strncasecmp(c, "TITLE=", 6) == 0) {
883 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
885 strncpy(title, c + 6, size - 6);
886 title[size - 6] = '\0';
889 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
892 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
896 if(strncasecmp(c, "ARTIST=", 7) == 0) {
898 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
900 strncpy(artist, c + 7, size - 7);
901 artist[size - 7] = '\0';
904 if(strncasecmp(c, "GENRE=", 6) == 0) {
907 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
909 strncpy(genrebuf, c + 6, size - 6);
910 genrebuf[size - 6] = '\0';
912 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
913 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
917 if(i == ID3_NR_OF_V1_GENRES)
924 fseek(fic, -OGG_BASE, SEEK_END);
925 lus = fread(buffer, 1, OGG_BASE, fic);
926 c = buffer + lus - 1;
927 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
932 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
933 duration = samples / sample_rate;
939 void parse_mpc(unsigned char *file)
944 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
950 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
953 if((fic = fopen(file, "r")) == NULL) {
954 fprintf(stderr, "Warning >> can't open file : %s\n", file);
957 lus = fread(buffer, 1, 12, fic);
960 if (strncmp(buffer, "MP+", 3) != 0) {
961 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
967 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
975 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
977 duration = frame_count * 1152 / sample_rates[*c & 3];
979 /* try APETAGEX footer */
980 fseek(fic, -32, SEEK_END);
981 lus = fread(buffer, 1, 32, fic);
982 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
985 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
989 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
990 fseek(fic, -size, SEEK_END);
991 lus = fread(buffer, 1, size, fic);
992 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
996 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
999 if(strcasecmp(c, "TITLE") == 0) {
1000 strncpy(title, c + 6, size);
1003 if(strcasecmp(c, "ARTIST") == 0) {
1004 strncpy(artist, c + 7, size);
1005 artist[size] = '\0';
1007 if(strcasecmp(c, "GENRE") == 0) {
1008 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
1009 strncpy(genrebuf, c + 6, size);
1010 genrebuf[size] = '\0';
1012 (ID3_v1_genre_description[i], genrebuf) == 0) {
1016 if(i == ID3_NR_OF_V1_GENRES)
1020 c += strlen(c) + 1 + size;
1029 #define MAXINO (1<<24)
1030 #define INOTYP unsigned long
1031 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1032 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1034 int hlink_check(struct stat *info)
1037 * for speed this subroutine should only be called
1038 * - if the file has more than one hardlink
1039 * - if the file is a resolved softlink
1041 /* the persistent variables */
1042 static INOTYP *list[FSN];
1043 static dev_t name[FSN];
1044 /* some temporary variables */
1045 int fsn, is_registered = 0;
1047 /* assertions - in case parameters are lowered for less memory usage */
1049 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1051 /* search which internal registration number is used for this filesystem */
1052 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1054 /* if file system is not registered yet, do it and leave */
1055 if(name[fsn] == 0) {
1056 name[fsn] = (info->st_dev);
1057 /* provide space for the bitmap that maps the inodes of this file system */
1058 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1059 /* no comparison is needed in empty lists ... return */
1062 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1063 (int)name[fsn], (long)&list[fsn]);
1065 /* this looks more complicated than it really is */
1066 /* the idea is very simple:
1067 * provide a bitmap that maps all inodes of a file system
1068 * to mark all files that have already been visited.
1069 * If it is already visited, do not add it to the playlist
1072 * The difficulty is as follows:
1073 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1074 * would be byte-aligned and would allocate at least eight times the needed space.
1075 * Feel free to change the definitions that are involved here, if you know better.
1077 if(regbit_qry(list[fsn], (info->st_ino)))
1080 regbit_set(list[fsn], (info->st_ino));
1082 * the debug expression is more complicated then the working stuff
1085 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1086 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1087 (int)info->st_dev, (int)info->st_ino, fsn,
1088 (int)((info->st_ino) / sizeof(INOTYP)),
1089 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1090 1 << ((info->st_ino) % sizeof(INOTYP)),
1091 is_registered ? "Already" : "Not");
1093 return is_registered;
1096 #ifdef HAVE_LIBURIPARSER
1097 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1099 char * absSourceFile;
1100 size_t absSourceLen;
1101 char * sourceUriString;
1102 char * baseUriString;
1103 UriParserStateA state;
1106 UriUriA relativeUri;
1111 if ((unixFilename == NULL) || (baseDir == NULL)) {
1116 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1117 if (baseUriString == NULL) {
1120 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1121 free(baseUriString);
1124 state.uri = &baseUri;
1125 if (uriParseUriA(&state, baseUriString) != 0) {
1126 free(baseUriString);
1127 uriFreeUriMembersA(&baseUri);
1132 if (unixFilename[0] != '/') {
1133 const int baseDirLen = strlen(baseDir);
1134 const int sourceFileLen = strlen(unixFilename);
1135 absSourceLen = baseDirLen + sourceFileLen;
1136 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1137 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1139 absSourceLen = strlen(unixFilename);
1140 absSourceFile = (char *)unixFilename;
1142 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1143 if (sourceUriString == NULL) {
1144 free(baseUriString);
1145 if (unixFilename[0] != '/') {
1146 free(absSourceFile);
1148 uriFreeUriMembersA(&baseUri);
1151 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1152 free(baseUriString);
1153 free(sourceUriString);
1154 if (unixFilename[0] != '/') {
1155 free(absSourceFile);
1157 uriFreeUriMembersA(&baseUri);
1160 state.uri = &sourceUri;
1161 if (uriParseUriA(&state, sourceUriString) != 0) {
1162 free(baseUriString);
1163 free(sourceUriString);
1164 uriFreeUriMembersA(&baseUri);
1165 uriFreeUriMembersA(&sourceUri);
1168 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1169 free(baseUriString);
1170 free(sourceUriString);
1171 if (unixFilename[0] != '/') {
1172 free(absSourceFile);
1174 uriFreeUriMembersA(&baseUri);
1175 uriFreeUriMembersA(&sourceUri);
1179 /* make relative (or keep absolute if necessary) */
1180 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1181 free(baseUriString);
1182 free(sourceUriString);
1183 if (unixFilename[0] != '/') {
1184 free(absSourceFile);
1186 uriFreeUriMembersA(&baseUri);
1187 uriFreeUriMembersA(&sourceUri);
1188 uriFreeUriMembersA(&relativeUri);
1192 /* back to string */
1193 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1194 free(baseUriString);
1195 free(sourceUriString);
1196 if (unixFilename[0] != '/') {
1197 free(absSourceFile);
1199 uriFreeUriMembersA(&baseUri);
1200 uriFreeUriMembersA(&sourceUri);
1201 uriFreeUriMembersA(&relativeUri);
1204 output = malloc((charsRequired + 1) * sizeof(char));
1205 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1206 free(baseUriString);
1207 free(sourceUriString);
1208 if (unixFilename[0] != '/') {
1209 free(absSourceFile);
1212 uriFreeUriMembersA(&baseUri);
1213 uriFreeUriMembersA(&sourceUri);
1214 uriFreeUriMembersA(&relativeUri);
1218 free(baseUriString);
1219 free(sourceUriString);
1220 if (unixFilename[0] != '/') {
1221 free(absSourceFile);
1223 uriFreeUriMembersA(&baseUri);
1224 uriFreeUriMembersA(&sourceUri);
1225 uriFreeUriMembersA(&relativeUri);
1230 char * xml_escape_malloc(const char * input)
1232 const char * read = input;
1236 if (input == NULL) {
1240 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1241 if (output == NULL) {
1247 if (*read == '\0') {
1252 switch ((unsigned char)*read) {
1254 strcpy(write, "&");
1258 strcpy(write, "<");
1262 strcpy(write, ">");
1266 strcpy(write, "'");
1270 strcpy(write, """);
1281 void parse_file(unsigned char *newpath, unsigned char * original_path)
1283 unsigned char ext[5];
1284 int j, encoding = 0;
1286 for(j = 0; j < 5; j++)
1287 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1291 if(strcmp(".mp2", ext) == 0) {
1296 if(strcmp(".mp3", ext) == 0) {
1301 if(strcmp(".mpc", ext) == 0) {
1306 if(strcmp(".mp+", ext) == 0) {
1311 if(strcmp(".ogg", ext) == 0) {
1316 if(strcmp(".wav", ext) == 0) {
1318 /* parse_wav(newpath); */
1321 if(strcmp(".wma", ext) == 0) {
1323 /* parse_wma(newpath); */
1327 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1328 // there are no tag infos read
1329 // use file name to state substitute it
1330 char *c = strrchr(newpath, separator);
1333 strcpy(artist, ++c);
1334 // arbitrarily use the first '-'
1335 // to separate artist and title
1336 c = strchr(artist, '-');
1337 if(c != NULL) { // if trenner found, divide file name
1340 c = strrchr(title, '.');
1343 } else { // no trenner found, assume
1344 // no artist, only title
1345 strcpy(title, artist);
1348 // replace underscores by spaces
1349 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1351 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1357 /* guesstitle() end */
1359 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1363 printf("#EXTINF:%d,", duration);
1364 if(strlen(artist) != 0)
1365 printf("%s - ", artist);
1366 printf("%s%s", title, eol);
1367 print_path(newpath);
1371 printf("File%d=", counter);
1372 print_path(newpath);
1373 printf("%sTitle%d=", eol, counter);
1374 if(strlen(artist) != 0)
1375 printf("%s - ", artist);
1376 printf("%s%s", title, eol);
1378 printf("Length%d=%d%s", counter, duration, eol);
1381 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1384 printf("?</td></tr>%s", eol);
1386 printf("%d:%s%d</td></tr>%s", duration / 60,
1387 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1390 if(duration != -1) {
1392 char timebuffer[256];
1394 if(stat(newpath, &infos) != 0) {
1395 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1399 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1400 printf("\t<item>%s", eol);
1401 printf("\t\t<author>");
1403 printf("</author>%s\t\t<title>", eol);
1405 printf("</title>%s", eol);
1407 if(referal == NULL) {
1408 noreferal(newpath, artist, title);
1411 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1413 print_webpath(newpath);
1414 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1415 (int)infos.st_size, magic[encoding], eol);
1416 print_pathtail(newpath);
1417 printf("</guid>%s", eol);
1420 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1421 duration / 3600, (duration / 60) % 60,
1422 duration % 60, eol);
1425 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1426 duration / 60, duration % 60, eol);
1427 if(strlen(artist) != 0) {
1428 printf("\t\t<itunes:author>");
1430 printf("</itunes:author>%s", eol);
1432 printf("\t</item>%s", eol);
1436 myplaputstr("HARP, ");
1437 myplaputstr(newpath);
1443 #ifdef HAVE_LIBURIPARSER
1445 printf("<track>\n");
1446 if (strlen(title) > 0) {
1447 char * escaped_title = xml_escape_malloc(title);
1448 if (escaped_title != NULL) {
1449 printf(" <title>%s</title>\n", escaped_title);
1450 free(escaped_title);
1453 if (strlen(artist) > 0) {
1454 char * escaped_artist = xml_escape_malloc(artist);
1455 if (escaped_artist != NULL) {
1456 printf(" <creator>%s</creator>\n", escaped_artist);
1457 free(escaped_artist);
1461 printf(" <duration>%d</duration>\n", duration);
1464 char * relative_location;
1465 char * escaped_location;
1466 relative_location = relative_uri_malloc(newpath, original_path);
1467 if (relative_location != NULL) {
1468 escaped_location = xml_escape_malloc(relative_location);
1469 if (escaped_location != NULL) {
1470 printf(" <location>%s</location>\n", escaped_location);
1471 free(escaped_location);
1473 free(relative_location);
1476 printf("</track>\n");
1483 void parse_directory(unsigned char *path, unsigned char * original_path)
1486 struct dirent **namelist;
1487 unsigned char newpath[PATH_MAX];
1491 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1492 if(stat(path, &infos) != 0) {
1493 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1496 /* check if it is a filename */
1497 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1498 parse_file(path, original_path);
1501 /* must be a directory - or something unusable like pipe, socket, etc */
1502 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1503 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1506 for(i = 0; i < n; i++) {
1507 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1509 if(stat(newpath, &infos) != 0) {
1510 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1513 if(recursive && S_ISDIR(infos.st_mode)
1514 && strcmp(namelist[i]->d_name, ".") != 0
1515 && strcmp(namelist[i]->d_name, "..") != 0)
1516 parse_directory(newpath, original_path);
1517 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1518 if(S_ISREG(infos.st_mode)
1519 && !(avoidhlinked && hlink_check(&infos))) {
1520 parse_file(newpath, original_path);
1527 int main(int argc, char **argv)
1529 winorunix = one2one;
1531 parse_options(argc, argv);
1533 if(optind == argc && !fromstdin)
1539 printf("#EXTM3U%s", eol);
1542 printf("[playlist]%s", eol);
1546 ("<!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 "
1548 "</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",
1549 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1550 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1551 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1552 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1553 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1559 char timebuffer[256];
1561 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1564 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1566 " -->%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 "
1568 "</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",
1569 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1570 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1571 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1572 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1573 eol, getenv("LANG"), eol, eol, eol);
1574 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1581 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1586 txxputheader(" iriver UMS PLA");
1589 #ifdef HAVE_LIBURIPARSER
1591 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1592 "<!-- generator=\"FAPG " VERSION " -->\n"
1593 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1599 /* iterate through files */
1601 const char * const pwd_source = getenv("PWD");
1602 const int pwdlen = strlen(pwd_source);
1603 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1604 sprintf(pwd, "%s/", pwd_source);
1607 unsigned char path[PATH_MAX];
1609 while(fgets(path, PATH_MAX, stdin)) {
1610 for(i = 0; i < PATH_MAX; i++)
1611 if(path[i] == '\r' || path[i] == '\n')
1617 /* strip trailing slash */
1618 if (path[i - 1] == '/') {
1622 parse_directory(path, pwd);
1625 for(; optind < argc; optind++) {
1626 /* strip trailing slash */
1627 char * dup = strdup(argv[optind]);
1628 const int len = strlen(dup);
1629 if ((len > 0) && (dup[len - 1] == '/')) {
1630 dup[len - 1] = '\0';
1633 parse_directory(dup, pwd);
1642 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1646 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1647 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1651 printf(" </channel>%s</rss>%s", eol, eol);
1654 txxputcounter(counter);
1656 #ifdef HAVE_LIBURIPARSER
1658 printf("</trackList>\n"