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("%s", 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;
446 printf("%s", prefix);
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);
505 fgets(buffer, 1020, pipe);
507 fputs(buffer, stdout);
508 fgets(buffer, 1020, pipe);
514 void parse_options(int argc, char **argv)
516 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
517 static struct option long_options[] = {
518 {"backslash", no_argument, NULL, 'b'},
519 {"command", required_argument, NULL, 'c'},
520 {"debug", no_argument, NULL, 'd'},
521 {"format", required_argument, NULL, 'f'},
522 {"genre", required_argument, NULL, 'g'},
523 {"nohardlink", no_argument, NULL, 'n'},
524 {"output", required_argument, NULL, 'o'},
525 {"prefix", required_argument, NULL, 'p'},
526 {"recursive", no_argument, NULL, 'r'},
527 {"stdin", no_argument, NULL, 's'},
528 {"windows", no_argument, NULL, 'w'},
529 {"exclude", required_argument, NULL, 'x'},
533 int option_index = 0;
535 getopt_long(argc, argv, short_options, long_options,
536 &option_index)) != -1) {
543 if(strncmp(optarg, "intern", 6) == 0)
546 referal = strdup(optarg);
552 if(strcmp(optarg, "m3u") == 0)
554 else if(strcmp(optarg, "pls") == 0)
556 else if(strcmp(optarg, "html") == 0)
557 format = FORMAT_HTML;
558 else if(strcmp(optarg, "rss") == 0)
560 else if(strcmp(optarg, "pla") == 0)
562 else if(strcmp(optarg, "txx") == 0)
564 #ifdef HAVE_LIBURIPARSER
565 else if(strcmp(optarg, "xspf") == 0)
566 format = FORMAT_XSPF;
572 if(genrelist == NULL)
573 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
574 if(genrelist == NULL) {
576 "Error >> unable to allocate cleared memory\n");
580 while(n < strlen(optarg)) {
583 "Debug >> genrelist entry activting : %d\n",
585 genrelist[atoi(&optarg[n])] = 1;
586 while(isdigit(optarg[n++]));
595 if(fopen(optarg, "w") == NULL) {
597 "Error >> unable to open output file : %s\n",
603 prefix = malloc(strlen(optarg) + 1);
604 strcpy(prefix, optarg);
605 base = malloc(strlen(prefix) + 1);
606 strcpy(base, prefix);
607 dir = strchr(base, '/');
608 if((dir != NULL) && (dir[1] == '/'))
609 dir = strchr(dir + 2, '/');
614 /* if prefix is a weblink, base is the baselink, dir is the path */
624 winorunix = unix2dos;
628 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
630 genrelist = calloc(257, sizeof(char));
634 if(genrelist == NULL) {
636 "Error >> unable to allocate cleared memory\n");
640 while(n < strlen(optarg)) {
643 "Debug >> genrelist entry activting : %d\n",
645 genrelist[atoi(&optarg[n])] = 0;
646 while(isdigit(optarg[n++]));
657 /* hostname = getenv("HOSTNAME"); */
658 if(genrelist == NULL) {
659 genrelist = calloc(257, sizeof(char));
660 if(genrelist == NULL) {
662 "Error >> unable to allocate cleared memory\n");
672 void parse_mp3(unsigned char *file)
674 int bitrates[2][3][15] =
675 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
677 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
679 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
681 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
682 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
683 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
692 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
695 if((fic = fopen(file, "r")) == NULL) {
696 fprintf(stderr, "Warning >> can't open file : %s\n", file);
699 lus = fread(buffer, 1, MP3_BASE, fic);
703 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
706 version = *(buffer + 3);
707 if(version < 2 || version > 4)
709 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
711 if(*(buffer + 5) != 0)
713 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
714 *(buffer + 5), file);
717 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
718 /* read more header */
719 if(size + lus > MAX) {
720 lus += fread(buffer + lus, 1, MAX - lus, fic);
722 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
725 lus += fread(buffer + lus, 1, size, fic);
730 while(c < buffer + size) {
731 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
734 if(strncmp(c, "TT2", 3) == 0) {
735 strncpy(title, c + 7, size - 1);
736 title[size - 1] = '\0';
738 if(strncmp(c, "TP1", 3) == 0) {
739 strncpy(artist, c + 7, size - 1);
740 artist[size - 1] = '\0';
742 if(strncmp(c, "TCO", 3) == 0) {
743 /* strncpy(genrebuf,c+7,size-1); */
744 /* genrebuf[size-1]='\0'; */
745 /* genre=atoi(&genrebuf[1]); */
750 if(version == 3 || version == 4)
751 while(c < buffer + size) {
753 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
757 if(strncmp(c, "TIT2", 4) == 0) {
758 strncpy(title, c + 11, size - 1);
759 title[size - 1] = '\0';
761 if(strncmp(c, "TPE1", 4) == 0) {
762 strncpy(artist, c + 11, size - 1);
763 artist[size - 1] = '\0';
765 if(strncmp(c, "TCON", 4) == 0) {
766 /* strncpy(genrebuf,c+11,size-1); */
767 /* genrebuf[size-1]='\0'; */
768 /* genre=atoi(&genrebuf[1]); */
769 genre = atoi(c + 12);
775 while(c < buffer + lus - 10) {
776 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
781 version = 2 - (*(c + 1) >> 3 & 1);
782 lay = 4 - (*(c + 1) >> 1 & 3);
783 bitrate_index = *(c + 2) >> 4 & 0xF;
784 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
785 && bitrate_index >= 0 && bitrate_index <= 14)
786 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
790 fseek(fic, 0, SEEK_END);
791 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
800 if(strlen(artist) == 0 && strlen(title) == 0) {
801 fseek(fic, -128, SEEK_END);
802 lus = fread(buffer, 1, 128, fic);
803 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
804 && buffer[2] == 'G') {
805 strncpy(title, buffer + 3, 30);
808 while(c > title && *c == ' ')
810 strncpy(artist, buffer + 33, 30);
813 while(c > artist && *c == ' ')
815 /* strncpy(album,buffer+65,30); */
816 /* strncpy(year,buffer+97,4); */
817 /* strncpy(comment,buffer+101,30); */
818 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
826 void parse_ogg(unsigned char *file)
835 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
838 if((fic = fopen(file, "r")) == NULL) {
839 fprintf(stderr, "Warning >> can't open file : %s\n", file);
842 lus = fread(buffer, 1, OGG_BASE, fic);
845 if(strncmp(buffer, "Ogg", 3) != 0) {
846 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
852 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
854 while(c < buffer + lus - 10) {
856 if(strncasecmp(c, "TITLE=", 6) == 0) {
858 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
860 strncpy(title, c + 6, size - 6);
861 title[size - 6] = '\0';
864 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
867 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
871 if(strncasecmp(c, "ARTIST=", 7) == 0) {
873 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
875 strncpy(artist, c + 7, size - 7);
876 artist[size - 7] = '\0';
879 if(strncasecmp(c, "GENRE=", 6) == 0) {
882 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
884 strncpy(genrebuf, c + 6, size - 6);
885 genrebuf[size - 6] = '\0';
887 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
888 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
892 if(i == ID3_NR_OF_V1_GENRES)
899 fseek(fic, -OGG_BASE, SEEK_END);
900 lus = fread(buffer, 1, OGG_BASE, fic);
901 c = buffer + lus - 1;
902 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
907 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
908 duration = samples / sample_rate;
914 void parse_mpc(unsigned char *file)
919 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
925 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
928 if((fic = fopen(file, "r")) == NULL) {
929 fprintf(stderr, "Warning >> can't open file : %s\n", file);
932 lus = fread(buffer, 1, 12, fic);
935 if (strncmp(buffer, "MP+", 3) != 0) {
936 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
942 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
950 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
952 duration = frame_count * 1152 / sample_rates[*c & 3];
954 /* try APETAGEX footer */
955 fseek(fic, -32, SEEK_END);
956 lus = fread(buffer, 1, 32, fic);
957 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
960 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
964 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
965 fseek(fic, -size, SEEK_END);
966 lus = fread(buffer, 1, size, fic);
967 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
971 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
974 if(strcasecmp(c, "TITLE") == 0) {
975 strncpy(title, c + 6, size);
978 if(strcasecmp(c, "ARTIST") == 0) {
979 strncpy(artist, c + 7, size);
982 if(strcasecmp(c, "GENRE") == 0) {
983 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
984 strncpy(genrebuf, c + 6, size);
985 genrebuf[size] = '\0';
987 (ID3_v1_genre_description[i], genrebuf) == 0) {
991 if(i == ID3_NR_OF_V1_GENRES)
995 c += strlen(c) + 1 + size;
1004 #define MAXINO (1<<24)
1005 #define INOTYP unsigned long
1006 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1007 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1009 int hlink_check(struct stat *info)
1012 * for speed this subroutine should only be called
1013 * - if the file has more than one hardlink
1014 * - if the file is a resolved softlink
1016 /* the persistent variables */
1017 static INOTYP *list[FSN];
1018 static dev_t name[FSN];
1019 /* some temporary variables */
1020 int fsn, is_registered = 0;
1022 /* assertions - in case parameters are lowered for less memory usage */
1024 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1026 /* search which internal registration number is used for this filesystem */
1027 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1029 /* if file system is not registered yet, do it and leave */
1030 if(name[fsn] == 0) {
1031 name[fsn] = (info->st_dev);
1032 /* provide space for the bitmap that maps the inodes of this file system */
1033 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1034 /* no comparison is needed in empty lists ... return */
1037 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1038 (int)name[fsn], (long)&list[fsn]);
1040 /* this looks more complicated than it really is */
1041 /* the idea is very simple:
1042 * provide a bitmap that maps all inodes of a file system
1043 * to mark all files that have already been visited.
1044 * If it is already visited, do not add it to the playlist
1047 * The difficulty is as follows:
1048 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1049 * would be byte-aligned and would allocate at least eight times the needed space.
1050 * Feel free to change the definitions that are involved here, if you know better.
1052 if(regbit_qry(list[fsn], (info->st_ino)))
1055 regbit_set(list[fsn], (info->st_ino));
1057 * the debug expression is more complicated then the working stuff
1060 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1061 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1062 (int)info->st_dev, (int)info->st_ino, fsn,
1063 (int)((info->st_ino) / sizeof(INOTYP)),
1064 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1065 1 << ((info->st_ino) % sizeof(INOTYP)),
1066 is_registered ? "Already" : "Not");
1068 return is_registered;
1071 #ifdef HAVE_LIBURIPARSER
1072 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1074 char * absSourceFile;
1075 size_t absSourceLen;
1076 char * sourceUriString;
1077 char * baseUriString;
1078 UriParserStateA state;
1081 UriUriA relativeUri;
1086 if ((unixFilename == NULL) || (baseDir == NULL)) {
1091 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1092 if (baseUriString == NULL) {
1095 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1096 free(baseUriString);
1099 state.uri = &baseUri;
1100 if (uriParseUriA(&state, baseUriString) != 0) {
1101 free(baseUriString);
1102 uriFreeUriMembersA(&baseUri);
1107 if (unixFilename[0] != '/') {
1108 const int baseDirLen = strlen(baseDir);
1109 const int sourceFileLen = strlen(unixFilename);
1110 absSourceLen = baseDirLen + sourceFileLen;
1111 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1112 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1114 absSourceLen = strlen(unixFilename);
1115 absSourceFile = (char *)unixFilename;
1117 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1118 if (sourceUriString == NULL) {
1119 free(baseUriString);
1120 if (unixFilename[0] != '/') {
1121 free(absSourceFile);
1123 uriFreeUriMembersA(&baseUri);
1126 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1127 free(baseUriString);
1128 free(sourceUriString);
1129 if (unixFilename[0] != '/') {
1130 free(absSourceFile);
1132 uriFreeUriMembersA(&baseUri);
1135 state.uri = &sourceUri;
1136 if (uriParseUriA(&state, sourceUriString) != 0) {
1137 free(baseUriString);
1138 free(sourceUriString);
1139 uriFreeUriMembersA(&baseUri);
1140 uriFreeUriMembersA(&sourceUri);
1143 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1144 free(baseUriString);
1145 free(sourceUriString);
1146 if (unixFilename[0] != '/') {
1147 free(absSourceFile);
1149 uriFreeUriMembersA(&baseUri);
1150 uriFreeUriMembersA(&sourceUri);
1154 /* make relative (or keep absolute if necessary) */
1155 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1156 free(baseUriString);
1157 free(sourceUriString);
1158 if (unixFilename[0] != '/') {
1159 free(absSourceFile);
1161 uriFreeUriMembersA(&baseUri);
1162 uriFreeUriMembersA(&sourceUri);
1163 uriFreeUriMembersA(&relativeUri);
1167 /* back to string */
1168 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1169 free(baseUriString);
1170 free(sourceUriString);
1171 if (unixFilename[0] != '/') {
1172 free(absSourceFile);
1174 uriFreeUriMembersA(&baseUri);
1175 uriFreeUriMembersA(&sourceUri);
1176 uriFreeUriMembersA(&relativeUri);
1179 output = malloc((charsRequired + 1) * sizeof(char));
1180 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1181 free(baseUriString);
1182 free(sourceUriString);
1183 if (unixFilename[0] != '/') {
1184 free(absSourceFile);
1187 uriFreeUriMembersA(&baseUri);
1188 uriFreeUriMembersA(&sourceUri);
1189 uriFreeUriMembersA(&relativeUri);
1193 free(baseUriString);
1194 free(sourceUriString);
1195 if (unixFilename[0] != '/') {
1196 free(absSourceFile);
1198 uriFreeUriMembersA(&baseUri);
1199 uriFreeUriMembersA(&sourceUri);
1200 uriFreeUriMembersA(&relativeUri);
1205 char * xml_escape_malloc(const char * input)
1207 const char * read = input;
1211 if (input == NULL) {
1215 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1216 if (output == NULL) {
1222 if (*read == '\0') {
1227 switch ((unsigned char)*read) {
1229 strcpy(write, "&");
1233 strcpy(write, "<");
1237 strcpy(write, ">");
1241 strcpy(write, "'");
1245 strcpy(write, """);
1256 void parse_file(unsigned char *newpath, unsigned char * original_path)
1258 unsigned char ext[5];
1259 int j, encoding = 0;
1261 for(j = 0; j < 5; j++)
1262 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1266 if(strcmp(".mp2", ext) == 0) {
1271 if(strcmp(".mp3", ext) == 0) {
1276 if(strcmp(".mpc", ext) == 0) {
1281 if(strcmp(".mp+", ext) == 0) {
1286 if(strcmp(".ogg", ext) == 0) {
1291 if(strcmp(".wav", ext) == 0) {
1293 /* parse_wav(newpath); */
1296 if(strcmp(".wma", ext) == 0) {
1298 /* parse_wma(newpath); */
1302 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1303 // there are no tag infos read
1304 // use file name to state substitute it
1305 char *c = strrchr(newpath, separator);
1308 strcpy(artist, ++c);
1309 // arbitrarily use the first '-'
1310 // to separate artist and title
1311 c = strchr(artist, '-');
1312 if(c != NULL) { // if trenner found, divide file name
1315 c = strrchr(title, '.');
1318 } else { // no trenner found, assume
1319 // no artist, only title
1320 strcpy(title, artist);
1323 // replace underscores by spaces
1324 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1326 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1332 /* guesstitle() end */
1334 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1338 if(duration != -1) {
1339 printf("#EXTINF:%d,", duration);
1340 if(strlen(artist) != 0)
1341 printf("%s - ", artist);
1342 printf("%s%s", title, eol);
1344 print_path(newpath);
1348 printf("File%d=", counter);
1349 print_path(newpath);
1350 printf("%sTitle%d=", eol, counter);
1351 if(strlen(artist) != 0)
1352 printf("%s - ", artist);
1353 printf("%s%s", title, eol);
1355 printf("Length%d=%d%s", counter, duration, eol);
1358 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1361 printf("?</td></tr>%s", eol);
1363 printf("%d:%s%d</td></tr>%s", duration / 60,
1364 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1367 if(duration != -1) {
1369 char timebuffer[256];
1371 if(stat(newpath, &infos) != 0) {
1372 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1376 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1377 printf("\t<item>%s", eol);
1378 printf("\t\t<author>");
1380 printf("</author>%s\t\t<title>", eol);
1382 printf("</title>%s", eol);
1384 if(referal == NULL) {
1385 noreferal(newpath, artist, title);
1388 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1390 print_webpath(newpath);
1391 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1392 (int)infos.st_size, magic[encoding], eol);
1393 print_pathtail(newpath);
1394 printf("</guid>%s", eol);
1397 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1398 duration / 3600, (duration / 60) % 60,
1399 duration % 60, eol);
1402 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1403 duration / 60, duration % 60, eol);
1404 if(strlen(artist) != 0) {
1405 printf("\t\t<itunes:author>");
1407 printf("</itunes:author>%s", eol);
1409 printf("\t</item>%s", eol);
1413 myplaputstr("HARP, ");
1414 myplaputstr(newpath);
1420 #ifdef HAVE_LIBURIPARSER
1422 printf("<track>\n");
1423 if (strlen(title) > 0) {
1424 char * escaped_title = xml_escape_malloc(title);
1425 if (escaped_title != NULL) {
1426 printf(" <title>%s</title>\n", escaped_title);
1427 free(escaped_title);
1430 if (strlen(artist) > 0) {
1431 char * escaped_artist = xml_escape_malloc(artist);
1432 if (escaped_artist != NULL) {
1433 printf(" <creator>%s</creator>\n", escaped_artist);
1434 free(escaped_artist);
1438 printf(" <duration>%d</duration>\n", duration);
1441 char * relative_location;
1442 char * escaped_location;
1443 relative_location = relative_uri_malloc(newpath, original_path);
1444 if (relative_location != NULL) {
1445 escaped_location = xml_escape_malloc(relative_location);
1446 if (escaped_location != NULL) {
1447 printf(" <location>%s</location>\n", escaped_location);
1448 free(escaped_location);
1450 free(relative_location);
1453 printf("</track>\n");
1460 void parse_directory(unsigned char *path, unsigned char * original_path)
1463 struct dirent **namelist;
1464 unsigned char newpath[PATH_MAX];
1468 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1469 if(stat(path, &infos) != 0) {
1470 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1473 /* check if it is a filename */
1474 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1475 parse_file(path, original_path);
1478 /* must be a directory - or something unusable like pipe, socket, etc */
1479 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1480 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1483 for(i = 0; i < n; i++) {
1484 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1486 if(stat(newpath, &infos) != 0) {
1487 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1490 if(recursive && S_ISDIR(infos.st_mode)
1491 && strcmp(namelist[i]->d_name, ".") != 0
1492 && strcmp(namelist[i]->d_name, "..") != 0)
1493 parse_directory(newpath, original_path);
1494 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1495 if(S_ISREG(infos.st_mode)
1496 && !(avoidhlinked && hlink_check(&infos))) {
1497 parse_file(newpath, original_path);
1504 int main(int argc, char **argv)
1506 winorunix = one2one;
1508 parse_options(argc, argv);
1510 if(optind == argc && !fromstdin)
1516 printf("#EXTM3U%s", eol);
1519 printf("[playlist]%s", eol);
1523 ("<!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 "
1525 "</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",
1526 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
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,
1536 char timebuffer[256];
1538 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1541 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1543 " -->%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 "
1545 "</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",
1546 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1547 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1548 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1549 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1550 eol, getenv("LANG"), eol, eol, eol);
1551 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1558 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1563 txxputheader(" iriver UMS PLA");
1566 #ifdef HAVE_LIBURIPARSER
1568 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1569 "<!-- generator=\"FAPG " VERSION " -->\n"
1570 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1576 /* iterate through files */
1578 const char * const pwd_source = getenv("PWD");
1579 const int pwdlen = strlen(pwd_source);
1580 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1581 sprintf(pwd, "%s/", pwd_source);
1584 unsigned char path[PATH_MAX];
1586 while(fgets(path, PATH_MAX, stdin)) {
1587 for(i = 0; i < PATH_MAX; i++)
1588 if(path[i] == '\r' || path[i] == '\n')
1594 /* strip trailing slash */
1595 if (path[i - 1] == '/') {
1599 parse_directory(path, pwd);
1602 for(; optind < argc; optind++) {
1603 /* strip trailing slash */
1604 char * dup = strdup(argv[optind]);
1605 const int len = strlen(dup);
1606 if ((len > 0) && (dup[len - 1] == '/')) {
1607 dup[len - 1] = '\0';
1610 parse_directory(dup, pwd);
1619 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1623 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1624 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1628 printf(" </channel>%s</rss>%s", eol, eol);
1631 txxputcounter(counter);
1633 #ifdef HAVE_LIBURIPARSER
1635 printf("</trackList>\n"