/* This file is part of Idest.
Copyright (C) 2009-2011, 2015 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;
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);
}