version 0.44 (by Andreas Neuper)
[FAPG] / fapg.c
diff --git a/fapg.c b/fapg.c
index 19eb30a..e9d2b4d 100644 (file)
--- a/fapg.c
+++ b/fapg.c
@@ -1,7 +1,3 @@
-/*
- *              FAPG
- */
-#define VERSION "0.35"
 /*
  * FAPG means Fast Audio Playlist Generator.
  * It is a tool to generate list of audio files (Wav, MP3, Ogg, etc)
 #include <time.h>
 #include <assert.h>
 #include "genres.h"
+#ifdef HAVE_LIBURIPARSER
+# include <uriparser/Uri.h>
+#endif
 
 #define MP3_BASE 1024
 #define OGG_BASE 1024*10
-#define MAX 1024*200            /* 200ko for ID3 with JPEG images in it */
+#define MAX 1024*250            /* 250ko for ID3 with JPEG images in it */
+#define MAX_ITEM 1024
+
+#define FORMAT_M3U 0  /* "0" is not a good choice for debugging, but OK for a default */
+#define FORMAT_PLS 1
+#define FORMAT_HTML 2
+#define FORMAT_RSS 3
+#define FORMAT_PLP 4
+#define FORMAT_UMS 5
+#ifdef HAVE_LIBURIPARSER
+# define FORMAT_XSPF 6
+#endif
 
 int debug = 0;
-int format = 0;                 /* 0 = m3u ; 1 = pls ; 2 = html ; 3 = rss */
+int format = FORMAT_M3U;
 char *genrelist = NULL;
 unsigned char *prefix = "";
 unsigned char *base = "";
@@ -56,6 +66,7 @@ unsigned char *hostname = "fritzserver.de";
 // unsigned char *referal="/usr/local/bin/fapg-rss.sh";
 unsigned char *referal = NULL;
 //int windows=0;
+int fromstdin = 0;
 int recursive = 0;
 int avoidhlinked = 0;
 int separator = '/';
@@ -64,9 +75,9 @@ unsigned char buffer[MAX];
 
 int counter = 0;
 
-unsigned char artist[1024];
-unsigned char title[1024];
-unsigned char genrebuf[1024];
+unsigned char artist[MAX_ITEM];
+unsigned char title[MAX_ITEM];
+unsigned char genrebuf[MAX_ITEM];
 unsigned char genre = 0;
 int duration;
 #define MP2ENC 1
@@ -75,11 +86,13 @@ int duration;
 #define MPPENC 4
 #define OGGENC 5
 #define WAVENC 6
+#define WMAENC 7
 
 char *magic[] = { NULL,
     "audio/mpeg", "audio/mpeg",
     "audio/mpeg", "audio/mpeg",
     "audio/ogg-vorbis", "audio/x-wav",
+    "audio/x-ms-wma", 
     NULL
 };
 
@@ -219,13 +232,25 @@ unsigned char *iso2web[256] = {
 
 void usage()
 {
+#ifdef HAVE_LIBURIPARSER
+# define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
+#else
+# define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
+#endif
     fprintf(stderr,
-            "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss] [-g|--genre=#:#:...] [-n|--nohardlink] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-c|--command=<intern|....>] [-x|--exclude=#:#:...] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");
+            "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");
+#undef FAPG_FORMATS
     exit(1);
 }
 
 #define mywebputchar(x) { fputs(iso2web[(unsigned char)winorunix[(unsigned char)x]], stdout); }
 #define    myputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]); }
+/* #define    myplaputchar(x) { putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);putchar('\0');} */
+void myplaputchar(const char x)
+{
+    putchar(basemap[(unsigned char)winorunix[(unsigned char)x]]);
+    putchar('\0');
+}
 
 void mywebputstr(const char *c)
 {
@@ -235,10 +260,44 @@ void mywebputstr(const char *c)
     }
 }
 
