X-Git-Url: http://royale.zerezo.com/git/?p=FAPG;a=blobdiff_plain;f=fapg.c;h=bd2b3f0ea2c1d79ee2c29ff3d177621c524e2de8;hp=ea50917948a613876062e88d00a24cf54698b563;hb=HEAD;hpb=57a557c838aebd9b7fbd1344f6fcb86b7d5da7fd diff --git a/fapg.c b/fapg.c index ea50917..e9d2b4d 100644 --- a/fapg.c +++ b/fapg.c @@ -37,14 +37,27 @@ #include #include #include "genres.h" +#ifdef HAVE_LIBURIPARSER +# include +#endif -#define VERSION "0.37" #define MP3_BASE 1024 #define OGG_BASE 1024*10 -#define MAX 1024*200 /* 200ko for ID3 with JPEG images in it */ +#define MAX 1024*250 /* 250ko for ID3 with JPEG images in it */ +#define MAX_ITEM 1024 + +#define FORMAT_M3U 0 /* "0" is not a good choice for debugging, but OK for a default */ +#define FORMAT_PLS 1 +#define FORMAT_HTML 2 +#define FORMAT_RSS 3 +#define FORMAT_PLP 4 +#define FORMAT_UMS 5 +#ifdef HAVE_LIBURIPARSER +# define FORMAT_XSPF 6 +#endif int debug = 0; -int format = 0; /* 0 = m3u ; 1 = pls ; 2 = html ; 3 = rss */ +int format = FORMAT_M3U; char *genrelist = NULL; unsigned char *prefix = ""; unsigned char *base = ""; @@ -62,9 +75,9 @@ unsigned char buffer[MAX]; int counter = 0; -unsigned char artist[1024]; -unsigned char title[1024]; -unsigned char genrebuf[1024]; +unsigned char artist[MAX_ITEM]; +unsigned char title[MAX_ITEM]; +unsigned char genrebuf[MAX_ITEM]; unsigned char genre = 0; int duration; #define MP2ENC 1 @@ -73,11 +86,13 @@ int duration; #define MPPENC 4 #define OGGENC 5 #define WAVENC 6 +#define WMAENC 7 char *magic[] = { NULL, "audio/mpeg", "audio/mpeg", "audio/mpeg", "audio/mpeg", "audio/ogg-vorbis", "audio/x-wav", + "audio/x-ms-wma", NULL }; @@ -217,8 +232,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] [-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=" FAPG_FORMATS "] [-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); } @@ -239,6 +260,23 @@ void mywebputstr(const char *c) } } +void utf16toutf8(char *c,int n) +{ + /* check whether the we need to convert UTF-16 to UTF-8 strings */ + if ( ( c[0] != '\377' ) || ( c[1] != '\376' ) ) { return; } + /* only continue here, if the first 2 letters are 0xfffe * + * c references an UTF-16 input, where latin letters are * + * separated by zero bytes, which we need to eliminate */ + int i=0; --n; + for(int j=2; (j> 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) { @@ -289,7 +443,7 @@ void print_webpath(const char *path) { const char *c = path; - printf(prefix); /* we must not modify this part */ + printf("%s", prefix); /* we must not modify this part */ if(*c == '.' && c[1] == '/') { /* remove leading "./" when parsing current directory */ c += 2; /* maybe there follow many slashes */ @@ -307,7 +461,7 @@ void print_webpath(const char *path) void print_path(const char *path) { const char *c = path; - printf(prefix); + printf("%s", prefix); /* skip leading "./" when parsing current directory */ if(*c == '.' && *(c + 1) == '/') { c += 2; @@ -364,7 +518,6 @@ void reference(const char *title) pipe = popen(command, "r"); if(pipe == NULL) { fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command); - free(command); return; } fgets(buffer, 1020, pipe); @@ -391,7 +544,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; @@ -414,15 +568,21 @@ void parse_options(int argc, char **argv) break; case 'f': if(strcmp(optarg, "m3u") == 0) - format = 0; + format = FORMAT_M3U; else if(strcmp(optarg, "pls") == 0) - format = 1; + format = FORMAT_PLS; else if(strcmp(optarg, "html") == 0) - format = 2; + format = FORMAT_HTML; else if(strcmp(optarg, "rss") == 0) - format = 3; + format = FORMAT_RSS; else if(strcmp(optarg, "pla") == 0) - format = 4; + 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; @@ -550,7 +710,7 @@ void parse_mp3(unsigned char *file) fprintf(stderr, "Debug >> parsing mp3 : %s\n", file); /* read header */ - if((fic = fopen(file, "r")) == NULL) { + if((fic = fopen(file, "rb")) == NULL) { fprintf(stderr, "Warning >> can't open file : %s\n", file); return; } @@ -590,10 +750,12 @@ void parse_mp3(unsigned char *file) if(*c == 0) break; if(strncmp(c, "TT2", 3) == 0) { + utf16toutf8(c+7,size); strncpy(title, c + 7, size - 1); title[size - 1] = '\0'; } if(strncmp(c, "TP1", 3) == 0) { + utf16toutf8(c+7,size); strncpy(artist, c + 7, size - 1); artist[size - 1] = '\0'; } @@ -613,10 +775,12 @@ void parse_mp3(unsigned char *file) if(*c == 0) break; if(strncmp(c, "TIT2", 4) == 0) { + utf16toutf8(c+11,size); strncpy(title, c + 11, size - 1); title[size - 1] = '\0'; } if(strncmp(c, "TPE1", 4) == 0) { + utf16toutf8(c+11,size); strncpy(artist, c + 11, size - 1); artist[size - 1] = '\0'; } @@ -626,6 +790,9 @@ void parse_mp3(unsigned char *file) /* genre=atoi(&genrebuf[1]); */ genre = atoi(c + 12); } + if(strncmp(c, "TLEN", 4) == 0) { + duration = atoi(c + 11) / 1000; + } c += size + 10; } } @@ -700,7 +867,7 @@ void parse_ogg(unsigned char *file) lus = fread(buffer, 1, OGG_BASE, fic); /* try Ogg */ - if(buffer[0] != 'O' && buffer[1] != 'g' && buffer[2] != 'g') { + if(strncmp(buffer, "Ogg", 3) != 0) { fprintf(stderr, "Warning >> not a Ogg header : %s\n", file); return; } @@ -719,6 +886,13 @@ void parse_ogg(unsigned char *file) title[size - 6] = '\0'; c += size; } + if(strncasecmp(c, "ALBUM ARTIST=", 13) == 0) { + // ignore tag + size = + *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) + + (*(c - 1) << 24); + c += size; + } if(strncasecmp(c, "ARTIST=", 7) == 0) { size = *(c - 4) + (*(c - 3) << 8) + (*(c - 2) << 16) + @@ -762,7 +936,6 @@ void parse_ogg(unsigned char *file) fclose(fic); } - void parse_mpc(unsigned char *file) { FILE *fic; @@ -784,7 +957,7 @@ void parse_mpc(unsigned char *file) lus = fread(buffer, 1, 12, fic); /* try Musepack */ - if(buffer[0] != 'M' && buffer[1] != 'P' && buffer[2] != '+') { + if (strncmp(buffer, "MP+", 3) != 0) { fprintf(stderr, "Warning >> not a Musepack header : %s\n", file); return; } @@ -920,8 +1093,192 @@ int hlink_check(struct stat *info) return is_registered; } +#ifdef HAVE_LIBURIPARSER +char * relative_uri_malloc(const char * unixFilename, const char * baseDir) +{ + char * absSourceFile; + size_t absSourceLen; + char * sourceUriString; + char * baseUriString; + UriParserStateA state; + UriUriA sourceUri; + UriUriA baseUri; + UriUriA relativeUri; + int charsRequired; + char * output; + + /* checks */ + if ((unixFilename == NULL) || (baseDir == NULL)) { + return NULL; + } -void parse_file(unsigned char *newpath) + /* 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++; + } +} +#endif + +void parse_file(unsigned char *newpath, unsigned char * original_path) { unsigned char ext[5]; int j, encoding = 0; @@ -957,9 +1314,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 @@ -996,17 +1359,15 @@ void parse_file(unsigned char *newpath) if(duration != -2 && genrelist[genre]) { /* is it an audio file ? */ counter++; switch (format) { - case 0: - if(duration != -1) { - printf("#EXTINF:%d,", duration); - if(strlen(artist) != 0) - printf("%s - ", artist); - printf("%s%s", title, eol); - } + case FORMAT_M3U: + printf("#EXTINF:%d,", duration); + if(strlen(artist) != 0) + printf("%s - ", artist); + printf("%s%s", title, eol); print_path(newpath); printf("%s", eol); break; - case 1: + case FORMAT_PLS: printf("File%d=", counter); print_path(newpath); printf("%sTitle%d=", eol, counter); @@ -1016,7 +1377,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("%d%s%s", counter, artist, title); if(duration == -1) @@ -1025,7 +1386,7 @@ void parse_file(unsigned char *newpath) 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]; @@ -1071,16 +1432,55 @@ void parse_file(unsigned char *newpath) printf("\t%s", eol); } break; - case 4: // printing output for Sansa players + case FORMAT_PLP: myplaputstr("HARP, "); myplaputstr(newpath); myplaputstr(eol); break; + case FORMAT_UMS: + txxputstr(newpath); + break; +#ifdef HAVE_LIBURIPARSER + 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; +#endif } } } -void parse_directory(unsigned char *path) +void parse_directory(unsigned char *path, unsigned char * original_path) { int i, n; struct dirent **namelist; @@ -1095,7 +1495,7 @@ void parse_directory(unsigned char *path) } /* check if it is a filename */ if(S_ISREG(infos.st_mode) || S_ISLNK(infos.st_mode)) { - parse_file(path); + parse_file(path, original_path); return; } /* must be a directory - or something unusable like pipe, socket, etc */ @@ -1104,7 +1504,7 @@ void parse_directory(unsigned char *path) return; } for(i = 0; i < n; i++) { - sprintf(newpath, "%s/%s", path, namelist[i]->d_name); + snprintf(newpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name); if(stat(newpath, &infos) != 0) { fprintf(stderr, "Warning >> can't stat entry : %s\n", newpath); @@ -1113,11 +1513,11 @@ void parse_directory(unsigned char *path) if(recursive && S_ISDIR(infos.st_mode) && strcmp(namelist[i]->d_name, ".") != 0 && strcmp(namelist[i]->d_name, "..") != 0) - parse_directory(newpath); + parse_directory(newpath, original_path); /* hlink_check() might be applied more selective ... avoidhlink is only a simple prereq */ if(S_ISREG(infos.st_mode) && !(avoidhlinked && hlink_check(&infos))) { - parse_file(newpath); + parse_file(newpath, original_path); } free(namelist[i]); } @@ -1129,16 +1529,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 ("%s%s%s%s%sPlaylist generated by FAPG " VERSION @@ -1150,7 +1553,7 @@ int main(int argc, char **argv) eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol, eol); break; - case 3: + case FORMAT_RSS: { time_t zeit; char timebuffer[256]; @@ -1172,40 +1575,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"); } - } - 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]); + 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 1: + case FORMAT_PLS: printf("NumberOfEntries=%d%sVersion=2%s", counter, eol, eol); break; - case 2: + case FORMAT_HTML: printf ("</table>%s%s<p>Playlist generated by <a href=\"http://royale.zerezo.com/fapg/\">FAPG " VERSION "</a></p>%s%s</body>%s%s</html>", eol, eol, eol, eol, eol, eol); break; - case 3: + case FORMAT_RSS: printf(" </channel>%s</rss>%s", eol, eol); break; + case FORMAT_UMS: + txxputcounter(counter); + break; +#ifdef HAVE_LIBURIPARSER + case FORMAT_XSPF: + printf("</trackList>\n" + "</playlist>\n"); + break; +#endif } + if(genrelist) free(genrelist); + exit(0); } +