#include "playlist.h" #include #include "main.h" #include "api__ml_plg.h" #include "../nu/MediaLibraryInterface.h" #include "impl_playlist.h" //#import "../gracenote/CDDBControlWinamp.dll" no_namespace, named_guids, raw_interfaces_only #include "../gracenote/cddbcontrolwinamp.tlh" #include "../winamp/ipc_pe.h" #include "resource.h" #include //extern Playlist currentPlaylist; ICddbPlaylist25Mgr *playlistMgr; ICddbMLDBManager *mldbMgr; Playlist currentPlaylist; Playlist seedPlaylist; class PlaylistEventHandler : public DPlaylist2Events { STDMETHODIMP STDMETHODCALLTYPE QueryInterface(REFIID riid, PVOID *ppvObject) { if (!ppvObject) return E_POINTER; else if (IsEqualIID(riid, __uuidof(DPlaylist2Events))) *ppvObject = (DPlaylist2Events *)this; else if (IsEqualIID(riid, IID_IDispatch)) *ppvObject = (IDispatch *)this; else if (IsEqualIID(riid, IID_IUnknown)) *ppvObject = this; else { *ppvObject = NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } ULONG STDMETHODCALLTYPE AddRef(void) { return 1; } ULONG STDMETHODCALLTYPE Release(void) { return 0; } HRESULT STDMETHODCALLTYPE Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR *pdispparams, VARIANT FAR *pvarResult, EXCEPINFO FAR * pexecinfo, unsigned int FAR *puArgErr) { switch (dispid) { case 1: break; case 2: break; case 3: break; case 4: break; case 5: break; } return DISP_E_MEMBERNOTFOUND; } HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgdispid) { *rgdispid = DISPID_UNKNOWN; return DISP_E_UNKNOWNNAME; } HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR * pctinfo) { return E_NOTIMPL; } }; static IConnectionPoint *GetConnectionPoint(IUnknown *punk, REFIID riid) { if (!punk) return 0; IConnectionPointContainer *pcpc; IConnectionPoint *pcp = 0; HRESULT hr = punk->QueryInterface(IID_IConnectionPointContainer, (void **) & pcpc); if (SUCCEEDED(hr)) { pcpc->FindConnectionPoint(riid, &pcp); pcpc->Release(); } return pcp; } static PlaylistEventHandler events; static DWORD m_dwCookie = 0; //static DWORD m_dwCookie_MldbMgr = 0; bool SetupPlaylistSDK() { if (!playlistMgr) { //playlistMgr = AGAVE_API_GRACENOTE->GetPlaylistManager(); //AGAVE_API_GRACENOTE->GetPlaylistManagerWithMLDBManager(&playlistMgr, &mldbMgr); AGAVE_API_GRACENOTE->GetPlaylistManager(&playlistMgr, &mldbMgr); IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events); if (icp) { icp->Advise(static_cast(&events), &m_dwCookie); icp->Release(); } } return !!playlistMgr; } void ShutdownPlaylistSDK() { if (playlistMgr) { if (mldbMgr) { mldbMgr->Detach(playlistMgr); // Detach the mldb manager from the playlist manager if it is not null } IConnectionPoint *icp = GetConnectionPoint(playlistMgr, DIID_DPlaylist2Events); if (icp) { icp->Unadvise(m_dwCookie); icp->Release(); } if (mldbMgr) mldbMgr->Release(); // Release the mldb manager if its not null playlistMgr->Shutdown(); playlistMgr->Release(); } mldbMgr=0; playlistMgr=0; } // Caller must cleanup the BSTR BSTR SetAndCreatePath(/*wchar_t *path_to_create,*/ const wchar_t *node) { wchar_t path_to_create[MAX_PATH] = {0}; BSTR bPath = 0; PathCombineW(path_to_create, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins"); CreateDirectoryW(path_to_create, 0); PathAppendW(path_to_create, node); CreateDirectoryW(path_to_create, 0); bPath = SysAllocString(path_to_create); return bPath; // modified path as return value } // IMPORTANT: Make sure to call this on the gracenote dedicated thread. int RestoreGracenoteMLDB(void) { long restoreFlags = PL_MLDB_RESTORE_BASE | PL_MLDB_RESTORE_INDEX; //wchar_t backupPath[MAX_PATH] = {0}; BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH); BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH); // Restore the db files. mldbMgr->RestoreDBFiles(restoreFlags, bDataPath, bBackupPath); SysFreeString(bDataPath); SysFreeString(bBackupPath); return NErr_Success; } // Backs up the gracenote MLDB so that it can be restored on corruption // IMPORTANT: Make sure to call this on the gracenote dedicated thread. int BackupGracenoteMLDB(void) { long backupFlags = PL_MLDB_BACKUP_BASE | PL_MLDB_BACKUP_INDEX; //wchar_t backupPath[MAX_PATH] = {0}; BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH); BSTR bBackupPath = SetAndCreatePath(GRACENOTE_DB_BACKUP_PATH); // Backup the db files. mldbMgr->BackupDBFiles(backupFlags, bDataPath, bBackupPath); SysFreeString(bDataPath); SysFreeString(bBackupPath); return NErr_Success; } /*BOOL DeleteGracenoteFile(char *filename) { BOOL result; char path[MAX_PATH] = {0}; //PathCombineA(path,mediaLibrary.GetIniDirectory(),"Plugins\\Gracenote"); PathCombineA(path,"C:\\Users\\bigg\\AppData\\Roaming\\Winamp\\","Plugins\\Gracenote"); PathAppendA(path, filename); result = DeleteFileA(path); return result; }*/ void CheckForResetError(HRESULT error) { if (error != S_OK) { MessageBoxW(0, WASABI_API_LNGSTRINGW(IDS_ERROR_RESET), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR); } } void CheckForShutdownError(HRESULT error) { if (error != S_OK) { MessageBoxW(plugin.hwndWinampParent, WASABI_API_LNGSTRINGW(IDS_CANNOT_SHUT_DOWN), (LPWSTR)plugin.description, MB_OK| MB_ICONERROR); } } // Deprecated: Currently not used, remove at some point /* INT_PTR CALLBACK ResettingProcedure(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: //ShowWindow(hwndDlg,SW_HIDE); ShowWindow(hwndDlg, SW_SHOW); return 0; break; case WM_QUIT: EndDialog(hwndDlg,0); return 0; break; } return 0; }*/ // IMPORTANT: Make sure to call this on the gracenote dedicated thread. int DeleteGracenoteMLDB(bool silent) { long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER | PL_MLDB_DELETE_BACKUPS; //long deleteFlags = PL_MLDB_DELETE_INDEX | PL_MLDB_DELETE_BASE | PL_MLDB_DELETE_OTHER; // | PL_MLDB_DELETE_BACKUPS; BSTR bDataPath = SetAndCreatePath(GRACENOTE_DB_BASE_PATH); HRESULT error = 0; if (playlistMgr) error = playlistMgr->Shutdown(); if (!silent) CheckForShutdownError(error); // Spawn the working window //HWND hwndResetWorking = WASABI_API_CREATEDIALOG(IDD_NAG, plugin.hwndWinampParent, ResettingProcedure); if (mldbMgr) error = mldbMgr->DeleteDBFiles(deleteFlags, bDataPath); //SendMessage(hwndResetWorking, WM_QUIT, 0, 0); if (!silent) CheckForResetError(error); SysFreeString(bDataPath); return NErr_Success; // NOT the HRESULT so that non zero values return as false } int InitializeMLDBManager(void) { // Other initializations for the MLDB manager can go here ///BackupGracenoteMLDB(); return NErr_Success; } static void ConfigureGeneratorPrefs(ICddbPLMoreLikeThisCfg *config) { // ToDo: (BigG) Consider using some of the different algorithms // GNPL_MORELIKETHIS_ALG_20 (Playlist SDK 2.0) // GNPL_MORELIKETHIS_ALG_25 (Playlist SDK 2.5) // GNPL_MORELIKETHIS_ALG_DSP_1 (Playlist SDK 2.6) // GNPL_MORELIKETHIS_ALG_DSP_25 (Playlist SDK 2.6) config->put_Algorithm(GNPL_MORELIKETHIS_ALG_DEFAULT); if(!multipleAlbums) config->put_MaxPerAlbum(1); if(!multipleArtists) config->put_MaxPerArtist(1); //config->put_TrackLimit(plLength); config->put_TrackLimit(0); // Dont put a limit on gracenote (return all tracks) } //void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, const wchar_t *seedfn=NULL, int useSeed=FALSE) void playPlaylist(Playlist &pl, bool enqueue=false, int startplaybackat=0/*-1 for don't start playback*/, int useSeed=FALSE) { extern winampMediaLibraryPlugin plugin; const size_t number_of_seeds = seedPlaylist.GetNumItems(); wchar_t seedFilename[MAX_PATH] = {0}; seedFilename[0] = 0; if(!enqueue) mediaLibrary.ClearPlaylist(); // Enqueue the seed tracks first if(useSeed) { for (size_t i = 0; i < number_of_seeds; i++) { seedPlaylist.GetItem(i, seedFilename, MAX_PATH); // Get the playlist filename enqueueFileWithMetaStructW s={seedFilename,NULL,PathFindExtensionW(seedFilename),-1}; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW); } } size_t listLength = pl.GetNumItems(); for(size_t i=0; i plItems ) // See if an extra item will put us over. return true; else return false; } break; case PL_MINUTES: // Check for minutes used { measurement = currentPlaylist.GetPlaylistLengthMilliseconds() + ( (useSeed) ? seedPlaylist.GetPlaylistLengthMilliseconds() : 0 ); if ( (measurement + (currentItemLength) ) > (plMinutes * 60 * 1000) ) return true; else return false; } break; case PL_MEGABYTES: // Check for megabytes used { measurement = currentPlaylist.GetPlaylistSizeBytes() + ( (useSeed) ? seedPlaylist.GetPlaylistSizeBytes() : 0 ); if ( (measurement + (uint64_t)currentItemSize) > ((uint64_t)plMegabytes * POWER_2_TO_20) ) return true; else return false; } break; } return true; } bool MatchesQuery(const wchar_t *filename, const wchar_t *user_query) { // Get an item that mathces both the filename and the query itemRecordW *result = AGAVE_API_MLDB->GetFileIf(filename, user_query); if (result) { AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it return true; } else { AGAVE_API_MLDB->FreeRecord(result); // Clean up the records list since we are done using it return false; } } // Callback for getting a tag for gracenote library items static wchar_t * TitleTagFuncGracenote(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag //tagItem * s = (tagItem *)p; //wchar_t *filename = (wchar_t *)p; ICddbPL2Result *gracenoteResult = (ICddbPL2Result *)p; BSTR tag_data; if (!_wcsicmp(tag, L"artist")) gracenoteResult->GetArtist(&tag_data)/*wsprintf(buf,L"%s",L"artist")*/; else if (!_wcsicmp(tag, L"album")) gracenoteResult->GetAlbum(&tag_data); else if (!_wcsicmp(tag, L"title")) gracenoteResult->GetTitle(&tag_data); else if (!_wcsicmp(tag, L"filename")) gracenoteResult->GetFilename(&tag_data); else return 0; //else if (!_wcsicmp(tag, L"genre")) -; //else if (!_wcsicmp(tag, L"year")) -; //else if (!_wcsicmp(tag, L"tracknumber")) -; //else if (!_wcsicmp(tag, L"discnumber")) -; //else if (!_wcsicmp(tag, L"bitrate")) -; //else if (!_wcsicmp(tag, L"albumartist")) -; //else if (!_wcsicmp(tag, L"composer")) -; //else if (!_wcsicmp(tag, L"publisher")) -; return tag_data; } // Callback for getting a tag for media library items static wchar_t * TitleTagFuncML(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag itemRecordW *mlResult = (itemRecordW *)p; if (!mlResult) return 0; // Return 0 because we dont have this ml object wchar_t buf[128] = {0}; wchar_t *tag_data = 0; if (!_wcsicmp(tag, L"artist")) tag_data = mlResult->artist; else if (!_wcsicmp(tag, L"album")) tag_data = mlResult->album; else if (!_wcsicmp(tag, L"title")) tag_data = mlResult->title; else if (!_wcsicmp(tag, L"filename")) tag_data = mlResult->filename; else if (!_wcsicmp(tag, L"genre")) tag_data = mlResult->genre; else if (!_wcsicmp(tag, L"year")) if (mlResult->year > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->year); tag_data = buf; } else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track")) if (mlResult->track > 0) { StringCchPrintfW(buf, 128, L"%04d", mlResult->track); tag_data = buf; } else if (!_wcsicmp(tag, L"discnumber")) if (mlResult->disc > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->disc); tag_data = buf; } else if (!_wcsicmp(tag, L"bitrate")) if (mlResult->bitrate > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bitrate); tag_data = buf; } else if (!_wcsicmp(tag, L"albumartist")) tag_data = mlResult->albumartist; else if (!_wcsicmp(tag, L"composer")) tag_data = mlResult->composer; else if (!_wcsicmp(tag, L"publisher")) tag_data = mlResult->publisher; else if (!_wcsicmp(tag, L"bpm")) if (mlResult->bpm > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->bpm); tag_data = buf; } else if (!_wcsicmp(tag, L"comment")) tag_data = mlResult->comment; else if (!_wcsicmp(tag, L"discs")) if (mlResult->discs > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->discs); tag_data = buf; } else if (!_wcsicmp(tag, L"filesize")) if (mlResult->filesize > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->filesize); tag_data = buf; } //else if (!_wcsicmp(tag, L"filetime")) tag_data = mlResult->filetime; else if (!_wcsicmp(tag, L"length")) if (mlResult->length > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->length); tag_data = buf; } else if (!_wcsicmp(tag, L"playcount")) if (mlResult->playcount > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->playcount); tag_data = buf; } else if (!_wcsicmp(tag, L"rating")) if (mlResult->rating > 0) { StringCchPrintfW(buf, 128, L"%d", mlResult->rating); tag_data = buf; } else return 0; return _wcsdup(tag_data); } // Callback to free a tag in gracenote library static void TitleTagFreeFuncGracenote(wchar_t *tag_data, void *p) { if(tag_data) SysFreeString(tag_data); } // Callback to free a tag in media library static void TitleTagFreeFuncML(wchar_t *tag_data, void *p) { if(tag_data) free(tag_data); } // Retreive the title formatting for gracenote library void GetTitleFormattingGracenote(const wchar_t *filename, ICddbPL2Result * gracenoteResult, wchar_t * buf, int len) { waFormatTitleExtended fmt={ filename, 1, NULL, (void *)gracenoteResult, buf, len, TitleTagFuncGracenote, TitleTagFreeFuncGracenote }; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED); } // Retreive the title formatting for media library void GetTitleFormattingML(const wchar_t *filename, itemRecordW *mlResult, wchar_t * buf, int len) { waFormatTitleExtended fmt={ filename, 1, NULL, (void *)mlResult, buf, len, TitleTagFuncML, TitleTagFreeFuncML }; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED); } static int MoreLikeTheseSongsFunc(HANDLE handle, void *user_data, intptr_t id) { Playlist *pl = (Playlist *)user_data; const int number_of_files = (int)pl->GetNumItems(); isGenerating = true; //Sleep(501); // Wait for the update timer to finish its cycles, This is EVIL, keep it removed... SetMarqueeProgress(true); // Turn the marquee off because we are actually generating the tracks SetDlgItemText(hwndDlgCurrent, IDC_STATIC_PROGRESS_STATE, WASABI_API_LNGSTRINGW(IDS_GENERATING)); //EnableWindow (GetDlgItem(hwndDlgCurrent, IDC_BUTTON_REGENERATE), FALSE ); SetButtonsEnabledState(false); // Disable the buttons once we start generating here if (SetupPlaylistSDK()) { HRESULT hr; ICddbPLMoreLikeThisCfgPtr cfg; cfg.CreateInstance(CLSID_CddbPLMoreLikeThisCfg); ConfigureGeneratorPrefs(cfg); ICddbPL2ResultListPtr results; wchar_t plFilename[MAX_PATH] = {0}; if (number_of_files == 1) // Call the regular morelikethis function if there is only the standard seed track { pl->GetItem(0, plFilename, MAX_PATH); BSTR i_dunno = SysAllocString(plFilename); hr=playlistMgr->MoreLikeThisSong(i_dunno, cfg, &results); SysFreeString(i_dunno); } else if (number_of_files > 1) // We have more than 1 seed track { // Create the variant of an array of filenames for MoreLikeTheseSongs VARIANT fileList; VariantInit((VARIANTARG *)&fileList); SAFEARRAY *psa = SafeArrayCreateVector (VT_BSTR, 0, number_of_files); BSTR *data; SafeArrayAccessData(psa, (void **)&data); for (size_t i=0;i!=number_of_files;i++) { pl->GetItem(i, plFilename, MAX_PATH); data[i] = SysAllocString(plFilename); } SafeArrayUnaccessData(psa); V_VT(&fileList) = VT_ARRAY|VT_BSTR; V_ARRAY(&fileList) = psa; hr=playlistMgr->MoreLikeTheseSongs(fileList, cfg, &results); } else // We dont have any seed tracks (this should not happen because we shouldnt get this far. { return 1; // Failure } long count=-1; if (results && SUCCEEDED(hr=results->get_Count(&count)) && count) { currentPlaylist.Clear(); for (long i=0;iGetResult(i+1, &result))) { BSTR filename = 0; BSTR title = 0; unsigned int length = -1; unsigned int size = 0; result->GetFilename(&filename); result->GetTitle(&title); result->GetTracklength(&length); // Gracenote is returning seconds here result->GetFilesize(&size); // Gracenote took the size as kilobytes but it is returning it to us as bytes length *= 1000; // Multiply length by 1000 to turn it into milliseconds from seconds // Get the winamp user formatted title. wchar_t winamp_title[512] = {0}; GetTitleFormattingGracenote(filename, result, winamp_title, 512); // Only check for the query if the user wants to apply one if ( useMLQuery == TRUE && MatchesQuery(filename, mlQuery ) == false ) { SysFreeString(filename); SysFreeString(title); result->Release(); continue; } // Lets check for the playlist limit to see if we should add this track if ( !PlaylistIsFull(¤tPlaylist, length, size) ) currentPlaylist.AppendWithInfo(filename, winamp_title, length, size); // DONT break here if we are full, keep going as we may find something that will fit into the playlist eventually SysFreeString(filename); SysFreeString(title); result->Release(); } } /*char cfg[1024 + 32] = {0}; char *dir = (char*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIDIRECTORY); PathCombineA(cfg, dir, "Plugins\\gen_ml.ini"); int en = GetPrivateProfileIntA("gen_ml_config","enqueuedef",0,cfg);*/ PopulateResults(¤tPlaylist); } else /*if (count == 0)*/ { PopulateResults(0); // Call populate with an empty playlist CantPopulateResults(); // Display warning about not being able to generate any tracks } } isGenerating = false; return 0; } void MoreLikeTheseSongs(Playlist *pl) { // Capture the stats, we dont care if its successful or not, we only care about the try if (AGAVE_API_STATS) AGAVE_API_STATS->IncrementStat(api_stats::PLG_COUNT); // Call the the mor like this fuction on the gracenote reserved thread WASABI_API_THREADPOOL->RunFunction(plg_thread, MoreLikeTheseSongsFunc, pl, 0, api_threadpool::FLAG_REQUIRE_COM_STA); }