+void utf16toutf8(char *c,int n)
+{
+    /* check whether the we need to convert UTF-16 to UTF-8 strings */
+    if ( ( c[0] != '\377' ) || ( c[1] != '\376' ) ) { return; }
+    /* only continue here, if the first 2 letters are 0xfffe *
+     * c references an UTF-16 input, where latin letters are *
+     * separated by zero bytes, which we need to eliminate   */
+    int i=0; --n;
+    for(int j=2; (j<n) && (j<MAX_ITEM); j++ ) {
+      if( isprint(c[j]) ) { /* this is not perfect ! */
+            c[i++]=c[j];
+    }   }   c[i+1]=c[i]='\0';
+    /* the index i follows the zero-terminated "string" *
+     * now the read buffer is modified, not the file    */
+    return;
+}
+
+void myplaputstr(const char *c)
+{
+    while(*c != 0) {
+        if(*c == '/')
+            myplaputchar('\\'); /* translate slash to backslash */
+        else
+            myplaputchar(*c);
+        c++;
+        /* remove multiple slashes "//" when parsing a directory ending with a "/" */
+        while(*c == '/' && c[1] == '/')
+            c++;
+    }
+}
+
 void myputstr(const char *c)
 {
     while(*c != 0) {
-        myputchar(*c);
+        if(*c == '/')
+            putchar(separator);
+        else
+            myputchar(*c);
         c++;
         /* remove multiple slashes "//" when parsing a directory ending with a "/" */
         while(*c == '/' && c[1] == '/')
@@ -246,11 +305,145 @@ void myputstr(const char *c)
     }
 }
 
