#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 MAX 1024*250 /* 250ko 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 = "";
#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
};
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);
}
{
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 */
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;
pipe = popen(command, "r");
if(pipe == NULL) {
fprintf(stderr, "Warning >> can't open pipe >%s< !\n", command);
- free(command);
return;
}
fgets(buffer, 1020, pipe);
{"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;
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;
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;
}
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) +
fclose(fic);
}
-
void parse_mpc(unsigned char *file)
{
FILE *fic;
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;
}
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, "&");
+ 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)
+void parse_file(unsigned char *newpath, unsigned char * original_path)
{
unsigned char ext[5];
int j, encoding = 0;
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
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)
print_path(newpath);
printf("%s", eol);
break;
- case 1:
+ case FORMAT_PLS:
printf("File%d=", counter);
print_path(newpath);
printf("%sTitle%d=", eol, counter);
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)
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];
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;
}
/* 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 */
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);
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]);
}
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
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];
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);
}
+