Mercurial > audlegacy-plugins
view src/madplug/replaygain.c @ 3073:fbe76a1d61e2
crossfade-ng: do not force ourselves as default, kthx
author | William Pitcock <nenolod@atheme.org> |
---|---|
date | Sun, 26 Apr 2009 00:03:22 -0500 |
parents | f1b6f1b2cdb3 |
children | bdfc90a5a183 |
line wrap: on
line source
/* * mad plugin for audacious * Copyright (C) 2005-2007 William Pitcock, Yoshiki Yazawa * * Portions derived from xmms-mad: * Copyright (C) 2001-2002 Sam Clegg - See COPYING * Copyright (C) 2001-2007 Samuel Krempp * * 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; under version 2 of the License. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugin.h" #include <stdlib.h> #include <math.h> #include <ctype.h> #include <assert.h> #include "replaygain.h" #define APE_MATCH_BUF (20000) #define APE_HEADER_SIZE (32) static const gchar *ape_header_magic_id = "APETAGEX"; typedef struct { guchar id[8]; guint32 version; guint32 length; guint32 tagCount; guint32 flags; guchar reserved[8]; } ape_header_t; static gboolean fetchLE32(guint32 *res, gchar **ptr, const gchar *end) { if (*ptr + sizeof(guint32) > end) return FALSE; else { *res = ((guint32) (*ptr)[0] ) | ((guint32) (*ptr)[1] << 8) | ((guint32) (*ptr)[2] << 16) | ((guint32) (*ptr)[3] << 24); (*ptr) += sizeof(guint32); return TRUE; } } static gdouble strgain2double(const gchar * s, const size_t len) { gchar *strval = g_strndup(s, len); gdouble res = g_strtod(s, NULL); // gain, in dB. g_free(strval); return res; } /* Check for APE tag header in current file position, and read * header data into given structure. Return 0 if OK. */ static gint checkAPEHeader(VFSFile * fp, ape_header_t *hdr) { /* Get magic id and check it */ if (aud_vfs_fread(&hdr->id, sizeof(hdr->id), 1, fp) != 1) return 2; if (memcmp(hdr->id, ape_header_magic_id, sizeof(hdr->id)) != 0) return 3; /* Currently we only support APEv2 */ if (!aud_vfs_fget_le32(&hdr->version, fp) || hdr->version != 2000) return 4; /* Validate header length */ if (!aud_vfs_fget_le32(&hdr->length, fp) || hdr->length < APE_HEADER_SIZE) return 5; /* Get other data */ if (!aud_vfs_fget_le32(&hdr->tagCount, fp) || !aud_vfs_fget_le32(&hdr->flags, fp) || aud_vfs_fread(&hdr->reserved, sizeof(hdr->reserved), 1, fp) != 1) return 6; return 0; } /* Reads APE v2.0 tag ending at current pos in fp */ static gint readAPE2Tag(VFSFile * fp, struct mad_info_t *file_info) { gchar *buff, *p, *end; gint res; ape_header_t hdr; if (aud_vfs_fseek(fp, -APE_HEADER_SIZE, SEEK_CUR) != 0) return 18; if ((res = checkAPEHeader(fp, &hdr)) != 0) return res; if (aud_vfs_fseek(fp, -hdr.length, SEEK_CUR) != 0) return 7; if ((buff = (gchar *) g_malloc(hdr.length)) == NULL) return 8; if (aud_vfs_fread(buff, hdr.length - APE_HEADER_SIZE, 1, fp) != 1) { g_free(buff); return 9; } AUDDBG("ver = %ld\n", hdr.version); AUDDBG("taglen = %ld\n", hdr.length); end = buff + hdr.length - APE_HEADER_SIZE; for (p = buff; p < end && hdr.tagCount--;) { guint32 vsize, flags; size_t isize; gchar *tmp; /* Get and check size and string */ if (!fetchLE32(&vsize, &p, end)) break; if (!fetchLE32(&flags, &p, end)) break; for (tmp = p, isize = 0; tmp < end && *tmp != 0; isize++, tmp++); if (*tmp != 0) break; tmp++; if (isize > 0 && vsize > 0) { gdouble *scale = NULL; gchar **str = NULL; if (g_ascii_strcasecmp(p, "REPLAYGAIN_ALBUM_GAIN") == 0) { scale = &file_info->replaygain_album_scale; str = &file_info->replaygain_album_str; } else if (g_ascii_strcasecmp(p, "REPLAYGAIN_TRACK_GAIN") == 0) { scale = &file_info->replaygain_track_scale; str = &file_info->replaygain_track_str; } if (str != NULL) { assert(scale != NULL); *scale = strgain2double(tmp, vsize); *str = g_strndup(tmp, vsize); } /* case of peak info tags : */ str = NULL; if (g_ascii_strcasecmp(p, "REPLAYGAIN_TRACK_PEAK") == 0) { scale = &file_info->replaygain_track_peak; str = &file_info->replaygain_track_peak_str; } else if (g_ascii_strcasecmp(p, "REPLAYGAIN_ALBUM_PEAK") == 0) { scale = &file_info->replaygain_album_peak; str = &file_info->replaygain_album_peak_str; } if (str != NULL) { *scale = strgain2double(tmp, vsize); *str = g_strndup(tmp, vsize); } /* mp3gain additional tags : the gain tag translates to scale = 2^(gain/4), i.e., in dB : 20*log(2)/log(10)*gain/4 -> 1.501*gain dB */ if (g_ascii_strcasecmp(p, "MP3GAIN_UNDO") == 0) { str = &file_info->mp3gain_undo_str; scale = &file_info->mp3gain_undo; assert(4 < vsize); /* this tag is +left,+right */ *str = g_strndup(p + isize + 1, vsize); *scale = 1.50515 * atoi(*str); } else if (g_ascii_strcasecmp(p, "MP3GAIN_MINMAX") == 0) { str = &file_info->mp3gain_minmax_str; scale = &file_info->mp3gain_minmax; *str = g_strndup(p + isize + 1, vsize); assert(4 < vsize); /* this tag is min,max */ *scale = 1.50515 * (atoi((*str) + 4) - atoi(*str)); } } p = tmp; } g_free(buff); return 0; } static gint findOffset(VFSFile * fp) { gchar buff[APE_MATCH_BUF]; gint matched = 0, last_match = -1; size_t N = 0, i; if (aud_vfs_fseek(fp, - APE_MATCH_BUF, SEEK_CUR) != 0); if ((N = aud_vfs_fread(buff, sizeof(gchar), APE_MATCH_BUF, fp)) < 16) return 1; for (i = 0; i < N; ++i) { if (buff[i] == ape_header_magic_id[matched]) ++matched; else { if (matched == 5 && buff[i] == 'P') matched = 2; // got "APET" + "AP" else matched = 0; } if (matched == 8) { last_match = i; matched = 0; } } if (last_match == -1) return 1; return last_match + 1 - 8 + APE_HEADER_SIZE - N; } /* Eugene Zagidullin: * Read ReplayGain info from foobar2000-style id3v2 frames. */ static gint readId3v2TXXX(struct mad_info_t *file_info) { gint i; gchar *key; gchar *value; struct id3_frame *frame; AUDDBG("f: ReadId3v2TXXX\n"); /* tag must be read before! */ if (! file_info->tag ) { AUDDBG("id3v2 not found\n"); return FALSE; } /* Partially based on code from MPD (http://www.musicpd.org/) */ for (i = 0; (frame = id3_tag_findframe(file_info->tag, "TXXX", i)); i++) { if (frame->nfields < 3) continue; key = (gchar *) id3_ucs4_latin1duplicate(id3_field_getstring(&frame->fields[1])); value = (gchar *) id3_ucs4_latin1duplicate(id3_field_getstring(&frame->fields[2])); if (strcasecmp(key, "replaygain_track_gain") == 0) { file_info->replaygain_track_scale = g_strtod(value, NULL); file_info->replaygain_track_str = g_strdup(value); } else if (strcasecmp(key, "replaygain_album_gain") == 0) { file_info->replaygain_album_scale = g_strtod(value, NULL); file_info->replaygain_album_str = g_strdup(value); } else if (strcasecmp(key, "replaygain_track_peak") == 0) { file_info->replaygain_track_peak = g_strtod(value, NULL); file_info->replaygain_track_peak_str = g_strdup(value); } else if (strcasecmp(key, "replaygain_album_peak") == 0) { file_info->replaygain_album_peak = g_strtod(value, NULL); file_info->replaygain_album_peak_str = g_strdup(value); } free(key); free(value); return TRUE; } return FALSE; } void audmad_read_replaygain(struct mad_info_t *file_info) { VFSFile *fp; glong curpos = 0; AUDDBG("f: read_replaygain\n"); file_info->replaygain_track_peak = 0.0; file_info->replaygain_track_scale = 0.0; file_info->replaygain_album_peak = 0.0; file_info->replaygain_album_scale = 0.0; file_info->mp3gain_undo = -77; file_info->mp3gain_minmax = -77; if (readId3v2TXXX(file_info)) { AUDDBG("found ReplayGain info in id3v2 tag\n"); #ifdef AUD_DEBUG gchar *tmp = g_filename_to_utf8(file_info->filename, -1, NULL, NULL, NULL); AUDDBG("RG album scale= %g, RG track scale = %g, in %s\n", file_info->replaygain_album_scale, file_info->replaygain_track_scale, tmp); g_free(tmp); #endif return; } /* APEv2 stuff */ if (file_info->infile) { fp = aud_vfs_dup(file_info->infile); curpos = aud_vfs_ftell(fp); } else { if ((fp = aud_vfs_fopen(file_info->filename, "rb")) == NULL) return; } if (aud_vfs_fseek(fp, 0L, SEEK_END) != 0) { aud_vfs_fclose(fp); return; } glong pos = aud_vfs_ftell(fp); gint res = -1, try_pos = 0; while (res != 0 && try_pos < 10) { // try skipping an id3 tag aud_vfs_fseek(fp, pos, SEEK_SET); aud_vfs_fseek(fp, try_pos * -128, SEEK_CUR); res = readAPE2Tag(fp, file_info); ++try_pos; } if (res != 0) { // try brute search (don't want to parse all possible kinds of tags..) aud_vfs_fseek(fp, pos, SEEK_SET); gint offs = findOffset(fp); if (offs <= 0) { // found ! aud_vfs_fseek(fp, pos, SEEK_SET); aud_vfs_fseek(fp, offs, SEEK_CUR); res = readAPE2Tag(fp, file_info); if (res != 0) { g_message ("hmpf, was supposed to find a tag.. offs=%d, res=%d", offs, res); } } else AUDDBG("replaygain: not found\n"); } #ifdef AUD_DEBUG if (res == 0) { // got APE tags, show the result gchar *tmp = g_filename_to_utf8(file_info->filename, -1, NULL, NULL, NULL); AUDDBG("RG album scale= %g, RG track scale = %g, in %s\n", file_info->replaygain_album_scale, file_info->replaygain_track_scale, tmp); g_free(tmp); } #endif if (file_info->infile) aud_vfs_fseek(fp, curpos, SEEK_SET); aud_vfs_fclose(fp); AUDDBG("e: read_replaygain\n"); }