+void txxputheader(const char *c)
+{
+    int cnt = 0;
+
+    while(*c != 0) {
+        myputchar(*c);
+        cnt++;
+        c++;
+    }
+
+    while(cnt < 512) {
+        putchar('\0');
+        cnt++;
+    }
+}
+
+void txxputnameoffset(const char *c)
+{
+    int pos = 0;
+    int cnt = 0;
+    char b;
+    unsigned char *prefx;
+
+    prefx = prefix;
+
+    if(*prefx != 0) {
+        while(*prefx != 0) {
+            if(*prefx == '/') {
+                pos = cnt;
+            }
+            cnt++;
+            prefx++;
+        }
+
+        cnt--;                  // skip the leading dot of the filepath
+    }
+
+    while(*c != 0) {
+        if(*c == '/') {
+            pos = cnt;
+        }
+        cnt++;
+        c++;
+    }
+
+    pos += 2;
+
+    b = (pos & 0xFF00) >> 8;
+    putchar(b);
+    b = (pos & 0x00FF);
+    putchar(b);
+}
+
+void txxputstr(const char *c)
+{
+    int cnt = 0;
+    int pos;
+    unsigned char *prefx;
+
+    txxputnameoffset(c);
+
+    prefx = prefix;
+    fprintf(stderr, "prefix: '%s'\n", prefx);
+
+    if(*prefx != 0) {
+        while(*prefx != 0) {
+            myputchar('\0');
+            cnt++;
+
+            if(*prefx == '/')
+                putchar(separator);
+            else
+                myputchar(*prefx);
+            cnt++;
+
+            prefx++;
+        }
+
+        c++;                    // skip the leading dot
+    }
+
+    while(*c != 0) {
+        myputchar('\0');
+        cnt++;
+
+        if(*c == '/')
+            putchar(separator);
+        else
+            myputchar(*c);
+        cnt++;
+
+        c++;
+    }
+
+    while(cnt < 510) {
+        myputchar('\0');
+        cnt++;
+    }
+}
+
+void txxputcounter(int c)
+{
+    int b;
+
+    rewind(stdout);
+
+    b = (c & 0xFF000000) >> 24;
+    putchar(b);
+    b = (c & 0x00FF0000) >> 16;
+    putchar(b);
+    b = (c & 0x0000FF00) >> 8;
+    putchar(b);
+    b = (c & 0x000000FF);
+    putchar(b);
+}
+
+/* remove spaces at beginning and end of string */
+void trim(char *c)
+{
+    char *p;
+    /* remove spaces at beginning ... */
+    while(*c == ' ') {
+        p = c;
+        while(*p != '\0') {
+            *p = *(p + 1);
+            p++;
+        }
+    }
+    /* ... and end of string */
+    p = c + strlen(c);
+    while(--p > c && *p == ' ')
+        *p = '\0';
+}
+
 void print_webpath(const char *path)
 {
     const char *c = path;
 
-    printf(prefix);             /* we must not modify this part */
+    printf("%s", prefix);             /* we must not modify this part */
     if(*c == '.' && c[1] == '/') {      /* remove leading "./" when parsing current directory */
         c += 2;
         /* maybe there follow many slashes */
@@ -268,7 +461,7 @@ void print_webpath(const char *path)
 void print_path(const char *path)
 {
     const char *c = path;
-    printf(prefix);
+    printf("%s", prefix);
     /* skip leading "./" when parsing current directory */
     if(*c == '.' && *(c + 1) == '/') {
         c += 2;
@@ -325,7 +518,6 @@ void reference(const char *title)
     pipe = popen(command, "r");
     if(pipe == NULL) {
         fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
-        free(command);
         return;
     }
     fgets(buffer, 1020, pipe);
@@ -339,10 +531,10 @@ void reference(const char *title)
 
 void parse_options(int argc, char **argv)
 {
-    static char const short_options[] = "c:bdf:g:lo:np:ruwx:";
+    static char const short_options[] = "bc:df:g:lo:np:rsuwx:";
     static struct option long_options[] = {
         {"backslash", no_argument, NULL, 'b'},
-        {"command", required_argument, NULL, 'b'},
+        {"command", required_argument, NULL, 'c'},
         {"debug", no_argument, NULL, 'd'},
         {"format", required_argument, NULL, 'f'},
         {"genre", required_argument, NULL, 'g'},
@@ -350,8 +542,10 @@ void parse_options(int argc, char **argv)
         {"output", required_argument, NULL, 'o'},
         {"prefix", required_argument, NULL, 'p'},
         {"recursive", no_argument, NULL, 'r'},
+        {"stdin", no_argument, NULL, 's'},
         {"windows", no_argument, NULL, 'w'},
-        {"exclude", required_argument, NULL, 'x'}
+        {"exclude", required_argument, NULL, 'x'},
+        {NULL, 0, NULL, 0}
     };
     int c;
     int option_index = 0;
@@ -374,13 +568,21 @@ void parse_options(int argc, char **argv)
             break;
         case 'f':
             if(strcmp(optarg, "m3u") == 0)
-                format = 0;
+                format = FORMAT_M3U;
             else if(strcmp(optarg, "pls") == 0)
-                format = 1;
+                format = FORMAT_PLS;
             else if(strcmp(optarg, "html") == 0)
-                format = 2;
+                format = FORMAT_HTML;
             else if(strcmp(optarg, "rss") == 0)
-                format = 3;
+                format = FORMAT_RSS;
+            else if(strcmp(optarg, "pla") == 0)
+                format = FORMAT_PLP;
+            else if(strcmp(optarg, "txx") == 0)
+                format = FORMAT_UMS;
+#ifdef HAVE_LIBURIPARSER
+            else if(strcmp(optarg, "xspf") == 0)
+                format = FORMAT_XSPF;
+#endif
             else
                 usage();
             break;
@@ -463,6 +665,9 @@ void parse_options(int argc, char **argv)
                 }
             }
             break;
+        case 's':
+            fromstdin = 1;
+            break;
         default:
             usage();
         }
@@ -505,7 +710,7 @@ void parse_mp3(unsigned char *file)
         fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
 
     /* read header */
-    if((fic = fopen(file, "r")) == NULL) {
+    if((fic = fopen(file, "rb")) == NULL) {
         fprintf(stderr, "Warning >> can't open file : %s\n", file);
         return;
     }
@@ -545,10 +750,12 @@ void parse_mp3(unsigned char *file)
                 if(*c == 0)
                     break;
                 if(strncmp(c, "TT2", 3) == 0) {
+                    utf16toutf8(c+7,size);
                     strncpy(title, c + 7, size - 1);
                     title[size - 1] = '\0';
                 }
                 if(strncmp(c, "TP1", 3) == 0) {
+                    utf16toutf8(c+7,size);
                     strncpy(artist, c + 7, size - 1);
                     artist[size - 1] = '\0';
                 }
@@ -568,10 +775,12 @@ void parse_mp3(unsigned char *file)
                 if(*c == 0)
                     break;
                 if(strncmp(c, "TIT2", 4) == 0) {
+                    utf16toutf8(c+11,size);
                     strncpy(title, c + 11, size - 1);
                     title[size - 1] = '\0';
                 }
                 if(strncmp(c, "TPE1", 4) == 0) {
+                    utf16toutf8(c+11,size);
                     strncpy(artist, c + 11, size - 1);
                     artist[size - 1] = '\0';
                 }
@@ -581,6 +790,9 @@ void parse_mp3(unsigned char *file)
                     /* genre=atoi(&genrebuf[1]); */
                     genre = atoi(c + 12);
                 }
+                if(strncmp(c, "TLEN", 4) == 0) {
+                    duration = atoi(c + 11) / 1000;
+                }
                 c += size + 10;
             }
     }
