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);
771 if(strncmp(c, "TLEN", 4) == 0) {
772 duration = atoi(c + 11) / 1000;
778 while(c < buffer + lus - 10) {
779 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
784 version = 2 - (*(c + 1) >> 3 & 1);
785 lay = 4 - (*(c + 1) >> 1 & 3);
786 bitrate_index = *(c + 2) >> 4 & 0xF;
787 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
788 && bitrate_index >= 0 && bitrate_index <= 14)
789 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
793 fseek(fic, 0, SEEK_END);
794 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
803 if(strlen(artist) == 0 && strlen(title) == 0) {
804 fseek(fic, -128, SEEK_END);
805 lus = fread(buffer, 1, 128, fic);
806 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
807 && buffer[2] == 'G') {
808 strncpy(title, buffer + 3, 30);
811 while(c > title && *c == ' ')
813 strncpy(artist, buffer + 33, 30);
816 while(c > artist && *c == ' ')
818 /* strncpy(album,buffer+65,30); */
819 /* strncpy(year,buffer+97,4); */
820 /* strncpy(comment,buffer+101,30); */
821 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
829 void parse_ogg(unsigned char *file)
838 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
841 if((fic = fopen(file, "r")) == NULL) {
842 fprintf(stderr, "Warning >> can't open file : %s\n", file);
845 lus = fread(buffer, 1, OGG_BASE, fic);
848 if(strncmp(buffer, "Ogg", 3) != 0) {
849 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
855 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
857 while(c < buffer + lus - 10) {
859 if(strncasecmp(c, "TITLE=", 6) == 0) {
861 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
863 strncpy(title, c + 6, size - 6);
864 title[size - 6] = '\0';
867 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
870 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
874 if(strncasecmp(c, "ARTIST=", 7) == 0) {
876 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
878 strncpy(artist, c + 7, size - 7);
879 artist[size - 7] = '\0';
882 if(strncasecmp(c, "GENRE=", 6) == 0) {
885 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
887 strncpy(genrebuf, c + 6, size - 6);
888 genrebuf[size - 6] = '\0';
890 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
891 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
895 if(i == ID3_NR_OF_V1_GENRES)
902 fseek(fic, -OGG_BASE, SEEK_END);
903 lus = fread(buffer, 1, OGG_BASE, fic);
904 c = buffer + lus - 1;
905 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
910 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
911 duration = samples / sample_rate;
917 void parse_mpc(unsigned char *file)
922 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
928 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
931 if((fic = fopen(file, "r")) == NULL) {
932 fprintf(stderr, "Warning >> can't open file : %s\n", file);
935 lus = fread(buffer, 1, 12, fic);
938 if (strncmp(buffer, "MP+", 3) != 0) {
939 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
945 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
953 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
955 duration = frame_count * 1152 / sample_rates[*c & 3];
957 /* try APETAGEX footer */
958 fseek(fic, -32, SEEK_END);
959 lus = fread(buffer, 1, 32, fic);
960 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
963 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
967 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
968 fseek(fic, -size, SEEK_END);
969 lus = fread(buffer, 1, size, fic);
970 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
974 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
977 if(strcasecmp(c, "TITLE") == 0) {
978 strncpy(title, c + 6, size);
981 if(strcasecmp(c, "ARTIST") == 0) {
982 strncpy(artist, c + 7, size);
985 if(strcasecmp(c, "GENRE") == 0) {
986 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
987 strncpy(genrebuf, c + 6, size);
988 genrebuf[size] = '\0';
990 (ID3_v1_genre_description[i], genrebuf) == 0) {
994 if(i == ID3_NR_OF_V1_GENRES)
998 c += strlen(c) + 1 + size;
1007 #define MAXINO (1<<24)
1008 #define INOTYP unsigned long
1009 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1010 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1012 int hlink_check(struct stat *info)
1015 * for speed this subroutine should only be called
1016 * - if the file has more than one hardlink
1017 * - if the file is a resolved softlink
1019 /* the persistent variables */
1020 static INOTYP *list[FSN];
1021 static dev_t name[FSN];
1022 /* some temporary variables */
1023 int fsn, is_registered = 0;
1025 /* assertions - in case parameters are lowered for less memory usage */
1027 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1029 /* search which internal registration number is used for this filesystem */
1030 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1032 /* if file system is not registered yet, do it and leave */
1033 if(name[fsn] == 0) {
1034 name[fsn] = (info->st_dev);
1035 /* provide space for the bitmap that maps the inodes of this file system */
1036 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1037 /* no comparison is needed in empty lists ... return */
1040 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1041 (int)name[fsn], (long)&list[fsn]);
1043 /* this looks more complicated than it really is */
1044 /* the idea is very simple:
1045 * provide a bitmap that maps all inodes of a file system
1046 * to mark all files that have already been visited.
1047 * If it is already visited, do not add it to the playlist
1050 * The difficulty is as follows:
1051 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1052 * would be byte-aligned and would allocate at least eight times the needed space.
1053 * Feel free to change the definitions that are involved here, if you know better.
1055 if(regbit_qry(list[fsn], (info->st_ino)))
1058 regbit_set(list[fsn], (info->st_ino));
1060 * the debug expression is more complicated then the working stuff
1063 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1064 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1065 (int)info->st_dev, (int)info->st_ino, fsn,
1066 (int)((info->st_ino) / sizeof(INOTYP)),
1067 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1068 1 << ((info->st_ino) % sizeof(INOTYP)),
1069 is_registered ? "Already" : "Not");
1071 return is_registered;
1074 #ifdef HAVE_LIBURIPARSER
1075 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1077 char * absSourceFile;
1078 size_t absSourceLen;
1079 char * sourceUriString;
1080 char * baseUriString;
1081 UriParserStateA state;
1084 UriUriA relativeUri;
1089 if ((unixFilename == NULL) || (baseDir == NULL)) {
1094 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1095 if (baseUriString == NULL) {
1098 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1099 free(baseUriString);
1102 state.uri = &baseUri;
1103 if (uriParseUriA(&state, baseUriString) != 0) {
1104 free(baseUriString);
1105 uriFreeUriMembersA(&baseUri);
1110 if (unixFilename[0] != '/') {
1111 const int baseDirLen = strlen(baseDir);
1112 const int sourceFileLen = strlen(unixFilename);
1113 absSourceLen = baseDirLen + sourceFileLen;
1114 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1115 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1117 absSourceLen = strlen(unixFilename);
1118 absSourceFile = (char *)unixFilename;
1120 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1121 if (sourceUriString == NULL) {
1122 free(baseUriString);
1123 if (unixFilename[0] != '/') {
1124 free(absSourceFile);
1126 uriFreeUriMembersA(&baseUri);
1129 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1130 free(baseUriString);
1131 free(sourceUriString);
1132 if (unixFilename[0] != '/') {
1133 free(absSourceFile);
1135 uriFreeUriMembersA(&baseUri);
1138 state.uri = &sourceUri;
1139 if (uriParseUriA(&state, sourceUriString) != 0) {
1140 free(baseUriString);
1141 free(sourceUriString);
1142 uriFreeUriMembersA(&baseUri);
1143 uriFreeUriMembersA(&sourceUri);
1146 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1147 free(baseUriString);
1148 free(sourceUriString);
1149 if (unixFilename[0] != '/') {
1150 free(absSourceFile);
1152 uriFreeUriMembersA(&baseUri);
1153 uriFreeUriMembersA(&sourceUri);
1157 /* make relative (or keep absolute if necessary) */
1158 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1159 free(baseUriString);
1160 free(sourceUriString);
1161 if (unixFilename[0] != '/') {
1162 free(absSourceFile);
1164 uriFreeUriMembersA(&baseUri);
1165 uriFreeUriMembersA(&sourceUri);
1166 uriFreeUriMembersA(&relativeUri);
1170 /* back to string */
1171 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1172 free(baseUriString);
1173 free(sourceUriString);
1174 if (unixFilename[0] != '/') {
1175 free(absSourceFile);
1177 uriFreeUriMembersA(&baseUri);
1178 uriFreeUriMembersA(&sourceUri);
1179 uriFreeUriMembersA(&relativeUri);
1182 output = malloc((charsRequired + 1) * sizeof(char));
1183 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1184 free(baseUriString);
1185 free(sourceUriString);
1186 if (unixFilename[0] != '/') {
1187 free(absSourceFile);
1190 uriFreeUriMembersA(&baseUri);
1191 uriFreeUriMembersA(&sourceUri);
1192 uriFreeUriMembersA(&relativeUri);
1196 free(baseUriString);
1197 free(sourceUriString);
1198 if (unixFilename[0] != '/') {
1199 free(absSourceFile);
1201 uriFreeUriMembersA(&baseUri);
1202 uriFreeUriMembersA(&sourceUri);
1203 uriFreeUriMembersA(&relativeUri);
1208 char * xml_escape_malloc(const char * input)
1210 const char * read = input;
1214 if (input == NULL) {
1218 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1219 if (output == NULL) {
1225 if (*read == '\0') {
1230 switch ((unsigned char)*read) {
1232 strcpy(write, "&");
1236 strcpy(write, "<");
1240 strcpy(write, ">");
1244 strcpy(write, "'");
1248 strcpy(write, """);
1259 void parse_file(unsigned char *newpath, unsigned char * original_path)
1261 unsigned char ext[5];
1262 int j, encoding = 0;
1264 for(j = 0; j < 5; j++)
1265 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1269 if(strcmp(".mp2", ext) == 0) {
1274 if(strcmp(".mp3", ext) == 0) {
1279 if(strcmp(".mpc", ext) == 0) {
1284 if(strcmp(".mp+", ext) == 0) {
1289 if(strcmp(".ogg", ext) == 0) {
1294 if(strcmp(".wav", ext) == 0) {
1296 /* parse_wav(newpath); */
1299 if(strcmp(".wma", ext) == 0) {
1301 /* parse_wma(newpath); */
1305 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1306 // there are no tag infos read
1307 // use file name to state substitute it
1308 char *c = strrchr(newpath, separator);
1311 strcpy(artist, ++c);
1312 // arbitrarily use the first '-'
1313 // to separate artist and title
1314 c = strchr(artist, '-');
1315 if(c != NULL) { // if trenner found, divide file name
1318 c = strrchr(title, '.');
1321 } else { // no trenner found, assume
1322 // no artist, only title
1323 strcpy(title, artist);
1326 // replace underscores by spaces
1327 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1329 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1335 /* guesstitle() end */
1337 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1341 printf("#EXTINF:%d,", duration);
1342 if(strlen(artist) != 0)
1343 printf("%s - ", artist);
1344 printf("%s%s", title, eol);
1345 print_path(newpath);
1349 printf("File%d=", counter);
1350 print_path(newpath);
1351 printf("%sTitle%d=", eol, counter);
1352 if(strlen(artist) != 0)
1353 printf("%s - ", artist);
1354 printf("%s%s", title, eol);
1356 printf("Length%d=%d%s", counter, duration, eol);
1359 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1362 printf("?</td></tr>%s", eol);
1364 printf("%d:%s%d</td></tr>%s", duration / 60,
1365 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1368 if(duration != -1) {
1370 char timebuffer[256];
1372 if(stat(newpath, &infos) != 0) {
1373 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1377 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1378 printf("\t<item>%s", eol);
1379 printf("\t\t<author>");
1381 printf("</author>%s\t\t<title>", eol);
1383 printf("</title>%s", eol);
1385 if(referal == NULL) {
1386 noreferal(newpath, artist, title);
1389 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1391 print_webpath(newpath);
1392 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1393 (int)infos.st_size, magic[encoding], eol);
1394 print_pathtail(newpath);
1395 printf("</guid>%s", eol);
1398 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1399 duration / 3600, (duration / 60) % 60,
1400 duration % 60, eol);
1403 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1404 duration / 60, duration % 60, eol);
1405 if(strlen(artist) != 0) {
1406 printf("\t\t<itunes:author>");
1408 printf("</itunes:author>%s", eol);
1410 printf("\t</item>%s", eol);
1414 myplaputstr("HARP, ");
1415 myplaputstr(newpath);
1421 #ifdef HAVE_LIBURIPARSER
1423 printf("<track>\n");
1424 if (strlen(title) > 0) {
1425 char * escaped_title = xml_escape_malloc(title);
1426 if (escaped_title != NULL) {
1427 printf(" <title>%s</title>\n", escaped_title);
1428 free(escaped_title);
1431 if (strlen(artist) > 0) {
1432 char * escaped_artist = xml_escape_malloc(artist);
1433 if (escaped_artist != NULL) {
1434 printf(" <creator>%s</creator>\n", escaped_artist);
1435 free(escaped_artist);
1439 printf(" <duration>%d</duration>\n", duration);
1442 char * relative_location;
1443 char * escaped_location;
1444 relative_location = relative_uri_malloc(newpath, original_path);
1445 if (relative_location != NULL) {
1446 escaped_location = xml_escape_malloc(relative_location);
1447 if (escaped_location != NULL) {
1448 printf(" <location>%s</location>\n", escaped_location);
1449 free(escaped_location);
1451 free(relative_location);
1454 printf("</track>\n");
1461 void parse_directory(unsigned char *path, unsigned char * original_path)
1464 struct dirent **namelist;
1465 unsigned char newpath[PATH_MAX];
1469 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1470 if(stat(path, &infos) != 0) {
1471 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1474 /* check if it is a filename */
1475 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1476 parse_file(path, original_path);
1479 /* must be a directory - or something unusable like pipe, socket, etc */
1480 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1481 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1484 for(i = 0; i < n; i++) {
1485 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1487 if(stat(newpath, &infos) != 0) {
1488 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1491 if(recursive && S_ISDIR(infos.st_mode)
1492 && strcmp(namelist[i]->d_name, ".") != 0
1493 && strcmp(namelist[i]->d_name, "..") != 0)
1494 parse_directory(newpath, original_path);
1495 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1496 if(S_ISREG(infos.st_mode)
1497 && !(avoidhlinked && hlink_check(&infos))) {
1498 parse_file(newpath, original_path);
1505 int main(int argc, char **argv)
1507 winorunix = one2one;
1509 parse_options(argc, argv);
1511 if(optind == argc && !fromstdin)
1517 printf("#EXTM3U%s", eol);
1520 printf("[playlist]%s", eol);
1524 ("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">%s%s<html>%s%s<head>%s<title>Playlist generated by FAPG "
1526 "</title>%s<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />%s<style type=\"text/css\">%s<!--%s%sbody,td,tr {%s font-family: Verdana, Arial, Helvetica, sans-serif;%s font-size: 12px;%s color: #000000;%s}%s%sbody {%s background: #ffffff;%s}%s%sth {%s text-align: center;%s background: #ffcccc;%s padding-left: 15px;%s padding-right: 15px;%s border: 1px #dd8888 solid;%s}%s%std {%s text-align: center;%s background: #eeeeee;%s padding-left: 15px;%s padding-right: 15px;%s border: 1px #cccccc solid;%s}%s%sh1 {%s font-size: 25px;%s}%s%sp {%s font-size: 10px;%s}%s%sa {%s color: #993333;%s text-decoration: none;%s}%s%sa:hover {%s text-decoration: underline;%s}%s%s-->%s</style>%s</head>%s%s<body>%s%s<h1>Playlist</h1>%s%s<table>%s<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>%s",
1527 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1528 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1529 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1530 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1531 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1537 char timebuffer[256];
1539 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1542 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1544 " -->%s<rss xmlns:itunes=\"http://www.itunes.com/DTDs/Podcast-1.0.dtd\" version=\"2.0\">%s <channel>%s\t<title>%s - %s - %s</title>%s\t<description>Directory Tree %s</description>%s\t<link>%s</link>%s\t<itunes:image href=\"%s/xml/podcast.jpg\"/>%s\t<lastBuildDate>%s</lastBuildDate>%s\t<generator>FAPG "
1546 "</generator>%s\t<image>%s\t\t<url>%s/podcast.jpg</url>%s\t\t<title>Server Logo</title>%s\t\t<link>%s</link>%s\t\t<description>Feed provided by FAPG. Click to visit.</description>%s\t</image>%s\t<itunes:owner>%s\t\t<itunes:name>Admin %s</itunes:name>%s\t\t<itunes:email>podcast@%s</itunes:email>%s\t</itunes:owner>%s\t<category>Various</category>%s\t<itunes:subtitle>Directory Tree %s</itunes:subtitle>%s\t<itunes:author>%s</itunes:author>%s\t<copyright>unknown</copyright>%s\t<language>%s</language>%s\t<itunes:explicit>No</itunes:explicit>%s\t<ttl>1800</ttl>%s",
1547 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1548 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1549 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1550 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1551 eol, getenv("LANG"), eol, eol, eol);
1552 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1559 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1564 txxputheader(" iriver UMS PLA");
1567 #ifdef HAVE_LIBURIPARSER
1569 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1570 "<!-- generator=\"FAPG " VERSION " -->\n"
1571 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1577 /* iterate through files */
1579 const char * const pwd_source = getenv("PWD");
1580 const int pwdlen = strlen(pwd_source);
1581 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1582 sprintf(pwd, "%s/", pwd_source);
1585 unsigned char path[PATH_MAX];
1587 while(fgets(path, PATH_MAX, stdin)) {
1588 for(i = 0; i < PATH_MAX; i++)
1589 if(path[i] == '\r' || path[i] == '\n')
1595 /* strip trailing slash */
1596 if (path[i - 1] == '/') {
1600 parse_directory(path, pwd);
1603 for(; optind < argc; optind++) {
1604 /* strip trailing slash */
1605 char * dup = strdup(argv[optind]);
1606 const int len = strlen(dup);
1607 if ((len > 0) && (dup[len - 1] == '/')) {
1608 dup[len - 1] = '\0';
1611 parse_directory(dup, pwd);
1620 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1624 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1625 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1629 printf(" </channel>%s</rss>%s", eol, eol);
1632 txxputcounter(counter);
1634 #ifdef HAVE_LIBURIPARSER
1636 printf("</trackList>\n"