#include "file.h" #include "list.h" #include "comment.h" #include "xmalloc.h" #include "utf8_encode.h" #include #include #include #include #include #include #include extern char *program_name; extern const char *charset; static int track_to_num(const char *track, int *num) { int i, n; n = 0; for (i = 0; track[i]; i++) { if (!isdigit(track[i])) return 1; n *= 10; n += track[i] - '0'; } *num = n; return 0; } static int comment_length(const struct list_head *comment_head, const char *name) { struct comment *comment; comment = comment_get(comment_head, name); if (comment == NULL) return 0; return strlen(comment->val); } static int comment_copy(const struct list_head *comment_head, const char *name, char *dst) { struct comment *comment; int i; comment = comment_get(comment_head, name); if (comment == NULL) return 0; for (i = 0; comment->val[i]; i++) { dst[i] = comment->val[i]; } return i; } /* * comment_head: * list of tags * pattern: * pattern of new filename * ext: * filename extension ("mp3" or "ogg") * s/%e/ext * * returns: * filename */ char *make_filename(const struct list_head *comment_head, const char *pattern, const char *ext) { char *name; int i, j, name_len; name_len = 0; for (i = 0; pattern[i]; i++) { if (pattern[i] != '%') { name_len++; continue; } i++; switch (pattern[i]) { case 'a': name_len += comment_length(comment_head, "ARTIST"); break; case 'l': name_len += comment_length(comment_head, "ALBUM"); break; case 't': name_len += comment_length(comment_head, "TITLE"); break; case 'n': { const struct comment *comment; comment = comment_get(comment_head, "TRACKNUMBER"); if (comment) { int num; if (track_to_num(comment->val, &num) == 0 && num < 100) { name_len += 2; } else { name_len += strlen(comment->val); } } } break; case 'g': name_len += comment_length(comment_head, "GENRE"); break; case 'd': name_len += comment_length(comment_head, "DATE"); break; case 'e': name_len += strlen(ext); break; case '%': name_len++; break; default: fprintf(stderr, "%s: BUG: invalid format character `%c'\n", program_name, pattern[i]); exit(1); } } name = (char *)xmalloc(name_len + 1); j = 0; for (i = 0; pattern[i]; i++) { if (pattern[i] != '%') { name[j++] = pattern[i]; continue; } i++; switch (pattern[i]) { case 'a': j += comment_copy(comment_head, "ARTIST", name + j); break; case 'l': j += comment_copy(comment_head, "ALBUM", name + j); break; case 't': j += comment_copy(comment_head, "TITLE", name + j); break; case 'n': { const struct comment *comment; comment = comment_get(comment_head, "TRACKNUMBER"); if (comment) { int num; if (track_to_num(comment->val, &num) == 0 && num < 100) { name[j++] = num / 10 + '0'; name[j++] = num % 10 + '0'; } else { int len; len = strlen(comment->val); memcpy(name + j, comment->val, len); j += len; } } } break; case 'g': j += comment_copy(comment_head, "GENRE", name + j); break; case 'd': j += comment_copy(comment_head, "DATE", name + j); break; case 'e': { int len = strlen(ext); memcpy(name + j, ext, len); j += len; } break; case '%': name[j++] = '%'; break; default: break; } } name[j] = 0; return name; } /* * filename: * used for error messages only * string: * pointer to string to be parsed * pattern: * points to the char after %x * s: * pointer to string where the parsed string is stored. * caller should free it */ static void get_str(const char *filename, const char **string, const char *pattern, char **s) { const char *str = *string; int delim_len; int s_len; delim_len = 0; while ((pattern[delim_len] != '%') && pattern[delim_len]) delim_len++; s_len = 0; while (str[s_len]) { if (delim_len && strncmp(&str[s_len], pattern, delim_len) == 0) break; s_len++; } if ((str[s_len] == 0) && delim_len) { fprintf(stderr, "%s: (%s) pattern mismatch\n", program_name, filename); exit(1); } *s = xstrndup(str, s_len); *string = &str[s_len]; } /* don't add '%e' here. should add ? and * instead */ void parse_filename(struct list_head *comment_head, const char *filename, const char *pattern) { const char *fname, *pat; fname = strrchr(filename, '/'); if (fname) { /* skip the '/' */ fname++; } else { fname = filename; } pat = pattern; while (*pat || *fname) { if (*pat == '%') { const char *key; char *val; char ch; pat++; ch = *pat; if (ch == 0) { fprintf(stderr, "%s: (%s) unexpected end of filename\n", program_name, filename); exit(1); } pat++; if (ch == '%') { if (*fname != '%') goto pattern_error; fname++; continue; } get_str(filename, &fname, pat, &val); switch (ch) { case 'a': key = "ARTIST"; break; case 'l': key = "ALBUM"; break; case 't': key = "TITLE"; break; case 'n': { char *s; int num; s = val; if (track_to_num(s, &num) == 0) { /* skip leading zeros */ while (s[0] == '0' && s[1]) s++; memmove(val, s, strlen(s) + 1); } } key = "TRACKNUMBER"; break; case 'g': key = "GENRE"; break; case 'd': key = "DATE"; break; case 'e': case '*': free(val); continue; default: exit(1); } /* printf(" %s=%s\n", key, val); */ comment_add(comment_head, key, val); free(val); } else { if (*pat != *fname) goto pattern_error; pat++; fname++; } } return; pattern_error: fprintf(stderr, "%s: pattern does not match filename `%s'\n", program_name, filename); exit(1); } static void validate_pattern(const char *pattern, const char *valid_chars) { int i, j; for (i = 0; pattern[i]; i++) { if (pattern[i] != '%') continue; i++; for (j = 0; valid_chars[j]; j++) { if (pattern[i] == valid_chars[j]) break; } if (valid_chars[j] == 0) { fprintf(stderr, "%s: error: invalid format character `%c' in pattern\n", program_name, pattern[i]); exit(1); } } if (i == 0) { fprintf(stderr, "%s: error: pattern can not zero-length\n", program_name); exit(1); } if (pattern[i - 1] == '/') { fprintf(stderr, "%s: error: pattern can not end with `/'\n", program_name); exit(1); } } void validate_parse_pattern(const char *pattern) { validate_pattern(pattern, "altngde*%"); } void validate_rename_pattern(const char *pattern) { validate_pattern(pattern, "altngde%"); } void underscores_to_spaces(struct list_head *comment_head) { struct list_head *item; list_for_each(item, comment_head) { struct comment *comment; char *str; int i; comment = list_entry(item, struct comment, node); str = comment->val; for (i = 0; str[i]; i++) { if (str[i] == '_') str[i] = ' '; } free(comment->utf8_val); if (utf8_encode(comment->val, charset, &comment->utf8_val)) { fprintf(stderr, "%s: error: can't convert `%s' to UTF8: %s\n", program_name, comment->val, strerror(errno)); exit(1); } } } void capitalize(struct list_head *comment_head) { struct list_head *item; list_for_each(item, comment_head) { struct comment *comment; char *str; int i, is_first = 1; comment = list_entry(item, struct comment, node); str = comment->val; for (i = 0; str[i]; i++) { if (is_first) { str[i] = toupper(str[i]); is_first = 0; } else { if (isspace(str[i]) || str[i] == '_') is_first = 1; } } free(comment->utf8_val); if (utf8_encode(comment->val, charset, &comment->utf8_val)) { fprintf(stderr, "%s: error: can't convert `%s' to UTF8: %s\n", program_name, comment->val, strerror(errno)); exit(1); } } } static void replace_chars(struct list_head *comment_head, const char *src, const char *dst) { struct list_head *item; list_for_each(item, comment_head) { struct comment *comment; char *str; int i, j; comment = list_entry(item, struct comment, node); str = comment->val; for (i = 0; str[i]; i++) { for (j = 0; src[j]; j++) { if (str[i] == src[j]) { str[i] = dst[j]; break; } } } } } /* replaces chars that can't be in filename * '/' => '-' * <0x20 => '_' */ void replace_invalid_chars(struct list_head *comment_head) { struct list_head *item; list_for_each(item, comment_head) { struct comment *comment; unsigned char *str; int i; comment = list_entry(item, struct comment, node); str = (unsigned char *)comment->val; for (i = 0; str[i]; i++) { if (str[i] == '/') { str[i] = '-'; } else if (str[i] < 0x20) { str[i] = '_'; } } } } /* replaces chars that are annoying in filename */ void replace_annoying_chars(struct list_head *comment_head) { const char *src = " !\"$&'()*,:;<=>?@[\\]^`{|}~"; /* FIXME: consider other chars */ const char *dst = "_________________________-"; replace_chars(comment_head, src, dst); } void lowercase(struct list_head *comment_head) { struct list_head *item; list_for_each(item, comment_head) { struct comment *comment; char *str; int i; comment = list_entry(item, struct comment, node); str = comment->val; for (i = 0; str[i]; i++) str[i] = tolower(str[i]); } }