2 * FAPG means Fast Audio Playlist Generator.
3 * It is a tool to generate list of audio files (Wav, MP3, Ogg, etc)
4 * in various formats (M3U, PLS, HTML, etc).
5 * It is very usefull if you have a large amount of audio files
6 * and you want to quickly and frequently build a playlist.
8 * Copyright (C) 2003-2004 Antoine Jacquet <royale@zerezo.com>
9 * http://royale.zerezo.com/fapg/
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #include <sys/types.h>
40 #ifdef HAVE_LIBURIPARSER
41 # include <uriparser/Uri.h>
45 #define OGG_BASE 1024*10
46 #define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */
54 #ifdef HAVE_LIBURIPARSER
55 # define FORMAT_XSPF 6
59 int format = FORMAT_M3U;
60 char *genrelist = NULL;
61 unsigned char *prefix = "";
62 unsigned char *base = "";
63 unsigned char *dir = "";
64 unsigned char *hostname = "fritzserver.de";
65 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
66 unsigned char *referal = NULL;
72 unsigned char *eol = "\n";
73 unsigned char buffer[MAX];
77 unsigned char artist[1024];
78 unsigned char title[1024];
79 unsigned char genrebuf[1024];
80 unsigned char genre = 0;
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, "ARTIST=", 7) == 0) {
867 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
869 strncpy(artist, c + 7, size - 7);
870 artist[size - 7] = '\0';
873 if(strncasecmp(c, "GENRE=", 6) == 0) {
876 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
878 strncpy(genrebuf, c + 6, size - 6);
879 genrebuf[size - 6] = '\0';
881 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
882 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
886 if(i == ID3_NR_OF_V1_GENRES)
893 fseek(fic, -OGG_BASE, SEEK_END);
894 lus = fread(buffer, 1, OGG_BASE, fic);
895 c = buffer + lus - 1;
896 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
901 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
902 duration = samples / sample_rate;
908 void parse_mpc(unsigned char *file)
913 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
919 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
922 if((fic = fopen(file, "r")) == NULL) {
923 fprintf(stderr, "Warning >> can't open file : %s\n", file);
926 lus = fread(buffer, 1, 12, fic);
929 if (strncmp(buffer, "MP+", 3) != 0) {
930 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
936 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
944 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
946 duration = frame_count * 1152 / sample_rates[*c & 3];
948 /* try APETAGEX footer */
949 fseek(fic, -32, SEEK_END);
950 lus = fread(buffer, 1, 32, fic);
951 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
954 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
958 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
959 fseek(fic, -size, SEEK_END);
960 lus = fread(buffer, 1, size, fic);
961 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
965 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
968 if(strcasecmp(c, "TITLE") == 0) {
969 strncpy(title, c + 6, size);
972 if(strcasecmp(c, "ARTIST") == 0) {
973 strncpy(artist, c + 7, size);
976 if(strcasecmp(c, "GENRE") == 0) {
977 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
978 strncpy(genrebuf, c + 6, size);
979 genrebuf[size] = '\0';
981 (ID3_v1_genre_description[i], genrebuf) == 0) {
985 if(i == ID3_NR_OF_V1_GENRES)
989 c += strlen(c) + 1 + size;
998 #define MAXINO (1<<24)
999 #define INOTYP unsigned long
1000 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1001 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1003 int hlink_check(struct stat *info)
1006 * for speed this subroutine should only be called
1007 * - if the file has more than one hardlink
1008 * - if the file is a resolved softlink
1010 /* the persistent variables */
1011 static INOTYP *list[FSN];
1012 static dev_t name[FSN];
1013 /* some temporary variables */
1014 int fsn, is_registered = 0;
1016 /* assertions - in case parameters are lowered for less memory usage */
1018 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1020 /* search which internal registration number is used for this filesystem */
1021 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1023 /* if file system is not registered yet, do it and leave */
1024 if(name[fsn] == 0) {
1025 name[fsn] = (info->st_dev);
1026 /* provide space for the bitmap that maps the inodes of this file system */
1027 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1028 /* no comparison is needed in empty lists ... return */
1031 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1032 (int)name[fsn], (long)&list[fsn]);
1034 /* this looks more complicated than it really is */
1035 /* the idea is very simple:
1036 * provide a bitmap that maps all inodes of a file system
1037 * to mark all files that have already been visited.
1038 * If it is already visited, do not add it to the playlist
1041 * The difficulty is as follows:
1042 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1043 * would be byte-aligned and would allocate at least eight times the needed space.
1044 * Feel free to change the definitions that are involved here, if you know better.
1046 if(regbit_qry(list[fsn], (info->st_ino)))
1049 regbit_set(list[fsn], (info->st_ino));
1051 * the debug expression is more complicated then the working stuff
1054 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1055 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1056 (int)info->st_dev, (int)info->st_ino, fsn,
1057 (int)((info->st_ino) / sizeof(INOTYP)),
1058 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1059 1 << ((info->st_ino) % sizeof(INOTYP)),
1060 is_registered ? "Already" : "Not");
1062 return is_registered;
1065 #ifdef HAVE_LIBURIPARSER
1066 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1068 char * absSourceFile;
1069 size_t absSourceLen;
1070 char * sourceUriString;
1071 char * baseUriString;
1072 UriParserStateA state;
1075 UriUriA relativeUri;
1080 if ((unixFilename == NULL) || (baseDir == NULL)) {
1085 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1086 if (baseUriString == NULL) {
1089 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1090 free(baseUriString);
1093 state.uri = &baseUri;
1094 if (uriParseUriA(&state, baseUriString) != 0) {
1095 free(baseUriString);
1096 uriFreeUriMembersA(&baseUri);
1101 if (unixFilename[0] != '/') {
1102 const int baseDirLen = strlen(baseDir);
1103 const int sourceFileLen = strlen(unixFilename);
1104 absSourceLen = baseDirLen + sourceFileLen;
1105 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1106 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1108 absSourceLen = strlen(unixFilename);
1109 absSourceFile = (char *)unixFilename;
1111 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1112 if (sourceUriString == NULL) {
1113 free(baseUriString);
1114 if (unixFilename[0] != '/') {
1115 free(absSourceFile);
1117 uriFreeUriMembersA(&baseUri);
1120 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1121 free(baseUriString);
1122 free(sourceUriString);
1123 if (unixFilename[0] != '/') {
1124 free(absSourceFile);
1126 uriFreeUriMembersA(&baseUri);
1129 state.uri = &sourceUri;
1130 if (uriParseUriA(&state, sourceUriString) != 0) {
1131 free(baseUriString);
1132 free(sourceUriString);
1133 uriFreeUriMembersA(&baseUri);
1134 uriFreeUriMembersA(&sourceUri);
1137 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1138 free(baseUriString);
1139 free(sourceUriString);
1140 if (unixFilename[0] != '/') {
1141 free(absSourceFile);
1143 uriFreeUriMembersA(&baseUri);
1144 uriFreeUriMembersA(&sourceUri);
1148 /* make relative (or keep absolute if necessary) */
1149 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1150 free(baseUriString);
1151 free(sourceUriString);
1152 if (unixFilename[0] != '/') {
1153 free(absSourceFile);
1155 uriFreeUriMembersA(&baseUri);
1156 uriFreeUriMembersA(&sourceUri);
1157 uriFreeUriMembersA(&relativeUri);
1161 /* back to string */
1162 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1163 free(baseUriString);
1164 free(sourceUriString);
1165 if (unixFilename[0] != '/') {
1166 free(absSourceFile);
1168 uriFreeUriMembersA(&baseUri);
1169 uriFreeUriMembersA(&sourceUri);
1170 uriFreeUriMembersA(&relativeUri);
1173 output = malloc((charsRequired + 1) * sizeof(char));
1174 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1175 free(baseUriString);
1176 free(sourceUriString);
1177 if (unixFilename[0] != '/') {
1178 free(absSourceFile);
1181 uriFreeUriMembersA(&baseUri);
1182 uriFreeUriMembersA(&sourceUri);
1183 uriFreeUriMembersA(&relativeUri);
1187 free(baseUriString);
1188 free(sourceUriString);
1189 if (unixFilename[0] != '/') {
1190 free(absSourceFile);
1192 uriFreeUriMembersA(&baseUri);
1193 uriFreeUriMembersA(&sourceUri);
1194 uriFreeUriMembersA(&relativeUri);
1199 char * xml_escape_malloc(const char * input)
1201 const char * read = input;
1205 if (input == NULL) {
1209 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1210 if (output == NULL) {
1216 if (*read == '\0') {
1221 switch ((unsigned char)*read) {
1223 strcpy(write, "&");
1227 strcpy(write, "<");
1231 strcpy(write, ">");
1235 strcpy(write, "'");
1239 strcpy(write, """);
1250 void parse_file(unsigned char *newpath, unsigned char * original_path)
1252 unsigned char ext[5];
1253 int j, encoding = 0;
1255 for(j = 0; j < 5; j++)
1256 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1260 if(strcmp(".mp2", ext) == 0) {
1265 if(strcmp(".mp3", ext) == 0) {
1270 if(strcmp(".mpc", ext) == 0) {
1275 if(strcmp(".mp+", ext) == 0) {
1280 if(strcmp(".ogg", ext) == 0) {
1285 if(strcmp(".wav", ext) == 0) {
1287 /* parse_wav(newpath); */
1290 if(strcmp(".wma", ext) == 0) {
1292 /* parse_wma(newpath); */
1296 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1297 // there are no tag infos read
1298 // use file name to state substitute it
1299 char *c = strrchr(newpath, separator);
1302 strcpy(artist, ++c);
1303 // arbitrarily use the first '-'
1304 // to separate artist and title
1305 c = strchr(artist, '-');
1306 if(c != NULL) { // if trenner found, divide file name
1309 c = strrchr(title, '.');
1312 } else { // no trenner found, assume
1313 // no artist, only title
1314 strcpy(title, artist);
1317 // replace underscores by spaces
1318 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1320 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1326 /* guesstitle() end */
1328 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1332 if(duration != -1) {
1333 printf("#EXTINF:%d,", duration);
1334 if(strlen(artist) != 0)
1335 printf("%s - ", artist);
1336 printf("%s%s", title, eol);
1338 print_path(newpath);
1342 printf("File%d=", counter);
1343 print_path(newpath);
1344 printf("%sTitle%d=", eol, counter);
1345 if(strlen(artist) != 0)
1346 printf("%s - ", artist);
1347 printf("%s%s", title, eol);
1349 printf("Length%d=%d%s", counter, duration, eol);
1352 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1355 printf("?</td></tr>%s", eol);
1357 printf("%d:%s%d</td></tr>%s", duration / 60,
1358 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1361 if(duration != -1) {
1363 char timebuffer[256];
1365 if(stat(newpath, &infos) != 0) {
1366 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1370 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1371 printf("\t<item>%s", eol);
1372 printf("\t\t<author>");
1374 printf("</author>%s\t\t<title>", eol);
1376 printf("</title>%s", eol);
1378 if(referal == NULL) {
1379 noreferal(newpath, artist, title);
1382 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1384 print_webpath(newpath);
1385 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1386 (int)infos.st_size, magic[encoding], eol);
1387 print_pathtail(newpath);
1388 printf("</guid>%s", eol);
1391 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1392 duration / 3600, (duration / 60) % 60,
1393 duration % 60, eol);
1396 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1397 duration / 60, duration % 60, eol);
1398 if(strlen(artist) != 0) {
1399 printf("\t\t<itunes:author>");
1401 printf("</itunes:author>%s", eol);
1403 printf("\t</item>%s", eol);
1407 myplaputstr("HARP, ");
1408 myplaputstr(newpath);
1414 #ifdef HAVE_LIBURIPARSER
1416 printf("<track>\n");
1417 if (strlen(title) > 0) {
1418 char * escaped_title = xml_escape_malloc(title);
1419 if (escaped_title != NULL) {
1420 printf(" <title>%s</title>\n", escaped_title);
1421 free(escaped_title);
1424 if (strlen(artist) > 0) {
1425 char * escaped_artist = xml_escape_malloc(artist);
1426 if (escaped_artist != NULL) {
1427 printf(" <creator>%s</creator>\n", escaped_artist);
1428 free(escaped_artist);
1432 printf(" <duration>%d</duration>\n", duration);
1435 char * relative_location;
1436 char * escaped_location;
1437 relative_location = relative_uri_malloc(newpath, original_path);
1438 if (relative_location != NULL) {
1439 escaped_location = xml_escape_malloc(relative_location);
1440 if (escaped_location != NULL) {
1441 printf(" <location>%s</location>\n", escaped_location);
1442 free(escaped_location);
1444 free(relative_location);
1447 printf("</track>\n");
1454 void parse_directory(unsigned char *path, unsigned char * original_path)
1457 struct dirent **namelist;
1458 unsigned char newpath[PATH_MAX];
1462 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1463 if(stat(path, &infos) != 0) {
1464 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1467 /* check if it is a filename */
1468 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1469 parse_file(path, original_path);
1472 /* must be a directory - or something unusable like pipe, socket, etc */
1473 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1474 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1477 for(i = 0; i < n; i++) {
1478 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1480 if(stat(newpath, &infos) != 0) {
1481 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1484 if(recursive && S_ISDIR(infos.st_mode)
1485 && strcmp(namelist[i]->d_name, ".") != 0
1486 && strcmp(namelist[i]->d_name, "..") != 0)
1487 parse_directory(newpath, original_path);
1488 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1489 if(S_ISREG(infos.st_mode)
1490 && !(avoidhlinked && hlink_check(&infos))) {
1491 parse_file(newpath, original_path);
1498 int main(int argc, char **argv)
1500 winorunix = one2one;
1502 parse_options(argc, argv);
1504 if(optind == argc && !fromstdin)
1510 printf("#EXTM3U%s", eol);
1513 printf("[playlist]%s", eol);
1517 ("<!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 "
1519 "</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",
1520 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1521 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1522 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1523 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1524 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1530 char timebuffer[256];
1532 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1535 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1537 " -->%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 "
1539 "</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",
1540 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1541 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1542 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1543 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1544 eol, getenv("LANG"), eol, eol, eol);
1545 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1552 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1557 txxputheader(" iriver UMS PLA");
1560 #ifdef HAVE_LIBURIPARSER
1562 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1563 "<!-- generator=\"FAPG " VERSION " -->\n"
1564 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1570 /* iterate through files */
1572 const char * const pwd_source = getenv("PWD");
1573 const int pwdlen = strlen(pwd_source);
1574 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1575 sprintf(pwd, "%s/", pwd_source);
1578 unsigned char path[PATH_MAX];
1580 while(fgets(path, PATH_MAX, stdin)) {
1581 for(i = 0; i < PATH_MAX; i++)
1582 if(path[i] == '\r' || path[i] == '\n')
1588 /* strip trailing slash */
1589 if (path[i - 1] == '/') {
1593 parse_directory(path, pwd);
1596 for(; optind < argc; optind++) {
1597 /* strip trailing slash */
1598 char * dup = strdup(argv[optind]);
1599 const int len = strlen(dup);
1600 if ((len > 0) && (dup[len - 1] == '/')) {
1601 dup[len - 1] = '\0';
1604 parse_directory(dup, pwd);
1613 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1617 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1618 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1622 printf(" </channel>%s</rss>%s", eol, eol);
1625 txxputcounter(counter);
1627 #ifdef HAVE_LIBURIPARSER
1629 printf("</trackList>\n"