@@ -655,7 +867,7 @@ void parse_ogg(unsigned char *file)
     lus = fread(buffer, 1, OGG_BASE, fic);
 
     /* try Ogg */
-    if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
+    if(strncmp(buffer, "Ogg", 3) != 0) {
         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
         return;
     }
@@ -674,6 +886,13 @@ void parse_ogg(unsigned char *file)
             title[size - 6] = '\0';
             c += size;
         }
+        if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) {
+            // ignore tag
+            size =
+                *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+                (*(c - 1) << 24);
+            c += size;
+        }
         if(strncasecmp(c, "ARTIST=", 7) == 0) {
             size =
                 *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
@@ -717,7 +936,6 @@ void parse_ogg(unsigned char *file)
     fclose(fic);
 }
 
-
 void parse_mpc(unsigned char *file)
 {
     FILE *fic;
@@ -739,7 +957,7 @@ void parse_mpc(unsigned char *file)
     lus = fread(buffer, 1, 12, fic);
 
     /* try Musepack */
-    if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
+    if (strncmp(buffer, "MP+", 3) != 0) {
         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
         return;
     }
@@ -875,8 +1093,192 @@ int hlink_check(struct stat *info)
     return is_registered;
 }
 
+#ifdef HAVE_LIBURIPARSER
+char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
+{
+    char * absSourceFile;
+    size_t absSourceLen;
+    char * sourceUriString;
+    char * baseUriString;
+    UriParserStateA state;
+    UriUriA sourceUri;
+    UriUriA baseUri;
+    UriUriA relativeUri;
+    int charsRequired;
+    char * output;
+  
+    /* checks */
+    if ((unixFilename == NULL) || (baseDir == NULL)) {
+        return NULL;
+    }
+
+    /* base URI */
+    baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
+    if (baseUriString == NULL) {
+        return NULL;
+    }
+    if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
+        free(baseUriString);
+        return NULL;
+    }
+    state.uri = &baseUri;
+    if (uriParseUriA(&state, baseUriString) != 0) {
+        free(baseUriString);
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+
+    /* source URI */
+    if (unixFilename[0] != '/') {
+        const int baseDirLen = strlen(baseDir);
+        const int sourceFileLen = strlen(unixFilename);
+        absSourceLen = baseDirLen + sourceFileLen;
+        absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
+        sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
+    } else {
+        absSourceLen = strlen(unixFilename);
+        absSourceFile = (char *)unixFilename;
+    }
+    sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
+    if (sourceUriString == NULL) {
+        free(baseUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+    if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+    state.uri = &sourceUri;
+    if (uriParseUriA(&state, sourceUriString) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        return NULL;
+    }
+    if (uriNormalizeSyntaxA(&sourceUri) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        return NULL;
+    }
+
+    /* make relative (or keep absolute if necessary) */
+    if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+    
+    /* back to string */
+    if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+    output = malloc((charsRequired + 1) * sizeof(char));
+    if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        free(output);
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+
+    free(baseUriString);
+    free(sourceUriString);
+    if (unixFilename[0] != '/') {
+        free(absSourceFile);
+    }
+    uriFreeUriMembersA(&baseUri);
+    uriFreeUriMembersA(&sourceUri);
+    uriFreeUriMembersA(&relativeUri);
+
+    return output;
+}
+
+char * xml_escape_malloc(const char * input)
+{
+    const char * read = input;
+    char * output;
+    char * write;
+    
+    if (input == NULL) {
+        return NULL;
+    }
+    
+    output = malloc((6 * strlen(input) + 1) * sizeof(char));
+    if (output == NULL) {
+        return NULL;
+    }
+    write = output;
+    
+    for (;;) {
+        if (*read == '\0') {
+            *write = '\0';
+            return output;
+        }
+    
+        switch ((unsigned char)*read) {
+        case '&':
+            strcpy(write, "&amp;");
+            write += 5;
+            break;
+        case '<':
+            strcpy(write, "&lt;");
+            write += 4;
+            break;
+        case '>':
+            strcpy(write, "&gt;");
+            write += 4;
+            break;
+        case '\'':
+            strcpy(write, "&apos;");
+            write += 6;
+            break;
+        case '"':
+            strcpy(write, "&quot;");
+            write += 6;
+            break;
+        default:
+            *(write++) = *read;
+        }
+        read++;
+    }
+}
+#endif
 
-void parse_file(unsigned char *newpath)
+void parse_file(unsigned char *newpath, unsigned char * original_path)
 {
     unsigned char ext[5];
     int j, encoding = 0;
@@ -912,14 +1314,22 @@ void parse_file(unsigned char *newpath)
         encoding = OGGENC;
     }
     if(strcmp(".wav", ext) == 0) {
-        duration = -1;          /* parse_wav(newpath); */
+        duration = -1;
+        /* parse_wav(newpath); */
         encoding = WAVENC;
     }
-    /* faketitle() */
+    if(strcmp(".wma", ext) == 0) {
+        duration = -1;
+        /* parse_wma(newpath); */
+        encoding = WMAENC;
+    }
+    /* guesstitle() */
     if((strlen(artist) == 0) && (strlen(title) == 0)) {
         // there are no tag infos read
         // use file name to state substitute it
         char *c = strrchr(newpath, separator);
+        if(c == NULL)
+            c = newpath;
         strcpy(artist, ++c);
         // arbitrarily use the first '-'
         // to separate artist and title
@@ -940,29 +1350,34 @@ void parse_file(unsigned char *newpath)
             *c = ' ';
         for(c = title; (c = strchr(c, '_')) != NULL; c++)
             *c = ' ';
+        // trim spaces
+        trim(artist);
+        trim(title);
     }
-    /* faketitle() end */
+    /* guesstitle() end */
 
     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
         counter++;
         switch (format) {
-        case 0:
-            if(duration != -1) {
-                printf("#EXTINF:%d,%s - %s%s", duration, artist, title,
-                       eol);
-            }
+        case FORMAT_M3U:
+            printf("#EXTINF:%d,", duration);
+            if(strlen(artist) != 0)
+                printf("%s - ", artist);
+            printf("%s%s", title, eol);
             print_path(newpath);
             printf("%s", eol);
             break;
-        case 1:
+        case FORMAT_PLS:
             printf("File%d=", counter);
             print_path(newpath);
-            printf("%sTitle%d=%s - %s%s", eol, counter, artist, title,
-                   eol);
+            printf("%sTitle%d=", eol, counter);
+            if(strlen(artist) != 0)
+                printf("%s - ", artist);
+            printf("%s%s", title, eol);
             if(duration != -1)
                 printf("Length%d=%d%s", counter, duration, eol);
             break;
-        case 2:
+        case FORMAT_HTML:
             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
                    artist, title);
             if(duration == -1)
@@ -971,7 +1386,7 @@ void parse_file(unsigned char *newpath)
                 printf("%d:%s%d</td></tr>%s", duration / 60,
                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
             break;
-        case 3:
+        case FORMAT_RSS:
             if(duration != -1) {
                 struct stat infos;
                 char timebuffer[256];
@@ -1017,11 +1432,55 @@ void parse_file(unsigned char *newpath)
                 printf("\t</item>%s", eol);
             }
             break;
+        case FORMAT_PLP:
+            myplaputstr("HARP, ");
+            myplaputstr(newpath);
+            myplaputstr(eol);
+            break;
+        case FORMAT_UMS:
+            txxputstr(newpath);
+            break;
+#ifdef HAVE_LIBURIPARSER
+        case FORMAT_XSPF:
+            printf("<track>\n");
+            if (strlen(title) > 0) {
+                char * escaped_title = xml_escape_malloc(title);
+                if (escaped_title != NULL) {
+                    printf("    <title>%s</title>\n", escaped_title);
+                    free(escaped_title);
+                }
+            }
+            if (strlen(artist) > 0) {
+                char * escaped_artist = xml_escape_malloc(artist);
+                if (escaped_artist != NULL) {
+                    printf("    <creator>%s</creator>\n", escaped_artist);
+                    free(escaped_artist);
+                }
+            }
+            if (duration > 0) {
+                printf("    <duration>%d</duration>\n", duration);
+            }
+            {
+                char * relative_location;
+                char * escaped_location;
+                relative_location = relative_uri_malloc(newpath, original_path);
+                if (relative_location != NULL) {
+                    escaped_location = xml_escape_malloc(relative_location);
+                    if (escaped_location != NULL) {
+                        printf("    <location>%s</location>\n", escaped_location);
+                        free(escaped_location);
+                    }
+                    free(relative_location);
+                }
+            }
+            printf("</track>\n");
+            break;
+#endif
         }
     }
 }
 
