/*
 * 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);
}

