#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 #include "../nu/AutoWide.h" #include "../nu/AutoChar.h" #include #include template 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( factory->getInterface() ); } } template 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 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 (l16) { 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; ivf,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;juser_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; iError()) { 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;icomments;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; } }