-void parse_directory(unsigned char *path)
+void parse_directory(unsigned char *path, unsigned char * original_path)
 {
     int i, n;
     struct dirent **namelist;
@@ -1036,7 +1495,7 @@ void parse_directory(unsigned char *path)
     }
     /* check if it is a filename */
     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
-        parse_file(path);
+        parse_file(path, original_path);
         return;
     }
     /* must be a directory - or something unusable like pipe, socket, etc */
@@ -1045,7 +1504,7 @@ void parse_directory(unsigned char *path)
         return;
     }
     for(i = 0; i < n; i++) {
-        sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
+        snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
 
         if(stat(newpath, &infos) != 0) {
             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
@@ -1054,11 +1513,11 @@ void parse_directory(unsigned char *path)
         if(recursive && S_ISDIR(infos.st_mode)
            && strcmp(namelist[i]->d_name, ".") != 0
            && strcmp(namelist[i]->d_name, "..") != 0)
-            parse_directory(newpath);
+            parse_directory(newpath, original_path);
         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
         if(S_ISREG(infos.st_mode)
            && !(avoidhlinked && hlink_check(&infos))) {
-            parse_file(newpath);
+            parse_file(newpath, original_path);
         }
         free(namelist[i]);
     }
@@ -1070,16 +1529,19 @@ int main(int argc, char **argv)
     winorunix = one2one;
     basemap = one2one;
     parse_options(argc, argv);
-    if(optind == argc)
+
+    if(optind == argc && !fromstdin)
         usage();
+
+    /* print header */
     switch (format) {
-    case 0:
+    case FORMAT_M3U:
         printf("#EXTM3U%s", eol);
         break;
-    case 1:
+    case FORMAT_PLS:
         printf("[playlist]%s", eol);
         break;
-    case 2:
+    case FORMAT_HTML:
         printf
             ("<!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 "
              VERSION
@@ -1091,7 +1553,7 @@ int main(int argc, char **argv)
              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
              eol, eol, eol);
         break;
-    case 3:
+    case FORMAT_RSS:
         {
             time_t zeit;
             char timebuffer[256];
@@ -1113,25 +1575,95 @@ int main(int argc, char **argv)
             basemap = noand;
         }
         break;
+    case FORMAT_PLP:
+        {
+            eol = "\r\n";
+            myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
+        }
+        break;
+    case FORMAT_UMS:
+        {
+            txxputheader("    iriver UMS PLA");
+        }
+        break;
+#ifdef HAVE_LIBURIPARSER
+    case FORMAT_XSPF:
+        printf("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
+                "<!-- generator=\"FAPG " VERSION " -->\n"
+                "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
+                "<trackList>\n");
+        break;
+#endif
     }
-    for(; optind < argc; optind++) {
-        parse_directory(argv[optind]);
+    
+    /* iterate through files */
+    {
+        const char * const pwd_source = getenv("PWD");
+        const int pwdlen = strlen(pwd_source);
+        char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
+        sprintf(pwd, "%s/", pwd_source);
+        
+        if(fromstdin) {
+            unsigned char path[PATH_MAX];
+            int i;
+            while(fgets(path, PATH_MAX, stdin)) {
+                for(i = 0; i < PATH_MAX; i++)
+                    if(path[i] == '\r' || path[i] == '\n')
+                        path[i] = '\0';
+                if (i <= 0) {
+                    continue;
+                }
+
+                /* strip trailing slash */
+                if (path[i - 1] == '/') {
+                    path[i - 1] = '\0';
+                }
+
+                parse_directory(path, pwd);
+            }
+        } else
+            for(; optind < argc; optind++) {
+                /* strip trailing slash */
+                char * dup = strdup(argv[optind]);
+                const int len = strlen(dup);
+                if ((len > 0) && (dup[len - 1] == '/')) {
+                    dup[len - 1] = '\0';
+                }
+
+                parse_directory(dup, pwd);
+            }
+            
+        free(pwd);
     }
+
+    /* print footer */
     switch (format) {
-    case 1:
+    case FORMAT_PLS:
         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
         break;
-    case 2:
+    case FORMAT_HTML:
         printf
             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
              eol, eol);
         break;
-    case 3:
+    case FORMAT_RSS:
         printf("    </channel>%s</rss>%s", eol, eol);
         break;
+    case FORMAT_UMS:
+        txxputcounter(counter);
+        break;
+#ifdef HAVE_LIBURIPARSER
+    case FORMAT_XSPF:
+        printf("</trackList>\n"
+                "</playlist>\n");
+        break;
+#endif
     }
+
     if(genrelist)
         free(genrelist);
+
     exit(0);
 }
+