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 */
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;
90 char *magic[] = { NULL,
91 "audio/mpeg", "audio/mpeg",
92 "audio/mpeg", "audio/mpeg",
93 "audio/ogg-vorbis", "audio/x-wav",
98 unsigned char unix2dos[] =
99 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
100 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
101 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
102 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
103 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
104 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
105 96, 97, 98, 99, 100, 101, 102, 103,
106 104, 105, 106, 107, 108, 109, 110, 111,
107 112, 113, 114, 115, 116, 117, 118, 119,
108 120, 121, 122, 123, 36, 125, 126, 127,
109 199, 252, 233, 226, 228, 224, 229, 231,
110 234, 235, 232, 239, 238, 236, 196, 197,
111 201, 230, 198, 244, 246, 242, 251, 249,
112 255, 214, 220, 248, 163, 216, 215, 131,
113 225, 237, 243, 250, 241, 209, 170, 186,
114 191, 174, 172, 189, 188, 161, 171, 187,
115 166, 166, 166, 166, 166, 193, 194, 192,
116 169, 166, 166, 43, 43, 162, 165, 43,
117 43, 45, 45, 43, 45, 43, 227, 195,
118 43, 43, 45, 45, 166, 45, 43, 164,
119 240, 208, 202, 203, 200, 105, 205, 206,
120 207, 43, 43, 166, 220, 166, 204, 175,
121 211, 223, 212, 210, 245, 213, 181, 254,
122 222, 218, 219, 217, 253, 221, 175, 180,
123 173, 177, 61, 190, 182, 167, 247, 184,
124 176, 168, 183, 185, 179, 178, 166, 160
127 unsigned char *basemap;
128 unsigned char *winorunix;
129 unsigned char one2one[] =
130 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
131 16, 17, 18, 19, 20, 21, 22, 23,
132 24, 25, 26, 27, 28, 29, 30, 31,
133 32, 33, 34, 35, 36, 37, 38, 39,
134 40, 41, 42, 43, 44, 45, 46, 47,
135 48, 49, 50, 51, 52, 53, 54, 55,
136 56, 57, 58, 59, 60, 61, 62, 63,
137 64, 65, 66, 67, 68, 69, 70, 71,
138 72, 73, 74, 75, 76, 77, 78, 79,
139 80, 81, 82, 83, 84, 85, 86, 87,
140 88, 89, 90, 91, 92, 93, 94, 95,
141 96, 97, 98, 99, 100, 101, 102, 103,
142 104, 105, 106, 107, 108, 109, 110, 111,
143 112, 113, 114, 115, 116, 117, 118, 119,
144 120, 121, 122, 123, 124, 125, 126, 127,
145 128, 129, 130, 131, 132, 133, 134, 135,
146 136, 137, 138, 139, 140, 141, 142, 143,
147 144, 145, 146, 147, 148, 149, 150, 151,
148 152, 153, 154, 155, 156, 157, 158, 159,
149 160, 161, 162, 163, 164, 165, 166, 167,
150 168, 169, 170, 171, 172, 173, 174, 175,
151 176, 177, 178, 179, 180, 181, 182, 183,
152 184, 185, 186, 187, 188, 189, 190, 191,
153 192, 193, 194, 195, 196, 197, 198, 199,
154 200, 201, 202, 203, 204, 205, 206, 207,
155 208, 209, 210, 211, 212, 213, 214, 215,
156 216, 217, 218, 219, 220, 221, 222, 223,
157 224, 225, 226, 227, 228, 229, 230, 231,
158 232, 233, 234, 235, 236, 237, 238, 239,
159 240, 241, 242, 243, 244, 245, 246, 247,
160 248, 249, 250, 251, 252, 253, 254, 255
161 }; /* identical mapping */
163 unsigned char noand[256] =
164 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
165 16, 17, 18, 19, 20, 21, 22, 23,
166 24, 25, 26, 27, 28, 29, 30, 31,
167 32, 33, 34, 35, 36, 37, 43, 39,
168 40, 41, 42, 43, 44, 45, 46, 47,
169 48, 49, 50, 51, 52, 53, 54, 55,
170 56, 57, 58, 59, 60, 61, 62, 63,
171 64, 65, 66, 67, 68, 69, 70, 71,
172 72, 73, 74, 75, 76, 77, 78, 79,
173 80, 81, 82, 83, 84, 85, 86, 87,
174 88, 89, 90, 91, 92, 93, 94, 95,
175 96, 97, 98, 99, 100, 101, 102, 103,
176 104, 105, 106, 107, 108, 109, 110, 111,
177 112, 113, 114, 115, 116, 117, 118, 119,
178 120, 121, 122, 123, 124, 125, 126, 127,
179 128, 129, 130, 131, 132, 133, 134, 135,
180 136, 137, 138, 139, 140, 141, 142, 143,
181 144, 145, 146, 147, 148, 149, 150, 151,
182 152, 153, 154, 155, 156, 157, 158, 159,
183 160, 161, 162, 163, 164, 165, 166, 167,
184 168, 169, 170, 171, 172, 173, 174, 175,
185 176, 177, 178, 179, 180, 181, 182, 183,
186 184, 185, 186, 187, 188, 189, 190, 191,
187 192, 193, 194, 195, 196, 197, 198, 199,
188 200, 201, 202, 203, 204, 205, 206, 207,
189 208, 209, 210, 211, 212, 213, 214, 215,
190 216, 217, 218, 219, 220, 221, 222, 223,
191 224, 225, 226, 227, 228, 229, 230, 231,
192 232, 233, 234, 235, 236, 237, 238, 239,
193 240, 241, 242, 243, 244, 245, 246, 247,
194 248, 249, 250, 251, 252, 253, 254, 255
195 }; /* only '&' is mapped to '+' */
197 unsigned char *iso2web[256] = {
198 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
199 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
200 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
201 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
202 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
203 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
204 "0", "1", "2", "3", "4", "5", "6", "7",
205 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
206 "@", "A", "B", "C", "D", "E", "F", "G",
207 "H", "I", "J", "K", "L", "M", "N", "O",
208 "P", "Q", "R", "S", "T", "U", "V", "W",
209 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
210 "`", "a", "b", "c", "d", "e", "f", "g",
211 "h", "i", "j", "k", "l", "m", "n", "o",
212 "p", "q", "r", "s", "t", "u", "v", "w",
213 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
214 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
215 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
216 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
217 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
218 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
219 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
220 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
221 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
222 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
223 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
224 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
225 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
226 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
227 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
228 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
229 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
234 #ifdef HAVE_LIBURIPARSER
235 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
237 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
240 "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");
245 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
246 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
247 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
248 void myplaputchar(const char x)
250 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
254 void mywebputstr(const char *c)
262 void myplaputstr(const char *c)
266 myplaputchar('\\'); /* translate slash to backslash */
270 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
271 while(*c == '/' && c[1] == '/')
276 void myputstr(const char *c)
284 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
285 while(*c == '/' && c[1] == '/')
290 void txxputheader(const char *c)
306 void txxputnameoffset(const char *c)
311 unsigned char *prefx;
324 cnt--; // skip the leading dot of the filepath
337 b = (pos & 0xFF00) >> 8;
343 void txxputstr(const char *c)
347 unsigned char *prefx;
352 fprintf(stderr, "prefix: '%s'\n", prefx);
368 c++; // skip the leading dot
390 void txxputcounter(int c)
396 b = (c & 0xFF000000) >> 24;
398 b = (c & 0x00FF0000) >> 16;
400 b = (c & 0x0000FF00) >> 8;
402 b = (c & 0x000000FF);
406 /* remove spaces at beginning and end of string */
410 /* remove spaces at beginning ... */
418 /* ... and end of string */
420 while(--p > c && *p == ' ')
424 void print_webpath(const char *path)
426 const char *c = path;
428 printf(prefix); /* we must not modify this part */
429 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
431 /* maybe there follow many slashes */
435 for(; *c != '\0'; c++) {
437 /* remove multiple "//" when parsing a directory ending with a "/" */
438 while(*c == '/' && c[1] == '/')
443 void print_path(const char *path)
445 const char *c = path;
447 /* skip leading "./" when parsing current directory */
448 if(*c == '.' && *(c + 1) == '/') {
450 /* maybe there follow more slashes */
457 void print_pathtail(const char *path)
460 c = strrchr(path, separator);
468 void noreferal(const char *path, const char *artist, const char *title)
470 printf("\t\t<description><![CDATA[<h4>");
474 printf("</h5><a href=\"");
477 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
481 void reference(const char *title)
484 static char command[2048], buffer[1024];
487 buflen = strlen(title) + strlen(referal) + 3;
488 assert((buflen < 2046));
489 strcpy(command, referal);
490 buflen = strlen(command);
491 command[buflen] = ' ';
492 command[buflen + 1] = '"';
493 command[buflen + 2] = 0;
494 strcat(command, title);
495 buflen = strlen(command);
496 command[buflen] = '"';
497 command[buflen + 1] = 0;
499 fprintf(stderr, "Debug >> processing command: %s\n", command);
500 pipe = popen(command, "r");
502 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
506 fgets(buffer, 1020, pipe);
508 fputs(buffer, stdout);
509 fgets(buffer, 1020, pipe);
515 void parse_options(int argc, char **argv)
517 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
518 static struct option long_options[] = {
519 {"backslash", no_argument, NULL, 'b'},
520 {"command", required_argument, NULL, 'c'},
521 {"debug", no_argument, NULL, 'd'},
522 {"format", required_argument, NULL, 'f'},
523 {"genre", required_argument, NULL, 'g'},
524 {"nohardlink", no_argument, NULL, 'n'},
525 {"output", required_argument, NULL, 'o'},
526 {"prefix", required_argument, NULL, 'p'},
527 {"recursive", no_argument, NULL, 'r'},
528 {"stdin", no_argument, NULL, 's'},
529 {"windows", no_argument, NULL, 'w'},
530 {"exclude", required_argument, NULL, 'x'},
534 int option_index = 0;
536 getopt_long(argc, argv, short_options, long_options,
537 &option_index)) != -1) {
544 if(strncmp(optarg, "intern", 6) == 0)
547 referal = strdup(optarg);
553 if(strcmp(optarg, "m3u") == 0)
555 else if(strcmp(optarg, "pls") == 0)
557 else if(strcmp(optarg, "html") == 0)
558 format = FORMAT_HTML;
559 else if(strcmp(optarg, "rss") == 0)
561 else if(strcmp(optarg, "pla") == 0)
563 else if(strcmp(optarg, "txx") == 0)
565 #ifdef HAVE_LIBURIPARSER
566 else if(strcmp(optarg, "xspf") == 0)
567 format = FORMAT_XSPF;
573 if(genrelist == NULL)
574 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
575 if(genrelist == NULL) {
577 "Error >> unable to allocate cleared memory\n");
581 while(n < strlen(optarg)) {
584 "Debug >> genrelist entry activting : %d\n",
586 genrelist[atoi(&optarg[n])] = 1;
587 while(isdigit(optarg[n++]));
596 if(fopen(optarg, "w") == NULL) {
598 "Error >> unable to open output file : %s\n",
604 prefix = malloc(strlen(optarg) + 1);
605 strcpy(prefix, optarg);
606 base = malloc(strlen(prefix) + 1);
607 strcpy(base, prefix);
608 dir = strchr(base, '/');
609 if((dir != NULL) && (dir[1] == '/'))
610 dir = strchr(dir + 2, '/');
615 /* if prefix is a weblink, base is the baselink, dir is the path */
625 winorunix = unix2dos;
629 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
631 genrelist = calloc(257, sizeof(char));
635 if(genrelist == NULL) {
637 "Error >> unable to allocate cleared memory\n");
641 while(n < strlen(optarg)) {
644 "Debug >> genrelist entry activting : %d\n",
646 genrelist[atoi(&optarg[n])] = 0;
647 while(isdigit(optarg[n++]));
658 /* hostname = getenv("HOSTNAME"); */
659 if(genrelist == NULL) {
660 genrelist = calloc(257, sizeof(char));
661 if(genrelist == NULL) {
663 "Error >> unable to allocate cleared memory\n");
673 void parse_mp3(unsigned char *file)
675 int bitrates[2][3][15] =
676 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
678 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
680 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
682 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
683 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
684 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
693 fprintf(stderr, "Debug >> parsing mp3 : %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, MP3_BASE, fic);
704 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
707 version = *(buffer + 3);
708 if(version < 2 || version > 4)
710 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
712 if(*(buffer + 5) != 0)
714 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
715 *(buffer + 5), file);
718 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
719 /* read more header */
720 if(size + lus > MAX) {
721 lus += fread(buffer + lus, 1, MAX - lus, fic);
723 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
726 lus += fread(buffer + lus, 1, size, fic);
731 while(c < buffer + size) {
732 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
735 if(strncmp(c, "TT2", 3) == 0) {
736 strncpy(title, c + 7, size - 1);
737 title[size - 1] = '\0';
739 if(strncmp(c, "TP1", 3) == 0) {
740 strncpy(artist, c + 7, size - 1);
741 artist[size - 1] = '\0';
743 if(strncmp(c, "TCO", 3) == 0) {
744 /* strncpy(genrebuf,c+7,size-1); */
745 /* genrebuf[size-1]='\0'; */
746 /* genre=atoi(&genrebuf[1]); */
751 if(version == 3 || version == 4)
752 while(c < buffer + size) {
754 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
758 if(strncmp(c, "TIT2", 4) == 0) {
759 strncpy(title, c + 11, size - 1);
760 title[size - 1] = '\0';
762 if(strncmp(c, "TPE1", 4) == 0) {
763 strncpy(artist, c + 11, size - 1);
764 artist[size - 1] = '\0';
766 if(strncmp(c, "TCON", 4) == 0) {
767 /* strncpy(genrebuf,c+11,size-1); */
768 /* genrebuf[size-1]='\0'; */
769 /* genre=atoi(&genrebuf[1]); */
770 genre = atoi(c + 12);
776 while(c < buffer + lus - 10) {
777 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
782 version = 2 - (*(c + 1) >> 3 & 1);
783 lay = 4 - (*(c + 1) >> 1 & 3);
784 bitrate_index = *(c + 2) >> 4 & 0xF;
785 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
786 && bitrate_index >= 0 && bitrate_index <= 14)
787 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
791 fseek(fic, 0, SEEK_END);
792 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
801 if(strlen(artist) == 0 && strlen(title) == 0) {
802 fseek(fic, -128, SEEK_END);
803 lus = fread(buffer, 1, 128, fic);
804 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
805 && buffer[2] == 'G') {
806 strncpy(title, buffer + 3, 30);
809 while(c > title && *c == ' ')
811 strncpy(artist, buffer + 33, 30);
814 while(c > artist && *c == ' ')
816 /* strncpy(album,buffer+65,30); */
817 /* strncpy(year,buffer+97,4); */
818 /* strncpy(comment,buffer+101,30); */
819 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
827 void parse_ogg(unsigned char *file)
836 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
839 if((fic = fopen(file, "r")) == NULL) {
840 fprintf(stderr, "Warning >> can't open file : %s\n", file);
843 lus = fread(buffer, 1, OGG_BASE, fic);
846 if(strncmp(buffer, "Ogg", 3) != 0) {
847 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
853 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
855 while(c < buffer + lus - 10) {
857 if(strncasecmp(c, "TITLE=", 6) == 0) {
859 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
861 strncpy(title, c + 6, size - 6);
862 title[size - 6] = '\0';
865 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
868 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
872 if(strncasecmp(c, "ARTIST=", 7) == 0) {
874 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
876 strncpy(artist, c + 7, size - 7);
877 artist[size - 7] = '\0';
880 if(strncasecmp(c, "GENRE=", 6) == 0) {
883 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
885 strncpy(genrebuf, c + 6, size - 6);
886 genrebuf[size - 6] = '\0';
888 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
889 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
893 if(i == ID3_NR_OF_V1_GENRES)
900 fseek(fic, -OGG_BASE, SEEK_END);
901 lus = fread(buffer, 1, OGG_BASE, fic);
902 c = buffer + lus - 1;
903 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
908 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
909 duration = samples / sample_rate;
915 void parse_mpc(unsigned char *file)
920 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
926 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
929 if((fic = fopen(file, "r")) == NULL) {
930 fprintf(stderr, "Warning >> can't open file : %s\n", file);
933 lus = fread(buffer, 1, 12, fic);
936 if (strncmp(buffer, "MP+", 3) != 0) {
937 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
943 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
951 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
953 duration = frame_count * 1152 / sample_rates[*c & 3];
955 /* try APETAGEX footer */
956 fseek(fic, -32, SEEK_END);
957 lus = fread(buffer, 1, 32, fic);
958 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
961 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
965 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
966 fseek(fic, -size, SEEK_END);
967 lus = fread(buffer, 1, size, fic);
968 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
972 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
975 if(strcasecmp(c, "TITLE") == 0) {
976 strncpy(title, c + 6, size);
979 if(strcasecmp(c, "ARTIST") == 0) {
980 strncpy(artist, c + 7, size);
983 if(strcasecmp(c, "GENRE") == 0) {
984 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
985 strncpy(genrebuf, c + 6, size);
986 genrebuf[size] = '\0';
988 (ID3_v1_genre_description[i], genrebuf) == 0) {
992 if(i == ID3_NR_OF_V1_GENRES)
996 c += strlen(c) + 1 + size;
1005 #define MAXINO (1<<24)
1006 #define INOTYP unsigned long
1007 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1008 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1010 int hlink_check(struct stat *info)
1013 * for speed this subroutine should only be called
1014 * - if the file has more than one hardlink
1015 * - if the file is a resolved softlink
1017 /* the persistent variables */
1018 static INOTYP *list[FSN];
1019 static dev_t name[FSN];
1020 /* some temporary variables */
1021 int fsn, is_registered = 0;
1023 /* assertions - in case parameters are lowered for less memory usage */
1025 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1027 /* search which internal registration number is used for this filesystem */
1028 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1030 /* if file system is not registered yet, do it and leave */
1031 if(name[fsn] == 0) {
1032 name[fsn] = (info->st_dev);
1033 /* provide space for the bitmap that maps the inodes of this file system */
1034 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1035 /* no comparison is needed in empty lists ... return */
1038 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1039 (int)name[fsn], (long)&list[fsn]);
1041 /* this looks more complicated than it really is */
1042 /* the idea is very simple:
1043 * provide a bitmap that maps all inodes of a file system
1044 * to mark all files that have already been visited.
1045 * If it is already visited, do not add it to the playlist
1048 * The difficulty is as follows:
1049 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1050 * would be byte-aligned and would allocate at least eight times the needed space.
1051 * Feel free to change the definitions that are involved here, if you know better.
1053 if(regbit_qry(list[fsn], (info->st_ino)))
1056 regbit_set(list[fsn], (info->st_ino));
1058 * the debug expression is more complicated then the working stuff
1061 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1062 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1063 (int)info->st_dev, (int)info->st_ino, fsn,
1064 (int)((info->st_ino) / sizeof(INOTYP)),
1065 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1066 1 << ((info->st_ino) % sizeof(INOTYP)),
1067 is_registered ? "Already" : "Not");
1069 return is_registered;
1072 #ifdef HAVE_LIBURIPARSER
1073 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1075 char * absSourceFile;
1076 size_t absSourceLen;
1077 char * sourceUriString;
1078 char * baseUriString;
1079 UriParserStateA state;
1082 UriUriA relativeUri;
1087 if ((unixFilename == NULL) || (baseDir == NULL)) {
1092 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1093 if (baseUriString == NULL) {
1096 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1097 free(baseUriString);
1100 state.uri = &baseUri;
1101 if (uriParseUriA(&state, baseUriString) != 0) {
1102 free(baseUriString);
1103 uriFreeUriMembersA(&baseUri);
1108 if (unixFilename[0] != '/') {
1109 const int baseDirLen = strlen(baseDir);
1110 const int sourceFileLen = strlen(unixFilename);
1111 absSourceLen = baseDirLen + sourceFileLen;
1112 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1113 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1115 absSourceLen = strlen(unixFilename);
1116 absSourceFile = (char *)unixFilename;
1118 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1119 if (sourceUriString == NULL) {
1120 free(baseUriString);
1121 if (unixFilename[0] != '/') {
1122 free(absSourceFile);
1124 uriFreeUriMembersA(&baseUri);
1127 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1128 free(baseUriString);
1129 free(sourceUriString);
1130 if (unixFilename[0] != '/') {
1131 free(absSourceFile);
1133 uriFreeUriMembersA(&baseUri);
1136 state.uri = &sourceUri;
1137 if (uriParseUriA(&state, sourceUriString) != 0) {
1138 free(baseUriString);
1139 free(sourceUriString);
1140 uriFreeUriMembersA(&baseUri);
1141 uriFreeUriMembersA(&sourceUri);
1144 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1145 free(baseUriString);
1146 free(sourceUriString);
1147 if (unixFilename[0] != '/') {
1148 free(absSourceFile);
1150 uriFreeUriMembersA(&baseUri);
1151 uriFreeUriMembersA(&sourceUri);
1155 /* make relative (or keep absolute if necessary) */
1156 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1157 free(baseUriString);
1158 free(sourceUriString);
1159 if (unixFilename[0] != '/') {
1160 free(absSourceFile);
1162 uriFreeUriMembersA(&baseUri);
1163 uriFreeUriMembersA(&sourceUri);
1164 uriFreeUriMembersA(&relativeUri);
1168 /* back to string */
1169 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1170 free(baseUriString);
1171 free(sourceUriString);
1172 if (unixFilename[0] != '/') {
1173 free(absSourceFile);
1175 uriFreeUriMembersA(&baseUri);
1176 uriFreeUriMembersA(&sourceUri);
1177 uriFreeUriMembersA(&relativeUri);
1180 output = malloc((charsRequired + 1) * sizeof(char));
1181 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1182 free(baseUriString);
1183 free(sourceUriString);
1184 if (unixFilename[0] != '/') {
1185 free(absSourceFile);
1188 uriFreeUriMembersA(&baseUri);
1189 uriFreeUriMembersA(&sourceUri);
1190 uriFreeUriMembersA(&relativeUri);
1194 free(baseUriString);
1195 free(sourceUriString);
1196 if (unixFilename[0] != '/') {
1197 free(absSourceFile);
1199 uriFreeUriMembersA(&baseUri);
1200 uriFreeUriMembersA(&sourceUri);
1201 uriFreeUriMembersA(&relativeUri);
1206 char * xml_escape_malloc(const char * input)
1208 const char * read = input;
1212 if (input == NULL) {
1216 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1217 if (output == NULL) {
1223 if (*read == '\0') {
1228 switch ((unsigned char)*read) {
1230 strcpy(write, "&");
1234 strcpy(write, "<");
1238 strcpy(write, ">");
1242 strcpy(write, "'");
1246 strcpy(write, """);
1257 void parse_file(unsigned char *newpath, unsigned char * original_path)
1259 unsigned char ext[5];
1260 int j, encoding = 0;
1262 for(j = 0; j < 5; j++)
1263 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1267 if(strcmp(".mp2", ext) == 0) {
1272 if(strcmp(".mp3", ext) == 0) {
1277 if(strcmp(".mpc", ext) == 0) {
1282 if(strcmp(".mp+", ext) == 0) {
1287 if(strcmp(".ogg", ext) == 0) {
1292 if(strcmp(".wav", ext) == 0) {
1294 /* parse_wav(newpath); */
1297 if(strcmp(".wma", ext) == 0) {
1299 /* parse_wma(newpath); */
1303 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1304 // there are no tag infos read
1305 // use file name to state substitute it
1306 char *c = strrchr(newpath, separator);
1309 strcpy(artist, ++c);
1310 // arbitrarily use the first '-'
1311 // to separate artist and title
1312 c = strchr(artist, '-');
1313 if(c != NULL) { // if trenner found, divide file name
1316 c = strrchr(title, '.');
1319 } else { // no trenner found, assume
1320 // no artist, only title
1321 strcpy(title, artist);
1324 // replace underscores by spaces
1325 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1327 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1333 /* guesstitle() end */
1335 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1339 if(duration != -1) {
1340 printf("#EXTINF:%d,", duration);
1341 if(strlen(artist) != 0)
1342 printf("%s - ", artist);
1343 printf("%s%s", title, eol);
1345 print_path(newpath);
1349 printf("File%d=", counter);
1350 print_path(newpath);
1351 printf("%sTitle%d=", eol, counter);
1352 if(strlen(artist) != 0)
1353 printf("%s - ", artist);
1354 printf("%s%s", title, eol);
1356 printf("Length%d=%d%s", counter, duration, eol);
1359 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1362 printf("?</td></tr>%s", eol);
1364 printf("%d:%s%d</td></tr>%s", duration / 60,
1365 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1368 if(duration != -1) {
1370 char timebuffer[256];
1372 if(stat(newpath, &infos) != 0) {
1373 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1377 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1378 printf("\t<item>%s", eol);
1379 printf("\t\t<author>");
1381 printf("</author>%s\t\t<title>", eol);
1383 printf("</title>%s", eol);
1385 if(referal == NULL) {
1386 noreferal(newpath, artist, title);
1389 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1391 print_webpath(newpath);
1392 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1393 (int)infos.st_size, magic[encoding], eol);
1394 print_pathtail(newpath);
1395 printf("</guid>%s", eol);
1398 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1399 duration / 3600, (duration / 60) % 60,
1400 duration % 60, eol);
1403 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1404 duration / 60, duration % 60, eol);
1405 if(strlen(artist) != 0) {
1406 printf("\t\t<itunes:author>");
1408 printf("</itunes:author>%s", eol);
1410 printf("\t</item>%s", eol);
1414 myplaputstr("HARP, ");
1415 myplaputstr(newpath);
1421 #ifdef HAVE_LIBURIPARSER
1423 printf("<track>\n");
1424 if (strlen(title) > 0) {
1425 char * escaped_title = xml_escape_malloc(title);
1426 if (escaped_title != NULL) {
1427 printf(" <title>%s</title>\n", escaped_title);
1428 free(escaped_title);
1431 if (strlen(artist) > 0) {
1432 char * escaped_artist = xml_escape_malloc(artist);
1433 if (escaped_artist != NULL) {
1434 printf(" <creator>%s</creator>\n", escaped_artist);
1435 free(escaped_artist);
1439 printf(" <duration>%d</duration>\n", duration);
1442 char * relative_location;
1443 char * escaped_location;
1444 relative_location = relative_uri_malloc(newpath, original_path);
1445 if (relative_location != NULL) {
1446 escaped_location = xml_escape_malloc(relative_location);
1447 if (escaped_location != NULL) {
1448 printf(" <location>%s</location>\n", escaped_location);
1449 free(escaped_location);
1451 free(relative_location);
1454 printf("</track>\n");
1461 void parse_directory(unsigned char *path, unsigned char * original_path)
1464 struct dirent **namelist;
1465 unsigned char newpath[PATH_MAX];
1469 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1470 if(stat(path, &infos) != 0) {
1471 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1474 /* check if it is a filename */
1475 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1476 parse_file(path, original_path);
1479 /* must be a directory - or something unusable like pipe, socket, etc */
1480 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1481 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1484 for(i = 0; i < n; i++) {
1485 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1487 if(stat(newpath, &infos) != 0) {
1488 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1491 if(recursive && S_ISDIR(infos.st_mode)
1492 && strcmp(namelist[i]->d_name, ".") != 0
1493 && strcmp(namelist[i]->d_name, "..") != 0)
1494 parse_directory(newpath, original_path);
1495 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1496 if(S_ISREG(infos.st_mode)
1497 && !(avoidhlinked && hlink_check(&infos))) {
1498 parse_file(newpath, original_path);
1505 int main(int argc, char **argv)
1507 winorunix = one2one;
1509 parse_options(argc, argv);
1511 if(optind == argc && !fromstdin)
1517 printf("#EXTM3U%s", eol);
1520 printf("[playlist]%s", eol);
1524 ("<!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 "
1526 "</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",
1527 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1528 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1529 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1530 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1531 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1537 char timebuffer[256];
1539 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1542 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1544 " -->%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 "
1546 "</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",
1547 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1548 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1549 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1550 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1551 eol, getenv("LANG"), eol, eol, eol);
1552 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1559 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1564 txxputheader(" iriver UMS PLA");
1567 #ifdef HAVE_LIBURIPARSER
1569 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1570 "<!-- generator=\"FAPG " VERSION " -->\n"
1571 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1577 /* iterate through files */
1579 const char * const pwd_source = getenv("PWD");
1580 const int pwdlen = strlen(pwd_source);
1581 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1582 sprintf(pwd, "%s/", pwd_source);
1585 unsigned char path[PATH_MAX];
1587 while(fgets(path, PATH_MAX, stdin)) {
1588 for(i = 0; i < PATH_MAX; i++)
1589 if(path[i] == '\r' || path[i] == '\n')
1595 /* strip trailing slash */
1596 if (path[i - 1] == '/') {
1600 parse_directory(path, pwd);
1603 for(; optind < argc; optind++) {
1604 /* strip trailing slash */
1605 char * dup = strdup(argv[optind]);
1606 const int len = strlen(dup);
1607 if ((len > 0) && (dup[len - 1] == '/')) {
1608 dup[len - 1] = '\0';
1611 parse_directory(dup, pwd);
1620 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1624 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1625 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1629 printf(" </channel>%s</rss>%s", eol, eol);
1632 txxputcounter(counter);
1634 #ifdef HAVE_LIBURIPARSER
1636 printf("</trackList>\n"