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 printf("#EXTINF:%d,", duration);
1339 if(strlen(artist) != 0)
1340 printf("%s - ", artist);
1341 printf("%s%s", title, eol);
1342 print_path(newpath);
1346 printf("File%d=", counter);
1347 print_path(newpath);
1348 printf("%sTitle%d=", eol, counter);
1349 if(strlen(artist) != 0)
1350 printf("%s - ", artist);
1351 printf("%s%s", title, eol);
1353 printf("Length%d=%d%s", counter, duration, eol);
1356 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1359 printf("?</td></tr>%s", eol);
1361 printf("%d:%s%d</td></tr>%s", duration / 60,
1362 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1365 if(duration != -1) {
1367 char timebuffer[256];
1369 if(stat(newpath, &infos) != 0) {
1370 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1374 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1375 printf("\t<item>%s", eol);
1376 printf("\t\t<author>");
1378 printf("</author>%s\t\t<title>", eol);
1380 printf("</title>%s", eol);
1382 if(referal == NULL) {
1383 noreferal(newpath, artist, title);
1386 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1388 print_webpath(newpath);
1389 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1390 (int)infos.st_size, magic[encoding], eol);
1391 print_pathtail(newpath);
1392 printf("</guid>%s", eol);
1395 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1396 duration / 3600, (duration / 60) % 60,
1397 duration % 60, eol);
1400 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1401 duration / 60, duration % 60, eol);
1402 if(strlen(artist) != 0) {
1403 printf("\t\t<itunes:author>");
1405 printf("</itunes:author>%s", eol);
1407 printf("\t</item>%s", eol);
1411 myplaputstr("HARP, ");
1412 myplaputstr(newpath);
1418 #ifdef HAVE_LIBURIPARSER
1420 printf("<track>\n");
1421 if (strlen(title) > 0) {
1422 char * escaped_title = xml_escape_malloc(title);
1423 if (escaped_title != NULL) {
1424 printf(" <title>%s</title>\n", escaped_title);
1425 free(escaped_title);
1428 if (strlen(artist) > 0) {
1429 char * escaped_artist = xml_escape_malloc(artist);
1430 if (escaped_artist != NULL) {
1431 printf(" <creator>%s</creator>\n", escaped_artist);
1432 free(escaped_artist);
1436 printf(" <duration>%d</duration>\n", duration);
1439 char * relative_location;
1440 char * escaped_location;
1441 relative_location = relative_uri_malloc(newpath, original_path);
1442 if (relative_location != NULL) {
1443 escaped_location = xml_escape_malloc(relative_location);
1444 if (escaped_location != NULL) {
1445 printf(" <location>%s</location>\n", escaped_location);
1446 free(escaped_location);
1448 free(relative_location);
1451 printf("</track>\n");
1458 void parse_directory(unsigned char *path, unsigned char * original_path)
1461 struct dirent **namelist;
1462 unsigned char newpath[PATH_MAX];
1466 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1467 if(stat(path, &infos) != 0) {
1468 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1471 /* check if it is a filename */
1472 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1473 parse_file(path, original_path);
1476 /* must be a directory - or something unusable like pipe, socket, etc */
1477 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1478 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1481 for(i = 0; i < n; i++) {
1482 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1484 if(stat(newpath, &infos) != 0) {
1485 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1488 if(recursive && S_ISDIR(infos.st_mode)
1489 && strcmp(namelist[i]->d_name, ".") != 0
1490 && strcmp(namelist[i]->d_name, "..") != 0)
1491 parse_directory(newpath, original_path);
1492 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1493 if(S_ISREG(infos.st_mode)
1494 && !(avoidhlinked && hlink_check(&infos))) {
1495 parse_file(newpath, original_path);
1502 int main(int argc, char **argv)
1504 winorunix = one2one;
1506 parse_options(argc, argv);
1508 if(optind == argc && !fromstdin)
1514 printf("#EXTM3U%s", eol);
1517 printf("[playlist]%s", eol);
1521 ("<!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 "
1523 "</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",
1524 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1525 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
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,
1534 char timebuffer[256];
1536 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1539 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1541 " -->%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 "
1543 "</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",
1544 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1545 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1546 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1547 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1548 eol, getenv("LANG"), eol, eol, eol);
1549 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1556 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1561 txxputheader(" iriver UMS PLA");
1564 #ifdef HAVE_LIBURIPARSER
1566 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1567 "<!-- generator=\"FAPG " VERSION " -->\n"
1568 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1574 /* iterate through files */
1576 const char * const pwd_source = getenv("PWD");
1577 const int pwdlen = strlen(pwd_source);
1578 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1579 sprintf(pwd, "%s/", pwd_source);
1582 unsigned char path[PATH_MAX];
1584 while(fgets(path, PATH_MAX, stdin)) {
1585 for(i = 0; i < PATH_MAX; i++)
1586 if(path[i] == '\r' || path[i] == '\n')
1592 /* strip trailing slash */
1593 if (path[i - 1] == '/') {
1597 parse_directory(path, pwd);
1600 for(; optind < argc; optind++) {
1601 /* strip trailing slash */
1602 char * dup = strdup(argv[optind]);
1603 const int len = strlen(dup);
1604 if ((len > 0) && (dup[len - 1] == '/')) {
1605 dup[len - 1] = '\0';
1608 parse_directory(dup, pwd);
1617 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1621 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1622 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1626 printf(" </channel>%s</rss>%s", eol, eol);
1629 txxputcounter(counter);
1631 #ifdef HAVE_LIBURIPARSER
1633 printf("</trackList>\n"