17b02b8cb2d58fb7b260a8a2964fb5361c98f8e4
1 /*
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.
7 *
8 * Copyright (C) 2003-2004 Antoine Jacquet <royale@zerezo.com>
9 * http://royale.zerezo.com/fapg/
10 *
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.
15 *
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.
20 *
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
24 */
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <getopt.h>
29 #include <dirent.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <limits.h>
35 #include <unistd.h>
36 #include <ctype.h>
37 #include <time.h>
38 #include <assert.h>
39 #include "genres.h"
40 #ifdef HAVE_LIBURIPARSER
41 # include <uriparser/Uri.h>
42 #endif
44 #define MP3_BASE 1024
45 #define OGG_BASE 1024*10
46 #define MAX 1024*250 /* 250ko for ID3 with JPEG images in it */
48 #define FORMAT_M3U 0
49 #define FORMAT_PLS 1
50 #define FORMAT_HTML 2
51 #define FORMAT_RSS 3
52 #define FORMAT_PLP 4
53 #define FORMAT_UMS 5
54 #ifdef HAVE_LIBURIPARSER
55 # define FORMAT_XSPF 6
56 #endif
58 int debug = 0;
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;
67 //int windows=0;
68 int fromstdin = 0;
69 int recursive = 0;
70 int avoidhlinked = 0;
71 int separator = '/';
72 unsigned char *eol = "\n";
73 unsigned char buffer[MAX];
75 int counter = 0;
77 unsigned char artist[1024];
78 unsigned char title[1024];
79 unsigned char genrebuf[1024];
80 unsigned char genre = 0;
81 int duration;
82 #define MP2ENC 1
83 #define MP3ENC 2
84 #define MPCENC 3
85 #define MPPENC 4
86 #define OGGENC 5
87 #define WAVENC 6
88 #define WMAENC 7
90 char *magic[] = { NULL,
91 "audio/mpeg", "audio/mpeg",
92 "audio/mpeg", "audio/mpeg",
93 "audio/ogg-vorbis", "audio/x-wav",
94 "audio/x-ms-wma",
95 NULL
96 };
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
125 };
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"
230 };
232 void usage()
233 {
234 #ifdef HAVE_LIBURIPARSER
235 # define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
236 #else
237 # define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
238 #endif
239 fprintf(stderr,
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");
241 #undef FAPG_FORMATS
242 exit(1);
243 }
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)
249 {
250 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
251 putchar('\0');
252 }
254 void mywebputstr(const char *c)
255 {
256 while(*c != 0) {
257 mywebputchar(*c);
258 c++;
259 }
260 }
262 void myplaputstr(const char *c)
263 {
264 while(*c != 0) {
265 if(*c == '/')
266 myplaputchar('\\'); /* translate slash to backslash */
267 else
268 myplaputchar(*c);
269 c++;
270 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
271 while(*c == '/' && c[1] == '/')
272 c++;
273 }
274 }
276 void myputstr(const char *c)
277 {
278 while(*c != 0) {
279 if(*c == '/')
280 putchar(separator);
281 else
282 myputchar(*c);
283 c++;
284 /* remove multiple slashes "//" when parsing a directory ending with a "/" */
285 while(*c == '/' && c[1] == '/')
286 c++;
287 }
288 }
290 void txxputheader(const char *c)
291 {
292 int cnt = 0;
294 while(*c != 0) {
295 myputchar(*c);
296 cnt++;
297 c++;
298 }
300 while(cnt < 512) {
301 putchar('\0');
302 cnt++;
303 }
304 }
306 void txxputnameoffset(const char *c)
307 {
308 int pos = 0;
309 int cnt = 0;
310 char b;
311 unsigned char *prefx;
313 prefx = prefix;
315 if(*prefx != 0) {
316 while(*prefx != 0) {
317 if(*prefx == '/') {
318 pos = cnt;
319 }
320 cnt++;
321 prefx++;
322 }
324 cnt--; // skip the leading dot of the filepath
325 }
327 while(*c != 0) {
328 if(*c == '/') {
329 pos = cnt;
330 }
331 cnt++;
332 c++;
333 }
335 pos += 2;
337 b = (pos & 0xFF00) >> 8;
338 putchar(b);
339 b = (pos & 0x00FF);
340 putchar(b);
341 }
343 void txxputstr(const char *c)
344 {
345 int cnt = 0;
346 int pos;
347 unsigned char *prefx;
349 txxputnameoffset(c);
351 prefx = prefix;
352 fprintf(stderr, "prefix: '%s'\n", prefx);
354 if(*prefx != 0) {
355 while(*prefx != 0) {
356 myputchar('\0');
357 cnt++;
359 if(*prefx == '/')
360 putchar(separator);
361 else
362 myputchar(*prefx);
363 cnt++;
365 prefx++;
366 }
368 c++; // skip the leading dot
369 }
371 while(*c != 0) {
372 myputchar('\0');
373 cnt++;
375 if(*c == '/')
376 putchar(separator);
377 else
378 myputchar(*c);
379 cnt++;
381 c++;
382 }
384 while(cnt < 510) {
385 myputchar('\0');
386 cnt++;
387 }
388 }
390 void txxputcounter(int c)
391 {
392 int b;
394 rewind(stdout);
396 b = (c & 0xFF000000) >> 24;
397 putchar(b);
398 b = (c & 0x00FF0000) >> 16;
399 putchar(b);
400 b = (c & 0x0000FF00) >> 8;
401 putchar(b);
402 b = (c & 0x000000FF);
403 putchar(b);
404 }
406 /* remove spaces at beginning and end of string */
407 void trim(char *c)
408 {
409 char *p;
410 /* remove spaces at beginning ... */
411 while(*c == ' ') {
412 p = c;
413 while(*p != '\0') {
414 *p = *(p + 1);
415 p++;
416 }
417 }
418 /* ... and end of string */
419 p = c + strlen(c);
420 while(--p > c && *p == ' ')
421 *p = '\0';
422 }
424 void print_webpath(const char *path)
425 {
426 const char *c = path;
428 printf(prefix); /* we must not modify this part */
429 if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */
430 c += 2;
431 /* maybe there follow many slashes */
432 while(*c == '/')
433 c++;
434 }
435 for(; *c != '\0'; c++) {
436 mywebputchar(*c);
437 /* remove multiple "//" when parsing a directory ending with a "/" */
438 while(*c == '/' && c[1] == '/')
439 c++;
440 }
441 }
443 void print_path(const char *path)
444 {
445 const char *c = path;
446 printf(prefix);
447 /* skip leading "./" when parsing current directory */
448 if(*c == '.' && *(c + 1) == '/') {
449 c += 2;
450 /* maybe there follow more slashes */
451 while(*c == '/')
452 c++;
453 }
454 myputstr(c);
455 }
457 void print_pathtail(const char *path)
458 {
459 const char *c;
460 c = strrchr(path, separator);
461 if(c != NULL)
462 c++;
463 else
464 c = path;
465 myputstr(c);
466 }
468 void noreferal(const char *path, const char *artist, const char *title)
469 {
470 printf("\t\t<description><![CDATA[<h4>");
471 myputstr(artist);
472 printf("</h4><h5>");
473 myputstr(title);
474 printf("</h5><a href=\"");
475 print_webpath(path);
476 printf
477 ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
478 eol);
479 }
481 void reference(const char *title)
482 {
483 FILE *pipe = NULL;
484 static char command[2048], buffer[1024];
485 int buflen = 8192;
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;
498 if(debug)
499 fprintf(stderr, "Debug >> processing command: %s\n", command);
500 pipe = popen(command, "r");
501 if(pipe == NULL) {
502 fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
503 free(command);
504 return;
505 }
506 fgets(buffer, 1020, pipe);
507 while(!feof(pipe)) {
508 fputs(buffer, stdout);
509 fgets(buffer, 1020, pipe);
510 }
511 pclose(pipe);
512 return;
513 }
515 void parse_options(int argc, char **argv)
516 {
517 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
518 static struct option long_options[] = {
519 {"backslash", no_argument, NULL, 'b'},
520 {"command", required_argument, NULL, 'c'},
521 {"debug", no_argument, NULL, 'd'},
522 {"format", required_argument, NULL, 'f'},
523 {"genre", required_argument, NULL, 'g'},
524 {"nohardlink", no_argument, NULL, 'n'},
525 {"output", required_argument, NULL, 'o'},
526 {"prefix", required_argument, NULL, 'p'},
527 {"recursive", no_argument, NULL, 'r'},
528 {"stdin", no_argument, NULL, 's'},
529 {"windows", no_argument, NULL, 'w'},
530 {"exclude", required_argument, NULL, 'x'},
531 {NULL, 0, NULL, 0}
532 };
533 int c;
534 int option_index = 0;
535 while((c =
536 getopt_long(argc, argv, short_options, long_options,
537 &option_index)) != -1) {
538 switch (c) {
539 case 'b':
540 separator = '\\';
541 noand['/'] = '\\';
542 break;
543 case 'c':
544 if(strncmp(optarg, "intern", 6) == 0)
545 referal = NULL;
546 else
547 referal = strdup(optarg);
548 break;
549 case 'd':
550 debug = 1 - debug;
551 break;
552 case 'f':
553 if(strcmp(optarg, "m3u") == 0)
554 format = FORMAT_M3U;
555 else if(strcmp(optarg, "pls") == 0)
556 format = FORMAT_PLS;
557 else if(strcmp(optarg, "html") == 0)
558 format = FORMAT_HTML;
559 else if(strcmp(optarg, "rss") == 0)
560 format = FORMAT_RSS;
561 else if(strcmp(optarg, "pla") == 0)
562 format = FORMAT_PLP;
563 else if(strcmp(optarg, "txx") == 0)
564 format = FORMAT_UMS;
565 #ifdef HAVE_LIBURIPARSER
566 else if(strcmp(optarg, "xspf") == 0)
567 format = FORMAT_XSPF;
568 #endif
569 else
570 usage();
571 break;
572 case 'g':
573 if(genrelist == NULL)
574 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
575 if(genrelist == NULL) {
576 fprintf(stderr,
577 "Error >> unable to allocate cleared memory\n");
578 exit(2);
579 } else {
580 unsigned int n = 0;
581 while(n < strlen(optarg)) {
582 if(debug)
583 fprintf(stderr,
584 "Debug >> genrelist entry activting : %d\n",
585 atoi(&optarg[n]));
586 genrelist[atoi(&optarg[n])] = 1;
587 while(isdigit(optarg[n++]));
588 }
589 }
590 break;
591 case 'n':
592 avoidhlinked = 1;
593 break;
594 case 'o':
595 close(1);
596 if(fopen(optarg, "w") == NULL) {
597 fprintf(stderr,
598 "Error >> unable to open output file : %s\n",
599 optarg);
600 exit(2);
601 }
602 break;
603 case 'p':
604 prefix = malloc(strlen(optarg) + 1);
605 strcpy(prefix, optarg);
606 base = malloc(strlen(prefix) + 1);
607 strcpy(base, prefix);
608 dir = strchr(base, '/');
609 if((dir != NULL) && (dir[1] == '/'))
610 dir = strchr(dir + 2, '/');
611 if(dir != NULL)
612 *dir++ = 0;
613 else
614 dir = "";
615 /* if prefix is a weblink, base is the baselink, dir is the path */
616 break;
617 case 'r':
618 recursive = 1;
619 break;
620 case 'u':
621 winorunix = one2one;
622 eol = "\n";
623 break;
624 case 'w':
625 winorunix = unix2dos;
626 eol = "\r\n";
627 break;
628 case 'x':
629 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
630 int n = 0;
631 genrelist = calloc(257, sizeof(char));
632 while(n < 256)
633 genrelist[n++] = 1;
634 }
635 if(genrelist == NULL) {
636 fprintf(stderr,
637 "Error >> unable to allocate cleared memory\n");
638 exit(2);
639 } else {
640 unsigned int n = 0;
641 while(n < strlen(optarg)) {
642 if(debug)
643 fprintf(stderr,
644 "Debug >> genrelist entry activting : %d\n",
645 atoi(&optarg[n]));
646 genrelist[atoi(&optarg[n])] = 0;
647 while(isdigit(optarg[n++]));
648 }
649 }
650 break;
651 case 's':
652 fromstdin = 1;
653 break;
654 default:
655 usage();
656 }
657 }
658 /* hostname = getenv("HOSTNAME"); */
659 if(genrelist == NULL) {
660 genrelist = calloc(257, sizeof(char));
661 if(genrelist == NULL) {
662 fprintf(stderr,
663 "Error >> unable to allocate cleared memory\n");
664 exit(2);
665 } else {
666 int n = 0;
667 while(n < 256)
668 genrelist[n++] = 1;
669 }
670 }
671 }
673 void parse_mp3(unsigned char *file)
674 {
675 int bitrates[2][3][15] =
676 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
677 416, 448},
678 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
679 384},
680 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
681 320}},
682 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
683 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
684 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
685 };
686 FILE *fic;
687 unsigned char *c;
688 int lus;
690 genre = 0;
691 genrebuf[0] = 0;
692 if(debug)
693 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
695 /* read header */
696 if((fic = fopen(file, "r")) == NULL) {
697 fprintf(stderr, "Warning >> can't open file : %s\n", file);
698 return;
699 }
700 lus = fread(buffer, 1, MP3_BASE, fic);
701 c = buffer;
703 /* try ID3v2 */
704 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
705 int size;
706 int version;
707 version = *(buffer + 3);
708 if(version < 2 || version > 4)
709 fprintf(stderr,
710 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
711 version, file);
712 if(*(buffer + 5) != 0)
713 fprintf(stderr,
714 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
715 *(buffer + 5), file);
716 c = buffer + 6;
717 size =
718 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
719 /* read more header */
720 if(size + lus > MAX) {
721 lus += fread(buffer + lus, 1, MAX - lus, fic);
722 fprintf(stderr,
723 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
724 size, file);
725 } else
726 lus += fread(buffer + lus, 1, size, fic);
727 if(size > lus)
728 size = lus;
729 c += 4;
730 if(version == 2)
731 while(c < buffer + size) {
732 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
733 if(*c == 0)
734 break;
735 if(strncmp(c, "TT2", 3) == 0) {
736 strncpy(title, c + 7, size - 1);
737 title[size - 1] = '\0';
738 }
739 if(strncmp(c, "TP1", 3) == 0) {
740 strncpy(artist, c + 7, size - 1);
741 artist[size - 1] = '\0';
742 }
743 if(strncmp(c, "TCO", 3) == 0) {
744 /* strncpy(genrebuf,c+7,size-1); */
745 /* genrebuf[size-1]='\0'; */
746 /* genre=atoi(&genrebuf[1]); */
747 genre = atoi(c + 8);
748 }
749 c += size + 6;
750 }
751 if(version == 3 || version == 4)
752 while(c < buffer + size) {
753 int size =
754 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
755 (*(c + 7));
756 if(*c == 0)
757 break;
758 if(strncmp(c, "TIT2", 4) == 0) {
759 strncpy(title, c + 11, size - 1);
760 title[size - 1] = '\0';
761 }
762 if(strncmp(c, "TPE1", 4) == 0) {
763 strncpy(artist, c + 11, size - 1);
764 artist[size - 1] = '\0';
765 }
766 if(strncmp(c, "TCON", 4) == 0) {
767 /* strncpy(genrebuf,c+11,size-1); */
768 /* genrebuf[size-1]='\0'; */
769 /* genre=atoi(&genrebuf[1]); */
770 genre = atoi(c + 12);
771 }
772 c += size + 10;
773 }
774 }
776 while(c < buffer + lus - 10) {
777 if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
778 int version;
779 int lay;
780 int bitrate_index;
781 int bitrate;
782 version = 2 - (*(c + 1) >> 3 & 1);
783 lay = 4 - (*(c + 1) >> 1 & 3);
784 bitrate_index = *(c + 2) >> 4 & 0xF;
785 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
786 && bitrate_index >= 0 && bitrate_index <= 14)
787 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
788 else
789 bitrate = 0;
790 if(bitrate != 0) {
791 fseek(fic, 0, SEEK_END);
792 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
793 } else
794 duration = 0;
795 break;
796 }
797 c++;
798 }
800 /* try ID3v1 */
801 if(strlen(artist) == 0 && strlen(title) == 0) {
802 fseek(fic, -128, SEEK_END);
803 lus = fread(buffer, 1, 128, fic);
804 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
805 && buffer[2] == 'G') {
806 strncpy(title, buffer + 3, 30);
807 title[30] = '\0';
808 c = title + 29;
809 while(c > title && *c == ' ')
810 *(c--) = '\0';
811 strncpy(artist, buffer + 33, 30);
812 artist[30] = '\0';
813 c = artist + 29;
814 while(c > artist && *c == ' ')
815 *(c--) = '\0';
816 /* strncpy(album,buffer+65,30); */
817 /* strncpy(year,buffer+97,4); */
818 /* strncpy(comment,buffer+101,30); */
819 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
820 genre = buffer[127];
821 }
822 }
824 fclose(fic);
825 }
827 void parse_ogg(unsigned char *file)
828 {
829 FILE *fic;
830 unsigned char *c;
831 int lus;
832 int sample_rate;
833 int samples;
835 if(debug)
836 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
838 /* read header */
839 if((fic = fopen(file, "r")) == NULL) {
840 fprintf(stderr, "Warning >> can't open file : %s\n", file);
841 return;
842 }
843 lus = fread(buffer, 1, OGG_BASE, fic);
845 /* try Ogg */
846 if(strncmp(buffer, "Ogg", 3) != 0) {
847 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
848 return;
849 }
851 c = buffer + 0x28;
852 sample_rate =
853 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
855 while(c < buffer + lus - 10) {
856 int size;
857 if(strncasecmp(c, "TITLE=", 6) == 0) {
858 size =
859 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
860 (*(c - 1) << 24);
861 strncpy(title, c + 6, size - 6);
862 title[size - 6] = '\0';
863 c += size;
864 }
865 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
866 // ignore tag
867 size =
868 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
869 (*(c - 1) << 24);
870 c += size;
871 }
872 if(strncasecmp(c, "ARTIST=", 7) == 0) {
873 size =
874 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
875 (*(c - 1) << 24);
876 strncpy(artist, c + 7, size - 7);
877 artist[size - 7] = '\0';
878 c += size;
879 }
880 if(strncasecmp(c, "GENRE=", 6) == 0) {
881 static int i = 0;
882 size =
883 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
884 (*(c - 1) << 24);
885 strncpy(genrebuf, c + 6, size - 6);
886 genrebuf[size - 6] = '\0';
887 c += size;
888 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
889 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
890 genre = i;
891 break;
892 }
893 if(i == ID3_NR_OF_V1_GENRES)
894 genre = 0;
895 }
896 }
897 c++;
898 }
900 fseek(fic, -OGG_BASE, SEEK_END);
901 lus = fread(buffer, 1, OGG_BASE, fic);
902 c = buffer + lus - 1;
903 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
904 c--;
905 if(c != buffer) {
906 c += 6;
907 samples =
908 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
909 duration = samples / sample_rate;
910 }
912 fclose(fic);
913 }
915 void parse_mpc(unsigned char *file)
916 {
917 FILE *fic;
918 unsigned char *c;
919 int lus;
920 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
921 int frame_count;
922 int size, items;
923 int i;
925 if(debug)
926 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
928 /* read header */
929 if((fic = fopen(file, "r")) == NULL) {
930 fprintf(stderr, "Warning >> can't open file : %s\n", file);
931 return;
932 }
933 lus = fread(buffer, 1, 12, fic);
935 /* try Musepack */
936 if (strncmp(buffer, "MP+", 3) != 0) {
937 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
938 return;
939 }
941 /* only version 7 */
942 if(buffer[3] != 7) {
943 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
944 file);
945 return;
946 }
948 /* duration */
949 c = buffer + 4;
950 frame_count =
951 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
952 c += 5;
953 duration = frame_count * 1152 / sample_rates[*c & 3];
955 /* try APETAGEX footer */
956 fseek(fic, -32, SEEK_END);
957 lus = fread(buffer, 1, 32, fic);
958 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
959 c = buffer + 12;
960 size =
961 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
962 size += 32;
963 c += 4;
964 items =
965 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
966 fseek(fic, -size, SEEK_END);
967 lus = fread(buffer, 1, size, fic);
968 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
969 c = buffer + 32;
970 while(items--) {
971 size =
972 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
973 (*(c + 3) << 24);
974 c += 8;
975 if(strcasecmp(c, "TITLE") == 0) {
976 strncpy(title, c + 6, size);
977 title[size] = '\0';
978 }
979 if(strcasecmp(c, "ARTIST") == 0) {
980 strncpy(artist, c + 7, size);
981 artist[size] = '\0';
982 }
983 if(strcasecmp(c, "GENRE") == 0) {
984 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
985 strncpy(genrebuf, c + 6, size);
986 genrebuf[size] = '\0';
987 if(strcasecmp
988 (ID3_v1_genre_description[i], genrebuf) == 0) {
989 genre = i;
990 break;
991 }
992 if(i == ID3_NR_OF_V1_GENRES)
993 genre = 0;
994 }
995 }
996 c += strlen(c) + 1 + size;
997 }
998 }
999 }
1001 fclose(fic);
1002 }
1004 #define FSN 32
1005 #define MAXINO (1<<24)
1006 #define INOTYP unsigned long
1007 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1008 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1010 int hlink_check(struct stat *info)
1011 {
1012 /*
1013 * for speed this subroutine should only be called
1014 * - if the file has more than one hardlink
1015 * - if the file is a resolved softlink
1016 */
1017 /* the persistent variables */
1018 static INOTYP *list[FSN];
1019 static dev_t name[FSN];
1020 /* some temporary variables */
1021 int fsn, is_registered = 0;
1023 /* assertions - in case parameters are lowered for less memory usage */
1024 assert(fsn < FSN);
1025 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1027 /* search which internal registration number is used for this filesystem */
1028 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1030 /* if file system is not registered yet, do it and leave */
1031 if(name[fsn] == 0) {
1032 name[fsn] = (info->st_dev);
1033 /* provide space for the bitmap that maps the inodes of this file system */
1034 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1035 /* no comparison is needed in empty lists ... return */
1036 if(debug)
1037 fprintf(stderr,
1038 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1039 (int)name[fsn], (long)&list[fsn]);
1040 } else {
1041 /* this looks more complicated than it really is */
1042 /* the idea is very simple:
1043 * provide a bitmap that maps all inodes of a file system
1044 * to mark all files that have already been visited.
1045 * If it is already visited, do not add it to the playlist
1046 */
1047 /*
1048 * The difficulty is as follows:
1049 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1050 * would be byte-aligned and would allocate at least eight times the needed space.
1051 * Feel free to change the definitions that are involved here, if you know better.
1052 */
1053 if(regbit_qry(list[fsn], (info->st_ino)))
1054 is_registered = 1;
1055 else
1056 regbit_set(list[fsn], (info->st_ino));
1057 /*
1058 * the debug expression is more complicated then the working stuff
1059 */
1060 if(debug)
1061 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1062 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1063 (int)info->st_dev, (int)info->st_ino, fsn,
1064 (int)((info->st_ino) / sizeof(INOTYP)),
1065 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1066 1 << ((info->st_ino) % sizeof(INOTYP)),
1067 is_registered ? "Already" : "Not");
1068 }
1069 return is_registered;
1070 }
1072 #ifdef HAVE_LIBURIPARSER
1073 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1074 {
1075 char * absSourceFile;
1076 size_t absSourceLen;
1077 char * sourceUriString;
1078 char * baseUriString;
1079 UriParserStateA state;
1080 UriUriA sourceUri;
1081 UriUriA baseUri;
1082 UriUriA relativeUri;
1083 int charsRequired;
1084 char * output;
1086 /* checks */
1087 if ((unixFilename == NULL) || (baseDir == NULL)) {
1088 return NULL;
1089 }
1091 /* base URI */
1092 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1093 if (baseUriString == NULL) {
1094 return NULL;
1095 }
1096 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1097 free(baseUriString);
1098 return NULL;
1099 }
1100 state.uri = &baseUri;
1101 if (uriParseUriA(&state, baseUriString) != 0) {
1102 free(baseUriString);
1103 uriFreeUriMembersA(&baseUri);
1104 return NULL;
1105 }
1107 /* source URI */
1108 if (unixFilename[0] != '/') {
1109 const int baseDirLen = strlen(baseDir);
1110 const int sourceFileLen = strlen(unixFilename);
1111 absSourceLen = baseDirLen + sourceFileLen;
1112 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1113 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1114 } else {
1115 absSourceLen = strlen(unixFilename);
1116 absSourceFile = (char *)unixFilename;
1117 }
1118 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1119 if (sourceUriString == NULL) {
1120 free(baseUriString);
1121 if (unixFilename[0] != '/') {
1122 free(absSourceFile);
1123 }
1124 uriFreeUriMembersA(&baseUri);
1125 return NULL;
1126 }
1127 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1128 free(baseUriString);
1129 free(sourceUriString);
1130 if (unixFilename[0] != '/') {
1131 free(absSourceFile);
1132 }
1133 uriFreeUriMembersA(&baseUri);
1134 return NULL;
1135 }
1136 state.uri = &sourceUri;
1137 if (uriParseUriA(&state, sourceUriString) != 0) {
1138 free(baseUriString);
1139 free(sourceUriString);
1140 uriFreeUriMembersA(&baseUri);
1141 uriFreeUriMembersA(&sourceUri);
1142 return NULL;
1143 }
1144 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1145 free(baseUriString);
1146 free(sourceUriString);
1147 if (unixFilename[0] != '/') {
1148 free(absSourceFile);
1149 }
1150 uriFreeUriMembersA(&baseUri);
1151 uriFreeUriMembersA(&sourceUri);
1152 return NULL;
1153 }
1155 /* make relative (or keep absolute if necessary) */
1156 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1157 free(baseUriString);
1158 free(sourceUriString);
1159 if (unixFilename[0] != '/') {
1160 free(absSourceFile);
1161 }
1162 uriFreeUriMembersA(&baseUri);
1163 uriFreeUriMembersA(&sourceUri);
1164 uriFreeUriMembersA(&relativeUri);
1165 return NULL;
1166 }
1168 /* back to string */
1169 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1170 free(baseUriString);
1171 free(sourceUriString);
1172 if (unixFilename[0] != '/') {
1173 free(absSourceFile);
1174 }
1175 uriFreeUriMembersA(&baseUri);
1176 uriFreeUriMembersA(&sourceUri);
1177 uriFreeUriMembersA(&relativeUri);
1178 return NULL;
1179 }
1180 output = malloc((charsRequired + 1) * sizeof(char));
1181 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1182 free(baseUriString);
1183 free(sourceUriString);
1184 if (unixFilename[0] != '/') {
1185 free(absSourceFile);
1186 }
1187 free(output);
1188 uriFreeUriMembersA(&baseUri);
1189 uriFreeUriMembersA(&sourceUri);
1190 uriFreeUriMembersA(&relativeUri);
1191 return NULL;
1192 }
1194 free(baseUriString);
1195 free(sourceUriString);
1196 if (unixFilename[0] != '/') {
1197 free(absSourceFile);
1198 }
1199 uriFreeUriMembersA(&baseUri);
1200 uriFreeUriMembersA(&sourceUri);
1201 uriFreeUriMembersA(&relativeUri);
1203 return output;
1204 }
1206 char * xml_escape_malloc(const char * input)
1207 {
1208 const char * read = input;
1209 char * output;
1210 char * write;
1212 if (input == NULL) {
1213 return NULL;
1214 }
1216 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1217 if (output == NULL) {
1218 return NULL;
1219 }
1220 write = output;
1222 for (;;) {
1223 if (*read == '\0') {
1224 *write = '\0';
1225 return output;
1226 }
1228 switch ((unsigned char)*read) {
1229 case '&':
1230 strcpy(write, "&");
1231 write += 5;
1232 break;
1233 case '<':
1234 strcpy(write, "<");
1235 write += 4;
1236 break;
1237 case '>':
1238 strcpy(write, ">");
1239 write += 4;
1240 break;
1241 case '\'':
1242 strcpy(write, "'");
1243 write += 6;
1244 break;
1245 case '"':
1246 strcpy(write, """);
1247 write += 6;
1248 break;
1249 default:
1250 *(write++) = *read;
1251 }
1252 read++;
1253 }
1254 }
1255 #endif
1257 void parse_file(unsigned char *newpath, unsigned char * original_path)
1258 {
1259 unsigned char ext[5];
1260 int j, encoding = 0;
1262 for(j = 0; j < 5; j++)
1263 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1264 artist[0] = '\0';
1265 title[0] = '\0';
1266 duration = -2;
1267 if(strcmp(".mp2", ext) == 0) {
1268 duration = -1;
1269 parse_mp3(newpath);
1270 encoding = MP2ENC;
1271 }
1272 if(strcmp(".mp3", ext) == 0) {
1273 duration = -1;
1274 parse_mp3(newpath);
1275 encoding = MP3ENC;
1276 }
1277 if(strcmp(".mpc", ext) == 0) {
1278 duration = -1;
1279 parse_mpc(newpath);
1280 encoding = MPCENC;
1281 }
1282 if(strcmp(".mp+", ext) == 0) {
1283 duration = -1;
1284 parse_mpc(newpath);
1285 encoding = MPPENC;
1286 }
1287 if(strcmp(".ogg", ext) == 0) {
1288 duration = -1;
1289 parse_ogg(newpath);
1290 encoding = OGGENC;
1291 }
1292 if(strcmp(".wav", ext) == 0) {
1293 duration = -1;
1294 /* parse_wav(newpath); */
1295 encoding = WAVENC;
1296 }
1297 if(strcmp(".wma", ext) == 0) {
1298 duration = -1;
1299 /* parse_wma(newpath); */
1300 encoding = WMAENC;
1301 }
1302 /* guesstitle() */
1303 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1304 // there are no tag infos read
1305 // use file name to state substitute it
1306 char *c = strrchr(newpath, separator);
1307 if(c == NULL)
1308 c = newpath;
1309 strcpy(artist, ++c);
1310 // arbitrarily use the first '-'
1311 // to separate artist and title
1312 c = strchr(artist, '-');
1313 if(c != NULL) { // if trenner found, divide file name
1314 *c = '\0';
1315 strcpy(title, ++c);
1316 c = strrchr(title, '.');
1317 if(c != NULL)
1318 *c = '\0';
1319 } else { // no trenner found, assume
1320 // no artist, only title
1321 strcpy(title, artist);
1322 artist[0] = '\0';
1323 }
1324 // replace underscores by spaces
1325 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1326 *c = ' ';
1327 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1328 *c = ' ';
1329 // trim spaces
1330 trim(artist);
1331 trim(title);
1332 }
1333 /* guesstitle() end */
1335 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1336 counter++;
1337 switch (format) {
1338 case FORMAT_M3U:
1339 if(duration != -1) {
1340 printf("#EXTINF:%d,", duration);
1341 if(strlen(artist) != 0)
1342 printf("%s - ", artist);
1343 printf("%s%s", title, eol);
1344 }
1345 print_path(newpath);
1346 printf("%s", eol);
1347 break;
1348 case FORMAT_PLS:
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);
1355 if(duration != -1)
1356 printf("Length%d=%d%s", counter, duration, eol);
1357 break;
1358 case FORMAT_HTML:
1359 printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
1360 artist, title);
1361 if(duration == -1)
1362 printf("?</td></tr>%s", eol);
1363 else
1364 printf("%d:%s%d</td></tr>%s", duration / 60,
1365 duration % 60 < 10 ? "0" : "", duration % 60, eol);
1366 break;
1367 case FORMAT_RSS:
1368 if(duration != -1) {
1369 struct stat infos;
1370 char timebuffer[256];
1372 if(stat(newpath, &infos) != 0) {
1373 fprintf(stderr, "Warning >> can't stat entry : %s\n",
1374 newpath);
1375 return;
1376 }
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>");
1380 myputstr(artist);
1381 printf("</author>%s\t\t<title>", eol);
1382 myputstr(title);
1383 printf("</title>%s", eol);
1385 if(referal == NULL) {
1386 noreferal(newpath, artist, title);
1387 } else
1388 reference(newpath);
1389 printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
1390 timebuffer, eol);
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);
1396 if(duration > 3599)
1397 printf
1398 ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
1399 duration / 3600, (duration / 60) % 60,
1400 duration % 60, eol);
1401 else
1402 printf
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>");
1407 myputstr(artist);
1408 printf("</itunes:author>%s", eol);
1409 }
1410 printf("\t</item>%s", eol);
1411 }
1412 break;
1413 case FORMAT_PLP:
1414 myplaputstr("HARP, ");
1415 myplaputstr(newpath);
1416 myplaputstr(eol);
1417 break;
1418 case FORMAT_UMS:
1419 txxputstr(newpath);
1420 break;
1421 #ifdef HAVE_LIBURIPARSER
1422 case FORMAT_XSPF:
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);
1429 }
1430 }
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);
1436 }
1437 }
1438 if (duration > 0) {
1439 printf(" <duration>%d</duration>\n", duration);
1440 }
1441 {
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);
1450 }
1451 free(relative_location);
1452 }
1453 }
1454 printf("</track>\n");
1455 break;
1456 #endif
1457 }
1458 }
1459 }
1461 void parse_directory(unsigned char *path, unsigned char * original_path)
1462 {
1463 int i, n;
1464 struct dirent **namelist;
1465 unsigned char newpath[PATH_MAX];
1466 struct stat infos;
1468 if(debug)
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);
1472 return;
1473 }
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);
1477 return;
1478 }
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);
1482 return;
1483 }
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);
1489 continue;
1490 }
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);
1499 }
1500 free(namelist[i]);
1501 }
1502 free(namelist);
1503 }
1505 int main(int argc, char **argv)
1506 {
1507 winorunix = one2one;
1508 basemap = one2one;
1509 parse_options(argc, argv);
1511 if(optind == argc && !fromstdin)
1512 usage();
1514 /* print header */
1515 switch (format) {
1516 case FORMAT_M3U:
1517 printf("#EXTM3U%s", eol);
1518 break;
1519 case FORMAT_PLS:
1520 printf("[playlist]%s", eol);
1521 break;
1522 case FORMAT_HTML:
1523 printf
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 "
1525 VERSION
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,
1532 eol, eol, eol);
1533 break;
1534 case FORMAT_RSS:
1535 {
1536 time_t zeit;
1537 char timebuffer[256];
1538 time(&zeit);
1539 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1540 localtime(&zeit));
1541 printf
1542 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
1543 VERSION
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 "
1545 VERSION
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
1553 basemap = noand;
1554 }
1555 break;
1556 case FORMAT_PLP:
1557 {
1558 eol = "\r\n";
1559 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1560 }
1561 break;
1562 case FORMAT_UMS:
1563 {
1564 txxputheader(" iriver UMS PLA");
1565 }
1566 break;
1567 #ifdef HAVE_LIBURIPARSER
1568 case FORMAT_XSPF:
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"
1572 "<trackList>\n");
1573 break;
1574 #endif
1575 }
1577 /* iterate through files */
1578 {
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);
1584 if(fromstdin) {
1585 unsigned char path[PATH_MAX];
1586 int i;
1587 while(fgets(path, PATH_MAX, stdin)) {
1588 for(i = 0; i < PATH_MAX; i++)
1589 if(path[i] == '\r' || path[i] == '\n')
1590 path[i] = '\0';
1591 if (i <= 0) {
1592 continue;
1593 }
1595 /* strip trailing slash */
1596 if (path[i - 1] == '/') {
1597 path[i - 1] = '\0';
1598 }
1600 parse_directory(path, pwd);
1601 }
1602 } else
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';
1609 }
1611 parse_directory(dup, pwd);
1612 }
1614 free(pwd);
1615 }
1617 /* print footer */
1618 switch (format) {
1619 case FORMAT_PLS:
1620 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1621 break;
1622 case FORMAT_HTML:
1623 printf
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,
1626 eol, eol);
1627 break;
1628 case FORMAT_RSS:
1629 printf(" </channel>%s</rss>%s", eol, eol);
1630 break;
1631 case FORMAT_UMS:
1632 txxputcounter(counter);
1633 break;
1634 #ifdef HAVE_LIBURIPARSER
1635 case FORMAT_XSPF:
1636 printf("</trackList>\n"
1637 "</playlist>\n");
1638 break;
1639 #endif
1640 }
1642 if(genrelist)
1643 free(genrelist);
1645 exit(0);
1646 }