diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-02-07 16:12:03 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2009-02-07 16:12:03 +0200 |
commit | 0efd765588da43ec8cc08c5eabde49254eeb44b8 (patch) | |
tree | ae43654b9e17c2e4008af9a3520de0bc6afe1c48 /src | |
parent | 9b5fb2470499d0d054e6dfab55611057d669ad76 (diff) | |
download | idest-0efd765588da43ec8cc08c5eabde49254eeb44b8.tar.gz idest-0efd765588da43ec8cc08c5eabde49254eeb44b8.tar.bz2 |
Implement ID3v2 read-only access.
* .gitignore: Add core.
* gnulib.modules: Add argmatch and linked-list.
* src/.gitignore: Add .gdbinit
* src/slist.c: New file
* src/Makefile.am (id3ed_SOURCES): Add new files.
* src/cmdline.opt: Remove all options, except -q and --latin1.
Add new options: --all and --set.
* src/getopt.m4 (BEGIN): Fix handling of optional arguments.
* src/id3ed.h: Include xalloc.h, gl_linked_list.h and argmatch.h
(v1_block,title,artist,album,year,comment,track,genre): Remove.
(DEFAULT_ED_LIST): New define.
(set_id3v1): Takes 2 arguments.
(string_list_action_fn): new data type.
(new_string_list,do_string_list,concat_string_list)
(print_string_list): New prototypes.
(ed_list_add_item,ed_list_print,ed_list_add_assignment): New prototypes.
* src/id3v1.c (id3v1_read,id3v1_write): Bugfixes.
(set_id3v1): Take a pointer to id3v1_block as 2nd argument.
* src/id3v2.c: Implement show_tags.
* src/main.c: Rewrite.
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/cmdline.opt | 79 | ||||
-rw-r--r-- | src/getopt.m4 | 2 | ||||
-rw-r--r-- | src/id3ed.h | 31 | ||||
-rw-r--r-- | src/id3v1.c | 13 | ||||
-rw-r--r-- | src/id3v2.c | 92 | ||||
-rw-r--r-- | src/main.c | 339 | ||||
-rw-r--r-- | src/slist.c | 60 |
9 files changed, 528 insertions, 98 deletions
diff --git a/src/.gitignore b/src/.gitignore index a48a3df..748504d 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,2 +1,3 @@ +.gdbinit cmdline.h id3ed diff --git a/src/Makefile.am b/src/Makefile.am index c4205a8..f9d0736 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,12 @@ bin_PROGRAMS=id3ed -id3ed_SOURCES=id3ed.h id3v1.c id3v1.h id3v2.c main.c cmdline.h +id3ed_SOURCES=\ + id3ed.h\ + id3v1.c\ + id3v1.h\ + id3v2.c\ + main.c\ + cmdline.h\ + slist.c BUILT_SOURCES=cmdline.h EXTRA_DIST=cmdline.opt getopt.m4 INCLUDES=-I$(top_srcdir)/gnu -I$(top_builddir)/gnu diff --git a/src/cmdline.opt b/src/cmdline.opt index 27a8298..bc2ef91 100644 --- a/src/cmdline.opt +++ b/src/cmdline.opt @@ -1,61 +1,42 @@ +static int mode_set = 0; +#define SET_MODE(m) do { \ + if (mode_set++ && mode != m) \ + error(1, 0, "only one of -q, -s, -d may be used"); \ + mode = m; \ + } while(0) + OPTIONS_BEGIN(gnu, "id3ed", [<id3ed - ID3 tag editor>], [<FILE [FILE...]>]) -OPTION(title,t,NUMBER, - [<set song title>]) -BEGIN - title = optarg; - mode = MODE_MOD; -END - -OPTION(artist,a,NAME, - [<set artist name>]) -BEGIN - artist = optarg; - mode = MODE_MOD; -END - -OPTION(album,A,NAME, - [<set album name>]) -BEGIN - album = optarg; - mode = MODE_MOD; -END - -OPTION(year,y,YEAR, - [<set year>]) -BEGIN - year = optarg; - mode = MODE_MOD; -END - -OPTION(comment,c,TEXT, - [<set comment>]) -BEGIN - comment = optarg; - mode = MODE_MOD; -END - -OPTION(track,T,NUMBER, - [<set track number>]) +OPTION(query,q,[FLIST], + [<query mode>]) BEGIN - track = atoi(optarg); - mode = MODE_MOD; + SET_MODE(MODE_QUERY); + if (optarg) + parse_ed_items(optarg); END -OPTION(genre,g,NAME, - [<set genre>]) +OPTION(all,a,, + [<query all frames>]) BEGIN - genre = optarg; - mode = MODE_MOD; + SET_MODE(MODE_QUERY); + all_frames = 1; END -OPTION(query,q,, - [<query mode>]) +OPTION(set,s,FIELD=VALUE, + [<set FIELD to VALUE>]) BEGIN - mode = MODE_QUERY; + char *p; + + SET_MODE(MODE_MOD); + p = strchr(optarg, '='); + if (!p) + error(1, 0, "missing `=' sign in assignment"); + *p++ = 0; + ed_list_add_assignment(optarg, p); END + OPTION(id-version,V,VERSION, [<set ID3 version>]) @@ -65,6 +46,12 @@ BEGIN error(1, 0, "unsupported version"); END +OPTION(latin1,,, + [<force latin1 output>]) +BEGIN + latin1_output = 1; +END + OPTIONS_END void diff --git a/src/getopt.m4 b/src/getopt.m4 index 6562667..5dd7494 100644 --- a/src/getopt.m4 +++ b/src/getopt.m4 @@ -140,7 +140,7 @@ divert(3) ifelse(SHORT_TAG,,LONG_TAG,[<SHORT_TAG[<>]ifelse(LONG_TAG,,,; LONG_TAG)>]), [<;>],[<,>])", ifelse(ARGNAME,,[<NULL, 0>], [<ifelse(ARGTYPE,[<optional_argument>], -[<patsubst([<ARGNAME>],[<\[\(.*\)\]>],[<N_("\1"), 1>])>],[<N_("ARGNAME"), 0>])>]), N_("DOCSTRING") }, +[<patsubst(ARGNAME,[<\[\(.*\)\]>],[<N_("\1"), 1>])>],[<N_("ARGNAME"), 0>])>]), N_("DOCSTRING") }, divert(-1)>]) popdef([<ARGTYPE>]) popdef([<ARGNAME>]) diff --git a/src/id3ed.h b/src/id3ed.h index 52f724d..7723598 100644 --- a/src/id3ed.h +++ b/src/id3ed.h @@ -5,7 +5,9 @@ #include <getopt.h> #include <error.h> #include <progname.h> - +#include <xalloc.h> +#include <gl_linked_list.h> +#include <argmatch.h> #include <id3tag.h> #define _(s) s @@ -13,16 +15,11 @@ #include "id3v1.h" -extern struct id3v1_block v1_block; -extern char *title; -extern char *artist; -extern char *album; -extern char *year; -extern char *comment; -extern unsigned track; -extern char *genre; +#define DEFAULT_ED_LIST "title,album,comment,artist,year,genre" + +extern int latin1_output; -void set_id3v1(const char *name); +void set_id3v1(const char *name, const struct id3v1_block *); void query_id3v1(const char *name); void del_id3v1(const char *name); @@ -30,3 +27,17 @@ void set_id3v2(const char *name); void query_id3v2(const char *name); void del_id3v2(const char *name); + +/* slist.c */ +typedef int (*string_list_action_fn) (const char *, void *); + +gl_list_t new_string_list(bool allow_duplicates); +int do_string_list(gl_list_t list, string_list_action_fn action, void *data); +void concat_string_list(gl_list_t list, gl_list_t addlist); +void print_string_list(FILE *fp, gl_list_t list); + + +void ed_list_add_item(const char *id, gl_list_t list); +void ed_list_print(void); +void ed_list_add_assignment(const char *name, const char *value); + diff --git a/src/id3v1.c b/src/id3v1.c index 9093eb8..f587a88 100644 --- a/src/id3v1.c +++ b/src/id3v1.c @@ -209,7 +209,7 @@ id3v1_read(FILE *fp, struct id3v1_block *blk) struct id3v1_block id; if (fseek(fp, -sizeof(id), SEEK_END)) return -1; - if (fread(blk, sizeof(id), 1, fp) != 1) + if (fread(&id, sizeof(id), 1, fp) != 1) return -1; if (memcmp(id.header, ID3V1_TAG, ID3V1_HEADER_LEN)) return 1; @@ -236,7 +236,7 @@ id3v1_write(FILE *fp, struct id3v1_block *blk) memcpy(blk, ID3V1_TAG, ID3V1_HEADER_LEN); if (fseek(fp, offset, SEEK_END)) return -1; - if (fwrite(&blk, sizeof(blk), 1, fp) != 1) + if (fwrite(blk, sizeof(blk[0]), 1, fp) != 1) return -1; return 0; } @@ -265,25 +265,26 @@ id3v1_init(struct id3v1_block *blk) void -set_id3v1(const char *name) +set_id3v1(const char *name, const struct id3v1_block *blk) { int rc; - struct id3v1_block old; + struct id3v1_block new, old; FILE *fp = fopen(name, "r+"); if (!fp) error(1, errno, "cannot open file %s", name); verify_mp3(fp, name); + memcpy(&new, blk, sizeof(new)); switch (id3v1_read(fp, &old)) { case 0: - id3v1_merge(&v1_block, &old); + id3v1_merge(&new, &old); break; case 1: break; default: error(1, errno, "%s: read error", name); } - if (id3v1_write(fp, &v1_block)) + if (id3v1_write(fp, &new)) error(1, errno, "%s: write error", name); fclose(fp); diff --git a/src/id3v2.c b/src/id3v2.c index f37606f..9401643 100644 --- a/src/id3v2.c +++ b/src/id3v2.c @@ -5,9 +5,97 @@ set_id3v2(const char *name) { } + +char * +ucs4_cvt(id3_ucs4_t const *ucs4) +{ + if (latin1_output) + return (char*)id3_ucs4_latin1duplicate(ucs4); + else + return (char*)id3_ucs4_utf8duplicate(ucs4); +} + +void +add_stringlist(gl_list_t list, struct id3_frame *frame, + union id3_field *field) +{ + unsigned i, nstrings = id3_field_getnstrings(field); + for (i = 0; i < nstrings; i++) { + id3_ucs4_t const *ucs4; + char *str; + + ucs4 = id3_field_getstrings(field, i); + if (!ucs4) + continue; + if (strcmp(frame->id, ID3_FRAME_GENRE) == 0) + ucs4 = id3_genre_name(ucs4); + str = ucs4_cvt(ucs4); + gl_list_add_last(list, str); + } +} + +void +add_field(gl_list_t list, struct id3_frame *frame, union id3_field *field) +{ + id3_ucs4_t const *ucs4; + char *str; + + switch (id3_field_type(field)) { + case ID3_FIELD_TYPE_TEXTENCODING: + break; + case ID3_FIELD_TYPE_LATIN1: + case ID3_FIELD_TYPE_LATIN1FULL: + case ID3_FIELD_TYPE_LATIN1LIST: + break; + case ID3_FIELD_TYPE_STRING: + break; + case ID3_FIELD_TYPE_STRINGFULL: + ucs4 = id3_field_getfullstring(field); + str = ucs4_cvt(ucs4); + gl_list_add_last(list, str); + break; + case ID3_FIELD_TYPE_STRINGLIST: + add_stringlist(list, frame, field); + break; + case ID3_FIELD_TYPE_LANGUAGE: + case ID3_FIELD_TYPE_FRAMEID: + case ID3_FIELD_TYPE_DATE: + case ID3_FIELD_TYPE_INT8: + case ID3_FIELD_TYPE_INT16: + case ID3_FIELD_TYPE_INT24: + case ID3_FIELD_TYPE_INT32: + case ID3_FIELD_TYPE_INT32PLUS: + case ID3_FIELD_TYPE_BINARYDATA:; + } +} + +gl_list_t +frame_to_list(struct id3_frame *frame) +{ + gl_list_t list; + unsigned i, j, nstrings; + union id3_field *field; + + list = new_string_list(true); + for (i = 0; field = id3_frame_field(frame, i); i++) + add_field(list, frame, field); + return list; +} + static void -show_tag(struct id3_tag *tag) +show_tags(struct id3_tag *tag) { + struct id3_frame *frame; + unsigned i; + + for (i = 0; frame = id3_tag_findframe(tag, NULL, i); i++) { + gl_list_t list = frame_to_list(frame); + if (gl_list_size(list) > 0) + ed_list_add_item(frame->id, list); + else + gl_list_free(list); + } + ed_list_print(); } void @@ -22,7 +110,7 @@ query_id3v2(const char *name) tag = id3_file_tag(file); if (tag) - show_tag(tag); + show_tags(tag); id3_file_close(file); } @@ -1,14 +1,244 @@ #include "id3ed.h" -unsigned version_option = 2; -struct id3v1_block v1_block; -char *title; -char *artist; -char *album; -char *year; -char *comment; -unsigned track; -char *genre; +unsigned version_option = 2; +int latin1_output = 0; + +struct ed_item { + char *name; + char id[5]; + union { + gl_list_t vlist; + char *value; + } v; +}; + +int all_frames = 0; +gl_list_t ed_list; + +static bool +ed_item_eq(const void *elt1, const void *elt2) +{ + const struct ed_item *i1 = elt1; + const struct ed_item *i2 = elt2; + return strcmp(i1->id, i2->id) == 0; +} + +static void +ed_item_dispose(const void *elt) +{ + free((void*)elt); +} + +void +ed_list_create() +{ + if (ed_list) + return; + ed_list = gl_list_create_empty(&gl_linked_list_implementation, + ed_item_eq, + NULL, + ed_item_dispose, + false); +} + +struct ed_item * +ed_item_create0(char *name, const char *id) +{ + struct ed_item *itm = xzalloc(sizeof(itm[0])); + itm->name = name; + strncpy(itm->id, id, sizeof(itm->id)); + return itm; +} + +struct ed_item * +ed_item_create(const char *name, const char *id) +{ + return ed_item_create0(xstrdup(name), id); +} + + +enum item_ids { + item_title, + item_artist, + item_album, + item_year, + item_comment, + item_track, + item_genre +}; + +int item_ids[] = { + item_title, + item_artist, + item_album, + item_year, + item_comment, + item_track, + item_genre +}; + +const char *item_names[] = { + "title", + "artist", + "album", + "year", + "comment", + "track", + "genre", + NULL +}; +ARGMATCH_VERIFY(item_names, item_ids); + +const char *item_frames[] = { + ID3_FRAME_TITLE, + ID3_FRAME_ARTIST, + ID3_FRAME_ALBUM, + ID3_FRAME_YEAR, + ID3_FRAME_COMMENT, + ID3_FRAME_TRACK, + ID3_FRAME_GENRE, + NULL +}; + +ARGMATCH_VERIFY(item_frames, item_ids); + +int +item_id(const char *arg) +{ + return (int) ARGMATCH(arg, item_names, item_ids); +} + +int +frame_id(const char *arg) +{ + return (int) ARGMATCH(arg, item_frames, item_ids); +} + +static struct ed_item * +ed_list_new_item(const char *arg, size_t len) +{ + int d; + char *name; + const char *id; + struct ed_item *itm; + + name = xmalloc(len+1); + memcpy(name, arg, len); + name[len] = 0; + + d = item_id(name); + if (d < 0) { + if (!id3_frametype_lookup(name, len)) + error(1, 0, d == -1 + ? "%s: unknown frame name" : + "%s: ambiguous frame name", + name); + id = name; + } else { + if (strcmp(item_names[d], name)) { + free(name); + name = xstrdup(item_names[d]); + } + id = item_frames[d]; + } + itm = ed_item_create0(name, id); + gl_list_add_last(ed_list, itm); + return itm; +} + +void +parse_ed_items(const char *arg) +{ + ed_list_create(); + while (*arg) { + size_t len = strcspn(arg, " \t,"); + + ed_list_new_item(arg, len); + + arg += len; + if (!*arg) + break; + arg += strspn(arg, " \t,"); + } +} + +void +ed_list_add_item(const char *id, gl_list_t list) +{ + if (all_frames) { + const char *name; + struct ed_item *itm; + int i; + + ed_list_create(); + i = frame_id(id); + if (i >= 0) + name = item_names[i]; + else + name = id; + itm = ed_item_create(name, id); + itm->v.vlist = list; + gl_list_add_last(ed_list, itm); + } else { + gl_list_node_t node; + struct ed_item item; + strncpy(item.id, id, sizeof(item.id)); + node = gl_list_search(ed_list, &item); + if (node) { + struct ed_item *itm = + (struct ed_item *) gl_list_node_value(ed_list, + node); + if (itm->v.vlist) { + concat_string_list(itm->v.vlist, list); + gl_list_free(list); + } else + itm->v.vlist = list; + } else + gl_list_free(list); + } +} + +void +ed_list_add_assignment(const char *name, const char *value) +{ + struct ed_item *itm; + + ed_list_create(); + itm = ed_list_new_item(name, strlen(name)); + itm->v.value = xstrdup(value); +} + +void +ed_list_print() +{ + gl_list_iterator_t itr; + const void *p; + if (!ed_list) + return; + itr = gl_list_iterator(ed_list); + while (gl_list_iterator_next(&itr, &p, NULL)) { + const struct ed_item *item = p; + printf("%s:", item->name); + if (item->v.vlist) + print_string_list(stdout, item->v.vlist); + putchar('\n'); + } + gl_list_iterator_free(&itr); +} + +const struct ed_item * +ed_item_find(int id) +{ + if (ed_list) { + gl_list_node_t node; + struct ed_item item; + strncpy(item.id, item_frames[id], sizeof(item.id)); + node = gl_list_search(ed_list, &item); + if (node) + return gl_list_node_value(ed_list, node); + } + return NULL; +} void @@ -38,15 +268,72 @@ verify_mp3(FILE *fp, const char *name) } } - #define MODE_QUERY 0 #define MODE_MOD 1 #define MODE_DELETE 2 -void (*id3_mode[][2])(const char *) = { - { query_id3v1, query_id3v2 }, - { set_id3v1, set_id3v2 }, - { del_id3v1, del_id3v2 } +void +query_id3(const char *name) +{ + query_id3v2(name); +} + +#define __cat2__(a,b) a ## b +#define V1_BLOCK_SET(blk, fld) do { \ + const struct ed_item *item = \ + ed_item_find(__cat2__(item_,fld)); \ + if (item) \ + ID3V1_SET((blk), fld, item->v.value); \ + } while(0) + +void +set_id3(const char *name) +{ + if (version_option == 1) { + const struct ed_item *item; + struct id3v1_block v1_block; + + id3v1_init(&v1_block); + V1_BLOCK_SET(&v1_block, title); + V1_BLOCK_SET(&v1_block, artist); + V1_BLOCK_SET(&v1_block, album); + V1_BLOCK_SET(&v1_block, year); + V1_BLOCK_SET(&v1_block, comment); + + item = ed_item_find(item_genre); + if (item && item->v.value) { + int n; + + n = id3v1_genre_to_n(item->v.value); + if (n == -1) + error(1, 0, "%s: unknown genre", + item->v.value); + else if (n == -2) + error(1, 0, "%s: ambiguous genre", + item->v.value); + v1_block.genre[0] = n; + } + + item = ed_item_find(item_track); + if (item && item->v.value) + v1_block.track[0] = atoi(item->v.value); + + set_id3v1(name, &v1_block); + } else + error(1, 0, "setting ID3 v2 frames is not yet implemented"); +} + +void +del_id3(const char *name) +{ + error(1, 0, "Deleting ID3 data is not yet implemented"); + abort(); +} + +void (*id3_mode[])(const char *) = { + query_id3, + set_id3, + del_id3, }; int mode = MODE_QUERY; @@ -67,26 +354,14 @@ main(int argc, char **argv) if (argc == 0) error(1, 0, "no files"); - - if (version_option == 1) { - int n; - - id3v1_init(&v1_block); - ID3V1_SET(&v1_block, title, title); - ID3V1_SET(&v1_block, artist, artist); - ID3V1_SET(&v1_block, album, album); - ID3V1_SET(&v1_block, year, year); - ID3V1_SET(&v1_block, comment, comment); - n = id3v1_genre_to_n(genre); - if (n == -1) - error(1, 0, "%s: unknown genre", genre); - else if (n == -2) - error(1, 0, "%s: ambiguous genre", genre); - v1_block.track[0] = n; + if (mode == MODE_QUERY) { + if (all_frames) + ed_list = NULL; + else if (!ed_list) + parse_ed_items(DEFAULT_ED_LIST); } - while (argc--) - id3_mode[mode][version_option - 1](*argv++); + id3_mode[mode](*argv++); exit(0); } diff --git a/src/slist.c b/src/slist.c new file mode 100644 index 0000000..fdc6343 --- /dev/null +++ b/src/slist.c @@ -0,0 +1,60 @@ +#include "id3ed.h" + +static bool +sl_eq(const void *elt1, const void *elt2) +{ + return strcmp(elt1, elt2) == 0; +} + +static void +sl_dispose(const void *elt) +{ + free((void*)elt); +} + +gl_list_t +new_string_list(bool allow_duplicates) +{ + return gl_list_create_empty(&gl_linked_list_implementation, + sl_eq, + NULL, + sl_dispose, + allow_duplicates); +} + +int +do_string_list(gl_list_t list, string_list_action_fn action, void *data) +{ + int rc = 0; + gl_list_iterator_t itr = gl_list_iterator(list); + const void *p; + while (gl_list_iterator_next(&itr, &p, NULL)) + if (rc = action((const char*)p, data)) + break; + gl_list_iterator_free(&itr); + return rc; +} + +void +concat_string_list(gl_list_t list, gl_list_t addlist) +{ + gl_list_iterator_t itr = gl_list_iterator(list); + const void *p; + while (gl_list_iterator_next(&itr, &p, NULL)) + gl_list_add_last(list, xstrdup((const char*)p)); + gl_list_iterator_free(&itr); +} + +static int +_sl_printer(const char *str, void *data) +{ + FILE *fp = data; + fprintf(fp, " %s", str); + return 0; +} + +void +print_string_list(FILE *fp, gl_list_t list) +{ + do_string_list(list, _sl_printer, fp); +} |