version 0.44 (by Andreas Neuper)
[FAPG] / fapg.c
diff --git a/fapg.c b/fapg.c
old mode 100755 (executable)
new mode 100644 (file)
index 4dc2cc1..e9d2b4d
--- a/fapg.c
+++ b/fapg.c
-/*\r
- * FAPG 0.1 released under GPL\r
- * http://royale.zerezo.com/fapg/\r
- */\r
-\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <getopt.h>\r
-#include <dirent.h>\r
-#include <sys/stat.h>\r
-#include <sys/types.h>\r
-#include <string.h>\r
-#include <limits.h>\r
-#include <unistd.h>\r
-#include <ctype.h>\r
-\r
-#define MAX 10240\r
-\r
-int debug=0;\r
-int format=0; /* 0 = m3u ; 1 = pls ; 2 = html */\r
-unsigned char *prefix="";\r
-int recursive=0;\r
-int separator='/';\r
-int skip=0;\r
-int windows=0;\r
-unsigned char buffer[MAX];\r
-\r
-int counter=0;\r
-\r
-unsigned char artist[1024];\r
-unsigned char title[1024];\r
-int duration;\r
-\r
-unsigned char unix2dos[256]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,70,35,36,37,38,39,40,41,82,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,84,59,36,61,65,71,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,36,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,36,125,126,127,199,252,233,226,228,224,229,231,234,235,232,239,238,236,196,197,201,230,198,244,246,242,251,249,255,214,220,248,163,216,215,131,225,237,243,250,241,209,170,186,191,174,172,189,188,161,171,187,166,166,166,166,166,193,194,192,169,166,166,43,43,162,165,43,43,45,45,43,45,43,227,195,43,43,45,45,166,45,43,164,240,208,202,203,200,105,205,206,207,43,43,166,220,166,204,175,211,223,212,210,245,213,181,254,222,218,219,217,253,221,175,180,173,177,61,190,182,167,247,184,176,168,183,185,179,178,166,160};\r
-\r
-void usage()\r
-{\r
-  fprintf(stderr,"Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n");\r
-  exit(1);\r
-}\r
-\r
-void parse_options(int argc,char **argv)\r
-{\r
-  static char const short_options[]="bdf:o:p:rw";\r
-  static struct option long_options[]=\r
-  {\r
-    {"backslash",no_argument,NULL,'b'},\r
-    {"debug",no_argument,NULL,'d'},\r
-    {"format",required_argument,NULL,'f'},\r
-    {"output",required_argument,NULL,'o'},\r
-    {"prefix",required_argument,NULL,'p'},\r
-    {"recursive",no_argument,NULL,'r'},\r
-    {"windows",no_argument,NULL,'w'}\r
-  };\r
-  int c;\r
-  int option_index=0;\r
-  while ((c=getopt_long(argc,argv,short_options,long_options,&option_index))!=-1)\r
-  {\r
-    switch(c)\r
-    {\r
-      case 'b':\r
-        separator='\\';\r
-        break;\r
-      case 'd':\r
-        debug=1;\r
-        break;\r
-      case 'f':\r
-        if (strcmp(optarg,"m3u")==0)  format=0; else\r
-        if (strcmp(optarg,"pls")==0)  format=1; else\r
-        if (strcmp(optarg,"html")==0) format=2; else\r
-        usage();\r
-        break;\r
-      case 'o':\r
-        close(1);\r
-        if (fopen(optarg,"w")==NULL) { fprintf(stderr,"Error >> unable to open output file : %s\n",optarg); exit(2); }\r
-        break;\r
-      case 'p':\r
-        prefix=malloc(strlen(optarg)+1);\r
-        strcpy(prefix,optarg);\r
-        break;\r
-      case 'r':\r
-        recursive=1;\r
-        break;\r
-      case 'w':\r
-        windows=1;\r
-        break;\r
-      default:\r
-        usage();\r
-    }\r
-  }\r
-}\r
-\r
-void parse_mp3(unsigned char *file)\r
-{\r
-  int bitrates[2][3][15]=\r
-    {{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448},\r
-      {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384},\r
-      {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}},\r
-     {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},\r
-      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},\r
-      {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}};\r
-  FILE *fic;\r
-  unsigned char *c;\r
-  int lus;\r
-\r
-  if (debug) fprintf(stderr,"Debug >> parsing mp3 : %s\n",file);\r
-\r
-  /* read header */\r
-  if ((fic=fopen(file,"r"))==NULL) { fprintf(stderr,"Warning >> can't open file : %s\n",file); return; }\r
-  lus=fread(buffer,1,MAX,fic);\r
-  c=buffer;  \r
-\r
-  /* try ID3v2 */\r
-  if (buffer[0]=='I' && buffer[1]=='D' && buffer[2]=='3')\r
-  {\r
-    int size;\r
-    int version;\r
-    version=*(buffer+3);\r
-    if (version<2 || version>4)\r
-      fprintf(stderr,"Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",version,file);\r
-    if (*(buffer+5)!=0)\r
-      fprintf(stderr,"Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",*(buffer+5),file);\r
-    c=buffer+6;\r
-    size=(*c<<24)+(*(c+1)<<16)+(*(c+2)<<8)+(*(c+3));\r
-    if (size>lus) size=lus;\r
-    c+=4;\r
-    if (version==2) while (c<buffer+size-10)\r
-    {\r
-      int size=(*(c+3)<<16)+(*(c+4)<<8)+(*(c+5));\r
-      if (*c==0) break;\r
-      if (strncmp(c,"TT2",3)==0)\r
-      {\r
-        strncpy(title,c+7,size-1);\r
-        title[size-1]='\0';\r
-      }\r
-      if (strncmp(c,"TP1",3)==0)\r
-      {\r
-        strncpy(artist,c+7,size-1);\r
-        artist[size-1]='\0';\r
-      }\r
-      c+=size+6;\r
-    }\r
-    if (version==3 || version==4) while (c<buffer+size-10)\r
-    {\r
-      int size=(*(c+4)<<24)+(*(c+5)<<16)+(*(c+6)<<8)+(*(c+7));\r
-      if (*c==0) break;\r
-      if (strncmp(c,"TIT2",4)==0)\r
-      {\r
-        strncpy(title,c+11,size-1);\r
-        title[size-1]='\0';\r
-      }\r
-      if (strncmp(c,"TPE1",4)==0)\r
-      {\r
-        strncpy(artist,c+11,size-1);\r
-        artist[size-1]='\0';\r
-      }\r
-      c+=size+10;\r
-    }\r
-  }\r
-  \r
-  while (c<buffer+lus-10)\r
-  {\r
-    if (*c==0xFF && (*(c+1)&0xF0)==0xF0)\r
-    {\r
-      int version;\r
-      int lay;\r
-      int bitrate_index;\r
-      int bitrate;\r
-      version=2-(*(c+1)>>3&1);\r
-      lay=4-(*(c+1)>>1&3);\r
-      bitrate_index=*(c+2)>>4&0xF;\r
-      if (version>=1 && version<=2 && lay-1>=0 && lay-1<=2 && bitrate_index>=0 && bitrate_index<=14)\r
-        bitrate=bitrates[version-1][lay-1][bitrate_index];\r
-      else\r
-        bitrate=0;\r
-      if (bitrate!=0)\r
-      {\r
-        fseek(fic,0,SEEK_END);\r
-        duration=(ftell(fic)+buffer-c)/125/bitrate;\r
-      }\r
-      else\r
-        duration=0;\r
-      break;\r
-    }\r
-    c++;\r
-  }\r
-\r
-  /* try ID3v1 */\r
-  if (strlen(artist)==0 && strlen(title)==0)\r
-  {\r
-    fseek(fic,-128,SEEK_END);\r
-    lus=fread(buffer,1,128,fic);\r
-    if (lus==128 && buffer[0]=='T' && buffer[1]=='A' && buffer[2]=='G')\r
-    {\r
-      strncpy(title,buffer+3,30);\r
-      title[30]='\0';\r
-      c=title+29;\r
-      while (c>title && *c==' ') *(c--)='\0';\r
-      strncpy(artist,buffer+33,30);\r
-      artist[30]='\0';\r
-      c=artist+29;\r
-      while (c>artist && *c==' ') *(c--)='\0';\r
-    }\r
-  }\r
-\r
-  fclose(fic);\r
-}  \r
-\r
-void parse_ogg(unsigned char *file)\r
-{\r
-  FILE *fic;\r
-  unsigned char *c;\r
-  int lus;\r
-  int sample_rate;\r
-  int samples;\r
-\r
-  if (debug) fprintf(stderr,"Debug >> parsing ogg : %s\n",file);\r
-\r
-  /* read header */\r
-  if ((fic=fopen(file,"r"))==NULL) { fprintf(stderr,"Warning >> can't open file : %s\n",file); return; }\r
-  lus=fread(buffer,1,MAX,fic);\r
-\r
-  /* try Ogg */\r
-  if (buffer[0]!='O' && buffer[1]!='g' && buffer[2]!='g')\r
-  {\r
-    fprintf(stderr,"Warning >> not a Ogg header : %s\n",file);\r
-    return;\r
-  }\r
-  \r
-  c=buffer+0x28;\r
-  sample_rate=(*c)+(*(c+1)<<8)+(*(c+2)<<16)+(*(c+3)<<24);\r
-\r
-  while (c<buffer+lus-10)\r
-  {\r
-    int size;\r
-    if (strncmp(c,"TITLE=",6)==0)\r
-    {\r
-      size=*(c-4)+(*(c-3)<<8)+(*(c-2)<<16)+(*(c-1)<<24);\r
-      strncpy(title,c+6,size-6);\r
-      title[size-6]='\0';\r
-      c+=size;\r
-    }\r
-    if (strncmp(c,"ARTIST=",7)==0)\r
-    {\r
-      size=*(c-4)+(*(c-3)<<8)+(*(c-2)<<16)+(*(c-1)<<24);\r
-      strncpy(artist,c+7,size-7);\r
-      artist[size-7]='\0';\r
-      c+=size;\r
-    }\r
-    c++;\r
-  }\r
-  \r
-  fseek(fic,-MAX,SEEK_END);\r
-  lus=fread(buffer,1,MAX,fic);\r
-  c=buffer+lus-1;\r
-  while (strncmp(c,"OggS",4)!=0 && c>buffer) c--;\r
-  if (c!=buffer)\r
-  {\r
-    c+=6;\r
-    samples=(*c)+(*(c+1)<<8)+(*(c+2)<<16)+(*(c+3)<<24);\r
-    duration=samples/sample_rate;\r
-  }\r
-  \r
-  fclose(fic);\r
-}\r
-\r
-void parse_directory(unsigned char *path)\r
-{\r
-  int i,n;\r
-  struct dirent **namelist;\r
-  unsigned char newpath[PATH_MAX];\r
-  unsigned char *c;\r
-  struct stat infos;\r
-\r
-  void print_faketitle()\r
-  {\r
-    c=newpath+strlen(newpath);\r
-    while (c>newpath && *c!='/') c--;\r
-    while (c<newpath+strlen(newpath)-5)\r
-    {\r
-      c++;\r
-      if (*c=='_') putchar(' '); else if (windows) putchar(unix2dos[*c]); else putchar(*c);\r
-    }\r
-  }\r
-  \r
-  void print_path()\r
-  {\r
-    printf(prefix);\r
-    for (c=newpath+skip;*c!='\0';c++) if (*c=='/') putchar(separator); else if (windows) putchar(unix2dos[*c]); else putchar(*c);\r
-  }\r
-\r
-  if (debug) fprintf(stderr,"Debug >> parsing directory : %s\n",path);\r
-  if ((n=scandir(path,&namelist,0,alphasort))<0) { fprintf(stderr,"Warning >> can't open directory : %s\n",path); return; }\r
-  for (i=0;i<n;i++)\r
-  {\r
-    sprintf(newpath,"%s/%s",path,namelist[i]->d_name);\r
-    if (stat(newpath,&infos)!=0) { fprintf(stderr,"Warning >> can't stat file : %s\n",newpath); continue; }\r
-    if (recursive && S_ISDIR(infos.st_mode) && strcmp(namelist[i]->d_name,".")!=0 && strcmp(namelist[i]->d_name,"..")!=0) parse_directory(newpath);\r
-    if (S_ISREG(infos.st_mode))\r
-    {\r
-      unsigned char ext[5];\r
-      int j;\r
-      for (j=0;j<5;j++) ext[j]=tolower(namelist[i]->d_name[strlen(namelist[i]->d_name)-4+j]);\r
-      artist[0]='\0';\r
-      title[0]='\0';\r
-      duration=-2;\r
-      if (strcmp(".mp3",ext)==0) { duration=-1; parse_mp3(newpath); }\r
-      if (strcmp(".ogg",ext)==0) { duration=-1; parse_ogg(newpath); }\r
-      if (strcmp(".wav",ext)==0) { duration=-1; /* parse_wav(newpath); */ }\r
-      \r
-      if (duration!=-2) /* is it an audio file ? */\r
-      {\r
-        counter++;\r
-        switch (format)\r
-        {\r
-          case 0:\r
-            if (duration!=-1)\r
-            {\r
-              printf("#EXTINF:%d,",duration);\r
-              if (strlen(artist)==0 && strlen(title)==0) print_faketitle();\r
-              else printf("%s - %s",artist,title);\r
-              putchar('\n');\r
-            }\r
-            print_path();\r
-            putchar('\n');\r
-            break;\r
-          case 1:\r
-            printf("File%d=",counter);\r
-            print_path();\r
-            putchar('\n');\r
-            printf("Title%d=",counter);\r
-            if (strlen(artist)==0 && strlen(title)==0) print_faketitle();\r
-            else printf("%s - %s",artist,title);\r
-            putchar('\n');\r
-            if (duration!=-1) printf("Length%d=%d\n",counter,duration);\r
-            break;\r
-          case 2:\r
-            printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>",counter,artist,title);\r
-            if (duration==-1) printf("?"); else printf("%d:%s%d",duration/60,duration%60<10?"0":"",duration%60);\r
-            printf("</td></tr>\n");\r
-            break;\r
-        }\r
-      }\r
-    }\r
-    free(namelist[i]);\r
-  }\r
-  free(namelist);\r
-}\r
-\r
-int main(int argc,char **argv)\r
-{\r
-  parse_options(argc,argv);\r
-  if (optind==argc) usage();\r
-  switch (format)\r
-  {\r
-    case 0:\r
-      printf("#EXTM3U\n");\r
-      break;\r
-    case 1:\r
-      printf("[playlist]\n");\r
-      break;\r
-    case 2:\r
-      printf("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\n<html>\n\n<head>\n<title>Playlist generated by FAPG 0.1</title>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\n<style type=\"text/css\">\n<!--\n\nbody,td,tr {\n  font-family: Verdana, Arial, Helvetica, sans-serif;\n  font-size: 12px;\n  color: #000000;\n}\n\nbody {\n  background: #ffffff;\n}\n\nth {\n  text-align: center;\n  background: #ffcccc;\n  padding-left: 15px;\n  padding-right: 15px;\n  border: 1px #dd8888 solid;\n}\n\ntd {\n  text-align: center;\n  background: #eeeeee;\n  padding-left: 15px;\n  padding-right: 15px;\n  border: 1px #cccccc solid;\n}\n\nh1 {\n  font-size: 25px;\n}\n\np {\n  font-size: 10px;\n}\n\na {\n  color: #993333;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\n-->\n</style>\n</head>\n\n<body>\n\n<h1>Playlist</h1>\n\n<table>\n<tr><th>Entry</th><th>Artist</th><th>Title</th><th>Length</th></tr>\n");\r
-      break;\r
-  }\r
-  for (;optind<argc;optind++)\r
-  {\r
-    if (strlen(prefix)!=0) skip=strlen(argv[optind]);\r
-    parse_directory(argv[optind]);\r
-  }\r
-  switch(format)\r
-  {\r
-    case 1:\r
-      printf("NumberOfEntries=%d\nVersion=2\n",counter);\r
-      break;\r
-    case 2:\r
-      printf("</table>\n\n<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG 0.1</a></p>\n\n</body>\n\n</html>");\r
-      break;\r
-  }\r
-  exit(0);\r
-}\r
+/*
+ * FAPG means Fast Audio Playlist Generator.
+ * It is a tool to generate list of audio files (Wav, MP3, Ogg, etc)
+ * in various formats (M3U, PLS, HTML, etc).
+ * It is very usefull if you have a large amount of audio files
+ * and you want to quickly and frequently build a playlist.
+ *
+ * Copyright (C) 2003-2004  Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/fapg/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <limits.h>
+#include <unistd.h>
+#include <ctype.h>
+#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*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 = FORMAT_M3U;
+char *genrelist = NULL;
+unsigned char *prefix = "";
+unsigned char *base = "";
+unsigned char *dir = "";
+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 = '/';
+unsigned char *eol = "\n";
+unsigned char buffer[MAX];
+
+int counter = 0;
+
+unsigned char artist[MAX_ITEM];
+unsigned char title[MAX_ITEM];
+unsigned char genrebuf[MAX_ITEM];
+unsigned char genre = 0;
+int duration;
+#define MP2ENC 1
+#define MP3ENC 2
+#define MPCENC 3
+#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
+};
+
+unsigned char unix2dos[] =
+    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+    32, 33, 70, 35, 36, 37, 38, 39, 40, 41, 82, 43, 44, 45, 46, 47,
+    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 84, 59, 36, 61, 65, 71,
+    64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+    80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 36, 93, 94, 95,
+    96, 97, 98, 99, 100, 101, 102, 103,
+    104, 105, 106, 107, 108, 109, 110, 111,
+    112, 113, 114, 115, 116, 117, 118, 119,
+    120, 121, 122, 123, 36, 125, 126, 127,
+    199, 252, 233, 226, 228, 224, 229, 231,
+    234, 235, 232, 239, 238, 236, 196, 197,
+    201, 230, 198, 244, 246, 242, 251, 249,
+    255, 214, 220, 248, 163, 216, 215, 131,
+    225, 237, 243, 250, 241, 209, 170, 186,
+    191, 174, 172, 189, 188, 161, 171, 187,
+    166, 166, 166, 166, 166, 193, 194, 192,
+    169, 166, 166, 43, 43, 162, 165, 43,
+    43, 45, 45, 43, 45, 43, 227, 195,
+    43, 43, 45, 45, 166, 45, 43, 164,
+    240, 208, 202, 203, 200, 105, 205, 206,
+    207, 43, 43, 166, 220, 166, 204, 175,
+    211, 223, 212, 210, 245, 213, 181, 254,
+    222, 218, 219, 217, 253, 221, 175, 180,
+    173, 177, 61, 190, 182, 167, 247, 184,
+    176, 168, 183, 185, 179, 178, 166, 160
+};
+
+unsigned char *basemap;
+unsigned char *winorunix;
+unsigned char one2one[] =
+    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    16, 17, 18, 19, 20, 21, 22, 23,
+    24, 25, 26, 27, 28, 29, 30, 31,
+    32, 33, 34, 35, 36, 37, 38, 39,
+    40, 41, 42, 43, 44, 45, 46, 47,
+    48, 49, 50, 51, 52, 53, 54, 55,
+    56, 57, 58, 59, 60, 61, 62, 63,
+    64, 65, 66, 67, 68, 69, 70, 71,
+    72, 73, 74, 75, 76, 77, 78, 79,
+    80, 81, 82, 83, 84, 85, 86, 87,
+    88, 89, 90, 91, 92, 93, 94, 95,
+    96, 97, 98, 99, 100, 101, 102, 103,
+    104, 105, 106, 107, 108, 109, 110, 111,
+    112, 113, 114, 115, 116, 117, 118, 119,
+    120, 121, 122, 123, 124, 125, 126, 127,
+    128, 129, 130, 131, 132, 133, 134, 135,
+    136, 137, 138, 139, 140, 141, 142, 143,
+    144, 145, 146, 147, 148, 149, 150, 151,
+    152, 153, 154, 155, 156, 157, 158, 159,
+    160, 161, 162, 163, 164, 165, 166, 167,
+    168, 169, 170, 171, 172, 173, 174, 175,
+    176, 177, 178, 179, 180, 181, 182, 183,
+    184, 185, 186, 187, 188, 189, 190, 191,
+    192, 193, 194, 195, 196, 197, 198, 199,
+    200, 201, 202, 203, 204, 205, 206, 207,
+    208, 209, 210, 211, 212, 213, 214, 215,
+    216, 217, 218, 219, 220, 221, 222, 223,
+    224, 225, 226, 227, 228, 229, 230, 231,
+    232, 233, 234, 235, 236, 237, 238, 239,
+    240, 241, 242, 243, 244, 245, 246, 247,
+    248, 249, 250, 251, 252, 253, 254, 255
+};                              /* identical mapping */
+
+unsigned char noand[256] =
+    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+    16, 17, 18, 19, 20, 21, 22, 23,
+    24, 25, 26, 27, 28, 29, 30, 31,
+    32, 33, 34, 35, 36, 37, 43, 39,
+    40, 41, 42, 43, 44, 45, 46, 47,
+    48, 49, 50, 51, 52, 53, 54, 55,
+    56, 57, 58, 59, 60, 61, 62, 63,
+    64, 65, 66, 67, 68, 69, 70, 71,
+    72, 73, 74, 75, 76, 77, 78, 79,
+    80, 81, 82, 83, 84, 85, 86, 87,
+    88, 89, 90, 91, 92, 93, 94, 95,
+    96, 97, 98, 99, 100, 101, 102, 103,
+    104, 105, 106, 107, 108, 109, 110, 111,
+    112, 113, 114, 115, 116, 117, 118, 119,
+    120, 121, 122, 123, 124, 125, 126, 127,
+    128, 129, 130, 131, 132, 133, 134, 135,
+    136, 137, 138, 139, 140, 141, 142, 143,
+    144, 145, 146, 147, 148, 149, 150, 151,
+    152, 153, 154, 155, 156, 157, 158, 159,
+    160, 161, 162, 163, 164, 165, 166, 167,
+    168, 169, 170, 171, 172, 173, 174, 175,
+    176, 177, 178, 179, 180, 181, 182, 183,
+    184, 185, 186, 187, 188, 189, 190, 191,
+    192, 193, 194, 195, 196, 197, 198, 199,
+    200, 201, 202, 203, 204, 205, 206, 207,
+    208, 209, 210, 211, 212, 213, 214, 215,
+    216, 217, 218, 219, 220, 221, 222, 223,
+    224, 225, 226, 227, 228, 229, 230, 231,
+    232, 233, 234, 235, 236, 237, 238, 239,
+    240, 241, 242, 243, 244, 245, 246, 247,
+    248, 249, 250, 251, 252, 253, 254, 255
+};                              /* only '&' is mapped to '+' */
+
+unsigned char *iso2web[256] = {
+    "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
+    "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
+    "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
+    "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
+    "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
+    "%28", "%29", "%2a", "+", ",", "-", ".", "/",
+    "0", "1", "2", "3", "4", "5", "6", "7",
+    "8", "9", ":", ";", "%3c", "=", "%3e", "%3f",
+    "@", "A", "B", "C", "D", "E", "F", "G",
+    "H", "I", "J", "K", "L", "M", "N", "O",
+    "P", "Q", "R", "S", "T", "U", "V", "W",
+    "X", "Y", "Z", "%5B", "\\", "%5D", "^", "_",
+    "`", "a", "b", "c", "d", "e", "f", "g",
+    "h", "i", "j", "k", "l", "m", "n", "o",
+    "p", "q", "r", "s", "t", "u", "v", "w",
+    "x", "y", "z", "%7b", "|", "%7d", "~", "%7f",
+    "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
+    "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
+    "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
+    "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
+    "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
+    "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
+    "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
+    "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
+    "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
+    "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
+    "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
+    "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
+    "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
+    "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
+    "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
+    "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
+};
+
+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=" 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)
+{
+    while(*c != 0) {
+        mywebputchar(*c);
+        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) {
+        if(*c == '/')
+            putchar(separator);
+        else
+            myputchar(*c);
+        c++;
+        /* remove multiple slashes "//" when parsing a directory ending with a "/" */
+        while(*c == '/' && c[1] == '/')
+            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("%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 */
+        while(*c == '/')
+            c++;
+    }
+    for(; *c != '\0'; c++) {
+        mywebputchar(*c);
+        /* remove multiple "//" when parsing a directory ending with a "/" */
+        while(*c == '/' && c[1] == '/')
+            c++;
+    }
+}
+
+void print_path(const char *path)
+{
+    const char *c = path;
+    printf("%s", prefix);
+    /* skip leading "./" when parsing current directory */
+    if(*c == '.' && *(c + 1) == '/') {
+        c += 2;
+        /* maybe there follow more slashes */
+        while(*c == '/')
+            c++;
+    }
+    myputstr(c);
+}
+
+void print_pathtail(const char *path)
+{
+    const char *c;
+    c = strrchr(path, separator);
+    if(c != NULL)
+        c++;
+    else
+        c = path;
+    myputstr(c);
+}
+
+void noreferal(const char *path, const char *artist, const char *title)
+{
+    printf("\t\t<description><![CDATA[<h4>");
+    myputstr(artist);
+    printf("</h4><h5>");
+    myputstr(title);
+    printf("</h5><a href=\"");
+    print_webpath(path);
+    printf
+        ("\"><br><br>Direct Link to Audiofile</a><br>]]></description>%s",
+         eol);
+}
+
+void reference(const char *title)
+{
+    FILE *pipe = NULL;
+    static char command[2048], buffer[1024];
+    int buflen = 8192;
+
+    buflen = strlen(title) + strlen(referal) + 3;
+    assert((buflen < 2046));
+    strcpy(command, referal);
+    buflen = strlen(command);
+    command[buflen] = ' ';
+    command[buflen + 1] = '"';
+    command[buflen + 2] = 0;
+    strcat(command, title);
+    buflen = strlen(command);
+    command[buflen] = '"';
+    command[buflen + 1] = 0;
+    if(debug)
+        fprintf(stderr, "Debug >> processing command: %s\n", command);
+    pipe = popen(command, "r");
+    if(pipe == NULL) {
+        fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
+        return;
+    }
+    fgets(buffer, 1020, pipe);
+    while(!feof(pipe)) {
+        fputs(buffer, stdout);
+        fgets(buffer, 1020, pipe);
+    }
+    pclose(pipe);
+    return;
+}
+
+void parse_options(int argc, char **argv)
+{
+    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, 'c'},
+        {"debug", no_argument, NULL, 'd'},
+        {"format", required_argument, NULL, 'f'},
+        {"genre", required_argument, NULL, 'g'},
+        {"nohardlink", no_argument, NULL, 'n'},
+        {"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'},
+        {NULL, 0, NULL, 0}
+    };
+    int c;
+    int option_index = 0;
+    while((c =
+           getopt_long(argc, argv, short_options, long_options,
+                       &option_index)) != -1) {
+        switch (c) {
+        case 'b':
+            separator = '\\';
+            noand['/'] = '\\';
+            break;
+        case 'c':
+            if(strncmp(optarg, "intern", 6) == 0)
+                referal = NULL;
+            else
+                referal = strdup(optarg);
+            break;
+        case 'd':
+            debug = 1 - debug;
+            break;
+        case 'f':
+            if(strcmp(optarg, "m3u") == 0)
+                format = FORMAT_M3U;
+            else if(strcmp(optarg, "pls") == 0)
+                format = FORMAT_PLS;
+            else if(strcmp(optarg, "html") == 0)
+                format = FORMAT_HTML;
+            else if(strcmp(optarg, "rss") == 0)
+                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;
+        case 'g':
+            if(genrelist == NULL)
+                genrelist = calloc(257, sizeof(char));  /* allow multiple includes/excludes */
+            if(genrelist == NULL) {
+                fprintf(stderr,
+                        "Error >> unable to allocate cleared memory\n");
+                exit(2);
+            } else {
+                unsigned int n = 0;
+                while(n < strlen(optarg)) {
+                    if(debug)
+                        fprintf(stderr,
+                                "Debug >> genrelist entry activting : %d\n",
+                                atoi(&optarg[n]));
+                    genrelist[atoi(&optarg[n])] = 1;
+                    while(isdigit(optarg[n++]));
+                }
+            }
+            break;
+        case 'n':
+            avoidhlinked = 1;
+            break;
+        case 'o':
+            close(1);
+            if(fopen(optarg, "w") == NULL) {
+                fprintf(stderr,
+                        "Error >> unable to open output file : %s\n",
+                        optarg);
+                exit(2);
+            }
+            break;
+        case 'p':
+            prefix = malloc(strlen(optarg) + 1);
+            strcpy(prefix, optarg);
+            base = malloc(strlen(prefix) + 1);
+            strcpy(base, prefix);
+            dir = strchr(base, '/');
+            if((dir != NULL) && (dir[1] == '/'))
+                dir = strchr(dir + 2, '/');
+            if(dir != NULL)
+                *dir++ = 0;
+            else
+                dir = "";
+            /* if prefix is a weblink, base is the baselink, dir is the path */
+            break;
+        case 'r':
+            recursive = 1;
+            break;
+        case 'u':
+            winorunix = one2one;
+            eol = "\n";
+            break;
+        case 'w':
+            winorunix = unix2dos;
+            eol = "\r\n";
+            break;
+        case 'x':
+            if(genrelist == NULL) {     /* allow multiple includes/excludes - not recommended (confusing) but possible */
+                int n = 0;
+                genrelist = calloc(257, sizeof(char));
+                while(n < 256)
+                    genrelist[n++] = 1;
+            }
+            if(genrelist == NULL) {
+                fprintf(stderr,
+                        "Error >> unable to allocate cleared memory\n");
+                exit(2);
+            } else {
+                unsigned int n = 0;
+                while(n < strlen(optarg)) {
+                    if(debug)
+                        fprintf(stderr,
+                                "Debug >> genrelist entry activting : %d\n",
+                                atoi(&optarg[n]));
+                    genrelist[atoi(&optarg[n])] = 0;
+                    while(isdigit(optarg[n++]));
+                }
+            }
+            break;
+        case 's':
+            fromstdin = 1;
+            break;
+        default:
+            usage();
+        }
+    }
+    /* hostname = getenv("HOSTNAME"); */
+    if(genrelist == NULL) {
+        genrelist = calloc(257, sizeof(char));
+        if(genrelist == NULL) {
+            fprintf(stderr,
+                    "Error >> unable to allocate cleared memory\n");
+            exit(2);
+        } else {
+            int n = 0;
+            while(n < 256)
+                genrelist[n++] = 1;
+        }
+    }
+}
+
+void parse_mp3(unsigned char *file)
+{
+    int bitrates[2][3][15] =
+        { {{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384,
+            416, 448},
+           {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
+            384},
+           {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256,
+            320}},
+    {{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
+     {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
+     {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}}
+    };
+    FILE *fic;
+    unsigned char *c;
+    int lus;
+
+    genre = 0;
+    genrebuf[0] = 0;
+    if(debug)
+        fprintf(stderr, "Debug >> parsing mp3 : %s\n", file);
+
+    /* read header */
+    if((fic = fopen(file, "rb")) == NULL) {
+        fprintf(stderr, "Warning >> can't open file : %s\n", file);
+        return;
+    }
+    lus = fread(buffer, 1, MP3_BASE, fic);
+    c = buffer;
+
+    /* try ID3v2 */
+    if(buffer[0] == 'I' && buffer[1] == 'D' && buffer[2] == '3') {
+        int size;
+        int version;
+        version = *(buffer + 3);
+        if(version < 2 || version > 4)
+            fprintf(stderr,
+                    "Warning >> ID3 v2.%d not implemented ! trying anyway : %s\n",
+                    version, file);
+        if(*(buffer + 5) != 0)
+            fprintf(stderr,
+                    "Warning >> specials headers not implemented (%d) ! trying anyway : %s\n",
+                    *(buffer + 5), file);
+        c = buffer + 6;
+        size =
+            (*c << 21) + (*(c + 1) << 14) + (*(c + 2) << 7) + (*(c + 3));
+        /* read more header */
+        if(size + lus > MAX) {
+            lus += fread(buffer + lus, 1, MAX - lus, fic);
+            fprintf(stderr,
+                    "Warning >> ID3 header is huge (%d bytes) ! trying anyway : %s\n",
+                    size, file);
+        } else
+            lus += fread(buffer + lus, 1, size, fic);
+        if(size > lus)
+            size = lus;
+        c += 4;
+        if(version == 2)
+            while(c < buffer + size) {
+                int size = (*(c + 3) << 16) + (*(c + 4) << 8) + (*(c + 5));
+                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';
+                }
+                if(strncmp(c, "TCO", 3) == 0) {
+                    /* strncpy(genrebuf,c+7,size-1); */
+                    /* genrebuf[size-1]='\0'; */
+                    /* genre=atoi(&genrebuf[1]); */
+                    genre = atoi(c + 8);
+                }
+                c += size + 6;
+            }
+        if(version == 3 || version == 4)
+            while(c < buffer + size) {
+                int size =
+                    (*(c + 4) << 24) + (*(c + 5) << 16) + (*(c + 6) << 8) +
+                    (*(c + 7));
+                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';
+                }
+                if(strncmp(c, "TCON", 4) == 0) {
+                    /* strncpy(genrebuf,c+11,size-1); */
+                    /* genrebuf[size-1]='\0'; */
+                    /* genre=atoi(&genrebuf[1]); */
+                    genre = atoi(c + 12);
+                }
+                if(strncmp(c, "TLEN", 4) == 0) {
+                    duration = atoi(c + 11) / 1000;
+                }
+                c += size + 10;
+            }
+    }
+
+    while(c < buffer + lus - 10) {
+        if(*c == 0xFF && (*(c + 1) & 0xF0) == 0xF0) {
+            int version;
+            int lay;
+            int bitrate_index;
+            int bitrate;
+            version = 2 - (*(c + 1) >> 3 & 1);
+            lay = 4 - (*(c + 1) >> 1 & 3);
+            bitrate_index = *(c + 2) >> 4 & 0xF;
+            if(version >= 1 && version <= 2 && lay - 1 >= 0 && lay - 1 <= 2
+               && bitrate_index >= 0 && bitrate_index <= 14)
+                bitrate = bitrates[version - 1][lay - 1][bitrate_index];
+            else
+                bitrate = 0;
+            if(bitrate != 0) {
+                fseek(fic, 0, SEEK_END);
+                duration = (ftell(fic) + buffer - c) / 125 / bitrate;
+            } else
+                duration = 0;
+            break;
+        }
+        c++;
+    }
+
+    /* try ID3v1 */
+    if(strlen(artist) == 0 && strlen(title) == 0) {
+        fseek(fic, -128, SEEK_END);
+        lus = fread(buffer, 1, 128, fic);
+        if(lus == 128 && buffer[0] == 'T' && buffer[1] == 'A'
+           && buffer[2] == 'G') {
+            strncpy(title, buffer + 3, 30);
+            title[30] = '\0';
+            c = title + 29;
+            while(c > title && *c == ' ')
+                *(c--) = '\0';
+            strncpy(artist, buffer + 33, 30);
+            artist[30] = '\0';
+            c = artist + 29;
+            while(c > artist && *c == ' ')
+                *(c--) = '\0';
+            /* strncpy(album,buffer+65,30); */
+            /* strncpy(year,buffer+97,4); */
+            /* strncpy(comment,buffer+101,30); */
+            /* strncpy(genrebuf,buffer+127,1); genre[1]=0; */
+            genre = buffer[127];
+        }
+    }
+
+    fclose(fic);
+}
+
+void parse_ogg(unsigned char *file)
+{
+    FILE *fic;
+    unsigned char *c;
+    int lus;
+    int sample_rate;
+    int samples;
+
+    if(debug)
+        fprintf(stderr, "Debug >> parsing ogg : %s\n", file);
+
+    /* read header */
+    if((fic = fopen(file, "r")) == NULL) {
+        fprintf(stderr, "Warning >> can't open file : %s\n", file);
+        return;
+    }
+    lus = fread(buffer, 1, OGG_BASE, fic);
+
+    /* try Ogg */
+    if(strncmp(buffer, "Ogg", 3) != 0) {
+        fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
+        return;
+    }
+
+    c = buffer + 0x28;
+    sample_rate =
+        (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+
+    while(c < buffer + lus - 10) {
+        int size;
+        if(strncasecmp(c, "TITLE=", 6) == 0) {
+            size =
+                *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+                (*(c - 1) << 24);
+            strncpy(title, c + 6, size - 6);
+            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) +
+                (*(c - 1) << 24);
+            strncpy(artist, c + 7, size - 7);
+            artist[size - 7] = '\0';
+            c += size;
+        }
+        if(strncasecmp(c, "GENRE=", 6) == 0) {
+            static int i = 0;
+            size =
+                *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) +
+                (*(c - 1) << 24);
+            strncpy(genrebuf, c + 6, size - 6);
+            genrebuf[size - 6] = '\0';
+            c += size;
+            for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
+                if(strcasecmp(ID3_v1_genre_description[i], genrebuf) == 0) {
+                    genre = i;
+                    break;
+                }
+                if(i == ID3_NR_OF_V1_GENRES)
+                    genre = 0;
+            }
+        }
+        c++;
+    }
+
+    fseek(fic, -OGG_BASE, SEEK_END);
+    lus = fread(buffer, 1, OGG_BASE, fic);
+    c = buffer + lus - 1;
+    while(strncmp(c, "OggS", 4) != 0 && c > buffer)
+        c--;
+    if(c != buffer) {
+        c += 6;
+        samples =
+            (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+        duration = samples / sample_rate;
+    }
+
+    fclose(fic);
+}
+
+void parse_mpc(unsigned char *file)
+{
+    FILE *fic;
+    unsigned char *c;
+    int lus;
+    int sample_rates[4] = { 44100, 48000, 37800, 32000 };
+    int frame_count;
+    int size, items;
+    int i;
+
+    if(debug)
+        fprintf(stderr, "Debug >> parsing mpc : %s\n", file);
+
+    /* read header */
+    if((fic = fopen(file, "r")) == NULL) {
+        fprintf(stderr, "Warning >> can't open file : %s\n", file);
+        return;
+    }
+    lus = fread(buffer, 1, 12, fic);
+
+    /* try Musepack */
+    if (strncmp(buffer, "MP+", 3) != 0) {
+        fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
+        return;
+    }
+
+    /* only version 7 */
+    if(buffer[3] != 7) {
+        fprintf(stderr, "Warning >> only Musepack SV7 supported : %s\n",
+                file);
+        return;
+    }
+
+    /* duration */
+    c = buffer + 4;
+    frame_count =
+        (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+    c += 5;
+    duration = frame_count * 1152 / sample_rates[*c & 3];
+
+    /* try APETAGEX footer */
+    fseek(fic, -32, SEEK_END);
+    lus = fread(buffer, 1, 32, fic);
+    if(lus == 32 && strncmp(buffer, "APETAGEX", 8) == 0) {
+        c = buffer + 12;
+        size =
+            (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+        size += 32;
+        c += 4;
+        items =
+            (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) + (*(c + 3) << 24);
+        fseek(fic, -size, SEEK_END);
+        lus = fread(buffer, 1, size, fic);
+        if(lus == size && strncmp(buffer, "APETAGEX", 8) == 0) {
+            c = buffer + 32;
+            while(items--) {
+                size =
+                    (*c) + (*(c + 1) << 8) + (*(c + 2) << 16) +
+                    (*(c + 3) << 24);
+                c += 8;
+                if(strcasecmp(c, "TITLE") == 0) {
+                    strncpy(title, c + 6, size);
+                    title[size] = '\0';
+                }
+                if(strcasecmp(c, "ARTIST") == 0) {
+                    strncpy(artist, c + 7, size);
+                    artist[size] = '\0';
+                }
+                if(strcasecmp(c, "GENRE") == 0) {
+                    for(i = 0; i < ID3_NR_OF_V1_GENRES; i++) {
+                        strncpy(genrebuf, c + 6, size);
+                        genrebuf[size] = '\0';
+                        if(strcasecmp
+                           (ID3_v1_genre_description[i], genrebuf) == 0) {
+                            genre = i;
+                            break;
+                        }
+                        if(i == ID3_NR_OF_V1_GENRES)
+                            genre = 0;
+                    }
+                }
+                c += strlen(c) + 1 + size;
+            }
+        }
+    }
+
+    fclose(fic);
+}
+
+#define FSN  32
+#define MAXINO  (1<<24)
+#define INOTYP  unsigned long
+#define regbit_qry(x,y)  ( x[( (y) / sizeof(INOTYP) )]  &  1<<( (y) % sizeof(INOTYP) ) )
+#define regbit_set(x,y)  ( x[( (y) / sizeof(INOTYP) )]  |=  1<<( (y) % sizeof(INOTYP) ) )
+
+int hlink_check(struct stat *info)
+{
+    /*
+     * for speed this subroutine should only be called
+     * - if the file has more than one hardlink
+     * - if the file is a resolved softlink
+     */
+    /* the persistent variables */
+    static INOTYP *list[FSN];
+    static dev_t name[FSN];
+    /* some temporary variables */
+    int fsn, is_registered = 0;
+
+    /* assertions - in case parameters are lowered for less memory usage */
+    assert(fsn < FSN);
+    assert((info->st_ino) / sizeof(INOTYP) < MAXINO);
+
+    /* search which internal registration number is used for this filesystem */
+    for(fsn = 0; (name[fsn] != (info->st_dev)) && (name[fsn] != 0); fsn++);
+
+    /* if file system is not registered yet, do it and leave */
+    if(name[fsn] == 0) {
+        name[fsn] = (info->st_dev);
+        /* provide space for the bitmap that maps the inodes of this file system */
+        list[fsn] = (INOTYP *) calloc(MAXINO, sizeof(INOTYP));
+        /* no comparison is needed in empty lists ... return */
+        if(debug)
+            fprintf(stderr,
+                    "Debug >> Linked >> Init List %04x @mem %04lx\n",
+                    (int)name[fsn], (long)&list[fsn]);
+    } else {
+        /* this looks more complicated than it really is */
+        /* the idea is very simple: 
+         *  provide a bitmap that maps all inodes of a file system
+         *  to mark all files that have already been visited.
+         *  If it is already visited, do not add it to the playlist
+         */
+        /*
+         * The difficulty is as follows:
+         *   struct inode_bitmap { char registered:1; } bitmap[1<<MAXINO];
+         * would be byte-aligned and would allocate at least eight times the needed space.
+         * Feel free to change the definitions that are involved here, if you know better.
+         */
+        if(regbit_qry(list[fsn], (info->st_ino)))
+            is_registered = 1;
+        else
+            regbit_set(list[fsn], (info->st_ino));
+        /*
+         * the debug expression is more complicated then the working stuff
+         */
+        if(debug)
+            fprintf(stderr, "Debug >> Linked >> DEV %04x INO %06x => "
+                    "list[%02x][%04x] = %04x & %04x --> %s registered\n",
+                    (int)info->st_dev, (int)info->st_ino, fsn,
+                    (int)((info->st_ino) / sizeof(INOTYP)),
+                    (int)list[fsn][(info->st_ino) / sizeof(INOTYP)],
+                    1 << ((info->st_ino) % sizeof(INOTYP)),
+                    is_registered ? "Already" : "Not");
+    }
+    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, unsigned char * original_path)
+{
+    unsigned char ext[5];
+    int j, encoding = 0;
+
+    for(j = 0; j < 5; j++)
+        ext[j] = tolower(newpath[strlen(newpath) - 4 + j]);
+    artist[0] = '\0';
+    title[0] = '\0';
+    duration = -2;
+    if(strcmp(".mp2", ext) == 0) {
+        duration = -1;
+        parse_mp3(newpath);
+        encoding = MP2ENC;
+    }
+    if(strcmp(".mp3", ext) == 0) {
+        duration = -1;
+        parse_mp3(newpath);
+        encoding = MP3ENC;
+    }
+    if(strcmp(".mpc", ext) == 0) {
+        duration = -1;
+        parse_mpc(newpath);
+        encoding = MPCENC;
+    }
+    if(strcmp(".mp+", ext) == 0) {
+        duration = -1;
+        parse_mpc(newpath);
+        encoding = MPPENC;
+    }
+    if(strcmp(".ogg", ext) == 0) {
+        duration = -1;
+        parse_ogg(newpath);
+        encoding = OGGENC;
+    }
+    if(strcmp(".wav", ext) == 0) {
+        duration = -1;
+        /* parse_wav(newpath); */
+        encoding = WAVENC;
+    }
+    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
+        c = strchr(artist, '-');
+        if(c != NULL) {         // if trenner found, divide file name 
+            *c = '\0';
+            strcpy(title, ++c);
+            c = strrchr(title, '.');
+            if(c != NULL)
+                *c = '\0';
+        } else {                // no trenner found, assume
+            // no artist, only title
+            strcpy(title, artist);
+            artist[0] = '\0';
+        }
+        // replace underscores by spaces
+        for(c = artist; (c = strchr(c, '_')) != NULL; c++)
+            *c = ' ';
+        for(c = title; (c = strchr(c, '_')) != NULL; c++)
+            *c = ' ';
+        // trim spaces
+        trim(artist);
+        trim(title);
+    }
+    /* guesstitle() end */
+
+    if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
+        counter++;
+        switch (format) {
+        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 FORMAT_PLS:
+            printf("File%d=", counter);
+            print_path(newpath);
+            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 FORMAT_HTML:
+            printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
+                   artist, title);
+            if(duration == -1)
+                printf("?</td></tr>%s", eol);
+            else
+                printf("%d:%s%d</td></tr>%s", duration / 60,
+                       duration % 60 < 10 ? "0" : "", duration % 60, eol);
+            break;
+        case FORMAT_RSS:
+            if(duration != -1) {
+                struct stat infos;
+                char timebuffer[256];
+
+                if(stat(newpath, &infos) != 0) {
+                    fprintf(stderr, "Warning >> can't stat entry : %s\n",
+                            newpath);
+                    return;
+                }
+                strftime(timebuffer, 255, "%a %d %b %Y %T %z", localtime(&(infos.st_mtime)));   /* ctime() had a trailing CR */
+                printf("\t<item>%s", eol);
+                printf("\t\t<author>");
+                myputstr(artist);
+                printf("</author>%s\t\t<title>", eol);
+                myputstr(title);
+                printf("</title>%s", eol);
+
+                if(referal == NULL) {
+                    noreferal(newpath, artist, title);
+                } else
+                    reference(newpath);
+                printf("\t\t<pubDate>%s</pubDate>%s\t\t<enclosure url=\"",
+                       timebuffer, eol);
+                print_webpath(newpath);
+                printf("\" length=\"%d\" type=\"%s\"/>%s\t\t<guid>",
+                       (int)infos.st_size, magic[encoding], eol);
+                print_pathtail(newpath);
+                printf("</guid>%s", eol);
+                if(duration > 3599)
+                    printf
+                        ("\t\t<itunes:duration>%d:%02d:%02d</itunes:duration>%s",
+                         duration / 3600, (duration / 60) % 60,
+                         duration % 60, eol);
+                else
+                    printf
+                        ("\t\t<itunes:duration>%d:%02d</itunes:duration>%s",
+                         duration / 60, duration % 60, eol);
+                if(strlen(artist) != 0) {
+                    printf("\t\t<itunes:author>");
+                    myputstr(artist);
+                    printf("</itunes:author>%s", eol);
+                }
+                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, unsigned char * original_path)
+{
+    int i, n;
+    struct dirent **namelist;
+    unsigned char newpath[PATH_MAX];
+    struct stat infos;
+
+    if(debug)
+        fprintf(stderr, "Debug >> parsing directory : %s\n", path);
+    if(stat(path, &infos) != 0) {
+        fprintf(stderr, "Warning >> can't stat entry : %s\n", path);
+        return;
+    }
+    /* check if it is a filename */
+    if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
+        parse_file(path, original_path);
+        return;
+    }
+    /* must be a directory - or something unusable like pipe, socket, etc */
+    if((n = scandir(path, &namelist, 0, alphasort)) < 0) {
+        fprintf(stderr, "Warning >> can't open directory : %s\n", path);
+        return;
+    }
+    for(i = 0; i < n; i++) {
+        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);
+            continue;
+        }
+        if(recursive && S_ISDIR(infos.st_mode)
+           && strcmp(namelist[i]->d_name, ".") != 0
+           && strcmp(namelist[i]->d_name, "..") != 0)
+            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, original_path);
+        }
+        free(namelist[i]);
+    }
+    free(namelist);
+}
+
+int main(int argc, char **argv)
+{
+    winorunix = one2one;
+    basemap = one2one;
+    parse_options(argc, argv);
+
+    if(optind == argc && !fromstdin)
+        usage();
+
+    /* print header */
+    switch (format) {
+    case FORMAT_M3U:
+        printf("#EXTM3U%s", eol);
+        break;
+    case FORMAT_PLS:
+        printf("[playlist]%s", eol);
+        break;
+    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
+             "</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",
+             eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
+             eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
+             eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
+             eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
+             eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
+             eol, eol, eol);
+        break;
+    case FORMAT_RSS:
+        {
+            time_t zeit;
+            char timebuffer[256];
+            time(&zeit);
+            strftime(timebuffer, 255, "%a %d %b %Y %T %z",
+                     localtime(&zeit));
+            printf
+                ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>%s<!-- generator=\"FAPG "
+                 VERSION
+                 " -->%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 "
+                 VERSION
+                 "</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",
+                 eol, eol, eol, eol, hostname, dir, argv[optind], eol,
+                 prefix, eol, base, eol, prefix, eol, timebuffer, eol, eol,
+                 eol, base, eol, eol, base, eol, eol, eol, eol, base, eol,
+                 hostname, eol, eol, eol, dir, eol, getenv("LOGNAME"), eol,
+                 eol, getenv("LANG"), eol, eol, eol);
+            unix2dos[38] = 43;  // I never made an rss feed work with '&' in it
+            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
+    }
+    
+    /* 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 FORMAT_PLS:
+        printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
+        break;
+    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 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);
+}
+