version 0.41
[FAPG] / fapg.c
diff --git a/fapg.c b/fapg.c
index a72d4e7..176c495 100644 (file)
--- a/fapg.c
+++ b/fapg.c
 #include <time.h>
 #include <assert.h>
 #include "genres.h"
+#ifdef HAVE_LIBURIPARSER
+# include <uriparser/Uri.h>
+#endif
 
-#define VERSION "0.38"
 #define MP3_BASE 1024
 #define OGG_BASE 1024*10
 #define MAX 1024*200            /* 200ko for ID3 with JPEG images in it */
 
+#define FORMAT_M3U 0
+#define FORMAT_PLS 1
+#define FORMAT_HTML 2
+#define FORMAT_RSS 3
+#define FORMAT_PLP 4
+#define FORMAT_UMS 5
+#ifdef HAVE_LIBURIPARSER
+# define FORMAT_XSPF 6
+#endif
+
 int debug = 0;
-int format = 0;                 /* 0 = m3u ; 1 = pls ; 2 = html ; 3 = rss */
+int format = FORMAT_M3U;
 char *genrelist = NULL;
 unsigned char *prefix = "";
 unsigned char *base = "";
@@ -73,11 +85,13 @@ int duration;
 #define MPPENC 4
 #define OGGENC 5
 #define WAVENC 6
+#define WMAENC 7
 
 char *magic[] = { NULL,
     "audio/mpeg", "audio/mpeg",
     "audio/mpeg", "audio/mpeg",
     "audio/ogg-vorbis", "audio/x-wav",
+    "audio/x-ms-wma", 
     NULL
 };
 
@@ -217,8 +231,14 @@ unsigned char *iso2web[256] = {
 
 void usage()
 {
+#ifdef HAVE_LIBURIPARSER
+# define FAPG_FORMATS "m3u|pls|xspf|html|rss|pla|txx"
+#else
+# define FAPG_FORMATS "m3u|pls|html|rss|pla|txx"
+#endif
     fprintf(stderr,
-            "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla|txx] [-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");
+            "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);
 }
 
@@ -507,7 +527,8 @@ void parse_options(int argc, char **argv)
         {"recursive", no_argument, NULL, 'r'},
         {"stdin", no_argument, NULL, 's'},
         {"windows", no_argument, NULL, 'w'},
-        {"exclude", required_argument, NULL, 'x'}
+        {"exclude", required_argument, NULL, 'x'},
+        {NULL, 0, NULL, 0}
     };
     int c;
     int option_index = 0;
@@ -530,17 +551,21 @@ void parse_options(int argc, char **argv)
             break;
         case 'f':
             if(strcmp(optarg, "m3u") == 0)
-                format = 0;
+                format = FORMAT_M3U;
             else if(strcmp(optarg, "pls") == 0)
-                format = 1;
+                format = FORMAT_PLS;
             else if(strcmp(optarg, "html") == 0)
-                format = 2;
+                format = FORMAT_HTML;
             else if(strcmp(optarg, "rss") == 0)
-                format = 3;
+                format = FORMAT_RSS;
             else if(strcmp(optarg, "pla") == 0)
-                format = 4;
+                format = FORMAT_PLP;
             else if(strcmp(optarg, "txx") == 0)
-                format = 5;
+                format = FORMAT_UMS;
+#ifdef HAVE_LIBURIPARSER
+            else if(strcmp(optarg, "xspf") == 0)
+                format = FORMAT_XSPF;
+#endif
             else
                 usage();
             break;
@@ -818,7 +843,7 @@ void parse_ogg(unsigned char *file)
     lus = fread(buffer, 1, OGG_BASE, fic);
 
     /* try Ogg */
-    if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') {
+    if(strncmp(buffer, "Ogg", 3) != 0) {
         fprintf(stderr, "Warning >> not a Ogg header : %s\n", file);
         return;
     }
@@ -880,7 +905,6 @@ void parse_ogg(unsigned char *file)
     fclose(fic);
 }
 
