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, "&amp;");
1233             write += 5;
1234             break;
1235         case '<':
1236             strcpy(write, "&lt;");
1237             write += 4;
1238             break;
1239         case '>':
1240             strcpy(write, "&gt;");
1241             write += 4;
1242             break;
1243         case '\'':
1244             strcpy(write, "&apos;");
1245             write += 6;
1246             break;
1247         case '"':
1248             strcpy(write, "&quot;");
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