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>
41 #define VERSION "0.38"
43 #define OGG_BASE 1024*10
44 #define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */
47 int format = 0; /* 0 = m3u ; 1 = pls ; 2 = html ; 3 = rss */
48 char *genrelist = NULL;
49 unsigned char *prefix = "";
50 unsigned char *base = "";
51 unsigned char *dir = "";
52 unsigned char *hostname = "fritzserver.de";
53 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
54 unsigned char *referal = NULL;
60 unsigned char *eol = "\n";
61 unsigned char buffer[MAX];
65 unsigned char artist[1024];
66 unsigned char title[1024];
67 unsigned char genrebuf[1024];
68 unsigned char genre = 0;
77 char *magic[] = { NULL,
78 "audio/mpeg", "audio/mpeg",
79 "audio/mpeg", "audio/mpeg",
80 "audio/ogg-vorbis", "audio/x-wav",
84 unsigned char unix2dos[] =
85 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
86 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
87 32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
88 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
89 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
90 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
91 96, 97, 98, 99, 100, 101, 102, 103,
92 104, 105, 106, 107, 108, 109, 110, 111,
93 112, 113, 114, 115, 116, 117, 118, 119,
94 120, 121, 122, 123, 36, 125, 126, 127,
95 199, 252, 233, 226, 228, 224, 229, 231,
96 234, 235, 232, 239, 238, 236, 196, 197,
97 201, 230, 198, 244, 246, 242, 251, 249,
98 255, 214, 220, 248, 163, 216, 215, 131,
99 225, 237, 243, 250, 241, 209, 170, 186,
100 191, 174, 172, 189, 188, 161, 171, 187,
101 166, 166, 166, 166, 166, 193, 194, 192,
102 169, 166, 166, 43, 43, 162, 165, 43,
103 43, 45, 45, 43, 45, 43, 227, 195,
104 43, 43, 45, 45, 166, 45, 43, 164,
105 240, 208, 202, 203, 200, 105, 205, 206,
106 207, 43, 43, 166, 220, 166, 204, 175,
107 211, 223, 212, 210, 245, 213, 181, 254,
108 222, 218, 219, 217, 253, 221, 175, 180,
109 173, 177, 61, 190, 182, 167, 247, 184,
110 176, 168, 183, 185, 179, 178, 166, 160
113 unsigned char *basemap;
114 unsigned char *winorunix;
115 unsigned char one2one[] =
116 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
117 16, 17, 18, 19, 20, 21, 22, 23,
118 24, 25, 26, 27, 28, 29, 30, 31,
119 32, 33, 34, 35, 36, 37, 38, 39,
120 40, 41, 42, 43, 44, 45, 46, 47,
121 48, 49, 50, 51, 52, 53, 54, 55,
122 56, 57, 58, 59, 60, 61, 62, 63,
123 64, 65, 66, 67, 68, 69, 70, 71,
124 72, 73, 74, 75, 76, 77, 78, 79,
125 80, 81, 82, 83, 84, 85, 86, 87,
126 88, 89, 90, 91, 92, 93, 94, 95,
127 96, 97, 98, 99, 100, 101, 102, 103,
128 104, 105, 106, 107, 108, 109, 110, 111,
129 112, 113, 114, 115, 116, 117, 118, 119,
130 120, 121, 122, 123, 124, 125, 126, 127,
131 128, 129, 130, 131, 132, 133, 134, 135,
132 136, 137, 138, 139, 140, 141, 142, 143,
133 144, 145, 146, 147, 148, 149, 150, 151,
134 152, 153, 154, 155, 156, 157, 158, 159,
135 160, 161, 162, 163, 164, 165, 166, 167,
136 168, 169, 170, 171, 172, 173, 174, 175,
137 176, 177, 178, 179, 180, 181, 182, 183,
138 184, 185, 186, 187, 188, 189, 190, 191,
139 192, 193, 194, 195, 196, 197, 198, 199,
140 200, 201, 202, 203, 204, 205, 206, 207,
141 208, 209, 210, 211, 212, 213, 214, 215,
142 216, 217, 218, 219, 220, 221, 222, 223,
143 224, 225, 226, 227, 228, 229, 230, 231,
144 232, 233, 234, 235, 236, 237, 238, 239,
145 240, 241, 242, 243, 244, 245, 246, 247,
146 248, 249, 250, 251, 252, 253, 254, 255
147 }; /* identical mapping */
149 unsigned char noand[256] =
150 { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
151 16, 17, 18, 19, 20, 21, 22, 23,
152 24, 25, 26, 27, 28, 29, 30, 31,
153 32, 33, 34, 35, 36, 37, 43, 39,
154 40, 41, 42, 43, 44, 45, 46, 47,
155 48, 49, 50, 51, 52, 53, 54, 55,
156 56, 57, 58, 59, 60, 61, 62, 63,
157 64, 65, 66, 67, 68, 69, 70, 71,
158 72, 73, 74, 75, 76, 77, 78, 79,
159 80, 81, 82, 83, 84, 85, 86, 87,
160 88, 89, 90, 91, 92, 93, 94, 95,
161 96, 97, 98, 99, 100, 101, 102, 103,
162 104, 105, 106, 107, 108, 109, 110, 111,
163 112, 113, 114, 115, 116, 117, 118, 119,
164 120, 121, 122, 123, 124, 125, 126, 127,
165 128, 129, 130, 131, 132, 133, 134, 135,
166 136, 137, 138, 139, 140, 141, 142, 143,
167 144, 145, 146, 147, 148, 149, 150, 151,
168 152, 153, 154, 155, 156, 157, 158, 159,
169 160, 161, 162, 163, 164, 165, 166, 167,
170 168, 169, 170, 171, 172, 173, 174, 175,
171 176, 177, 178, 179, 180, 181, 182, 183,
172 184, 185, 186, 187, 188, 189, 190, 191,
173 192, 193, 194, 195, 196, 197, 198, 199,
174 200, 201, 202, 203, 204, 205, 206, 207,
175 208, 209, 210, 211, 212, 213, 214, 215,
176 216, 217, 218, 219, 220, 221, 222, 223,
177 224, 225, 226, 227, 228, 229, 230, 231,
178 232, 233, 234, 235, 236, 237, 238, 239,
179 240, 241, 242, 243, 244, 245, 246, 247,
180 248, 249, 250, 251, 252, 253, 254, 255
181 }; /* only '&' is mapped to '+' */
183 unsigned char *iso2web[256] = {
184 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
185 "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
186 "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
187 "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
188 "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
189 "%28", "%29", "%2a", "+", ",", "-", ".", "/",
190 "0", "1", "2", "3", "4", "5", "6", "7",
191 "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
192 "@", "A", "B", "C", "D", "E", "F", "G",
193 "H", "I", "J", "K", "L", "M", "N", "O",
194 "P", "Q", "R", "S", "T", "U", "V", "W",
195 "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
196 "`", "a", "b", "c", "d", "e", "f", "g",
197 "h", "i", "j", "k", "l", "m", "n", "o",
198 "p", "q", "r", "s", "t", "u", "v", "w",
199 "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
200 "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
201 "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
202 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
203 "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
204 "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
205 "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
206 "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
207 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
208 "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
209 "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
210 "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
211 "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
212 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
213 "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
214 "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
215 "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
221 "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla|txx] [-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");
225 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
226 #define myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
227 /* #define myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
228 void myplaputchar(const char x)
230 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
234 void mywebputstr(const char *c)
242 void myplaputstr(const char *c)
246 myplaputchar('\\'); /* translate slash to backslash */
250 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
251 while(*c == '/' && c[1] == '/')
256 void myputstr(const char *c)
264 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
265 while(*c == '/' && c[1] == '/')
270 void txxputheader(const char *c)
286 void txxputnameoffset(const char *c)
291 unsigned char *prefx;
304 cnt--; // skip the leading dot of the filepath
317 b = (pos & 0xFF00) >> 8;
323 void txxputstr(const char *c)
327 unsigned char *prefx;
332 fprintf(stderr, "prefix: '%s'\n", prefx);
348 c++; // skip the leading dot
370 void txxputcounter(int c)
376 b = (c & 0xFF000000) >> 24;
378 b = (c & 0x00FF0000) >> 16;
380 b = (c & 0x0000FF00) >> 8;
382 b = (c & 0x000000FF);
386 /* remove spaces at beginning and end of string */
390 /* remove spaces at beginning ... */
398 /* ... and end of string */
400 while(--p > c && *p == ' ')
404 void print_webpath(const char *path)
406 const char *c = path;
408 printf(prefix); /* we must not modify this part */
409 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
411 /* maybe there follow many slashes */
415 for(; *c != '\0'; c++) {
417 /* remove multiple "//" when parsing a directory ending with a "/" */
418 while(*c == '/' && c[1] == '/')
423 void print_path(const char *path)
425 const char *c = path;
427 /* skip leading "./" when parsing current directory */
428 if(*c == '.' && *(c + 1) == '/') {
430 /* maybe there follow more slashes */
437 void print_pathtail(const char *path)
440 c = strrchr(path, separator);
448 void noreferal(const char *path, const char *artist, const char *title)
450 printf("\t\t<description><![CDATA[<h4>");
454 printf("</h5><a href=\"");
457 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
461 void reference(const char *title)
464 static char command[2048], buffer[1024];
467 buflen = strlen(title) + strlen(referal) + 3;
468 assert((buflen < 2046));
469 strcpy(command, referal);
470 buflen = strlen(command);
471 command[buflen] = ' ';
472 command[buflen + 1] = '"';
473 command[buflen + 2] = 0;
474 strcat(command, title);
475 buflen = strlen(command);
476 command[buflen] = '"';
477 command[buflen + 1] = 0;
479 fprintf(stderr, "Debug >> processing command: %s\n", command);
480 pipe = popen(command, "r");
482 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
486 fgets(buffer, 1020, pipe);
488 fputs(buffer, stdout);
489 fgets(buffer, 1020, pipe);
495 void parse_options(int argc, char **argv)
497 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
498 static struct option long_options[] = {
499 {"backslash", no_argument, NULL, 'b'},
500 {"command", required_argument, NULL, 'c'},
501 {"debug", no_argument, NULL, 'd'},
502 {"format", required_argument, NULL, 'f'},
503 {"genre", required_argument, NULL, 'g'},
504 {"nohardlink", no_argument, NULL, 'n'},
505 {"output", required_argument, NULL, 'o'},
506 {"prefix", required_argument, NULL, 'p'},
507 {"recursive", no_argument, NULL, 'r'},
508 {"stdin", no_argument, NULL, 's'},
509 {"windows", no_argument, NULL, 'w'},
510 {"exclude", required_argument, NULL, 'x'}
513 int option_index = 0;
515 getopt_long(argc, argv, short_options, long_options,
516 &option_index)) != -1) {
523 if(strncmp(optarg, "intern", 6) == 0)
526 referal = strdup(optarg);
532 if(strcmp(optarg, "m3u") == 0)
534 else if(strcmp(optarg, "pls") == 0)
536 else if(strcmp(optarg, "html") == 0)
538 else if(strcmp(optarg, "rss") == 0)
540 else if(strcmp(optarg, "pla") == 0)
542 else if(strcmp(optarg, "txx") == 0)
548 if(genrelist == NULL)
549 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
550 if(genrelist == NULL) {
552 "Error >> unable to allocate cleared memory\n");
556 while(n < strlen(optarg)) {
559 "Debug >> genrelist entry activting : %d\n",
561 genrelist[atoi(&optarg[n])] = 1;
562 while(isdigit(optarg[n++]));
571 if(fopen(optarg, "w") == NULL) {
573 "Error >> unable to open output file : %s\n",
579 prefix = malloc(strlen(optarg) + 1);
580 strcpy(prefix, optarg);
581 base = malloc(strlen(prefix) + 1);
582 strcpy(base, prefix);
583 dir = strchr(base, '/');
584 if((dir != NULL) && (dir[1] == '/'))
585 dir = strchr(dir + 2, '/');
590 /* if prefix is a weblink, base is the baselink, dir is the path */
600 winorunix = unix2dos;
604 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
606 genrelist = calloc(257, sizeof(char));
610 if(genrelist == NULL) {
612 "Error >> unable to allocate cleared memory\n");
616 while(n < strlen(optarg)) {
619 "Debug >> genrelist entry activting : %d\n",
621 genrelist[atoi(&optarg[n])] = 0;
622 while(isdigit(optarg[n++]));
633 /* hostname = getenv("HOSTNAME"); */
634 if(genrelist == NULL) {
635 genrelist = calloc(257, sizeof(char));
636 if(genrelist == NULL) {
638 "Error >> unable to allocate cleared memory\n");
648 void parse_mp3(unsigned char *file)
650 int bitrates[2][3][15] =
651 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
653 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
655 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
657 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
658 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
659 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
668 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
671 if((fic = fopen(file, "r")) == NULL) {
672 fprintf(stderr, "Warning >> can't open file : %s\n", file);
675 lus = fread(buffer, 1, MP3_BASE, fic);
679 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
682 version = *(buffer + 3);
683 if(version < 2 || version > 4)
685 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
687 if(*(buffer + 5) != 0)
689 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
690 *(buffer + 5), file);
693 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
694 /* read more header */
695 if(size + lus > MAX) {
696 lus += fread(buffer + lus, 1, MAX - lus, fic);
698 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
701 lus += fread(buffer + lus, 1, size, fic);
706 while(c < buffer + size) {
707 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
710 if(strncmp(c, "TT2", 3) == 0) {
711 strncpy(title, c + 7, size - 1);
712 title[size - 1] = '\0';
714 if(strncmp(c, "TP1", 3) == 0) {
715 strncpy(artist, c + 7, size - 1);
716 artist[size - 1] = '\0';
718 if(strncmp(c, "TCO", 3) == 0) {
719 /* strncpy(genrebuf,c+7,size-1); */
720 /* genrebuf[size-1]='\0'; */
721 /* genre=atoi(&genrebuf[1]); */
726 if(version == 3 || version == 4)
727 while(c < buffer + size) {
729 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
733 if(strncmp(c, "TIT2", 4) == 0) {
734 strncpy(title, c + 11, size - 1);
735 title[size - 1] = '\0';
737 if(strncmp(c, "TPE1", 4) == 0) {
738 strncpy(artist, c + 11, size - 1);
739 artist[size - 1] = '\0';
741 if(strncmp(c, "TCON", 4) == 0) {
742 /* strncpy(genrebuf,c+11,size-1); */
743 /* genrebuf[size-1]='\0'; */
744 /* genre=atoi(&genrebuf[1]); */
745 genre = atoi(c + 12);
751 while(c < buffer + lus - 10) {
752 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
757 version = 2 - (*(c + 1) >> 3 & 1);
758 lay = 4 - (*(c + 1) >> 1 & 3);
759 bitrate_index = *(c + 2) >> 4 & 0xF;
760 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
761 && bitrate_index >= 0 && bitrate_index <= 14)
762 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
766 fseek(fic, 0, SEEK_END);
767 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
776 if(strlen(artist) == 0 && strlen(title) == 0) {
777 fseek(fic, -128, SEEK_END);
778 lus = fread(buffer, 1, 128, fic);
779 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
780 && buffer[2] == 'G') {
781 strncpy(title, buffer + 3, 30);
784 while(c > title && *c == ' ')
786 strncpy(artist, buffer + 33, 30);
789 while(c > artist && *c == ' ')
791 /* strncpy(album,buffer+65,30); */
792 /* strncpy(year,buffer+97,4); */
793 /* strncpy(comment,buffer+101,30); */
794 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
802 void parse_ogg(unsigned char *file)
811 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
814 if((fic = fopen(file, "r")) == NULL) {
815 fprintf(stderr, "Warning >> can't open file : %s\n", file);
818 lus = fread(buffer, 1, OGG_BASE, fic);
821 if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
822 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
828 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
830 while(c < buffer + lus - 10) {
832 if(strncasecmp(c, "TITLE=", 6) == 0) {
834 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
836 strncpy(title, c + 6, size - 6);
837 title[size - 6] = '\0';
840 if(strncasecmp(c, "ARTIST=", 7) == 0) {
842 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
844 strncpy(artist, c + 7, size - 7);
845 artist[size - 7] = '\0';
848 if(strncasecmp(c, "GENRE=", 6) == 0) {
851 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
853 strncpy(genrebuf, c + 6, size - 6);
854 genrebuf[size - 6] = '\0';
856 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
857 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
861 if(i == ID3_NR_OF_V1_GENRES)
868 fseek(fic, -OGG_BASE, SEEK_END);
869 lus = fread(buffer, 1, OGG_BASE, fic);
870 c = buffer + lus - 1;
871 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
876 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
877 duration = samples / sample_rate;
884 void parse_mpc(unsigned char *file)
889 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
895 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
898 if((fic = fopen(file, "r")) == NULL) {
899 fprintf(stderr, "Warning >> can't open file : %s\n", file);
902 lus = fread(buffer, 1, 12, fic);
905 if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
906 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
912 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
920 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
922 duration = frame_count * 1152 / sample_rates[*c & 3];
924 /* try APETAGEX footer */
925 fseek(fic, -32, SEEK_END);
926 lus = fread(buffer, 1, 32, fic);
927 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
930 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
934 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
935 fseek(fic, -size, SEEK_END);
936 lus = fread(buffer, 1, size, fic);
937 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
941 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
944 if(strcasecmp(c, "TITLE") == 0) {
945 strncpy(title, c + 6, size);
948 if(strcasecmp(c, "ARTIST") == 0) {
949 strncpy(artist, c + 7, size);
952 if(strcasecmp(c, "GENRE") == 0) {
953 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
954 strncpy(genrebuf, c + 6, size);
955 genrebuf[size] = '\0';
957 (ID3_v1_genre_description[i], genrebuf) == 0) {
961 if(i == ID3_NR_OF_V1_GENRES)
965 c += strlen(c) + 1 + size;
974 #define MAXINO (1<<24)
975 #define INOTYP unsigned long
976 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
977 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
979 int hlink_check(struct stat *info)
982 * for speed this subroutine should only be called
983 * - if the file has more than one hardlink
984 * - if the file is a resolved softlink
986 /* the persistent variables */
987 static INOTYP *list[FSN];
988 static dev_t name[FSN];
989 /* some temporary variables */
990 int fsn, is_registered = 0;
992 /* assertions - in case parameters are lowered for less memory usage */
994 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
996 /* search which internal registration number is used for this filesystem */
997 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
999 /* if file system is not registered yet, do it and leave */
1000 if(name[fsn] == 0) {
1001 name[fsn] = (info->st_dev);
1002 /* provide space for the bitmap that maps the inodes of this file system */
1003 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1004 /* no comparison is needed in empty lists ... return */
1007 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1008 (int)name[fsn], (long)&list[fsn]);
1010 /* this looks more complicated than it really is */
1011 /* the idea is very simple:
1012 * provide a bitmap that maps all inodes of a file system
1013 * to mark all files that have already been visited.
1014 * If it is already visited, do not add it to the playlist
1017 * The difficulty is as follows:
1018 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1019 * would be byte-aligned and would allocate at least eight times the needed space.
1020 * Feel free to change the definitions that are involved here, if you know better.
1022 if(regbit_qry(list[fsn], (info->st_ino)))
1025 regbit_set(list[fsn], (info->st_ino));
1027 * the debug expression is more complicated then the working stuff
1030 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1031 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1032 (int)info->st_dev, (int)info->st_ino, fsn,
1033 (int)((info->st_ino) / sizeof(INOTYP)),
1034 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1035 1 << ((info->st_ino) % sizeof(INOTYP)),
1036 is_registered ? "Already" : "Not");
1038 return is_registered;
1042 void parse_file(unsigned char *newpath)
1044 unsigned char ext[5];
1045 int j, encoding = 0;
1047 for(j = 0; j < 5; j++)
1048 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1052 if(strcmp(".mp2", ext) == 0) {
1057 if(strcmp(".mp3", ext) == 0) {
1062 if(strcmp(".mpc", ext) == 0) {
1067 if(strcmp(".mp+", ext) == 0) {
1072 if(strcmp(".ogg", ext) == 0) {
1077 if(strcmp(".wav", ext) == 0) {
1078 duration = -1; /* parse_wav(newpath); */
1082 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1083 // there are no tag infos read
1084 // use file name to state substitute it
1085 char *c = strrchr(newpath, separator);
1088 strcpy(artist, ++c);
1089 // arbitrarily use the first '-'
1090 // to separate artist and title
1091 c = strchr(artist, '-');
1092 if(c != NULL) { // if trenner found, divide file name
1095 c = strrchr(title, '.');
1098 } else { // no trenner found, assume
1099 // no artist, only title
1100 strcpy(title, artist);
1103 // replace underscores by spaces
1104 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1106 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1112 /* guesstitle() end */
1114 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1118 if(duration != -1) {
1119 printf("#EXTINF:%d,", duration);
1120 if(strlen(artist) != 0)
1121 printf("%s - ", artist);
1122 printf("%s%s", title, eol);
1124 print_path(newpath);
1128 printf("File%d=", counter);
1129 print_path(newpath);
1130 printf("%sTitle%d=", eol, counter);
1131 if(strlen(artist) != 0)
1132 printf("%s - ", artist);
1133 printf("%s%s", title, eol);
1135 printf("Length%d=%d%s", counter, duration, eol);
1138 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1141 printf("?</td></tr>%s", eol);
1143 printf("%d:%s%d</td></tr>%s", duration / 60,
1144 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1147 if(duration != -1) {
1149 char timebuffer[256];
1151 if(stat(newpath, &infos) != 0) {
1152 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1156 strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime))); /* ctime() had a trailing CR */
1157 printf("\t<item>%s", eol);
1158 printf("\t\t<author>");
1160 printf("</author>%s\t\t<title>", eol);
1162 printf("</title>%s", eol);
1164 if(referal == NULL) {
1165 noreferal(newpath, artist, title);
1168 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1170 print_webpath(newpath);
1171 printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
1172 (int)infos.st_size, magic[encoding], eol);
1173 print_pathtail(newpath);
1174 printf("</guid>%s", eol);
1177 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1178 duration / 3600, (duration / 60) % 60,
1179 duration % 60, eol);
1182 ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
1183 duration / 60, duration % 60, eol);
1184 if(strlen(artist) != 0) {
1185 printf("\t\t<itunes:author>");
1187 printf("</itunes:author>%s", eol);
1189 printf("\t</item>%s", eol);
1192 case 4: // printing output for Sansa players
1193 myplaputstr("HARP, ");
1194 myplaputstr(newpath);
1197 case 5: //t-series playlist
1204 void parse_directory(unsigned char *path)
1207 struct dirent **namelist;
1208 unsigned char newpath[PATH_MAX];
1212 fprintf(stderr, "Debug >> parsing directory : %s\n", path);
1213 if(stat(path, &infos) != 0) {
1214 fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
1217 /* check if it is a filename */
1218 if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
1222 /* must be a directory - or something unusable like pipe, socket, etc */
1223 if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
1224 fprintf(stderr, "Warning >> can't open directory : %s\n", path);
1227 for(i = 0; i < n; i++) {
1228 sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
1230 if(stat(newpath, &infos) != 0) {
1231 fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
1234 if(recursive && S_ISDIR(infos.st_mode)
1235 && strcmp(namelist[i]->d_name, ".") != 0
1236 && strcmp(namelist[i]->d_name, "..") != 0)
1237 parse_directory(newpath);
1238 /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
1239 if(S_ISREG(infos.st_mode)
1240 && !(avoidhlinked && hlink_check(&infos))) {
1241 parse_file(newpath);
1248 int main(int argc, char **argv)
1250 winorunix = one2one;
1252 parse_options(argc, argv);
1253 if(optind == argc && !fromstdin)
1257 printf("#EXTM3U%s", eol);
1260 printf("[playlist]%s", eol);
1264 ("<!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 "
1266 "</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",
1267 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1268 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1269 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1270 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1271 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1277 char timebuffer[256];
1279 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1282 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1284 " -->%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 "
1286 "</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",
1287 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1288 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1289 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1290 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1291 eol, getenv("LANG"), eol, eol, eol);
1292 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1299 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1304 txxputheader(" iriver UMS PLA");
1308 unsigned char path[PATH_MAX];
1310 while(fgets(path, PATH_MAX, stdin)) {
1311 for(i = 0; i < PATH_MAX; i++)
1312 if(path[i] == '\r' || path[i] == '\n')
1314 parse_directory(path);
1317 for(; optind < argc; optind++) {
1318 parse_directory(argv[optind]);
1322 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1326 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1327 VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1331 printf(" </channel>%s</rss>%s", eol, eol);
1334 txxputcounter(counter);