-
 void parse_mpc(unsigned char *file)
 {
     FILE *fic;
@@ -902,7 +926,7 @@ void parse_mpc(unsigned char *file)
     lus = fread(buffer, 1, 12, fic);
 
     /* try Musepack */
-    if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') {
+    if (strncmp(buffer, "MP+", 3) != 0) {
         fprintf(stderr, "Warning >> not a Musepack header : %s\n", file);
         return;
     }
@@ -1038,8 +1062,192 @@ int hlink_check(struct stat *info)
     return is_registered;
 }
 
+#ifdef HAVE_LIBURIPARSER
+char * relative_uri_malloc(const char * unixFilename, const char * baseDir)
+{
+    char * absSourceFile;
+    size_t absSourceLen;
+    char * sourceUriString;
+    char * baseUriString;
+    UriParserStateA state;
+    UriUriA sourceUri;
+    UriUriA baseUri;
+    UriUriA relativeUri;
+    int charsRequired;
+    char * output;
+  
+    /* checks */
+    if ((unixFilename == NULL) || (baseDir == NULL)) {
+        return NULL;
+    }
+
+    /* base URI */
+    baseUriString = malloc((7 + 3 * strlen(baseDir) + 1) * sizeof(char));
+    if (baseUriString == NULL) {
+        return NULL;
+    }
+    if (uriUnixFilenameToUriStringA(baseDir, baseUriString) != 0) {
+        free(baseUriString);
+        return NULL;
+    }
+    state.uri = &baseUri;
+    if (uriParseUriA(&state, baseUriString) != 0) {
+        free(baseUriString);
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+
+    /* source URI */
+    if (unixFilename[0] != '/') {
+        const int baseDirLen = strlen(baseDir);
+        const int sourceFileLen = strlen(unixFilename);
+        absSourceLen = baseDirLen + sourceFileLen;
+        absSourceFile = malloc((absSourceLen + 1) * sizeof(char));
+        sprintf(absSourceFile, "%s%s", baseDir, unixFilename);
+    } else {
+        absSourceLen = strlen(unixFilename);
+        absSourceFile = (char *)unixFilename;
+    }
+    sourceUriString = malloc((7 + 3 * absSourceLen + 1) * sizeof(char));
+    if (sourceUriString == NULL) {
+        free(baseUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+    if (uriUnixFilenameToUriStringA(absSourceFile, sourceUriString) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        return NULL;
+    }
+    state.uri = &sourceUri;
+    if (uriParseUriA(&state, sourceUriString) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        return NULL;
+    }
+    if (uriNormalizeSyntaxA(&sourceUri) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        return NULL;
+    }
+
+    /* make relative (or keep absolute if necessary) */
+    if (uriRemoveBaseUriA(&relativeUri, &sourceUri, &baseUri, URI_FALSE) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+    
+    /* back to string */
+    if (uriToStringCharsRequiredA(&relativeUri, &charsRequired) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+    output = malloc((charsRequired + 1) * sizeof(char));
+    if (uriToStringA(output, &relativeUri, charsRequired + 1, NULL) != 0) {
+        free(baseUriString);
+        free(sourceUriString);
+        if (unixFilename[0] != '/') {
+            free(absSourceFile);
+        }
+        free(output);
+        uriFreeUriMembersA(&baseUri);
+        uriFreeUriMembersA(&sourceUri);
+        uriFreeUriMembersA(&relativeUri);
+        return NULL;
+    }
+
+    free(baseUriString);
+    free(sourceUriString);
+    if (unixFilename[0] != '/') {
+        free(absSourceFile);
+    }
+    uriFreeUriMembersA(&baseUri);
+    uriFreeUriMembersA(&sourceUri);
+    uriFreeUriMembersA(&relativeUri);
+
+    return output;
+}
+
+char * xml_escape_malloc(const char * input)
+{
+    const char * read = input;
+    char * output;
+    char * write;
+    
+    if (input == NULL) {
+        return NULL;
+    }
+    
+    output = malloc((6 * strlen(input) + 1) * sizeof(char));
+    if (output == NULL) {
+        return NULL;
+    }
+    write = output;
+    
+    for (;;) {
+        if (*read == '\0') {
+            *write = '\0';
+            return output;
+        }
+    
+        switch ((unsigned char)*read) {
+        case '&':
+            strcpy(write, "&amp;");
+            write += 5;
+            break;
+        case '<':
+            strcpy(write, "&lt;");
+            write += 4;
+            break;
+        case '>':
+            strcpy(write, "&gt;");
+            write += 4;
+            break;
+        case '\'':
+            strcpy(write, "&apos;");
+            write += 6;
+            break;
+        case '"':
+            strcpy(write, "&quot;");
+            write += 6;
+            break;
+        default:
+            *(write++) = *read;
+        }
+        read++;
+    }
+}
+#endif
 
-void parse_file(unsigned char *newpath)
+void parse_file(unsigned char *newpath, unsigned char * original_path)
 {
     unsigned char ext[5];
     int j, encoding = 0;
@@ -1075,9 +1283,15 @@ void parse_file(unsigned char *newpath)
         encoding = OGGENC;
     }
     if(strcmp(".wav", ext) == 0) {
-        duration = -1;          /* parse_wav(newpath); */
+        duration = -1;
+        /* parse_wav(newpath); */
         encoding = WAVENC;
     }
+    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
@@ -1114,7 +1328,7 @@ void parse_file(unsigned char *newpath)
     if(duration != -2 && genrelist[genre]) {    /* is it an audio file ? */
         counter++;
         switch (format) {
-        case 0:
+        case FORMAT_M3U:
             if(duration != -1) {
                 printf("#EXTINF:%d,", duration);
                 if(strlen(artist) != 0)
@@ -1124,7 +1338,7 @@ void parse_file(unsigned char *newpath)
             print_path(newpath);
             printf("%s", eol);
             break;
-        case 1:
+        case FORMAT_PLS:
             printf("File%d=", counter);
             print_path(newpath);
             printf("%sTitle%d=", eol, counter);
@@ -1134,7 +1348,7 @@ void parse_file(unsigned char *newpath)
             if(duration != -1)
                 printf("Length%d=%d%s", counter, duration, eol);
             break;
-        case 2:
+        case FORMAT_HTML:
             printf("<tr><td>%d</td><td>%s</td><td>%s</td><td>", counter,
                    artist, title);
             if(duration == -1)
@@ -1143,7 +1357,7 @@ void parse_file(unsigned char *newpath)
                 printf("%d:%s%d</td></tr>%s", duration / 60,
                        duration % 60 < 10 ? "0" : "", duration % 60, eol);
             break;
-        case 3:
+        case FORMAT_RSS:
             if(duration != -1) {
                 struct stat infos;
                 char timebuffer[256];
@@ -1189,19 +1403,55 @@ void parse_file(unsigned char *newpath)
                 printf("\t</item>%s", eol);
             }
             break;
-        case 4:                // printing output for Sansa players
+        case FORMAT_PLP:
             myplaputstr("HARP, ");
             myplaputstr(newpath);
             myplaputstr(eol);
             break;
-        case 5:                //t-series playlist
+        case FORMAT_UMS:
             txxputstr(newpath);
             break;
+#ifdef HAVE_LIBURIPARSER
+        case FORMAT_XSPF:
+            printf("<track>\n");
+            if (strlen(title) > 0) {
+                char * escaped_title = xml_escape_malloc(title);
+                if (escaped_title != NULL) {
+                    printf("    <title>%s</title>\n", escaped_title);
+                    free(escaped_title);
+                }
+            }
+            if (strlen(artist) > 0) {
+                char * escaped_artist = xml_escape_malloc(artist);
+                if (escaped_artist != NULL) {
+                    printf("    <creator>%s</creator>\n", escaped_artist);
+                    free(escaped_artist);
+                }
+            }
+            if (duration > 0) {
+                printf("    <duration>%d</duration>\n", duration);
+            }
+            {
+                char * relative_location;
+                char * escaped_location;
+                relative_location = relative_uri_malloc(newpath, original_path);
+                if (relative_location != NULL) {
+                    escaped_location = xml_escape_malloc(relative_location);
+                    if (escaped_location != NULL) {
+                        printf("    <location>%s</location>\n", escaped_location);
+                        free(escaped_location);
+                    }
+                    free(relative_location);
+                }
+            }
+            printf("</track>\n");
+            break;
+#endif
         }
     }
 }
 
-void parse_directory(unsigned char *path)
+void parse_directory(unsigned char *path, unsigned char * original_path)
 {
     int i, n;
     struct dirent **namelist;
@@ -1216,7 +1466,7 @@ void parse_directory(unsigned char *path)
     }
     /* check if it is a filename */
     if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) {
-        parse_file(path);
+        parse_file(path, original_path);
         return;
     }
     /* must be a directory - or something unusable like pipe, socket, etc */
@@ -1225,7 +1475,7 @@ void parse_directory(unsigned char *path)
         return;
     }
     for(i = 0; i < n; i++) {
-        sprintf(newpath, "%s/%s", path, namelist[i]->d_name);
+        snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name);
 
         if(stat(newpath, &infos) != 0) {
             fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath);
@@ -1234,11 +1484,11 @@ void parse_directory(unsigned char *path)
         if(recursive && S_ISDIR(infos.st_mode)
            && strcmp(namelist[i]->d_name, ".") != 0
            && strcmp(namelist[i]->d_name, "..") != 0)
-            parse_directory(newpath);
+            parse_directory(newpath, original_path);
         /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */
         if(S_ISREG(infos.st_mode)
            && !(avoidhlinked && hlink_check(&infos))) {
-            parse_file(newpath);
+            parse_file(newpath, original_path);
         }
         free(namelist[i]);
     }
@@ -1250,16 +1500,19 @@ int main(int argc, char **argv)
     winorunix = one2one;
     basemap = one2one;
     parse_options(argc, argv);
+
     if(optind == argc && !fromstdin)
         usage();
+
+    /* print header */
     switch (format) {
-    case 0:
+    case FORMAT_M3U:
         printf("#EXTM3U%s", eol);
         break;
-    case 1:
+    case FORMAT_PLS:
         printf("[playlist]%s", eol);
         break;
-    case 2:
+    case FORMAT_HTML:
         printf
             ("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">%s%s<html>%s%s<head>%s<title>Playlist generated by FAPG "
              VERSION
@@ -1271,7 +1524,7 @@ int main(int argc, char **argv)
              eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol,
              eol, eol, eol);
         break;
-    case 3:
+    case FORMAT_RSS:
         {
             time_t zeit;
             char timebuffer[256];
@@ -1293,48 +1546,95 @@ int main(int argc, char **argv)
             basemap = noand;
         }
         break;
-    case 4:
+    case FORMAT_PLP:
         {
             eol = "\r\n";
             myplaputstr("PLP PLAYLIST\r\nVERSION 1.20\r\n\r\n");
         }
         break;
-    case 5:
+    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
     }
-    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';
-            parse_directory(path);
-        }
-    } else
-        for(; optind < argc; optind++) {
-            parse_directory(argv[optind]);
-        }
+    
+    /* iterate through files */
+    {
+        const char * const pwd_source = getenv("PWD");
+        const int pwdlen = strlen(pwd_source);
+        char * const pwd = malloc((pwdlen + 1 + 1) * sizeof(char));
+        sprintf(pwd, "%s/", pwd_source);
+        
+        if(fromstdin) {
+            unsigned char path[PATH_MAX];
+            int i;
+            while(fgets(path, PATH_MAX, stdin)) {
+                for(i = 0; i < PATH_MAX; i++)
+                    if(path[i] == '\r' || path[i] == '\n')
+                        path[i] = '\0';
+                if (i <= 0) {
+                    continue;
+                }
+
+                /* strip trailing slash */
+                if (path[i - 1] == '/') {
+                    path[i - 1] = '\0';
+                }
+
+                parse_directory(path, pwd);
+            }
+        } else
+            for(; optind < argc; optind++) {
+                /* strip trailing slash */
+                char * dup = strdup(argv[optind]);
+                const int len = strlen(dup);
+                if ((len > 0) && (dup[len - 1] == '/')) {
+                    dup[len - 1] = '\0';
+                }
+
+                parse_directory(dup, pwd);
+            }
+            
+        free(pwd);
+    }
+
+    /* print footer */
     switch (format) {
-    case 1:
+    case FORMAT_PLS:
         printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol);
         break;
-    case 2:
+    case FORMAT_HTML:
         printf
             ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG "
              VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol,
              eol, eol);
         break;
-    case 3:
+    case FORMAT_RSS:
         printf("    </channel>%s</rss>%s", eol, eol);
         break;
-    case 5:
+    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);
 }
+