/* * Copyright 2008-2013 Various Authors * Copyright 2004 Timo Hirvonen * * This program 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 2 of the * License, or (at your option) any later version. * * This program 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 this program; if not, see . */ #include "../ip.h" #include "nomad.h" #include "../id3.h" #include "../ape.h" #include "../xmalloc.h" #include "../read_wrapper.h" #include "../debug.h" #include "../utils.h" #include "../comment.h" #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------------- */ static ssize_t read_func(void *datasource, void *buffer, size_t count) { struct input_plugin_data *ip_data = datasource; return read_wrapper(ip_data, buffer, count); } static off_t lseek_func(void *datasource, off_t offset, int whence) { struct input_plugin_data *ip_data = datasource; return lseek(ip_data->fd, offset, whence); } static int close_func(void *datasource) { struct input_plugin_data *ip_data = datasource; int rc; rc = ip_data->fd != -1 ? close(ip_data->fd) : 0; ip_data->fd = -1; return rc; } static struct nomad_callbacks callbacks = { .read = read_func, .lseek = lseek_func, .close = close_func }; /* ------------------------------------------------------------------------- */ static int mad_open(struct input_plugin_data *ip_data) { struct nomad *nomad; const struct nomad_info *info; int rc; rc = nomad_open_callbacks(&nomad, ip_data, &callbacks); switch (rc) { case -NOMAD_ERROR_ERRNO: return -1; case -NOMAD_ERROR_FILE_FORMAT: return -IP_ERROR_FILE_FORMAT; } ip_data->private = nomad; info = nomad_info(nomad); /* always 16-bit signed little-endian */ ip_data->sf = sf_rate(info->sample_rate) | sf_channels(info->channels) | sf_bits(16) | sf_signed(1); channel_map_init_waveex(info->channels, 0, ip_data->channel_map); return 0; } static int mad_close(struct input_plugin_data *ip_data) { struct nomad *nomad; nomad = ip_data->private; nomad_close(nomad); ip_data->fd = -1; ip_data->private = NULL; return 0; } static int mad_read(struct input_plugin_data *ip_data, char *buffer, int count) { struct nomad *nomad; nomad = ip_data->private; return nomad_read(nomad, buffer, count); } static int mad_seek(struct input_plugin_data *ip_data, double offset) { struct nomad *nomad; nomad = ip_data->private; return nomad_time_seek(nomad, offset); } static int mad_read_comments(struct input_plugin_data *ip_data, struct keyval **comments) { struct nomad *nomad = ip_data->private; const struct nomad_lame *lame = nomad_lame(nomad); struct id3tag id3; int fd, rc, save, i; APETAG(ape); GROWING_KEYVALS(c); fd = open(ip_data->filename, O_RDONLY); if (fd == -1) { return -1; } d_print("filename: %s\n", ip_data->filename); id3_init(&id3); rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2); save = errno; close(fd); errno = save; if (rc) { if (rc == -1) { d_print("error: %s\n", strerror(errno)); return -1; } d_print("corrupted tag?\n"); goto next; } for (i = 0; i < NUM_ID3_KEYS; i++) { char *val = id3_get_comment(&id3, i); if (val) comments_add(&c, id3_key_names[i], val); } next: id3_free(&id3); rc = ape_read_tags(&ape, ip_data->fd, 0); if (rc < 0) goto out; for (i = 0; i < rc; i++) { char *k, *v; k = ape_get_comment(&ape, &v); if (!k) break; comments_add(&c, k, v); free(k); } out: ape_free(&ape); /* add last so the other tags get preference */ if (lame && !isnan(lame->trackGain)) { char buf[64]; if (!isnan(lame->peak)) { sprintf(buf, "%f", lame->peak); comments_add_const(&c, "replaygain_track_peak", buf); } sprintf(buf, "%+.1f dB", lame->trackGain); comments_add_const(&c, "replaygain_track_gain", buf); } keyvals_terminate(&c); *comments = c.keyvals; return 0; } static int mad_duration(struct input_plugin_data *ip_data) { struct nomad *nomad = ip_data->private; return nomad_info(nomad)->duration; } static long mad_bitrate(struct input_plugin_data *ip_data) { struct nomad *nomad = ip_data->private; long bitrate = nomad_info(nomad)->avg_bitrate; return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED; } static long mad_current_bitrate(struct input_plugin_data *ip_data) { struct nomad *nomad = ip_data->private; return nomad_current_bitrate(nomad); } static char *mad_codec(struct input_plugin_data *ip_data) { struct nomad *nomad = ip_data->private; switch (nomad_info(nomad)->layer) { case 3: return xstrdup("mp3"); case 2: return xstrdup("mp2"); case 1: return xstrdup("mp1"); } return NULL; } static char *mad_codec_profile(struct input_plugin_data *ip_data) { struct nomad *nomad = ip_data->private; const struct nomad_lame *lame = nomad_lame(nomad); const char *mode = nomad_info(nomad)->vbr ? "VBR" : "CBR"; if (lame) { /* LAME: * 0: unknown * 1: cbr * 2: abr * 3: vbr rh (--vbr-old) * 4: vbr mtrh (--vbr-new) * 5: vbr mt (obsolete) */ int method = lame->vbr_method; if (method == 2) mode = "ABR"; else if (method >= 3 && method <= 5) { const struct nomad_xing *xing = nomad_xing(nomad); if (xing && xing->flags & XING_SCALE && xing->scale && xing->scale <= 100) { char buf[16]; int v = 10 - (xing->scale + 9) / 10; /* quality (-q): 10 - (xing->scale - ((9 - v) * 10)) */ sprintf(buf, "VBR V%d", v); return xstrdup(buf); } } } return xstrdup(mode); } const struct input_plugin_ops ip_ops = { .open = mad_open, .close = mad_close, .read = mad_read, .seek = mad_seek, .read_comments = mad_read_comments, .duration = mad_duration, .bitrate = mad_bitrate, .bitrate_current = mad_current_bitrate, .codec = mad_codec, .codec_profile = mad_codec_profile }; const int ip_priority = 55; const char * const ip_extensions[] = { "mp3", "mp2", NULL }; const char * const ip_mime_types[] = { "audio/mpeg", "audio/x-mp3", "audio/x-mpeg", NULL }; const struct input_plugin_opt ip_options[] = { { NULL } }; const unsigned ip_abi_version = IP_ABI_VERSION;