Mercurial > audlegacy-plugins
view src/madplug/replaygain.c @ 2284:d19b53359b24
cleaned up the sndfile wav plugin, currently limiting it ONLY TO WAV
PLAYBACK. if somebody is more experienced with it and wants to restore
the other formats, go ahead (maybe change the name of the plugin too?).
author | mf0102 <0102@gmx.at> |
---|---|
date | Wed, 09 Jan 2008 15:41:22 +0100 |
parents | d25cd7e7eddb |
children | 59addab003d7 |
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "plugin.h" #include <stdlib.h> #include <math.h> #include <ctype.h> #include <assert.h> #include "replaygain.h" static unsigned long Read_LE_Uint32(const unsigned char *p) { return ((unsigned long) p[0] << 0) | ((unsigned long) p[1] << 8) | ((unsigned long) p[2] << 16) | ((unsigned long) p[3] << 24); } static int uncase_strcmp(const char *s1, const char *s2) { int l1 = strlen(s1); int l2 = strlen(s2); int i; for (i = 0; i < l1 && i < l2; ++i) { if (toupper(s1[i]) < toupper(s2[i])) return -1; } if (l1 == l2) return 0; return (l1 < l2) ? -1 : +1; } static gdouble strgain2double(gchar * s, int len) { gdouble res = g_strtod(s, NULL); // gain, in dB. if (res == 0) return 1; return pow(10, res / 20); } // Reads APE v2.0 tag ending at current pos in fp static int ReadAPE2Tag(VFSFile * fp, struct mad_info_t *file_info) { unsigned long vsize; unsigned long isize; unsigned long flags; char *buff; char *p; char *end; struct APETagFooterStruct T, *tp; unsigned long TagLen; unsigned long TagCount; tp = &T; if (aud_vfs_fseek(fp, -sizeof(T), SEEK_CUR) != 0) return 18; if (aud_vfs_fread(tp, 1, sizeof(T), fp) != sizeof T) return 2; if (memcmp(tp->ID, "APETAGEX", sizeof(tp->ID)) != 0) return 3; if (Read_LE_Uint32(tp->Version) != 2000) return 4; TagLen = Read_LE_Uint32(tp->Length); if (TagLen < sizeof(T)) return 5; if (aud_vfs_fseek(fp, -TagLen, SEEK_CUR) != 0) return 6; if ((buff = (char *) malloc(TagLen)) == NULL) { return 7; } if (aud_vfs_fread(buff, 1, TagLen - sizeof(T), fp) != TagLen - sizeof(T)) { free(buff); return 8; } AUDDBG("ver = %ld\n", Read_LE_Uint32(tp->Version)); AUDDBG("taglen = %ld\n", TagLen); TagCount = Read_LE_Uint32(tp->TagCount); end = buff + TagLen - sizeof(T); for (p = buff; p < end && TagCount--;) { vsize = Read_LE_Uint32((unsigned char *)p); p += 4; flags = Read_LE_Uint32((unsigned char *)p); p += 4; isize = strlen((char *) p); if (isize > 0 && vsize > 0) { gdouble *scale = NULL; gchar **str = NULL; if (uncase_strcmp(p, "REPLAYGAIN_ALBUM_GAIN") == 0) { scale = &file_info->replaygain_album_scale; str = &file_info->replaygain_album_str; } if (uncase_strcmp(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(p + isize + 1, vsize); *str = g_strndup(p + isize + 1, vsize); } //* case of peak info tags : */ str = NULL; if (uncase_strcmp(p, "REPLAYGAIN_TRACK_PEAK") == 0) { scale = &file_info->replaygain_track_peak; str = &file_info->replaygain_track_peak_str; } if (uncase_strcmp(p, "REPLAYGAIN_ALBUM_PEAK") == 0) { scale = &file_info->replaygain_album_peak; str = &file_info->replaygain_album_peak_str; } if (str != NULL) { *scale = g_strtod(p + isize + 1, NULL); *str = g_strndup(p + isize + 1, 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 (uncase_strcmp(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); } if (uncase_strcmp(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 += isize + 1 + vsize; } free(buff); return 0; } static int find_offset(VFSFile * fp) { static const char *key = "APETAGEX"; char buff[20000]; int N = 0; if (aud_vfs_fseek(fp, -20000, SEEK_CUR) != 0); if ((N = aud_vfs_fread(buff, 1, 20000, fp)) < 16) return 1; int matched = 0; int i, last_match = -1; for (i = 0; i < N; ++i) { if (buff[i] == key[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 + sizeof(struct APETagFooterStruct) - N; } /* Eugene Zagidullin: * Read ReplayGain info from foobar2000-style id3v2 frames */ static int ReadId3v2TXXX(struct mad_info_t *file_info) { int i; char *key; char *value; struct id3_frame *frame; AUDDBG("f: ReadId3v2TXXX\n"); /* tag must be read before! */ if (! file_info->tag ) { AUDDBG("id3v2 not found\n"); return 0; } /* 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 = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[1])); value = (char *) id3_ucs4_latin1duplicate(id3_field_getstring (&frame->fields[2])); if (strcasecmp(key, "replaygain_track_gain") == 0) { file_info->replaygain_track_scale = strgain2double(value, strlen(value)); file_info->replaygain_track_str = g_strdup(value); } else if (strcasecmp(key, "replaygain_album_gain") == 0) { file_info->replaygain_album_scale = strgain2double(value, strlen(value)); 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); } if (file_info->replaygain_track_scale != -1 || file_info->replaygain_album_scale != -1) { file_info->has_replaygain = TRUE; return 1; } return 0; } void read_replaygain(struct mad_info_t *file_info) { VFSFile *fp; glong curpos = 0; AUDDBG("f: read_replaygain\n"); file_info->has_replaygain = FALSE; file_info->replaygain_album_scale = -1; file_info->replaygain_track_scale = -1; file_info->mp3gain_undo = -77; file_info->mp3gain_minmax = -77; if (ReadId3v2TXXX(file_info)) { #ifdef AUD_DEBUG AUDDBG("found ReplayGain info in id3v2 tag\n"); 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; } long pos = aud_vfs_ftell(fp); int res = -1; int try = 0; while (res != 0 && try < 10) { // try skipping an id3 tag aud_vfs_fseek(fp, pos, SEEK_SET); aud_vfs_fseek(fp, try * -128, SEEK_CUR); res = ReadAPE2Tag(fp, file_info); ++try; } if (res != 0) { // try brute search (don't want to parse all possible kinds of tags..) aud_vfs_fseek(fp, pos, SEEK_SET); int offs = find_offset(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); } } #ifdef AUD_DEBUG else AUDDBG("replaygain: not found\n"); #endif } #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->replaygain_album_scale != -1 || file_info->replaygain_track_scale != -1) file_info->has_replaygain = TRUE; if (file_info->infile) aud_vfs_fseek(fp, curpos, SEEK_SET); aud_vfs_fclose(fp); AUDDBG("e: read_replaygain\n"); }