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;
89 char *magic[] = { NULL,
90 "audio/mpeg", "audio/mpeg",
91 "audio/mpeg", "audio/mpeg",
92 "audio/ogg-vorbis", "audio/x-wav",
96 unsigned char unix2dos[] =
97 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
98 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
99 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
100 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
101 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
102 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
103 96, 97, 98, 99, 100, 101, 102, 103,
104 104, 105, 106, 107, 108, 109, 110, 111,
105 112, 113, 114, 115, 116, 117, 118, 119,
106 120, 121, 122, 123, 36, 125, 126, 127,
107 199, 252, 233, 226, 228, 224, 229, 231,
108 234, 235, 232, 239, 238, 236, 196, 197,
109 201, 230, 198, 244, 246, 242, 251, 249,
110 255, 214, 220, 248, 163, 216, 215, 131,
111 225, 237, 243, 250, 241, 209, 170, 186,
112 191, 174, 172, 189, 188, 161, 171, 187,
113 166, 166, 166, 166, 166, 193, 194, 192,
114 169, 166, 166, 43, 43, 162, 165, 43,
115 43, 45, 45, 43, 45, 43, 227, 195,
116 43, 43, 45, 45, 166, 45, 43, 164,
117 240, 208, 202, 203, 200, 105, 205, 206,
118 207, 43, 43, 166, 220, 166, 204, 175,
119 211, 223, 212, 210, 245, 213, 181, 254,
120 222, 218, 219, 217, 253, 221, 175, 180,
121 173, 177, 61, 190, 182, 167, 247, 184,
122 176, 168, 183, 185, 179, 178, 166, 160
125 unsigned char *basemap;
126 unsigned char *winorunix;
127 unsigned char one2one[] =
128 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
129 16, 17, 18, 19, 20, 21, 22, 23,
130 24, 25, 26, 27, 28, 29, 30, 31,
131 32, 33, 34, 35, 36, 37, 38, 39,
132 40, 41, 42, 43, 44, 45, 46, 47,
133 48, 49, 50, 51, 52, 53, 54, 55,
134 56, 57, 58, 59, 60, 61, 62, 63,
135 64, 65, 66, 67, 68, 69, 70, 71,
136 72, 73, 74, 75, 76, 77, 78, 79,
137 80, 81, 82, 83, 84, 85, 86, 87,
138 88, 89, 90, 91, 92, 93, 94, 95,
139 96, 97, 98, 99, 100, 101, 102, 103,
140 104, 105, 106, 107, 108, 109, 110, 111,
141 112, 113, 114, 115, 116, 117, 118, 119,
142 120, 121, 122, 123, 124, 125, 126, 127,
143 128, 129, 130, 131, 132, 133, 134, 135,
144 136, 137, 138, 139, 140, 141, 142, 143,
145 144, 145, 146, 147, 148, 149, 150, 151,
146 152, 153, 154, 155, 156, 157, 158, 159,
147 160, 161, 162, 163, 164, 165, 166, 167,
148 168, 169, 170, 171, 172, 173, 174, 175,
149 176, 177, 178, 179, 180, 181, 182, 183,
150 184, 185, 186, 187, 188, 189, 190, 191,
151 192, 193, 194, 195, 196, 197, 198, 199,
152 200, 201, 202, 203, 204, 205, 206, 207,
153 208, 209, 210, 211, 212, 213, 214, 215,
154 216, 217, 218, 219, 220, 221, 222, 223,
155 224, 225, 226, 227, 228, 229, 230, 231,
156 232, 233, 234, 235, 236, 237, 238, 239,
157 240, 241, 242, 243, 244, 245, 246, 247,
158 248, 249, 250, 251, 252, 253, 254, 255
159 }; /* identical mapping */
161 unsigned char noand[256] =
162 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
163 16, 17, 18, 19, 20, 21, 22, 23,
164 24, 25, 26, 27, 28, 29, 30, 31,
165 32, 33, 34, 35, 36, 37, 43, 39,
166 40, 41, 42, 43, 44, 45, 46, 47,
167 48, 49, 50, 51, 52, 53, 54, 55,
168 56, 57, 58, 59, 60, 61, 62, 63,
169 64, 65, 66, 67, 68, 69, 70, 71,
170 72, 73, 74, 75, 76, 77, 78, 79,
171 80, 81, 82, 83, 84, 85, 86, 87,
172 88, 89, 90, 91, 92, 93, 94, 95,
173 96, 97, 98, 99, 100, 101, 102, 103,
174 104, 105, 106, 107, 108, 109, 110, 111,
175 112, 113, 114, 115, 116, 117, 118, 119,
176 120, 121, 122, 123, 124, 125, 126, 127,
177 128, 129, 130, 131, 132, 133, 134, 135,
178 136, 137, 138, 139, 140, 141, 142, 143,
179 144, 145, 146, 147, 148, 149, 150, 151,
180 152, 153, 154, 155, 156, 157, 158, 159,
181 160, 161, 162, 163, 164, 165, 166, 167,
182 168, 169, 170, 171, 172, 173, 174, 175,
183 176, 177, 178, 179, 180, 181, 182, 183,
184 184, 185, 186, 187, 188, 189, 190, 191,
185 192, 193, 194, 195, 196, 197, 198, 199,
186 200, 201, 202, 203, 204, 205, 206, 207,
187 208, 209, 210, 211, 212, 213, 214, 215,
188 216, 217, 218, 219, 220, 221, 222, 223,
189 224, 225, 226, 227, 228, 229, 230, 231,
190 232, 233, 234, 235, 236, 237, 238, 239,
191 240, 241, 242, 243, 244, 245, 246, 247,
192 248, 249, 250, 251, 252, 253, 254, 255
193 }; /* only '&' is mapped to '+' */
195 unsigned char *iso2web[256] = {
196 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
197 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
198 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
199 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
200 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
201 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
202 "0", "1", "2", "3", "4", "5", "6", "7",
203 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
204 "@", "A", "B", "C", "D", "E", "F", "G",
205 "H", "I", "J", "K", "L", "M", "N", "O",
206 "P", "Q", "R", "S", "T", "U", "V", "W",
207 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
208 "`", "a", "b", "c", "d", "e", "f", "g",
209 "h", "i", "j", "k", "l", "m", "n", "o",
210 "p", "q", "r", "s", "t", "u", "v", "w",
211 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
212 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
213 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
214 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
215 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
216 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
217 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
218 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
219 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
220 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
221 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
222 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
223 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
224 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
225 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
226 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
227 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
232 #ifdef HAVE_LIBURIPARSER
233 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
235 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
238 "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");
243 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
244 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
245 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
246 void myplaputchar(const char x)
248 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
252 void mywebputstr(const char *c)
260 void myplaputstr(const char *c)
264 myplaputchar('\\'); /* translate slash to backslash */
268 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
269 while(*c == '/' && c[1] == '/')
274 void myputstr(const char *c)
282 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
283 while(*c == '/' && c[1] == '/')
288 void txxputheader(const char *c)
304 void txxputnameoffset(const char *c)
309 unsigned char *prefx;
322 cnt--; // skip the leading dot of the filepath
335 b = (pos & 0xFF00) >> 8;
341 void txxputstr(const char *c)
345 unsigned char *prefx;
350 fprintf(stderr, "prefix: '%s'\n", prefx);
366 c++; // skip the leading dot
388 void txxputcounter(int c)
394 b = (c & 0xFF000000) >> 24;
396 b = (c & 0x00FF0000) >> 16;
398 b = (c & 0x0000FF00) >> 8;
400 b = (c & 0x000000FF);
404 /* remove spaces at beginning and end of string */
408 /* remove spaces at beginning ... */
416 /* ... and end of string */
418 while(--p > c && *p == ' ')
422 void print_webpath(const char *path)
424 const char *c = path;
426 printf(prefix); /* we must not modify this part */
427 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
429 /* maybe there follow many slashes */
433 for(; *c != '\0'; c++) {
435 /* remove multiple "//" when parsing a directory ending with a "/" */
436 while(*c == '/' && c[1] == '/')
441 void print_path(const char *path)
443 const char *c = path;
445 /* skip leading "./" when parsing current directory */
446 if(*c == '.' && *(c + 1) == '/') {
448 /* maybe there follow more slashes */
455 void print_pathtail(const char *path)
458 c = strrchr(path, separator);
466 void noreferal(const char *path, const char *artist, const char *title)
468 printf("\t\t<description><![CDATA[<h4>");
472 printf("</h5><a href=\"");
475 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
479 void reference(const char *title)
482 static char command[2048], buffer[1024];
485 buflen = strlen(title) + strlen(referal) + 3;
486 assert((buflen < 2046));
487 strcpy(command, referal);
488 buflen = strlen(command);
489 command[buflen] = ' ';
490 command[buflen + 1] = '"';
491 command[buflen + 2] = 0;
492 strcat(command, title);
493 buflen = strlen(command);
494 command[buflen] = '"';
495 command[buflen + 1] = 0;
497 fprintf(stderr, "Debug >> processing command: %s\n", command);
498 pipe = popen(command, "r");
500 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
504 fgets(buffer, 1020, pipe);
506 fputs(buffer, stdout);
507 fgets(buffer, 1020, pipe);
513 void parse_options(int argc, char **argv)
515 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
516 static struct option long_options[] = {
517 {"backslash", no_argument, NULL, 'b'},
518 {"command", required_argument, NULL, 'c'},
519 {"debug", no_argument, NULL, 'd'},
520 {"format", required_argument, NULL, 'f'},
521 {"genre", required_argument, NULL, 'g'},
522 {"nohardlink", no_argument, NULL, 'n'},
523 {"output", required_argument, NULL, 'o'},
524 {"prefix", required_argument, NULL, 'p'},
525 {"recursive", no_argument, NULL, 'r'},
526 {"stdin", no_argument, NULL, 's'},
527 {"windows", no_argument, NULL, 'w'},
528 {"exclude", required_argument, NULL, 'x'},
532 int option_index = 0;
534 getopt_long(argc, argv, short_options, long_options,
535 &option_index)) != -1) {
542 if(strncmp(optarg, "intern", 6) == 0)
545 referal = strdup(optarg);
551 if(strcmp(optarg, "m3u") == 0)
553 else if(strcmp(optarg, "pls") == 0)
555 else if(strcmp(optarg, "html") == 0)
556 format = FORMAT_HTML;
557 else if(strcmp(optarg, "rss") == 0)
559 else if(strcmp(optarg, "pla") == 0)
561 else if(strcmp(optarg, "txx") == 0)
563 #ifdef HAVE_LIBURIPARSER
564 else if(strcmp(optarg, "xspf") == 0)
565 format = FORMAT_XSPF;
571 if(genrelist == NULL)
572 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
573 if(genrelist == NULL) {
575 "Error >> unable to allocate cleared memory\n");
579 while(n < strlen(optarg)) {
582 "Debug >> genrelist entry activting : %d\n",
584 genrelist[atoi(&optarg[n])] = 1;
585 while(isdigit(optarg[n++]));
594 if(fopen(optarg, "w") == NULL) {
596 "Error >> unable to open output file : %s\n",
602 prefix = malloc(strlen(optarg) + 1);
603 strcpy(prefix, optarg);
604 base = malloc(strlen(prefix) + 1);
605 strcpy(base, prefix);
606 dir = strchr(base, '/');
607 if((dir != NULL) && (dir[1] == '/'))
608 dir = strchr(dir + 2, '/');
613 /* if prefix is a weblink, base is the baselink, dir is the path */
623 winorunix = unix2dos;
627 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
629 genrelist = calloc(257, sizeof(char));
633 if(genrelist == NULL) {
635 "Error >> unable to allocate cleared memory\n");
639 while(n < strlen(optarg)) {
642 "Debug >> genrelist entry activting : %d\n",
644 genrelist[atoi(&optarg[n])] = 0;
645 while(isdigit(optarg[n++]));
656 /* hostname = getenv("HOSTNAME"); */
657 if(genrelist == NULL) {
658 genrelist = calloc(257, sizeof(char));
659 if(genrelist == NULL) {
661 "Error >> unable to allocate cleared memory\n");
671 void parse_mp3(unsigned char *file)
673 int bitrates[2][3][15] =
674 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
676 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
678 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
680 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
681 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
682 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
691 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
694 if((fic = fopen(file, "r")) == NULL) {
695 fprintf(stderr, "Warning >> can't open file : %s\n", file);
698 lus = fread(buffer, 1, MP3_BASE, fic);
702 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
705 version = *(buffer + 3);
706 if(version < 2 || version > 4)
708 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
710 if(*(buffer + 5) != 0)
712 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
713 *(buffer + 5), file);
716 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
717 /* read more header */
718 if(size + lus > MAX) {
719 lus += fread(buffer + lus, 1, MAX - lus, fic);
721 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
724 lus += fread(buffer + lus, 1, size, fic);
729 while(c < buffer + size) {
730 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
733 if(strncmp(c, "TT2", 3) == 0) {
734 strncpy(title, c + 7, size - 1);
735 title[size - 1] = '\0';
737 if(strncmp(c, "TP1", 3) == 0) {
738 strncpy(artist, c + 7, size - 1);
739 artist[size - 1] = '\0';
741 if(strncmp(c, "TCO", 3) == 0) {
742 /* strncpy(genrebuf,c+7,size-1); */
743 /* genrebuf[size-1]='\0'; */
744 /* genre=atoi(&genrebuf[1]); */
749 if(version == 3 || version == 4)
750 while(c < buffer + size) {
752 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
756 if(strncmp(c, "TIT2", 4) == 0) {
757 strncpy(title, c + 11, size - 1);
758 title[size - 1] = '\0';
760 if(strncmp(c, "TPE1", 4) == 0) {
761 strncpy(artist, c + 11, size - 1);
762 artist[size - 1] = '\0';
764 if(strncmp(c, "TCON", 4) == 0) {
765 /* strncpy(genrebuf,c+11,size-1); */
766 /* genrebuf[size-1]='\0'; */
767 /* genre=atoi(&genrebuf[1]); */
768 genre = atoi(c + 12);
774 while(c < buffer + lus - 10) {
775 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
780 version = 2 - (*(c + 1) >> 3 & 1);
781 lay = 4 - (*(c + 1) >> 1 & 3);
782 bitrate_index = *(c + 2) >> 4 & 0xF;
783 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
784 && bitrate_index >= 0 && bitrate_index <= 14)
785 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
789 fseek(fic, 0, SEEK_END);
790 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
799 if(strlen(artist) == 0 && strlen(title) == 0) {
800 fseek(fic, -128, SEEK_END);
801 lus = fread(buffer, 1, 128, fic);
802 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
803 && buffer[2] == 'G') {
804 strncpy(title, buffer + 3, 30);
807 while(c > title && *c == ' ')
809 strncpy(artist, buffer + 33, 30);
812 while(c > artist && *c == ' ')
814 /* strncpy(album,buffer+65,30); */
815 /* strncpy(year,buffer+97,4); */
816 /* strncpy(comment,buffer+101,30); */
817 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
825 void parse_ogg(unsigned char *file)
834 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
837 if((fic = fopen(file, "r")) == NULL) {
838 fprintf(stderr, "Warning >> can't open file : %s\n", file);
841 lus = fread(buffer, 1, OGG_BASE, fic);
844 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
845 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
851 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
853 while(c < buffer + lus - 10) {
855 if(strncasecmp(c, "TITLE=", 6) == 0) {
857 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
859 strncpy(title, c + 6, size - 6);
860 title[size - 6] = '\0';
863 if(strncasecmp(c, "ARTIST=", 7) == 0) {
865 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
867 strncpy(artist, c + 7, size - 7);
868 artist[size - 7] = '\0';
871 if(strncasecmp(c, "GENRE=", 6) == 0) {
874 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
876 strncpy(genrebuf, c + 6, size - 6);
877 genrebuf[size - 6] = '\0';
879 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
880 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
884 if(i == ID3_NR_OF_V1_GENRES)
891 fseek(fic, -OGG_BASE, SEEK_END);
892 lus = fread(buffer, 1, OGG_BASE, fic);
893 c = buffer + lus - 1;
894 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
899 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
900 duration = samples / sample_rate;
906 void parse_mpc(unsigned char *file)
911 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
917 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
920 if((fic = fopen(file, "r")) == NULL) {
921 fprintf(stderr, "Warning >> can't open file : %s\n", file);
924 lus = fread(buffer, 1, 12, fic);
927 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
928 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
934 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
942 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
944 duration = frame_count * 1152 / sample_rates[*c & 3];
946 /* try APETAGEX footer */
947 fseek(fic, -32, SEEK_END);
948 lus = fread(buffer, 1, 32, fic);
949 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
952 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
956 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
957 fseek(fic, -size, SEEK_END);
958 lus = fread(buffer, 1, size, fic);
959 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
963 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
966 if(strcasecmp(c, "TITLE") == 0) {
967 strncpy(title, c + 6, size);
970 if(strcasecmp(c, "ARTIST") == 0) {
971 strncpy(artist, c + 7, size);
974 if(strcasecmp(c, "GENRE") == 0) {
975 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
976 strncpy(genrebuf, c + 6, size);
977 genrebuf[size] = '\0';
979 (ID3_v1_genre_description[i], genrebuf) == 0) {
983 if(i == ID3_NR_OF_V1_GENRES)
987 c += strlen(c) + 1 + size;
996 #define MAXINO (1<<24)
997 #define INOTYP unsigned long
998 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
999 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1001 int hlink_check(struct stat *info)
1004 * for speed this subroutine should only be called
1005 * - if the file has more than one hardlink
1006 * - if the file is a resolved softlink
1008 /* the persistent variables */
1009 static INOTYP *list[FSN];
1010 static dev_t name[FSN];
1011 /* some temporary variables */
1012 int fsn, is_registered = 0;
1014 /* assertions - in case parameters are lowered for less memory usage */
1016 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1018 /* search which internal registration number is used for this filesystem */
1019 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1021 /* if file system is not registered yet, do it and leave */
1022 if(name[fsn] == 0) {
1023 name[fsn] = (info->st_dev);
1024 /* provide space for the bitmap that maps the inodes of this file system */
1025 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1026 /* no comparison is needed in empty lists ... return */
1029 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1030 (int)name[fsn], (long)&list[fsn]);
1032 /* this looks more complicated than it really is */
1033 /* the idea is very simple:
1034 * provide a bitmap that maps all inodes of a file system
1035 * to mark all files that have already been visited.
1036 * If it is already visited, do not add it to the playlist
1039 * The difficulty is as follows:
1040 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1041 * would be byte-aligned and would allocate at least eight times the needed space.
1042 * Feel free to change the definitions that are involved here, if you know better.
1044 if(regbit_qry(list[fsn], (info->st_ino)))
1047 regbit_set(list[fsn], (info->st_ino));
1049 * the debug expression is more complicated then the working stuff
1052 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1053 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1054 (int)info->st_dev, (int)info->st_ino, fsn,
1055 (int)((info->st_ino) / sizeof(INOTYP)),
1056 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1057 1 << ((info->st_ino) % sizeof(INOTYP)),
1058 is_registered ? "Already" : "Not");
1060 return is_registered;
1063 #ifdef HAVE_LIBURIPARSER
1064 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1066 char * absSourceFile;
1067 size_t absSourceLen;
1068 char * sourceUriString;
1069 char * baseUriString;
1070 UriParserStateA state;
1073 UriUriA relativeUri;
1078 if ((unixFilename == NULL) || (baseDir == NULL)) {
1083 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1084 if (baseUriString == NULL) {
1087 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1088 free(baseUriString);
1091 state.uri = &baseUri;
1092 if (uriParseUriA(&state, baseUriString) != 0) {
1093 free(baseUriString);
1094 uriFreeUriMembersA(&baseUri);
1099 if (unixFilename[0] != '/') {
1100 const int baseDirLen = strlen(baseDir);
1101 const int sourceFileLen = strlen(unixFilename);
1102 absSourceLen = baseDirLen + sourceFileLen;
1103 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1104 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1106 absSourceLen = strlen(unixFilename);
1107 absSourceFile = (char *)unixFilename;
1109 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1110 if (sourceUriString == NULL) {
1111 free(baseUriString);
1112 if (unixFilename[0] != '/') {
1113 free(absSourceFile);
1115 uriFreeUriMembersA(&baseUri);
1118 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1119 free(baseUriString);
1120 free(sourceUriString);
1121 if (unixFilename[0] != '/') {
1122 free(absSourceFile);
1124 uriFreeUriMembersA(&baseUri);
1127 state.uri = &sourceUri;
1128 if (uriParseUriA(&state, sourceUriString) != 0) {
1129 free(baseUriString);
1130 free(sourceUriString);
1131 uriFreeUriMembersA(&baseUri);
1132 uriFreeUriMembersA(&sourceUri);
1135 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1136 free(baseUriString);
1137 free(sourceUriString);
1138 if (unixFilename[0] != '/') {
1139 free(absSourceFile);
1141 uriFreeUriMembersA(&baseUri);
1142 uriFreeUriMembersA(&sourceUri);
1146 /* make relative (or keep absolute if necessary) */
1147 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1148 free(baseUriString);
1149 free(sourceUriString);
1150 if (unixFilename[0] != '/') {
1151 free(absSourceFile);
1153 uriFreeUriMembersA(&baseUri);
1154 uriFreeUriMembersA(&sourceUri);
1155 uriFreeUriMembersA(&relativeUri);
1159 /* back to string */
1160 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1161 free(baseUriString);
1162 free(sourceUriString);
1163 if (unixFilename[0] != '/') {
1164 free(absSourceFile);
1166 uriFreeUriMembersA(&baseUri);
1167 uriFreeUriMembersA(&sourceUri);
1168 uriFreeUriMembersA(&relativeUri);
1171 output = malloc((charsRequired + 1) * sizeof(char));
1172 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1173 free(baseUriString);
1174 free(sourceUriString);
1175 if (unixFilename[0] != '/') {
1176 free(absSourceFile);
1179 uriFreeUriMembersA(&baseUri);
1180 uriFreeUriMembersA(&sourceUri);
1181 uriFreeUriMembersA(&relativeUri);
1185 free(baseUriString);
1186 free(sourceUriString);
1187 if (unixFilename[0] != '/') {
1188 free(absSourceFile);
1190 uriFreeUriMembersA(&baseUri);
1191 uriFreeUriMembersA(&sourceUri);
1192 uriFreeUriMembersA(&relativeUri);
1197 char * xml_escape_malloc(const char * input)
1199 const char * read = input;
1203 if (input == NULL) {
1207 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1208 if (output == NULL) {
1214 if (*read == '\0') {
1219 switch ((unsigned char)*read) {
1221 strcpy(write, "&");
1225 strcpy(write, "<");
1229 strcpy(write, ">");
1233 strcpy(write, "'");
1237 strcpy(write, """);
1248 void parse_file(unsigned char *newpath, unsigned char * original_path)
1250 unsigned char ext[5];
1251 int j, encoding = 0;
1253 for(j = 0; j < 5; j++)
1254 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1258 if(strcmp(".mp2", ext) == 0) {
1263 if(strcmp(".mp3", ext) == 0) {
1268 if(strcmp(".mpc", ext) == 0) {
1273 if(strcmp(".mp+", ext) == 0) {
1278 if(strcmp(".ogg", ext) == 0) {
1283 if(strcmp(".wav", ext) == 0) {
1284 duration = -1; /* parse_wav(newpath); */
1288 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1289 // there are no tag infos read
1290 // use file name to state substitute it
1291 char *c = strrchr(newpath, separator);
1294 strcpy(artist, ++c);
1295 // arbitrarily use the first '-'
1296 // to separate artist and title
1297 c = strchr(artist, '-');
1298 if(c != NULL) { // if trenner found, divide file name
1301 c = strrchr(title, '.');
1304 } else { // no trenner found, assume
1305 // no artist, only title
1306 strcpy(title, artist);
1309 // replace underscores by spaces
1310 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1312 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1318 /* guesstitle() end */
1320 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1324 if(duration != -1) {
1325 printf("#EXTINF:%d,", duration);
1326 if(strlen(artist) != 0)
1327 printf("%s - ", artist);
1328 printf("%s%s", title, eol);
1330 print_path(newpath);
1334 printf("File%d=", counter);
1335 print_path(newpath);
1336 printf("%sTitle%d=", eol, counter);
1337 if(strlen(artist) != 0)
1338 printf("%s - ", artist);
1339 printf("%s%s", title, eol);
1341 printf("Length%d=%d%s", counter, duration, eol);
1344 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1347 printf("?</td></tr>%s", eol);
1349 printf("%d:%s%d</td></tr>%s", duration / 60,
1350 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1353 if(duration != -1) {
1355 char timebuffer[256];
1357 if(stat(newpath, &infos) != 0) {
1358 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1362 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1363 printf("\t<item>%s", eol);
1364 printf("\t\t<author>");
1366 printf("</author>%s\t\t<title>", eol);
1368 printf("</title>%s", eol);
1370 if(referal == NULL) {
1371 noreferal(newpath, artist, title);
1374 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1376 print_webpath(newpath);
1377 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1378 (int)infos.st_size, magic[encoding], eol);
1379 print_pathtail(newpath);
1380 printf("</guid>%s", eol);
1383 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1384 duration / 3600, (duration / 60) % 60,
1385 duration % 60, eol);
1388 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1389 duration / 60, duration % 60, eol);
1390 if(strlen(artist) != 0) {
1391 printf("\t\t<itunes:author>");
1393 printf("</itunes:author>%s", eol);
1395 printf("\t</item>%s", eol);
1399 myplaputstr("HARP, ");
1400 myplaputstr(newpath);
1406 #ifdef HAVE_LIBURIPARSER
1408 printf("<track>\n");
1409 if (strlen(title) > 0) {
1410 char * escaped_title = xml_escape_malloc(title);
1411 if (escaped_title != NULL) {
1412 printf(" <title>%s</title>\n", escaped_title);
1413 free(escaped_title);
1416 if (strlen(artist) > 0) {
1417 char * escaped_artist = xml_escape_malloc(artist);
1418 if (escaped_artist != NULL) {
1419 printf(" <creator>%s</creator>\n", escaped_artist);
1420 free(escaped_artist);
1424 printf(" <duration>%d</duration>\n", duration);
1427 char * relative_location;
1428 char * escaped_location;
1429 relative_location = relative_uri_malloc(newpath, original_path);
1430 if (relative_location != NULL) {
1431 escaped_location = xml_escape_malloc(relative_location);
1432 if (escaped_location != NULL) {
1433 printf(" <location>%s</location>\n", escaped_location);
1434 free(escaped_location);
1436 free(relative_location);
1439 printf("</track>\n");
1446 void parse_directory(unsigned char *path, unsigned char * original_path)
1449 struct dirent **namelist;
1450 unsigned char newpath[PATH_MAX];
1454 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1455 if(stat(path, &infos) != 0) {
1456 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1459 /* check if it is a filename */
1460 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1461 parse_file(path, original_path);
1464 /* must be a directory - or something unusable like pipe, socket, etc */
1465 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1466 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1469 for(i = 0; i < n; i++) {
1470 snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
1472 if(stat(newpath, &infos) != 0) {
1473 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1476 if(recursive && S_ISDIR(infos.st_mode)
1477 && strcmp(namelist[i]->d_name, ".") != 0
1478 && strcmp(namelist[i]->d_name, "..") != 0)
1479 parse_directory(newpath, original_path);
1480 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1481 if(S_ISREG(infos.st_mode)
1482 && !(avoidhlinked && hlink_check(&infos))) {
1483 parse_file(newpath, original_path);
1490 int main(int argc, char **argv)
1492 winorunix = one2one;
1494 parse_options(argc, argv);
1496 if(optind == argc && !fromstdin)
1502 printf("#EXTM3U%s", eol);
1505 printf("[playlist]%s", eol);
1509 ("<!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 "
1511 "</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",
1512 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1513 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1514 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1515 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1516 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1522 char timebuffer[256];
1524 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1527 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1529 " -->%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 "
1531 "</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",
1532 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1533 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1534 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1535 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1536 eol, getenv("LANG"), eol, eol, eol);
1537 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1544 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1549 txxputheader(" iriver UMS PLA");
1552 #ifdef HAVE_LIBURIPARSER
1554 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1555 "<!-- generator=\"FAPG " VERSION " -->\n"
1556 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1562 /* iterate through files */
1564 const char * const pwd_source = getenv("PWD");
1565 const int pwdlen = strlen(pwd_source);
1566 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1567 sprintf(pwd, "%s/", pwd_source);
1570 unsigned char path[PATH_MAX];
1572 while(fgets(path, PATH_MAX, stdin)) {
1573 for(i = 0; i < PATH_MAX; i++)
1574 if(path[i] == '\r' || path[i] == '\n')
1580 /* strip trailing slash */
1581 if (path[i - 1] == '/') {
1585 parse_directory(path, pwd);
1588 for(; optind < argc; optind++) {
1589 /* strip trailing slash */
1590 char * dup = strdup(argv[optind]);
1591 const int len = strlen(dup);
1592 if ((len > 0) && (dup[len - 1] == '/')) {
1593 dup[len - 1] = '\0';
1596 parse_directory(dup, pwd);
1605 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1609 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1610 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1614 printf(" </channel>%s</rss>%s", eol, eol);
1617 txxputcounter(counter);
1619 #ifdef HAVE_LIBURIPARSER
1621 printf("</trackList>\n"