/* This file is part of Idest. Copyright (C) 2009-2011 Sergey Poznyakoff Idest is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Idest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Idest. If not, see . */ #include "idest.h" #include #include #include gl_list_t input_list, output_list, filter_list; void input_list_add_assignment(const char *name, const char *value) { struct ed_item *itm; if (!input_list) input_list = ed_list_create(); itm = ed_item_from_frame_spec(name, strlen(name)); itm->value = xstrdup(value); gl_list_add_last(input_list, itm); } void parse_ed_items(gl_list_t *plist, const char *arg) { gl_list_t list; if (!*plist) *plist = ed_list_create(); list = *plist; while (*arg) { struct ed_item *itm; size_t len = strcspn(arg, ","); itm = ed_item_from_frame_spec(arg, len); gl_list_add_last(list, itm); arg += len; if (!*arg) break; arg += strspn(arg, ","); } } void parse_filter_items(const char *arg) { parse_ed_items(&filter_list, arg); } void query_filter_list_init() { if (all_frames) filter_list = NULL; else if (!filter_list) parse_filter_items(DEFAULT_ED_LIST); } void output_list_append(struct ed_item const *item, struct ed_item const *ref) { struct ed_item *elt = ed_item_dup(item); if (!output_list) output_list = ed_list_create(); elt->ref = ref; gl_list_add_last(output_list, elt); } void output_list_print() { gl_list_iterator_t itr; const void *p; if (all_frames) { if (!output_list) return; itr = gl_list_iterator(output_list); while (gl_list_iterator_next(&itr, &p, NULL)) { const struct ed_item *item = p; ed_item_print(item); } gl_list_iterator_free(&itr); } else { itr = gl_list_iterator(filter_list); while (gl_list_iterator_next(&itr, &p, NULL)) { int printed = 0; const struct ed_item *input_item = p; if (output_list) { gl_list_iterator_t oitr; oitr = gl_list_iterator(output_list); while (gl_list_iterator_next(&oitr, &p, NULL)) { const struct ed_item *output_item = p; if (output_item->ref == input_item) { ed_item_print(output_item); printed = 1; } } gl_list_iterator_free(&oitr); } if (!printed) ed_item_print(input_item); } gl_list_iterator_free(&itr); } } void output_list_free() { if (output_list) { gl_list_free(output_list); output_list = NULL; } } #define DESCR_COLUMN 24 static int describe_frame(const struct idest_frametab *ft, void *data) { struct id3_frametype const *frametype; int n; frametype = id3_frametype_lookup(ft->id, 4); assert(frametype != NULL); printf("%s", ft->id); n = 0; if (ft->qc) { int i; for (i = 0; i < ft->qc; i++) n += printf(":%s", ft->qv[i]); } do putchar(' '); while (n++ < DESCR_COLUMN); printf("%s\n", frametype->description); return 0; } void list_supported_frames(void) { if (filter_list) { gl_list_iterator_t itr; const void *p; itr = gl_list_iterator(filter_list); while (gl_list_iterator_next(&itr, &p, NULL)) { const struct ed_item *item = p; const struct idest_frametab *ft = idest_frame_lookup(item->id); if (!ft) error(1, 0, "no such frame: %s", item->id); describe_frame(ft, NULL); } gl_list_iterator_free(&itr); } else frametab_enumerate(describe_frame, NULL, 1); } void safe_id3_file_update_and_close(struct id3_file *file) { sigset_t set, oldset; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); sigprocmask(SIG_BLOCK, &set, &oldset); id3_file_update(file); id3_file_close(file); sigprocmask(SIG_SETMASK, &oldset, NULL); } char * idest_ucs4_cvt(id3_ucs4_t const *ucs4) { if (latin1_option) return (char*)id3_ucs4_latin1duplicate(ucs4); else return (char*)id3_ucs4_utf8duplicate(ucs4); } static void add_stringlist(gl_list_t list, union id3_field *field, int isgenre, size_t *psize) { unsigned i, nstrings = id3_field_getnstrings(field); size_t size = 0; for (i = 0; i < nstrings; i++) { id3_ucs4_t const *ucs4; char *str; ucs4 = id3_field_getstrings(field, i); if (!ucs4) continue; if (isgenre) ucs4 = id3_genre_name(ucs4); str = idest_ucs4_cvt(ucs4); size += strlen(str); gl_list_add_last(list, str); } if (psize) *psize = size; } char * field_binary_to_string(union id3_field *field) { size_t size = field->binary.length * 2 + 1; char *ret, *p; size_t i; ret = xmalloc(size); for (i = 0, p = ret; i < field->binary.length; i++, p += 2) sprintf(p, "%02X", field->binary.data[i]); *p = 0; return ret; } int field_binary_from_string(union id3_field *field, const char *str) { char xdig[] = "0123456789ABCDEF"; size_t len, i; if (field->type != ID3_FIELD_TYPE_BINARYDATA) return IDEST_ERR_BADTYPE; len = strlen(str); if (len % 2) return IDEST_ERR_BADCONV; len /= 2; field->binary.length = len; field->binary.data = xmalloc(len); for (i = 0; i < len; i++) { unsigned char b; char *p; p = strchr(xdig, toupper(*str++)); if (!p) { free(field->binary.data); field->binary.length = 0; field->binary.data = NULL; return IDEST_ERR_BADCONV; } b = (p - xdig) << 4; p = strchr(xdig, toupper(*str++)); if (!p) { free(field->binary.data); field->binary.length = 0; field->binary.data = NULL; return IDEST_ERR_BADCONV; } b |= p - xdig; field->binary.data[i] = b; } return IDEST_OK; } char * field_to_string(union id3_field *field, int isgenre) { id3_ucs4_t const *ucs4; char *ret = NULL; char buf[128]; switch (id3_field_type(field)) { case ID3_FIELD_TYPE_TEXTENCODING: case ID3_FIELD_TYPE_INT8: case ID3_FIELD_TYPE_INT16: case ID3_FIELD_TYPE_INT24: case ID3_FIELD_TYPE_INT32: snprintf(buf, sizeof(buf), "%ld", field->number.value); ret = xstrdup(buf); break; case ID3_FIELD_TYPE_LATIN1: case ID3_FIELD_TYPE_LATIN1FULL: /* FIXME */ ret = xstrdup((char*)id3_field_getlatin1(field)); break; case ID3_FIELD_TYPE_LATIN1LIST: /* FIXME */ break; case ID3_FIELD_TYPE_STRING: ucs4 = id3_field_getstring(field);; if (ucs4) ret = idest_ucs4_cvt(ucs4); break; case ID3_FIELD_TYPE_STRINGFULL: ucs4 = id3_field_getfullstring(field); ret = idest_ucs4_cvt(ucs4); break; case ID3_FIELD_TYPE_STRINGLIST: { gl_list_t list; size_t sz; gl_list_iterator_t itr; const void *p; list = new_string_list(true); add_stringlist(list, field, isgenre, &sz); ret = xmalloc(sz + 1); ret[0] = 0; itr = gl_list_iterator(list); while (gl_list_iterator_next(&itr, &p, NULL)) strcat(ret, p); gl_list_iterator_free(&itr); gl_list_free(list); break; } case ID3_FIELD_TYPE_LANGUAGE: case ID3_FIELD_TYPE_FRAMEID: case ID3_FIELD_TYPE_DATE: ret = xstrdup(field->immediate.value); break; case ID3_FIELD_TYPE_INT32PLUS: case ID3_FIELD_TYPE_BINARYDATA: ret = field_binary_to_string(field); break; } return ret; } int set_frame_value(struct id3_frame *frame, const struct ed_item *item) { const struct idest_frametab *ft = idest_frame_lookup(frame->id); if (!ft) return IDEST_ERR_BADTYPE; return ft->encode(frame, item); } static int build_tag_options(struct id3_tag *tag, unsigned long location, id3_length_t length, void *data) { unsigned int ver = id3_tag_version(tag); if (ID3_TAG_VERSION_MAJOR(ver) == 1) *(unsigned int*)data |= IDEST_ID3V_1; else *(unsigned int*)data |= IDEST_ID3V_2; return 0; } static void set_tag_options(struct id3_tag *tag, int vopt) { int opts = 0; if (vopt & IDEST_ID3V_1) opts |= ID3_TAG_OPTION_ID3V1; if (!(vopt & IDEST_ID3V_2)) opts |= ID3_TAG_OPTION_NO_ID3V2; if (opts) id3_tag_options(tag, ID3_TAG_OPTION_ID3V1|ID3_TAG_OPTION_NO_ID3V2, opts); } int guess_file_tag_options(struct id3_file *file, int *modified) { int vopt = version_option; int m = 0; if (!modified) modified = &m; if (convert_version == 0) { if (*modified && !vopt) { if (id3_file_struct_ntags(file)) { id3_file_struct_iterate(file, build_tag_options, &vopt); } else vopt = default_version_option; } } else if (id3_file_struct_ntags(file)) { id3_file_struct_iterate(file, build_tag_options, &vopt); if (vopt != convert_version) { vopt = convert_version; *modified |= 1; } } return vopt; } struct id3_frame * find_matching_frame(struct id3_tag *tag, const struct ed_item *item, idest_frame_cmp_t cmp) { struct id3_frame *frame; if (item->qc == 0 || cmp == NULL) frame = id3_tag_findframe(tag, item->id, 0); else { int i; for (i = 0; frame = id3_tag_findframe(tag, item->id, i); i++) { if (cmp(frame, item) == 0) break; } } return frame; } static int update_frame(struct id3_tag *tag, const struct ed_item *item) { const struct idest_frametab *ft = idest_frame_lookup(item->id); struct id3_frame *frame; if (!ft) return IDEST_ERR_BADTYPE; frame = find_matching_frame(tag, item, ft->cmp); if (!frame) { frame = id3_frame_new(item->id); if (id3_tag_attachframe(tag, frame)) error(1, 0, "cannot attach new frame"); } return ft->encode(frame, item); } static int copy_source_tags(struct id3_tag *tag) { int i; struct id3_frame *frame; if (!source_tag) return 0; for (i = 0; (frame = id3_tag_findframe(source_tag, NULL, i)); i++) { const struct idest_frametab *ft = idest_frame_lookup(frame->id); if (!ft) { if (verbose_option) error(0, 0, "%s: unsupported frame", frame->id); continue; } if (!filter_list || ed_list_locate(filter_list, frame, ft->cmp)) { int rc; struct ed_item itm; ed_item_zero(&itm); itm.name = xstrdup(frame->id); memcpy(itm.id, frame->id, 4); if (ft->decode(&itm, frame)) { error(0, 0, "%s: decoding failed", frame->id); continue; } rc = update_frame(tag, &itm); if (rc) error(1, 0, "cannot set frame %s: %s", frame->id, idest_strerror(rc)); ed_item_free_content(&itm); } } return 1; } void set_tags(const char *name) { struct id3_file *file; struct id3_tag *tag; int modified = 0; int vopt; int has_tags; file = id3_file_open(name, ID3_FILE_MODE_READWRITE); if (!file) error(1, errno, "cannot open file %s", name); has_tags = id3_file_struct_ntags(file); tag = id3_file_tag(file); if (!tag) abort(); /* FIXME */ if (input_list) { gl_list_iterator_t itr; const void *p; itr = gl_list_iterator(input_list); while (gl_list_iterator_next(&itr, &p, NULL)) { const struct ed_item *item = p; int rc = update_frame(tag, item); if (rc) error(1, 0, "cannot set frame %s: %s", item->id, idest_strerror(rc)); modified |= 1; } gl_list_iterator_free(&itr); } modified |= copy_source_tags(tag); /* FIXME */ modified |= guile_transform(name, tag); vopt = (source_tag && !has_tags) ? source_vopt : guess_file_tag_options(file, &modified); if (modified) { set_tag_options(tag, vopt); safe_id3_file_update_and_close(file); } else id3_file_close(file); } void del_tags(const char *name) { struct id3_file *file; struct id3_tag *tag; file = id3_file_open(name, ID3_FILE_MODE_READWRITE); if (!file) error(1, errno, "cannot open file %s", name); tag = id3_file_tag(file); if (!tag) abort(); /* FIXME */ if (filter_list) { int i; struct id3_frame *frame; int modified = 0; int vopt; for (i = 0; (frame = id3_tag_findframe(tag, NULL, i)); ) { const struct idest_frametab *ft = idest_frame_lookup(frame->id); if (ed_list_locate(filter_list, frame, ft ? ft->cmp : NULL)) { id3_tag_detachframe(tag, frame); id3_frame_delete(frame); modified = 1; } else i++; } vopt = guess_file_tag_options(file, &modified); if (modified) set_tag_options(tag, vopt); } else id3_tag_clearframes(tag); safe_id3_file_update_and_close(file); } static void show_tags(struct id3_tag *tag) { struct id3_frame *frame; unsigned i; for (i = 0; (frame = id3_tag_findframe(tag, NULL, i)); i++) { const struct idest_frametab *ft = idest_frame_lookup(frame->id); if (!ft) { if (verbose_option) error(0, 0, "%s: unsupported frame", frame->id); continue; } if (all_frames) { struct ed_item outitm; ed_item_zero(&outitm); if (describe_option) { struct id3_frametype const *frametype; frametype = id3_frametype_lookup(frame->id, 4); outitm.name = xstrdup(frametype->description); } else outitm.name = xstrdup(frame->id); memcpy(outitm.id, frame->id, 4); if (ft->decode(&outitm, frame)) { error(0, 0, "%s: decoding failed", frame->id); continue; } output_list_append(&outitm, NULL); ed_item_free_content(&outitm); } else { struct ed_item const *ref; ref = ed_list_locate(filter_list, frame, ft->cmp); if (ref) { struct ed_item outitm; ed_item_zero(&outitm); outitm.name = xstrdup(ref->name); memcpy(outitm.id, ref->id, 4); if (ft->decode(&outitm, frame)) { error(0, 0, "%s: decoding failed", frame->id); continue; } output_list_append(&outitm, ref); ed_item_free_content(&outitm); } } } output_list_print(); output_list_free(); } void query_tags(const char *name) { struct id3_file *file; struct id3_tag *tag; query_filter_list_init(); file = id3_file_open(name, ID3_FILE_MODE_READONLY); if (!file) error(1, errno, "cannot open file %s", name); tag = id3_file_tag(file); if (tag) { if (guile_list(name, tag) == 0) show_tags(tag); } id3_file_close(file); } static int prinfo(struct id3_tag *tag, unsigned long location, id3_length_t length, void *data) { unsigned int ver = id3_tag_version(tag); unsigned int major = ID3_TAG_VERSION_MAJOR(ver); if (major > 1) printf("version: 2.%u.%u\n", major, ID3_TAG_VERSION_MINOR(ver)); else printf("version: %u.%u\n", major, ID3_TAG_VERSION_MINOR(ver)); printf("offset: %lu\n", location); printf("length: %lu\n", length); return 0; } void info_id3(const char *name) { struct id3_file *file; unsigned long ntags; file = id3_file_open(name, ID3_FILE_MODE_READONLY); if (!file) error(1, errno, "cannot open file %s", name); ntags = id3_file_struct_ntags(file); printf("file: %s\n", name); printf("ntags: %lu\n", ntags); id3_file_struct_iterate(file, prinfo, NULL); id3_file_close(file); }