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 */
25
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
43
44 #define MP3_BASE 1024
45 #define OGG_BASE 1024*10
46 #define MAX 1024*250 /* 250ko for ID3 with JPEG images in it */
47
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
57
58 int debug = 0;
59 int format = FORMAT_M3U;
60 static char *genrelist = NULL;
61 static char *prefix = "";
62 static char *base = "";
63 static char *dir = "";
64 static const char *hostname = "fritzserver.de";
65 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
66 static const char *referal = NULL;
67 //int windows=0;
68 int fromstdin = 0;
69 int recursive = 0;
70 int avoidhlinked = 0;
71 int separator = '/';
72 static const char *eol = "\n";
73 static char buffer[MAX];
74
75 int counter = 0;
76
77 static char artist[1024];
78 static char title[1024];
79 static char genrebuf[1024];
80 static 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
89
90 static const 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 };
97
98 static 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 };
126
127 unsigned char *basemap;
128 unsigned char *winorunix;
129 static 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 */
162
163 static 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 '+' */
196
197 static const 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 };
231
usage()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 }
244
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');} */
myplaputchar(const char x)248 void myplaputchar(const char x)
249 {
250 putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
251 putchar('\0');
252 }
253
mywebputstr(const char * c)254 void mywebputstr(const char *c)
255 {
256 while(*c != 0) {
257 mywebputchar(*c);
258 c++;
259 }
260 }
261
myplaputstr(const char * c)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 }
275
myputstr(const char * c)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 }
289
txxputheader(const char * c)290 void txxputheader(const char *c)
291 {
292 int cnt = 0;
293
294 while(*c != 0) {
295 myputchar(*c);
296 cnt++;
297 c++;
298 }
299
300 while(cnt < 512) {
301 putchar('\0');
302 cnt++;
303 }
304 }
305
txxputnameoffset(const char * c)306 void txxputnameoffset(const char *c)
307 {
308 int pos = 0;
309 int cnt = 0;
310 char b;
311 char *prefx;
312
313 prefx = prefix;
314
315 if(*prefx != 0) {
316 while(*prefx != 0) {
317 if(*prefx == '/') {
318 pos = cnt;
319 }
320 cnt++;
321 prefx++;
322 }
323
324 cnt--; // skip the leading dot of the filepath
325 }
326
327 while(*c != 0) {
328 if(*c == '/') {
329 pos = cnt;
330 }
331 cnt++;
332 c++;
333 }
334
335 pos += 2;
336
337 b = (pos & 0xFF00) >> 8;
338 putchar(b);
339 b = (pos & 0x00FF);
340 putchar(b);
341 }
342
txxputstr(const char * c)343 void txxputstr(const char *c)
344 {
345 int cnt = 0;
346 int pos;
347 char *prefx;
348
349 txxputnameoffset(c);
350
351 prefx = prefix;
352 fprintf(stderr, "prefix: '%s'\n", prefx);
353
354 if(*prefx != 0) {
355 while(*prefx != 0) {
356 myputchar('\0');
357 cnt++;
358
359 if(*prefx == '/')
360 putchar(separator);
361 else
362 myputchar(*prefx);
363 cnt++;
364
365 prefx++;
366 }
367
368 c++; // skip the leading dot
369 }
370
371 while(*c != 0) {
372 myputchar('\0');
373 cnt++;
374
375 if(*c == '/')
376 putchar(separator);
377 else
378 myputchar(*c);
379 cnt++;
380
381 c++;
382 }
383
384 while(cnt < 510) {
385 myputchar('\0');
386 cnt++;
387 }
388 }
389
txxputcounter(int c)390 void txxputcounter(int c)
391 {
392 int b;
393
394 rewind(stdout);
395
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 }
405
406 /* remove spaces at beginning and end of string */
trim(char * c)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 }
423
print_webpath(const char * path)424 void print_webpath(const char *path)
425 {
426 const char *c = path;
427
428 printf("%s", 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 }
442
print_path(const char * path)443 void print_path(const char *path)
444 {
445 const char *c = path;
446 printf("%s", 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 }
456
print_pathtail(const char * path)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 }
467
noreferal(const char * path,const char * artist,const char * title)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 }
480
reference(const char * title)481 void reference(const char *title)
482 {
483 FILE *pipe = NULL;
484 static char command[2048], buffer[1024];
485 int buflen = 8192;
486
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 return;
504 }
505 fgets(buffer, 1020, pipe);
506 while(!feof(pipe)) {
507 fputs(buffer, stdout);
508 fgets(buffer, 1020, pipe);
509 }
510 pclose(pipe);
511 return;
512 }
513
parse_options(int argc,char ** argv)514 void parse_options(int argc, char **argv)
515 {
516 static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
517 static struct option long_options[] = {
518 {"backslash", no_argument, NULL, 'b'},
519 {"command", required_argument, NULL, 'c'},
520 {"debug", no_argument, NULL, 'd'},
521 {"format", required_argument, NULL, 'f'},
522 {"genre", required_argument, NULL, 'g'},
523 {"nohardlink", no_argument, NULL, 'n'},
524 {"output", required_argument, NULL, 'o'},
525 {"prefix", required_argument, NULL, 'p'},
526 {"recursive", no_argument, NULL, 'r'},
527 {"stdin", no_argument, NULL, 's'},
528 {"windows", no_argument, NULL, 'w'},
529 {"exclude", required_argument, NULL, 'x'},
530 {NULL, 0, NULL, 0}
531 };
532 int c;
533 int option_index = 0;
534 while((c =
535 getopt_long(argc, argv, short_options, long_options,
536 &option_index)) != -1) {
537 switch (c) {
538 case 'b':
539 separator = '\\';
540 noand['/'] = '\\';
541 break;
542 case 'c':
543 if(strncmp(optarg, "intern", 6) == 0)
544 referal = NULL;
545 else
546 referal = strdup(optarg);
547 break;
548 case 'd':
549 debug = 1 - debug;
550 break;
551 case 'f':
552 if(strcmp(optarg, "m3u") == 0)
553 format = FORMAT_M3U;
554 else if(strcmp(optarg, "pls") == 0)
555 format = FORMAT_PLS;
556 else if(strcmp(optarg, "html") == 0)
557 format = FORMAT_HTML;
558 else if(strcmp(optarg, "rss") == 0)
559 format = FORMAT_RSS;
560 else if(strcmp(optarg, "pla") == 0)
561 format = FORMAT_PLP;
562 else if(strcmp(optarg, "txx") == 0)
563 format = FORMAT_UMS;
564 #ifdef HAVE_LIBURIPARSER
565 else if(strcmp(optarg, "xspf") == 0)
566 format = FORMAT_XSPF;
567 #endif
568 else
569 usage();
570 break;
571 case 'g':
572 if(genrelist == NULL)
573 genrelist = calloc(257, sizeof(char)); /* allow multiple includes/excludes */
574 if(genrelist == NULL) {
575 fprintf(stderr,
576 "Error >> unable to allocate cleared memory\n");
577 exit(2);
578 } else {
579 unsigned int n = 0;
580 while(n < strlen(optarg)) {
581 if(debug)
582 fprintf(stderr,
583 "Debug >> genrelist entry activting : %d\n",
584 atoi(&optarg[n]));
585 genrelist[atoi(&optarg[n])] = 1;
586 while(isdigit(optarg[n++]));
587 }
588 }
589 break;
590 case 'n':
591 avoidhlinked = 1;
592 break;
593 case 'o':
594 close(1);
595 if(fopen(optarg, "w") == NULL) {
596 fprintf(stderr,
597 "Error >> unable to open output file : %s\n",
598 optarg);
599 exit(2);
600 }
601 break;
602 case 'p':
603 prefix = malloc(strlen(optarg) + 1);
604 strcpy(prefix, optarg);
605 base = malloc(strlen(prefix) + 1);
606 strcpy(base, prefix);
607 dir = strchr(base, '/');
608 if((dir != NULL) && (dir[1] == '/'))
609 dir = strchr(dir + 2, '/');
610 if(dir != NULL)
611 *dir++ = 0;
612 else
613 dir = "";
614 /* if prefix is a weblink, base is the baselink, dir is the path */
615 break;
616 case 'r':
617 recursive = 1;
618 break;
619 case 'u':
620 winorunix = one2one;
621 eol = "\n";
622 break;
623 case 'w':
624 winorunix = unix2dos;
625 eol = "\r\n";
626 break;
627 case 'x':
628 if(genrelist == NULL) { /* allow multiple includes/excludes - not recommended (confusing) but possible */
629 int n = 0;
630 genrelist = calloc(257, sizeof(char));
631 while(n < 256)
632 genrelist[n++] = 1;
633 }
634 if(genrelist == NULL) {
635 fprintf(stderr,
636 "Error >> unable to allocate cleared memory\n");
637 exit(2);
638 } else {
639 unsigned int n = 0;
640 while(n < strlen(optarg)) {
641 if(debug)
642 fprintf(stderr,
643 "Debug >> genrelist entry activting : %d\n",
644 atoi(&optarg[n]));
645 genrelist[atoi(&optarg[n])] = 0;
646 while(isdigit(optarg[n++]));
647 }
648 }
649 break;
650 case 's':
651 fromstdin = 1;
652 break;
653 default:
654 usage();
655 }
656 }
657 /* hostname = getenv("HOSTNAME"); */
658 if(genrelist == NULL) {
659 genrelist = calloc(257, sizeof(char));
660 if(genrelist == NULL) {
661 fprintf(stderr,
662 "Error >> unable to allocate cleared memory\n");
663 exit(2);
664 } else {
665 int n = 0;
666 while(n < 256)
667 genrelist[n++] = 1;
668 }
669 }
670 }
671
parse_mp3(const char * file)672 void parse_mp3(const char *file)
673 {
674 int bitrates[2][3][15] =
675 { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
676 416, 448},
677 {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
678 384},
679 {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
680 320}},
681 {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
682 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
683 {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
684 };
685 FILE *fic;
686 char *c;
687 int lus;
688
689 genre = 0;
690 genrebuf[0] = 0;
691 if(debug)
692 fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
693
694 /* read header */
695 if((fic = fopen(file, "r")) == NULL) {
696 fprintf(stderr, "Warning >> can't open file : %s\n", file);
697 return;
698 }
699 lus = fread(buffer, 1, MP3_BASE, fic);
700 c = buffer;
701
702 /* try ID3v2 */
703 if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
704 int size;
705 int version;
706 version = *(buffer + 3);
707 if(version < 2 || version > 4)
708 fprintf(stderr,
709 "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
710 version, file);
711 if(*(buffer + 5) != 0)
712 fprintf(stderr,
713 "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
714 *(buffer + 5), file);
715 c = buffer + 6;
716 size =
717 (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
718 /* read more header */
719 if(size + lus > MAX) {
720 lus += fread(buffer + lus, 1, MAX - lus, fic);
721 fprintf(stderr,
722 "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
723 size, file);
724 } else
725 lus += fread(buffer + lus, 1, size, fic);
726 if(size > lus)
727 size = lus;
728 c += 4;
729 if(version == 2)
730 while(c < buffer + size) {
731 int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
732 if(*c == 0)
733 break;
734 if(strncmp(c, "TT2", 3) == 0) {
735 strncpy(title, c + 7, size - 1);
736 title[size - 1] = '\0';
737 }
738 if(strncmp(c, "TP1", 3) == 0) {
739 strncpy(artist, c + 7, size - 1);
740 artist[size - 1] = '\0';
741 }
742 if(strncmp(c, "TCO", 3) == 0) {
743 /* strncpy(genrebuf,c+7,size-1); */
744 /* genrebuf[size-1]='\0'; */
745 /* genre=atoi(&genrebuf[1]); */
746 genre = atoi(c + 8);
747 }
748 c += size + 6;
749 }
750 if(version == 3 || version == 4)
751 while(c < buffer + size) {
752 int size =
753 (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
754 (*(c + 7));
755 if(*c == 0)
756 break;
757 if(strncmp(c, "TIT2", 4) == 0) {
758 strncpy(title, c + 11, size - 1);
759 title[size - 1] = '\0';
760 }
761 if(strncmp(c, "TPE1", 4) == 0) {
762 strncpy(artist, c + 11, size - 1);
763 artist[size - 1] = '\0';
764 }
765 if(strncmp(c, "TCON", 4) == 0) {
766 /* strncpy(genrebuf,c+11,size-1); */
767 /* genrebuf[size-1]='\0'; */
768 /* genre=atoi(&genrebuf[1]); */
769 genre = atoi(c + 12);
770 }
771 if(strncmp(c, "TLEN", 4) == 0) {
772 duration = atoi(c + 11) / 1000;
773 }
774 c += size + 10;
775 }
776 }
777
778 while(c < buffer + lus - 10) {
779 if((unsigned char)*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
780 int version;
781 int lay;
782 int bitrate_index;
783 int bitrate;
784 version = 2 - (*(c + 1) >> 3 & 1);
785 lay = 4 - (*(c + 1) >> 1 & 3);
786 bitrate_index = *(c + 2) >> 4 & 0xF;
787 if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
788 && bitrate_index >= 0 && bitrate_index <= 14)
789 bitrate = bitrates[version - 1][lay - 1][bitrate_index];
790 else
791 bitrate = 0;
792 if(bitrate != 0) {
793 fseek(fic, 0, SEEK_END);
794 duration = (ftell(fic) + buffer - c) / 125 / bitrate;
795 } else
796 duration = 0;
797 break;
798 }
799 c++;
800 }
801
802 /* try ID3v1 */
803 if(strlen(artist) == 0 && strlen(title) == 0) {
804 fseek(fic, -128, SEEK_END);
805 lus = fread(buffer, 1, 128, fic);
806 if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
807 && buffer[2] == 'G') {
808 strncpy(title, buffer + 3, 30);
809 title[30] = '\0';
810 c = title + 29;
811 while(c > title && *c == ' ')
812 *(c--) = '\0';
813 strncpy(artist, buffer + 33, 30);
814 artist[30] = '\0';
815 c = artist + 29;
816 while(c > artist && *c == ' ')
817 *(c--) = '\0';
818 /* strncpy(album,buffer+65,30); */
819 /* strncpy(year,buffer+97,4); */
820 /* strncpy(comment,buffer+101,30); */
821 /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
822 genre = buffer[127];
823 }
824 }
825
826 fclose(fic);
827 }
828
parse_ogg(const char * file)829 void parse_ogg(const char *file)
830 {
831 FILE *fic;
832 char *c;
833 int lus;
834 int sample_rate;
835 int samples;
836
837 if(debug)
838 fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
839
840 /* read header */
841 if((fic = fopen(file, "r")) == NULL) {
842 fprintf(stderr, "Warning >> can't open file : %s\n", file);
843 return;
844 }
845 lus = fread(buffer, 1, OGG_BASE, fic);
846
847 /* try Ogg */
848 if(strncmp(buffer, "Ogg", 3) != 0) {
849 fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
850 return;
851 }
852
853 c = buffer + 0x28;
854 sample_rate =
855 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
856
857 while(c < buffer + lus - 10) {
858 int size;
859 if(strncasecmp(c, "TITLE=", 6) == 0) {
860 size =
861 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
862 (*(c - 1) << 24);
863 strncpy(title, c + 6, size - 6);
864 title[size - 6] = '\0';
865 c += size;
866 }
867 if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
868 // ignore tag
869 size =
870 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
871 (*(c - 1) << 24);
872 c += size;
873 }
874 if(strncasecmp(c, "ARTIST=", 7) == 0) {
875 size =
876 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
877 (*(c - 1) << 24);
878 strncpy(artist, c + 7, size - 7);
879 artist[size - 7] = '\0';
880 c += size;
881 }
882 if(strncasecmp(c, "GENRE=", 6) == 0) {
883 static int i = 0;
884 size =
885 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
886 (*(c - 1) << 24);
887 strncpy(genrebuf, c + 6, size - 6);
888 genrebuf[size - 6] = '\0';
889 c += size;
890 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
891 if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
892 genre = i;
893 break;
894 }
895 if(i == ID3_NR_OF_V1_GENRES)
896 genre = 0;
897 }
898 }
899 c++;
900 }
901
902 fseek(fic, -OGG_BASE, SEEK_END);
903 lus = fread(buffer, 1, OGG_BASE, fic);
904 c = buffer + lus - 1;
905 while(strncmp(c, "OggS", 4) != 0 && c > buffer)
906 c--;
907 if(c != buffer) {
908 c += 6;
909 samples =
910 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
911 duration = samples / sample_rate;
912 }
913
914 fclose(fic);
915 }
916
parse_mpc(const char * file)917 void parse_mpc(const char *file)
918 {
919 FILE *fic;
920 char *c;
921 int lus;
922 int sample_rates[4] = { 44100, 48000, 37800, 32000 };
923 int frame_count;
924 int size, items;
925 int i;
926
927 if(debug)
928 fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
929
930 /* read header */
931 if((fic = fopen(file, "r")) == NULL) {
932 fprintf(stderr, "Warning >> can't open file : %s\n", file);
933 return;
934 }
935 lus = fread(buffer, 1, 12, fic);
936
937 /* try Musepack */
938 if (strncmp(buffer, "MP+", 3) != 0) {
939 fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
940 return;
941 }
942
943 /* only version 7 */
944 if(buffer[3] != 7) {
945 fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
946 file);
947 return;
948 }
949
950 /* duration */
951 c = buffer + 4;
952 frame_count =
953 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
954 c += 5;
955 duration = frame_count * 1152 / sample_rates[*c & 3];
956
957 /* try APETAGEX footer */
958 fseek(fic, -32, SEEK_END);
959 lus = fread(buffer, 1, 32, fic);
960 if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
961 c = buffer + 12;
962 size =
963 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
964 size += 32;
965 c += 4;
966 items =
967 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
968 fseek(fic, -size, SEEK_END);
969 lus = fread(buffer, 1, size, fic);
970 if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
971 c = buffer + 32;
972 while(items--) {
973 size =
974 (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
975 (*(c + 3) << 24);
976 c += 8;
977 if(strcasecmp(c, "TITLE") == 0) {
978 strncpy(title, c + 6, size);
979 title[size] = '\0';
980 }
981 if(strcasecmp(c, "ARTIST") == 0) {
982 strncpy(artist, c + 7, size);
983 artist[size] = '\0';
984 }
985 if(strcasecmp(c, "GENRE") == 0) {
986 for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
987 strncpy(genrebuf, c + 6, size);
988 genrebuf[size] = '\0';
989 if(strcasecmp
990 (ID3_v1_genre_description[i], genrebuf) == 0) {
991 genre = i;
992 break;
993 }
994 if(i == ID3_NR_OF_V1_GENRES)
995 genre = 0;
996 }
997 }
998 c += strlen(c) + 1 + size;
999 }
1000 }
1001 }
1002
1003 fclose(fic);
1004 }
1005
1006 #define FSN 32
1007 #define MAXINO (1<<24)
1008 #define INOTYP unsigned long
1009 #define regbit_qry(x,y) ( x[( (y) / sizeof(INOTYP) )] & 1<<( (y) % sizeof(INOTYP) ) )
1010 #define regbit_set(x,y) ( x[( (y) / sizeof(INOTYP) )] |= 1<<( (y) % sizeof(INOTYP) ) )
1011
hlink_check(struct stat * info)1012 int hlink_check(struct stat *info)
1013 {
1014 /*
1015 * for speed this subroutine should only be called
1016 * - if the file has more than one hardlink
1017 * - if the file is a resolved softlink
1018 */
1019 /* the persistent variables */
1020 static INOTYP *list[FSN];
1021 static dev_t name[FSN];
1022 /* some temporary variables */
1023 int fsn, is_registered = 0;
1024
1025 /* assertions - in case parameters are lowered for less memory usage */
1026 assert(fsn < FSN);
1027 assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
1028
1029 /* search which internal registration number is used for this filesystem */
1030 for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
1031
1032 /* if file system is not registered yet, do it and leave */
1033 if(name[fsn] == 0) {
1034 name[fsn] = (info->st_dev);
1035 /* provide space for the bitmap that maps the inodes of this file system */
1036 list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
1037 /* no comparison is needed in empty lists ... return */
1038 if(debug)
1039 fprintf(stderr,
1040 "Debug >> Linked >> Init List %04x @mem %04lx\n",
1041 (int)name[fsn], (long)&list[fsn]);
1042 } else {
1043 /* this looks more complicated than it really is */
1044 /* the idea is very simple:
1045 * provide a bitmap that maps all inodes of a file system
1046 * to mark all files that have already been visited.
1047 * If it is already visited, do not add it to the playlist
1048 */
1049 /*
1050 * The difficulty is as follows:
1051 * struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
1052 * would be byte-aligned and would allocate at least eight times the needed space.
1053 * Feel free to change the definitions that are involved here, if you know better.
1054 */
1055 if(regbit_qry(list[fsn], (info->st_ino)))
1056 is_registered = 1;
1057 else
1058 regbit_set(list[fsn], (info->st_ino));
1059 /*
1060 * the debug expression is more complicated then the working stuff
1061 */
1062 if(debug)
1063 fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
1064 "list[%02x][%04x] = %04x & %04x --> %s registered\n",
1065 (int)info->st_dev, (int)info->st_ino, fsn,
1066 (int)((info->st_ino) / sizeof(INOTYP)),
1067 (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
1068 1 << ((info->st_ino) % sizeof(INOTYP)),
1069 is_registered ? "Already" : "Not");
1070 }
1071 return is_registered;
1072 }
1073
1074 #ifdef HAVE_LIBURIPARSER
relative_uri_malloc(const char * unixFilename,const char * baseDir)1075 char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
1076 {
1077 char * absSourceFile;
1078 size_t absSourceLen;
1079 char * sourceUriString;
1080 char * baseUriString;
1081 UriParserStateA state;
1082 UriUriA sourceUri;
1083 UriUriA baseUri;
1084 UriUriA relativeUri;
1085 int charsRequired;
1086 char * output;
1087
1088 /* checks */
1089 if ((unixFilename == NULL) || (baseDir == NULL)) {
1090 return NULL;
1091 }
1092
1093 /* base URI */
1094 baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
1095 if (baseUriString == NULL) {
1096 return NULL;
1097 }
1098 if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
1099 free(baseUriString);
1100 return NULL;
1101 }
1102 state.uri = &baseUri;
1103 if (uriParseUriA(&state, baseUriString) != 0) {
1104 free(baseUriString);
1105 uriFreeUriMembersA(&baseUri);
1106 return NULL;
1107 }
1108
1109 /* source URI */
1110 if (unixFilename[0] != '/') {
1111 const int baseDirLen = strlen(baseDir);
1112 const int sourceFileLen = strlen(unixFilename);
1113 absSourceLen = baseDirLen + sourceFileLen;
1114 absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
1115 sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
1116 } else {
1117 absSourceLen = strlen(unixFilename);
1118 absSourceFile = (char *)unixFilename;
1119 }
1120 sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
1121 if (sourceUriString == NULL) {
1122 free(baseUriString);
1123 if (unixFilename[0] != '/') {
1124 free(absSourceFile);
1125 }
1126 uriFreeUriMembersA(&baseUri);
1127 return NULL;
1128 }
1129 if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
1130 free(baseUriString);
1131 free(sourceUriString);
1132 if (unixFilename[0] != '/') {
1133 free(absSourceFile);
1134 }
1135 uriFreeUriMembersA(&baseUri);
1136 return NULL;
1137 }
1138 state.uri = &sourceUri;
1139 if (uriParseUriA(&state, sourceUriString) != 0) {
1140 free(baseUriString);
1141 free(sourceUriString);
1142 uriFreeUriMembersA(&baseUri);
1143 uriFreeUriMembersA(&sourceUri);
1144 return NULL;
1145 }
1146 if (uriNormalizeSyntaxA(&sourceUri) != 0) {
1147 free(baseUriString);
1148 free(sourceUriString);
1149 if (unixFilename[0] != '/') {
1150 free(absSourceFile);
1151 }
1152 uriFreeUriMembersA(&baseUri);
1153 uriFreeUriMembersA(&sourceUri);
1154 return NULL;
1155 }
1156
1157 /* make relative (or keep absolute if necessary) */
1158 if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
1159 free(baseUriString);
1160 free(sourceUriString);
1161 if (unixFilename[0] != '/') {
1162 free(absSourceFile);
1163 }
1164 uriFreeUriMembersA(&baseUri);
1165 uriFreeUriMembersA(&sourceUri);
1166 uriFreeUriMembersA(&relativeUri);
1167 return NULL;
1168 }
1169
1170 /* back to string */
1171 if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
1172 free(baseUriString);
1173 free(sourceUriString);
1174 if (unixFilename[0] != '/') {
1175 free(absSourceFile);
1176 }
1177 uriFreeUriMembersA(&baseUri);
1178 uriFreeUriMembersA(&sourceUri);
1179 uriFreeUriMembersA(&relativeUri);
1180 return NULL;
1181 }
1182 output = malloc((charsRequired + 1) * sizeof(char));
1183 if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
1184 free(baseUriString);
1185 free(sourceUriString);
1186 if (unixFilename[0] != '/') {
1187 free(absSourceFile);
1188 }
1189 free(output);
1190 uriFreeUriMembersA(&baseUri);
1191 uriFreeUriMembersA(&sourceUri);
1192 uriFreeUriMembersA(&relativeUri);
1193 return NULL;
1194 }
1195
1196 free(baseUriString);
1197 free(sourceUriString);
1198 if (unixFilename[0] != '/') {
1199 free(absSourceFile);
1200 }
1201 uriFreeUriMembersA(&baseUri);
1202 uriFreeUriMembersA(&sourceUri);
1203 uriFreeUriMembersA(&relativeUri);
1204
1205 return output;
1206 }
1207
xml_escape_malloc(const char * input)1208 char * xml_escape_malloc(const char * input)
1209 {
1210 const char * read = input;
1211 char * output;
1212 char * write;
1213
1214 if (input == NULL) {
1215 return NULL;
1216 }
1217
1218 output = malloc((6 * strlen(input) + 1) * sizeof(char));
1219 if (output == NULL) {
1220 return NULL;
1221 }
1222 write = output;
1223
1224 for (;;) {
1225 if (*read == '\0') {
1226 *write = '\0';
1227 return output;
1228 }
1229
1230 switch ((unsigned char)*read) {
1231 case '&':
1232 strcpy(write, "&");
1233 write += 5;
1234 break;
1235 case '<':
1236 strcpy(write, "<");
1237 write += 4;
1238 break;
1239 case '>':
1240 strcpy(write, ">");
1241 write += 4;
1242 break;
1243 case '\'':
1244 strcpy(write, "'");
1245 write += 6;
1246 break;
1247 case '"':
1248 strcpy(write, """);
1249 write += 6;
1250 break;
1251 default:
1252 *(write++) = *read;
1253 }
1254 read++;
1255 }
1256 }
1257 #endif
1258
parse_file(char * newpath,const char * original_path)1259 void parse_file(char *newpath, const char * original_path)
1260 {
1261 char ext[5];
1262 int j, encoding = 0;
1263
1264 for(j = 0; j < 5; j++)
1265 ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
1266 artist[0] = '\0';
1267 title[0] = '\0';
1268 duration = -2;
1269 if(strcmp(".mp2", ext) == 0) {
1270 duration = -1;
1271 parse_mp3(newpath);
1272 encoding = MP2ENC;
1273 }
1274 if(strcmp(".mp3", ext) == 0) {
1275 duration = -1;
1276 parse_mp3(newpath);
1277 encoding = MP3ENC;
1278 }
1279 if(strcmp(".mpc", ext) == 0) {
1280 duration = -1;
1281 parse_mpc(newpath);
1282 encoding = MPCENC;
1283 }
1284 if(strcmp(".mp+", ext) == 0) {
1285 duration = -1;
1286 parse_mpc(newpath);
1287 encoding = MPPENC;
1288 }
1289 if(strcmp(".ogg", ext) == 0) {
1290 duration = -1;
1291 parse_ogg(newpath);
1292 encoding = OGGENC;
1293 }
1294 if(strcmp(".wav", ext) == 0) {
1295 duration = -1;
1296 /* parse_wav(newpath); */
1297 encoding = WAVENC;
1298 }
1299 if(strcmp(".wma", ext) == 0) {
1300 duration = -1;
1301 /* parse_wma(newpath); */
1302 encoding = WMAENC;
1303 }
1304 /* guesstitle() */
1305 if((strlen(artist) == 0) && (strlen(title) == 0)) {
1306 // there are no tag infos read
1307 // use file name to state substitute it
1308 char *c = strrchr(newpath, separator);
1309 if(c == NULL)
1310 c = newpath;
1311 strcpy(artist, ++c);
1312 // arbitrarily use the first '-'
1313 // to separate artist and title
1314 c = strchr(artist, '-');
1315 if(c != NULL) { // if trenner found, divide file name
1316 *c = '\0';
1317 strcpy(title, ++c);
1318 c = strrchr(title, '.');
1319 if(c != NULL)
1320 *c = '\0';
1321 } else { // no trenner found, assume
1322 // no artist, only title
1323 strcpy(title, artist);
1324 artist[0] = '\0';
1325 }
1326 // replace underscores by spaces
1327 for(c = artist; (c = strchr(c, '_')) != NULL; c++)
1328 *c = ' ';
1329 for(c = title; (c = strchr(c, '_')) != NULL; c++)
1330 *c = ' ';
1331 // trim spaces
1332 trim(artist);
1333 trim(title);
1334 }
1335 /* guesstitle() end */
1336
1337 if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */
1338 counter++;
1339 switch (format) {
1340 case FORMAT_M3U:
1341 printf("#EXTINF:%d,", duration);
1342 if(strlen(artist) != 0)
1343 printf("%s - ", artist);
1344 printf("%s%s", title, eol);
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];
1371
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);
1384
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 }
1460
parse_directory(char * path,const char * original_path)1461 void parse_directory(char *path, const char * original_path)
1462 {
1463 int i, n;
1464 struct dirent **namelist;
1465 char newpath[PATH_MAX];
1466 struct stat infos;
1467
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);
1486
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 }
1504
main(int argc,char ** argv)1505 int main(int argc, char **argv)
1506 {
1507 winorunix = one2one;
1508 basemap = one2one;
1509 parse_options(argc, argv);
1510
1511 if(optind == argc && !fromstdin)
1512 usage();
1513
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 0.42"
1525 "</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",
1526 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1527 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
1528 eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
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);
1532 break;
1533 case FORMAT_RSS:
1534 {
1535 time_t zeit;
1536 char timebuffer[256];
1537 time(&zeit);
1538 strftime(timebuffer, 255, "%a %d %b %Y %T %z",
1539 localtime(&zeit));
1540 printf
1541 ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG 0.42"
1542 " -->%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 0.42"
1543 "</generator>%s\t<image>%s\t\t<url>%s/podcast.jpg</url>%s\t\t<title>Server Logo</title>%s\t\t<link>%s</link>%s\t\t<description>Feed provided by FAPG. Click to visit.</description>%s\t</image>%s\t<itunes:owner>%s\t\t<itunes:name>Admin %s</itunes:name>%s\t\t<itunes:email>podcast@%s</itunes:email>%s\t</itunes:owner>%s\t<category>Various</category>%s\t<itunes:subtitle>Directory Tree %s</itunes:subtitle>%s\t<itunes:author>%s</itunes:author>%s\t<copyright>unknown</copyright>%s\t<language>%s</language>%s\t<itunes:explicit>No</itunes:explicit>%s\t<ttl>1800</ttl>%s",
1544 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
1545 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
1546 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
1547 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
1548 eol, getenv("LANG"), eol, eol, eol);
1549 unix2dos[38] = 43; // I never made an rss feed work with '&' in it
1550 basemap = noand;
1551 }
1552 break;
1553 case FORMAT_PLP:
1554 {
1555 eol = "\r\n";
1556 myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
1557 }
1558 break;
1559 case FORMAT_UMS:
1560 {
1561 txxputheader(" iriver UMS PLA");
1562 }
1563 break;
1564 #ifdef HAVE_LIBURIPARSER
1565 case FORMAT_XSPF:
1566 printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
1567 "<!-- generator=\"FAPG 0.42 -->\n"
1568 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
1569 "<trackList>\n");
1570 break;
1571 #endif
1572 }
1573
1574 /* iterate through files */
1575 {
1576 const char * const pwd_source = getenv("PWD");
1577 const int pwdlen = strlen(pwd_source);
1578 char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
1579 sprintf(pwd, "%s/", pwd_source);
1580
1581 if(fromstdin) {
1582 char path[PATH_MAX];
1583 int i;
1584 while(fgets(path, PATH_MAX, stdin)) {
1585 for(i = 0; i < PATH_MAX; i++)
1586 if(path[i] == '\r' || path[i] == '\n')
1587 path[i] = '\0';
1588 if (i <= 0) {
1589 continue;
1590 }
1591
1592 /* strip trailing slash */
1593 if (path[i - 1] == '/') {
1594 path[i - 1] = '\0';
1595 }
1596
1597 parse_directory(path, pwd);
1598 }
1599 } else
1600 for(; optind < argc; optind++) {
1601 /* strip trailing slash */
1602 char * dup = strdup(argv[optind]);
1603 const int len = strlen(dup);
1604 if ((len > 0) && (dup[len - 1] == '/')) {
1605 dup[len - 1] = '\0';
1606 }
1607
1608 parse_directory(dup, pwd);
1609 }
1610
1611 free(pwd);
1612 }
1613
1614 /* print footer */
1615 switch (format) {
1616 case FORMAT_PLS:
1617 printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
1618 break;
1619 case FORMAT_HTML:
1620 printf
1621 ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
1622 "0.42</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
1623 eol, eol);
1624 break;
1625 case FORMAT_RSS:
1626 printf(" </channel>%s</rss>%s", eol, eol);
1627 break;
1628 case FORMAT_UMS:
1629 txxputcounter(counter);
1630 break;
1631 #ifdef HAVE_LIBURIPARSER
1632 case FORMAT_XSPF:
1633 printf("</trackList>\n"
1634 "</playlist>\n");
1635 break;
1636 #endif
1637 }
1638
1639 if(genrelist)
1640 free(genrelist);
1641
1642 exit(0);
1643 }
1644
1645