winamp/Src/Plugins/Input/in_vorbis/wa2.cpp

1097 lines
23 KiB
C++
Raw Normal View History

2024-09-24 13:54:57 +01:00
#include "main.h"
#include "genres.h"
#include "decoder.h"
#include "api__in_vorbis.h"
#include "../Winamp/wa_ipc.h"
#include "../nu/Singleton.h"
#include "mkv_vorbis_decoder.h"
#include <shlwapi.h>
#include "../nu/AutoWide.h"
#include "../nu/AutoChar.h"
#include <strsafe.h>
#include <api/service/waservicefactory.h>
template <class api_T>
void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
{
if (mod.service)
{
waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t);
if (factory)
api_t = reinterpret_cast<api_T *>( factory->getInterface() );
}
}
template <class api_T>
void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
{
if (mod.service && api_t)
{
waServiceFactory *factory = mod.service->service_getServiceByGuid(factoryGUID_t);
if (factory)
factory->releaseInterface(api_t);
}
api_t = NULL;
}
VorbisFile * theFile = 0;
extern CfgInt cfg_abr,cfg_httpseek2;
OSVERSIONINFO os_ver = {0};
static int pos_ms;
static int seek_to=-1;
static int length;
static bool kill;
StringW stat_disp;
void show_stat(const wchar_t* txt)
{
if (txt)
{
stat_disp=txt;
PostMessage(mod.hMainWindow,WM_USER,0,243);
}
else
stat_disp=L"";
}
static int is_out_open;
static int paused;
static int volume=255;
static int pan=0;
StringW cur_file;
CRITICAL_SECTION sync;
HANDLE hThread=0;
void Config(HWND);
void About(HWND p);
void do_cfg(int s);
void GetFileInfo(const in_char *file, wchar_t *title, int *len);
const char *INI_FILE=0;
const wchar_t *INI_DIRECTORY=0;
int (*warand)()=0;
float (*warandf)()=0;
api_application *WASABI_API_APP = 0;
// wasabi based services for localisation support
api_language *WASABI_API_LNG = 0;
HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
api_memmgr* WASABI_API_MEMMGR = 0;
api_config *AGAVE_API_CONFIG=0;
static MKVDecoderCreator mkvCreator;
static SingletonServiceFactory<svc_mkvdecoder, MKVDecoderCreator> mkvFactory;
void SetFileExtensions(void)
{
static char fileExtensionsString[1200] = {0}; // "OGG\0Ogg files (*.OGG)\0"
char* end = 0;
StringCchCopyExA(fileExtensionsString, 1200, "OGG;OGA", &end, 0, 0);
StringCchCopyExA(end+1, 1200, WASABI_API_LNGSTRING(IDS_OGG_FILES), 0, 0, 0);
mod.FileExtensions = fileExtensionsString;
}
int Init()
{
if (!IsWindow(mod.hMainWindow))
return IN_INIT_FAILURE;
mod.UsesOutputPlug|=8;
warand = (int (*)())SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_RANDFUNC);
warandf = (float (*)())SendMessage(mod.hMainWindow, WM_WA_IPC, 1, IPC_GET_RANDFUNC);
ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid);
ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
// loader so that we can get the localisation service api for use
ServiceBuild(WASABI_API_LNG, languageApiGUID);
ServiceBuild(WASABI_API_APP, applicationApiServiceGuid);
mkvFactory.Register(mod.service, &mkvCreator);
// need to have this initialised before we try to do anything with localisation features
WASABI_API_START_LANG(mod.hDllInstance,InVorbisLangGUID);
static wchar_t szDescription[256];
StringCchPrintfW(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_VORBIS_DECODER),VER);
mod.description = (char*)szDescription;
SetFileExtensions();
INI_FILE = (const char *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIFILE);
INI_DIRECTORY = (const wchar_t *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORYW);
os_ver.dwOSVersionInfoSize=sizeof(os_ver);
GetVersionEx(&os_ver);
InitializeCriticalSection(&sync);
do_cfg(0);
return IN_INIT_SUCCESS;
}
void Quit()
{
winampGetExtendedFileInfoW_Cleanup();
DeleteCriticalSection(&sync);
mkvFactory.Deregister(mod.service);
ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid);
ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
ServiceRelease(WASABI_API_LNG, languageApiGUID);
ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
}
int GetLength()
{
return length;
}
int IsOurFile(const in_char *fn)
{
if (PathIsURLW(fn))
{
const wchar_t *foo=wcsrchr(fn,L'.');
return foo ? !_wcsicmp(foo,L".ogg") : 0;
}
else return 0;
}
static UINT kbps_disp;
static void out_close()
{
if (is_out_open)
{
mod.outMod->Close();
mod.SAVSADeInit();
is_out_open=0;
}
}
static bool need_full_setinfo;
static int out_open(const Decoder &dec)
{
int max_l=mod.outMod->Open(dec.sr,dec.nch,dec.bps,-1,-1);
if (max_l<0) return 0;
mod.outMod->SetVolume(-666);
mod.outMod->SetPan(pan);
mod.SAVSAInit(max_l,dec.sr);
mod.VSASetInfo(dec.sr,dec.nch);
is_out_open=1;
need_full_setinfo=1;
return 1;
}
void Decoder::wa2_setinfo(UINT cur)
{
UINT disp=file->get_avg_bitrate();
if (!cfg_abr)
{
disp=cur;
}
if ((disp && disp!=kbps_disp) || need_full_setinfo)
{
kbps_disp=disp;
if (need_full_setinfo)
{
mod.SetInfo(disp,sr/1000,nch,1);
need_full_setinfo=0;
}
else mod.SetInfo(disp,-1,-1,1);
}
}
static bool need_movefile;
static void process_movefile();
void alloc_buffers(Decoder & dec,short ** visbuf,char ** sample_buf,int * s_size)
{
*s_size=576 * (dec.bps>>3) * dec.nch;
if (*sample_buf) *sample_buf=(char*)realloc(*sample_buf,*s_size*2);
else *sample_buf=(char*)malloc(*s_size*2);
if (dec.bps>16)
{
int vs=576*2*dec.nch;
if (*visbuf) *visbuf=(short*)realloc(*visbuf,vs);
else *visbuf=(short*)malloc(vs);
}
else if (*visbuf) {free(*visbuf);*visbuf=0;}
}
static DWORD WINAPI PlayThread(Decoder &dec)
{
int pos_base=0;
int samp_wr=0;
int done=0;
int upd=0;
__int64 brate;
int br_div,br_t;
short* visbuf=0;
char *sample_buf=0;
int retries=0;
int s_size=0;
pos_ms=0;
{
int r;
r=dec.play_init();
if (r)
{
if (!kill) Sleep(50);
if (!kill) Sleep(50);
if (!kill) Sleep(50);
if (!kill) Sleep(50);
if (!kill)
{
if (r==2) PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0);
else PostMessage(mod.hMainWindow,WM_COMMAND,40047,0);
}
delete &dec;
return 0;
}
theFile->do_prebuf();
}
brate=0;
br_div=0;
upd=0;
alloc_buffers(dec,&visbuf,&sample_buf,&s_size);
//int f_type=theFile->GetType();
bool is_live=theFile->IsLive();
while(!kill)
{
if (!theFile) break;//ugh
if (seek_to!= -1)
{
UINT _st=seek_to;
int r=1;
seek_to=-1;
if (theFile)
{
theFile->use_prebuf=0;
int link=theFile->vf.current_link;
r=dec.Seek((double)_st*0.001);
if (link!=theFile->vf.current_link) PostMessage(mod.hMainWindow,WM_USER,0,243);
}
else r=1;
if (!r)
{
pos_base=pos_ms=_st;
mod.outMod->Flush(pos_ms);
samp_wr=0;
done=0;
theFile->do_prebuf();
}
}
if (need_movefile && paused)//HACK, prevent stupid lockup
{
process_movefile();
if (!theFile) break;//#@!
dec.file=theFile;
dec.Flush();
}
if (done)
{
// mod.outMod->CanWrite();
if (!mod.outMod->IsPlaying())
{
PostMessage(mod.hMainWindow,WM_WA_MPEG_EOF,0,0);
break;
}
Sleep(10);
}
else if (mod.outMod->CanWrite() >= (s_size<<(mod.dsp_isactive()?1:0)))
{
int l=0;
while(1)
{
if (!dec.need_reopen)
{
l+=dec.Read(s_size-l,sample_buf+l);
if (l>=s_size) break;
int link=theFile->vf.current_link;
if (need_movefile)//safe not to flush here
{
process_movefile();
if (!theFile) break;//#@!
dec.file=theFile;
}
if (!dec.DoFrame()) break;
if (kill) break;
if (link!=theFile->vf.current_link)
{
PostMessage(mod.hMainWindow,WM_USER,0,243);
}
br_t=ov_bitrate_instant(&theFile->vf);
if (br_t>0)
{
int i = dec.DataAvailable()/((dec.bps/8)*dec.nch);
br_div+=i;
brate+=(__int64)(br_t*i);
}
if (need_full_setinfo || (!((++upd)%200) && br_div))
{
if (!br_div) {br_div=1;brate=theFile->get_avg_bitrate();}
dec.wa2_setinfo((int)((__int64)brate/(__int64)br_div/(__int64)1000));
brate=0;
br_div=0;
}
}
if (dec.need_reopen)
{//blargh, new PCM format
if (l>0) break;//got samples to play, we'll deal with this later
//l=0;
while(!kill && mod.outMod->IsPlaying()) Sleep(1);
if (kill) break;
out_close();
if (!out_open(dec))//boo
{
PostMessage(mod.hMainWindow,WM_COMMAND,40047,0);
kill=1;
break;
}
alloc_buffers(dec,&visbuf,&sample_buf,&s_size);
dec.need_reopen=0;
}
}
if (kill || !theFile) break;
if (l<=0 && (!is_live || (--retries)<0))
{
mod.outMod->Write(sample_buf,0);
done=1;
}
else if (l<=0)
{
int r;
out_close();
EnterCriticalSection(&sync);
delete theFile;
theFile=0;
LeaveCriticalSection(&sync);
if (sample_buf)
{
free(sample_buf);
sample_buf=0;
}
r=dec.play_init();
if (r)
{
mod.outMod->Write(sample_buf,0);
done=1;
}
else
{
theFile->do_prebuf();
}
}
else
{
if (l<s_size) memset(sample_buf+l,dec.bps==8 ? 0x80 : 0,s_size-l);
char * vis=sample_buf;
UINT vis_bps=dec.bps;
if (dec.bps>16)
{
UINT n;
UINT d=dec.bps>>3;
char * foo=sample_buf+d-2;
for(n=0;n<576*dec.nch;n++)
{
visbuf[n]=*(short*)foo;
foo+=d;
}
vis=(char*)visbuf;
vis_bps=16;
}
mod.SAAddPCMData(vis,dec.nch,vis_bps,pos_ms);
mod.VSAAddPCMData(vis,dec.nch,vis_bps,pos_ms);
if (mod.dsp_isactive())
{
l=(l<<3)/(dec.bps*dec.nch);
l=mod.dsp_dosamples((short*)sample_buf,l,dec.bps,dec.nch,dec.sr);
l*=(dec.nch*dec.bps)>>3;
}
if (kill) break;
mod.outMod->Write((char*)sample_buf,l);
samp_wr+=(8*l)/(dec.bps*dec.nch);
pos_ms=pos_base+MulDiv(1000,samp_wr,dec.sr);
}
}
else
{
theFile->Idle();
}
}
// out_close();
// gay gapless plugins puke, need to call this from stop
// ok, hetero (out_wave v2.x / out_ds v1.4+) gapless plugins wouldn't puke anymore
if (theFile)
{
VorbisFile * t=theFile;
EnterCriticalSection(&sync);
theFile=0;
LeaveCriticalSection(&sync);
delete t;
}
if (sample_buf)
{
free(sample_buf);
sample_buf=0;
}
if (need_movefile) process_movefile();
/* if (!kill)
{
CloseHandle(hThread);
hThread=0;
}*/
if (visbuf) free(visbuf);
delete &dec;
return 0;
}
static StringW move_src,move_dst;
static bool mf_ret;
static void do_movefile()
{
mf_ret=1;
winampGetExtendedFileInfoW_Cleanup();
if (!DeleteFileW(move_dst)) mf_ret=0;
else
{
if (!MoveFileW(move_src,move_dst))
{
if (!CopyFileW(move_src,move_dst,0)) mf_ret=0;
DeleteFileW(move_src);
}
}
}
static void process_movefile()
{
if (theFile)
{
StringW f_path;
f_path.AddString(theFile->url);
double pos=theFile->GetPos();
EnterCriticalSection(&sync);
delete theFile;
theFile=0;
do_movefile();
theFile=VorbisFile::Create(f_path,0);
LeaveCriticalSection(&sync);
if (theFile)
{
theFile->Seek(pos);
}
}
else do_movefile();
need_movefile=0;
}
bool sync_movefile(const wchar_t * src,const wchar_t * dst)//called from info_.cpp
{
move_src=src;
move_dst=dst;
need_movefile=1;
if (!theFile) process_movefile();
else
{
while(need_movefile && hThread) Sleep(1);
if (need_movefile) process_movefile();//shouldnt really happen
move_src=L"";
move_dst=L"";
PostMessage(mod.hMainWindow,WM_USER,0,243);
}
return mf_ret;
}
int Decoder::play_init()//still messy
{
if (play_inited) return 0;
kbps_disp=0;
VorbisFile * t=VorbisFile::Create(cur_file,0);
if (!t)
{
#ifdef _DEBUG
OutputDebugString(L"can't open file\n");
#endif
// if (scream) MessageBox(mod.hMainWindow,"error opening file",0,MB_ICONERROR);
return 2;
}
Init(t);
if (!out_open(*this))
{
#ifdef _DEBUG
OutputDebugString(L"can't open output\n");
#endif
delete t;
return 1;
}
EnterCriticalSection(&sync);
theFile=t;
LeaveCriticalSection(&sync);
wa2_setinfo(theFile->get_avg_bitrate());
{
double v=theFile->Length();
if (v==OV_EINVAL || v<=0) length=-1;
else length=(int)(v*1000.0);
}
play_inited=1;
return 0;
}
int Play(const in_char *fn)
{
seek_to=-1;
kill=0;
length=0;
paused=0;
show_stat(0);
EnterCriticalSection(&sync);
cur_file=fn;
LeaveCriticalSection(&sync);
Decoder * dec=new Decoder;
if (!PathIsURLW(fn))
{
mod.is_seekable=1;
#if 1
int rv=dec->play_init();
if (rv)
{
delete dec;
if (rv==2) return -1;
return 1;
}
#endif
}
else mod.is_seekable=cfg_httpseek2;
{
DWORD id;
hThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)PlayThread,dec,CREATE_SUSPENDED,&id);
}
if (hThread)
{
SetThreadPriority(hThread, (int)AGAVE_API_CONFIG->GetInt(playbackConfigGroupGUID, L"priority", THREAD_PRIORITY_HIGHEST));
ResumeThread(hThread);
return 0;
}
else
{
out_close();
delete dec;
return 1;
}
}
void Pause()
{
if (!paused)
{
mod.outMod->Pause(1);
paused=1;
}
}
void UnPause()
{
if (paused)
{
mod.outMod->Pause(0);
paused=0;
}
}
int IsPaused()
{
return paused;
}
void Stop()
{
if (hThread)
{
kill=1;
EnterCriticalSection(&sync);
if (theFile) theFile->stopping=1;
LeaveCriticalSection(&sync);
if (WaitForSingleObject(hThread,10000)!=WAIT_OBJECT_0)
{
TerminateThread(hThread,0);
//MessageBox(mod.hMainWindow,"error asking thread to die",0,MB_ICONERROR);
}
CloseHandle(hThread);
hThread=0;
out_close();
}
show_stat(0);
winampGetExtendedFileInfoW_Cleanup();
}
void EQSet(int on, char data[10], int preamp)
{
}
int GetOutputTime()
{
return pos_ms+(mod.outMod->GetOutputTime()-mod.outMod->GetWrittenTime());
}
void SetOutputTime(int t)
{
seek_to=t;
EnterCriticalSection(&sync);
if (theFile) theFile->abort_prebuf=1;
LeaveCriticalSection(&sync);
}
void SetVolume(int v)
{
mod.outMod->SetVolume(volume=v);
}
void SetPan(int p)
{
mod.outMod->SetPan(pan=p);
}
//int InfoBox(char *file, HWND parent); //old
int RunInfoDlg(const in_char * url,HWND parent);
In_Module mod=
{
IN_VER_RET,
"nullsoft(in_vorbis.dll)",
0,0,
0,
1,
1,
Config,
About,
Init,
Quit,
GetFileInfo,
RunInfoDlg,
IsOurFile,
Play,
Pause,
UnPause,
IsPaused,
Stop,
GetLength,
GetOutputTime,
SetOutputTime,
SetVolume,
SetPan,
0,0,0,0,0,0,0,0,0,0,0,
EQSet,
0,
0,
};
extern "C" {
__declspec( dllexport ) In_Module * winampGetInModule2()
{
return &mod;
}
}
void VorbisFile::Status(const wchar_t * zzz)
{
if (primary)
show_stat(zzz);
}
bool VorbisFile::Aborting()
{
return stopping || (primary && kill);
}
Info::Info(const wchar_t *filename) : filename(filename), vc(0)
{
VorbisFile * vf = VorbisFile::Create(filename,true);
if(!vf)
return;
numstreams = vf->vf.links;
if(numstreams)
{
// now copy the comment section to our own memory...
stream = vf->vf.current_link; // this is the stream we're editing...
vc = (vorbis_comment*)calloc(sizeof(vorbis_comment),numstreams);
for(int i=0; i<numstreams; i++)
{ // one comment section per stream
vorbis_comment *c = ov_comment(&vf->vf,i);
vc[i].comments = c->comments;
vc[i].user_comments = (char **)malloc(sizeof(char*)*c->comments);
vc[i].comment_lengths = (int *)malloc(sizeof(int)*c->comments);
for(int j=0;j<vc[i].comments;j++)
{ // copy the comments over
vc[i].user_comments[j] = _strdup(c->user_comments[j]);
vc[i].comment_lengths[j] = c->comment_lengths[j];
}
vc[i].vendor=_strdup(c->vendor);
}
}
delete vf;
}
Info::~Info()
{
if(vc) {
for(int i=0; i < numstreams; i++)
vorbis_comment_clear(&vc[i]);
free(vc);
}
}
bool Info::Save()
{
return !!modify_file(filename,vc,numstreams);
}
int Info::GetNumMetadataItems()
{
return vc[stream].comments;
}
void Info::EnumMetadata(int n, wchar_t *key, int keylen, wchar_t *val, int vallen)
{
if(keylen) key[0]=0;
if(vallen) val[0]=0;
if(!vc) return;
if(!vc[stream].user_comments[n]) return;
AutoWide comment(vc[stream].user_comments[n],CP_UTF8);
const wchar_t* eq = wcschr((const wchar_t*)comment,L'=');
if(eq)
{
if(keylen) lstrcpynW(key,comment,(int)min(eq - comment + 1,keylen));
if(vallen) lstrcpynW(val,eq+1,vallen);
}
else
{
if(keylen) lstrcpynW(key,L"COMMENT",keylen);
if(vallen) lstrcpynW(val,comment,vallen);
}
}
void Info::RemoveMetadata(wchar_t * key)
{
wchar_t k[256] = {0};
for(int i=0; i<GetNumMetadataItems(); i++)
{
EnumMetadata(i,k,256,0,0);
if(_wcsicmp(k,key)==0)
RemoveMetadata(i);
}
}
void Info::RemoveMetadata(int n)
{
if(!vc) return;
free(vc[stream].user_comments[n]);
for(int i=n+1; i<vc[stream].comments; i++)
{
vc[stream].user_comments[i-1] = vc[stream].user_comments[i];
if(vc[stream].comment_lengths)
vc[stream].comment_lengths[i-1] = vc[stream].comment_lengths[i];
}
vc[stream].comments--;
vc[stream].user_comments = (char**)realloc(vc[stream].user_comments,sizeof(vc[stream].user_comments[0]) * vc[stream].comments);
if(vc[stream].comment_lengths)
vc[stream].comment_lengths = (int*)realloc(vc[stream].comment_lengths,sizeof(vc[stream].comment_lengths[0]) * vc[stream].comments);
}
void Info::SetMetadata(wchar_t *key, wchar_t *val)
{
bool set=false;
wchar_t k[256] = {0};
for(int i=0; i<GetNumMetadataItems(); i++)
{
EnumMetadata(i,k,256,0,0);
if(_wcsicmp(k,key)==0)
{
SetMetadata(i,key,val);
set=true;
}
}
if(!set)
{
int n = vc[stream].comments++;
vc[stream].user_comments = (char**)realloc(vc[stream].user_comments,sizeof(vc[stream].user_comments[0]) * vc[stream].comments);
if(vc[stream].comment_lengths)
vc[stream].comment_lengths = (int*)realloc(vc[stream].comment_lengths,sizeof(vc[stream].comment_lengths[0]) * vc[stream].comments);
vc[stream].user_comments[n] = NULL;
SetMetadata(n,key,val);
}
}
void Info::SetMetadata(int n, wchar_t *key, wchar_t *val)
{
AutoChar k(key,CP_UTF8);
AutoChar v(val,CP_UTF8);
int l = (int)(strlen(k)+strlen(v)+2);
char * c = (char*)malloc(l);
StringCchPrintfA(c,l,"%s=%s",(char*)k,(char*)v);
if(vc[stream].user_comments[n])
free(vc[stream].user_comments[n]);
vc[stream].user_comments[n] = c;
if(vc[stream].comment_lengths)
vc[stream].comment_lengths[n] = l-1;
}
void Info::SetTag(int n,wchar_t *key) // changes the key name
{
wchar_t val[2048] = {0};
EnumMetadata(n,NULL,0,val,2048);
SetMetadata(n,key,val);
}
Info *setMetadata = 0;
extern "C"
{
static wchar_t m_lastfn[2048];
#define START_TAG_ALIAS(name, alias) if (KeywordMatch(data, name)) lookup=alias
#define TAG_ALIAS(name, alias) else if (KeywordMatch(data, name)) lookup=alias
__declspec( dllexport ) int winampSetExtendedFileInfoW(const wchar_t *fn, const char *data, wchar_t *val)
{
if (!setMetadata || setMetadata && wcscmp(fn,m_lastfn))
{
if (setMetadata)
{
delete setMetadata;
setMetadata = 0;
}
setMetadata = new Info(fn);
if(setMetadata->Error())
{
delete setMetadata;
m_lastfn[0] = 0;
return 0;
}
lstrcpynW(m_lastfn,fn, 2048);
}
wchar_t *lookup=0;
START_TAG_ALIAS("artist", L"ARTIST");
TAG_ALIAS("title", L"TITLE");
TAG_ALIAS("album", L"ALBUM");
TAG_ALIAS("genre", L"GENRE");
TAG_ALIAS("comment", L"COMMENT");
TAG_ALIAS("year", L"DATE");
TAG_ALIAS("track", L"TRACKNUMBER");
TAG_ALIAS("albumartist", L"ALBUM ARTIST");
TAG_ALIAS("composer", L"COMPOSER");
TAG_ALIAS("disc", L"DISCNUMBER");
TAG_ALIAS("publisher", L"PUBLISHER");
TAG_ALIAS("conductor", L"CONDUCTOR");
TAG_ALIAS("tool", L"ENCODED-BY");
TAG_ALIAS("replaygain_track_gain", L"REPLAYGAIN_TRACK_GAIN");
TAG_ALIAS("replaygain_track_peak", L"REPLAYGAIN_TRACK_PEAK");
TAG_ALIAS("replaygain_album_gain", L"REPLAYGAIN_ALBUM_GAIN");
TAG_ALIAS("replaygain_album_peak", L"REPLAYGAIN_ALBUM_PEAK");
TAG_ALIAS("GracenoteFileID", L"GRACENOTEFILEID");
TAG_ALIAS("GracenoteExtData", L"GRACENOTEEXTDATA");
TAG_ALIAS("bpm", L"BPM");
TAG_ALIAS("remixing", L"REMIXING");
TAG_ALIAS("subtitle", L"VERSION");
TAG_ALIAS("isrc", L"ISRC");
TAG_ALIAS("category", L"CATEGORY");
TAG_ALIAS("rating", L"RATING");
TAG_ALIAS("producer", L"PRODUCER");
if (!lookup)
return 0;
#if 0
if (val && *val)
{
if(KeywordMatch("rating",data))
{
wchar_t temp[128] = {0};
StringCchPrintfW(temp, 128, L"%u", _wtoi(val)*20);
val=temp;
}
}
AutoChar utf8(val, CP_UTF8);
for(int i=0;i<m_vc->comments;i++)
{
char *c=m_vc[m_curstream].user_comments[i];
if(!c) continue;
char *p=strchr(c,'=');
if (p && *p)
{
if(strlen(data) == (p-c) && !_strnicmp(c,data,p-c))
{
//found!
if (val && val[0])
{
int added_buf_len = strlen(utf8)+strlen(lookup)+2;
m_vc[m_curstream].user_comments[i]=(char *)realloc(m_vc[m_curstream].user_comments[i],added_buf_len);
StringCchPrintfA(m_vc[m_curstream].user_comments[i],added_buf_len,"%s=%s",lookup,(char *)utf8);
m_vc[m_curstream].comment_lengths[i]=strlen(m_vc[m_curstream].user_comments[i]);
}
else
{
free(m_vc[m_curstream].user_comments[i]);
m_vc[m_curstream].user_comments[i]=0;
m_vc[m_curstream].comment_lengths[i]=0;
}
return 1;
}
}
}
//not found, so create new field
if (val && val[0])
{
int k=m_vc[m_curstream].comments++;
m_vc[m_curstream].user_comments=(char **)realloc(m_vc[m_curstream].user_comments,sizeof(char*)*m_vc[m_curstream].comments);
m_vc[m_curstream].comment_lengths=(int *)realloc(m_vc[m_curstream].comment_lengths,sizeof(int)*m_vc[m_curstream].comments);
int added_buf_len = strlen(utf8)+strlen(lookup)+2;
m_vc[m_curstream].user_comments[k]=(char *)malloc(added_buf_len);
StringCchPrintfA(m_vc[m_curstream].user_comments[k],added_buf_len,"%s=%s",lookup,(char *)utf8);
m_vc[m_curstream].comment_lengths[k]=strlen(m_vc[m_curstream].user_comments[k]);
}
#endif
if (val && *val)
{
if(KeywordMatch("rating",data))
{
wchar_t temp[128] = {0};
StringCchPrintfW(temp, 128, L"%u", _wtoi(val)*20);
setMetadata->SetMetadata(lookup, temp);
}
else
{
setMetadata->SetMetadata(lookup, val);
}
}
else
{
setMetadata->RemoveMetadata(lookup);
if(KeywordMatch("comment",data))
{
// need to remove this one also, or else it's gonna look like delete doesn't work
// if the file was tagged using this alternate field
setMetadata->RemoveMetadata(L"DESCRIPTION");
}
else if(KeywordMatch("year",data))
{
// need to remove this one also, or else it's gonna look like delete doesn't work
// if the file was tagged using this alternate field
setMetadata->RemoveMetadata(L"YEAR");
}
else if(KeywordMatch("track",data))
{
// need to remove this one also, or else it's gonna look like delete doesn't work
// if the file was tagged using this alternate field
setMetadata->RemoveMetadata(L"TRACK");
}
else if(KeywordMatch("albumartist",data))
{
// need to remove these two, also, or else it's gonna look like delete doesn't work
// if the file was tagged using these alternate fields
setMetadata->RemoveMetadata(L"ALBUMARTIST");
setMetadata->RemoveMetadata(L"ENSEMBLE");
}
else if(KeywordMatch("publisher",data))
{
// need to remove this one also, or else it's gonna look like delete doesn't work
// if the file was tagged using this alternate field
setMetadata->RemoveMetadata(L"ORGANIZATION");
}
else if(KeywordMatch("category",data))
{
// need to remove these two also, or else it's gonna look like delete doesn't work
// if the file was tagged using these alternate fields
setMetadata->RemoveMetadata(L"CONTENTGROUP");
setMetadata->RemoveMetadata(L"GROUPING");
}
}
return 1;
}
__declspec( dllexport ) int winampWriteExtendedFileInfo()
{
if(!setMetadata) return 0;
bool ret = setMetadata->Save();
delete setMetadata;
setMetadata = 0;
// update last modified so we're not re-queried on our own updates
UpdateFileTimeChanged(m_lastfn);
return ret;
}
}