diff -aur fapg-0.38/fapg.1 fapg-0.38_xspf/fapg.1 --- fapg-0.38/fapg.1 2007-03-13 17:56:22.000000000 +0100 +++ fapg-0.38_xspf/fapg.1 2008-02-16 02:19:16.000000000 +0100 @@ -18,7 +18,7 @@ .SH DESCRIPTION .B fapg is a tool to generate list of audio files (Wav, MP2, MP3, Ogg, etc) in various -formats (M3U, PLS, HTML, RSS, etc). It is very useful if you have a large amount +formats (M3U, PLS, XSPF, HTML, RSS, etc). It is very useful if you have a large amount of audio files and you want to quickly and frequently build a playlist. .P It is coded in C to be as fast as possible, and does not use any specific @@ -32,7 +32,7 @@ Replace the '/' with '\' in Unix path. .IP -d|--debug Display useful messages if the program fails ;) -.IP -f|--format=m3u|pls|html|rss|pla|txx +.IP -f|--format=m3u|pls|xspf|html|rss|pla|txx Choose which format of playlist you want to generate (default is m3u). .IP -g|--genre=#:#:... Choose which genres (numerical values only) will be included in the generated playlist (default is all). diff -aur fapg-0.38/fapg.c fapg-0.38_xspf/fapg.c --- fapg-0.38/fapg.c 2007-03-13 17:57:53.000000000 +0100 +++ fapg-0.38_xspf/fapg.c 2008-02-16 02:22:32.000000000 +0100 @@ -36,15 +36,24 @@ #include #include #include +#include #include "genres.h" -#define VERSION "0.38" +#define VERSION "0.38 + XSPF patch" #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 +#define FORMAT_XSPF 6 + 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 = ""; @@ -218,7 +227,8 @@ void usage() { 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=] [-x|--exclude=#:#:...] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n"); + "Usage >> fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|xspf|html|rss|pla|txx] [-g|--genre=#:#:...] [-n|--nohardlink] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-c|--command=] [-x|--exclude=#:#:...] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...]\n"); +#undef FAPG_FORMATS exit(1); } @@ -530,17 +540,19 @@ 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; + else if(strcmp(optarg, "xspf") == 0) + format = FORMAT_XSPF; else usage(); break; @@ -880,7 +892,6 @@ fclose(fic); } - void parse_mpc(unsigned char *file) { FILE *fic; @@ -1038,8 +1049,190 @@ return is_registered; } +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, "&"); + write += 5; + break; + case '<': + strcpy(write, "<"); + write += 4; + break; + case '>': + strcpy(write, ">"); + write += 4; + break; + case '\'': + strcpy(write, "'"); + write += 6; + break; + case '"': + strcpy(write, """); + write += 6; + break; + default: + *(write++) = *read; + } + read++; + } +} -void parse_file(unsigned char *newpath) +void parse_file(unsigned char *newpath, unsigned char * original_path) { unsigned char ext[5]; int j, encoding = 0; @@ -1114,7 +1307,7 @@ 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 +1317,7 @@ 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 +1327,7 @@ if(duration != -1) printf("Length%d=%d%s", counter, duration, eol); break; - case 2: + case FORMAT_HTML: printf("%d%s%s", counter, artist, title); if(duration == -1) @@ -1143,7 +1336,7 @@ printf("%d:%s%d%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 +1382,53 @@ printf("\t%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; + case FORMAT_XSPF: + printf("\n"); + if (strlen(title) > 0) { + char * escaped_title = xml_escape_malloc(title); + if (escaped_title != NULL) { + printf(" %s\n", escaped_title); + free(escaped_title); + } + } + if (strlen(artist) > 0) { + char * escaped_artist = xml_escape_malloc(artist); + if (escaped_artist != NULL) { + printf(" %s\n", escaped_artist); + free(escaped_artist); + } + } + if (duration > 0) { + printf(" %d\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(" %s\n", escaped_location); + free(escaped_location); + } + free(relative_location); + } + } + printf("\n"); + break; } } } -void parse_directory(unsigned char *path) +void parse_directory(unsigned char *path, unsigned char * original_path) { int i, n; struct dirent **namelist; @@ -1216,7 +1443,7 @@ } /* 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 +1452,7 @@ 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 +1461,11 @@ 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 +1477,19 @@ 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 ("%s%s%s%s%sPlaylist generated by FAPG " VERSION @@ -1271,7 +1501,7 @@ 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 +1523,91 @@ 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; + 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; } - 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; + case FORMAT_XSPF: + printf("</trackList>\n" + "</playlist>\n"); + break; } + if(genrelist) free(genrelist); + exit(0); } + diff -aur fapg-0.38/Makefile fapg-0.38_xspf/Makefile --- fapg-0.38/Makefile 2007-03-06 15:48:54.000000000 +0100 +++ fapg-0.38_xspf/Makefile 2008-02-16 02:27:49.000000000 +0100 @@ -3,6 +3,7 @@ DOC = $(PRE)/share/doc/fapg MAN = $(PRE)/share/man/man1 CFLAGS=-g2 -funsigned-char +LDFLAGS=-luriparser fapg: fapg.c diff -aur fapg-0.38/README fapg-0.38_xspf/README --- fapg-0.38/README 2007-03-13 17:57:40.000000000 +0100 +++ fapg-0.38_xspf/README 2008-02-16 02:30:12.000000000 +0100 @@ -1,4 +1,4 @@ -FAPG 0.38 (Fast Audio Playlist Generator) +FAPG 0.38 + XSPF patch (Fast Audio Playlist Generator) site: http://royale.zerezo.com/fapg/ mail: royale@zerezo.com @@ -7,7 +7,7 @@ make install usage: -fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|html|rss|pla|txx] [-g|--genre=#:#:...] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-x|--exclude=#:#:...] [-c|--command=<intern|...>] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...] +fapg [-b|--backslash] [-d|--debug] [-f|--format=m3u|pls|xspf|html|rss|pla|txx] [-g|--genre=#:#:...] [-o|--output=/path/to/file.m3u] [-p|--prefix=/the/prefix] [-r|--recursive] [-w|--windows] [-x|--exclude=#:#:...] [-c|--command=<intern|...>] [-s|--stdin] /path/to/mp3/dir1 [/path/to/mp3/dir2 ...] - backslash : replace the '/' with '\' in Unix path. - debug : display useful messages if the program fails ;) - format : choose which format of playlist you want to generate (default is m3u). @@ -25,3 +25,5 @@ http://id3lib.sourceforge.net/ http://id3.org/ http://www.xiph.org/ogg/vorbis/docs.html +http://xspf.org/specs/ +