#define PLUGIN_VERSION L"2.79" #include #include #include #include #include #include "..\..\General\gen_ml/ml.h" #include "../winamp/wa_ipc.h" #include "../nu/AutoWide.h" #include "../nu/ns_wc.h" #include "../nu/AutoChar.h" #include "..\..\General\gen_ml/itemlist.h" #include "../playlist/ifc_playlistloadercallback.h" #include "LinkedQueue.h" #include "resource.h" #include #include #include "api__ml_transcode.h" #include #define WM_TRANSCODE_START WM_APP+1 #define WM_TRANSCODE_ADD WM_APP+10 #define WM_TRANSCODE_UPDATEUI WM_APP+11 #define WM_TRANSCODE_ABORT WM_APP+12 static int init(); static void quit(); static HWND GetDialogBoxParent(); static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3); extern "C" winampMediaLibraryPlugin plugin = { MLHDR_VER, "nullsoft(ml_transcode.dll)", init, quit, PluginMessageProc, 0, 0, 0, }; typedef std::vector PtrListWCharPtr; LinkedQueue transcodeQueue; int transcodeConfig(HWND parent); // returns fourcc void transcode(const itemRecordListW *ice, HWND parent); void transcode(PtrListWCharPtr &filenames, HWND parent); void addTrackToTranscodeQueue(itemRecordW *track, unsigned int format, const wchar_t* dest, const wchar_t* folder); void addTrackToTranscodeQueue(const wchar_t *track, unsigned int format, const wchar_t* dest, const wchar_t* folder); void filenameToItemRecord(const wchar_t *file, itemRecordW *ice); void copyTags(const itemRecordW *in, const wchar_t *out); extern void RecursiveCreateDirectory(wchar_t *buf1); extern wchar_t *FixReplacementVars(wchar_t *str, size_t str_size, itemRecordW *song); void FixFileLength(wchar_t *str); HWND transcoderWnd = NULL; wchar_t inifile[MAX_PATH] = {0}; char inifileA[MAX_PATH] = {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_metadata *AGAVE_API_METADATA=0; api_application *WASABI_API_APP=0; api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0; api_albumart *AGAVE_API_ALBUMART = 0; api_stats *AGAVE_API_STATS = 0; template void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) { if (plugin.service) { waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); if (factory) api_t = reinterpret_cast( factory->getInterface() ); } } template void ServiceRelease(api_T *api_t, GUID factoryGUID_t) { if (plugin.service && api_t) { waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); if (factory) factory->releaseInterface(api_t); } api_t = NULL; } int init() { ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); ServiceBuild(WASABI_API_LNG, languageApiGUID); ServiceBuild(AGAVE_API_METADATA, api_metadataGUID); ServiceBuild(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID); ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID); ServiceBuild(AGAVE_API_STATS, AnonymousStatsGUID); const wchar_t *iniDirectory = WASABI_API_APP->path_getUserSettingsPath(); PathCombine(inifile, iniDirectory, L"Plugins\\ml\\ml_transcode.ini"); lstrcpynA(inifileA, AutoChar(inifile), MAX_PATH); // need to have this initialised before we try to do anything with localisation features WASABI_API_START_LANG(plugin.hDllInstance,MlTranscodeLangGUID); static wchar_t szDescription[256]; StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_FORMAT_CONVERTER ), PLUGIN_VERSION ); plugin.description = (char*)szDescription; return ML_INIT_SUCCESS; } void quit() { if(IsWindow(transcoderWnd)) SendMessage(transcoderWnd,WM_TRANSCODE_ABORT,0,0); ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); ServiceRelease(WASABI_API_LNG, languageApiGUID); ServiceRelease(AGAVE_API_METADATA, api_metadataGUID); ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID); ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID); ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID); } class TranscodePlaylistLoader : public ifc_playlistloadercallback { public: TranscodePlaylistLoader(PtrListWCharPtr*_fileList); void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info); protected: PtrListWCharPtr*fileList; RECVS_DISPATCH; }; TranscodePlaylistLoader::TranscodePlaylistLoader(PtrListWCharPtr*_fileList) { fileList = _fileList; } void TranscodePlaylistLoader::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info) { fileList->push_back(_wcsdup(filename)); } #define CBCLASS TranscodePlaylistLoader START_DISPATCH; VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile) END_DISPATCH; #undef CBCLASS INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3) { if (message_type == ML_MSG_ONSENDTOBUILD) { if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_FILENAMESW || (AGAVE_API_PLAYLISTMANAGER && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS))) { mlAddToSendToStructW s; s.context=param2; s.desc=WASABI_API_LNGSTRINGW(IDS_FORMAT_CONVERTER); s.user32=(intptr_t)PluginMessageProc; SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&s,ML_IPC_ADDTOSENDTOW); } } else if (message_type == ML_MSG_ONSENDTOSELECT) { if (param2 && param3 == (INT_PTR)PluginMessageProc) { if(param1 == ML_TYPE_FILENAMES) { PtrListWCharPtr fileList; TranscodePlaylistLoader loader(&fileList); const char * filenames = (const char *)param2; while(filenames && *filenames) { // try to load as playlist first if (AGAVE_API_PLAYLISTMANAGER->Load(AutoWide(filenames), &loader) != PLAYLISTMANAGER_SUCCESS) { // not a playlist.. just add it directly fileList.push_back(AutoWideDup(filenames)); } filenames+=strlen(filenames)+1; } transcode(fileList,GetDialogBoxParent()); //fileList.freeAll(); for (auto file : fileList) { free((void*)file); } fileList.clear(); return 1; } else if(param1 == ML_TYPE_FILENAMESW) { PtrListWCharPtr fileList; TranscodePlaylistLoader loader(&fileList); const wchar_t * filenames = (const wchar_t *)param2; while(filenames && *filenames) { // try to load as playlist first if (AGAVE_API_PLAYLISTMANAGER->Load(filenames, &loader) != PLAYLISTMANAGER_SUCCESS) { // not a playlist.. just add it directly fileList.push_back(filenames); } filenames+=wcslen(filenames)+1; } transcode(fileList,GetDialogBoxParent()); return 1; } else if(param1 == ML_TYPE_ITEMRECORDLIST) { const itemRecordList *ico=(const itemRecordList*)param2; itemRecordListW list = {0,}; convertRecordList(&list, ico); transcode(&list, GetDialogBoxParent()); freeRecordList(&list); return 1; } else if(param1 == ML_TYPE_ITEMRECORDLISTW) { const itemRecordListW *list =(const itemRecordListW*)param2; transcode(list,GetDialogBoxParent()); return 1; } else if (param1 == ML_TYPE_PLAYLIST) { mlPlaylist *playlist = (mlPlaylist *)param2; PtrListWCharPtr fileList; fileList.reserve(playlist->numItems); TranscodePlaylistLoader loader(&fileList); AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader); transcode(fileList,GetDialogBoxParent()); //fileList.freeAll(); for (auto file : fileList) { free((void*)file); } fileList.clear(); return 1; } else if (param1 == ML_TYPE_PLAYLISTS) { mlPlaylist **playlists = (mlPlaylist **)param2; PtrListWCharPtr fileList; while (playlists && *playlists) { mlPlaylist *playlist = *playlists; fileList.reserve(fileList.size() + playlist->numItems); TranscodePlaylistLoader loader(&fileList); AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader); playlists++; } transcode(fileList,GetDialogBoxParent()); //fileList.freeAll(); for (auto file : fileList) { free((void*)file); } fileList.clear(); return 1; } } } else if (message_type == ML_MSG_CONFIG) { transcodeConfig((HWND)param1); return 1; } return 0; } class TranscodeItem { public: itemRecordW ice; unsigned int fourcc; wchar_t *outfile; TranscodeItem( unsigned int fourcc, const wchar_t *folder, const wchar_t *outfile, const itemRecordW *i ) : fourcc( fourcc ) { ZeroMemory( &ice, sizeof( ice ) ); copyRecord( &ice, const_cast( i ) ); // TODO: remove for 5.53. this just works around some 5.52 build weirdness makefn( folder, outfile ); } TranscodeItem( unsigned int fourcc, const wchar_t *folder, const wchar_t *outfile, const wchar_t *i ) : fourcc( fourcc ) { ZeroMemory( &ice, sizeof( ice ) ); filenameToItemRecord( i, &ice ); makefn( folder, outfile ); } void makefn( const wchar_t *folder, const wchar_t *outfile ) { if ( GetPrivateProfileInt( L"transcoder", L"usefilename", 0, inifile ) ) { size_t len = wcslen( ice.filename ) + 10; this->outfile = (wchar_t *)calloc( len, sizeof( this->outfile[ 0 ] ) ); StringCchCopyW( this->outfile, len, ice.filename ); const wchar_t *extn = wcsrchr( outfile, L'.' ); wchar_t *exto = wcsrchr( this->outfile, L'.' ); if ( extn && exto && wcslen( extn ) < 10 ) StringCchCopy( exto, len, extn ); } else { wchar_t filename[ 2048 ] = { 0 }; StringCchCopy( filename, 2048, outfile ); FixReplacementVars( filename, 2048, &ice ); FixFileLength( filename ); PathCombine( filename, folder, filename ); this->outfile = _wcsdup( filename ); } } ~TranscodeItem() { free( outfile ); freeRecord( &ice ); } }; void getViewport( RECT *r, HWND wnd, int full, RECT *sr ) { POINT *p = NULL; if ( p || sr || wnd ) { HMONITOR hm = NULL; if ( sr ) hm = MonitorFromRect( sr, MONITOR_DEFAULTTONEAREST ); else if ( wnd ) hm = MonitorFromWindow( wnd, MONITOR_DEFAULTTONEAREST ); else if ( p ) hm = MonitorFromPoint( *p, MONITOR_DEFAULTTONEAREST ); if ( hm ) { MONITORINFOEX mi; memset( &mi, 0, sizeof( mi ) ); mi.cbSize = sizeof( mi ); if ( GetMonitorInfoW( hm, &mi ) ) { if ( !full ) *r = mi.rcWork; else *r = mi.rcMonitor; return; } } } if ( full ) { // this might be borked =) r->top = r->left = 0; r->right = GetSystemMetrics( SM_CXSCREEN ); r->bottom = GetSystemMetrics( SM_CYSCREEN ); } else { SystemParametersInfoW( SPI_GETWORKAREA, 0, r, 0 ); } } BOOL windowOffScreen( HWND hwnd, POINT pt ) { RECT r = { 0 }, wnd = { 0 }, sr = { 0 }; GetWindowRect( hwnd, &wnd ); sr.left = pt.x; sr.top = pt.y; sr.right = sr.left + ( wnd.right - wnd.left ); sr.bottom = sr.top + ( wnd.bottom - wnd.top ); getViewport( &r, hwnd, 0, &sr ); return !PtInRect( &r, pt ); } bool transcoding = false; bool transcoderIdle = false; int totalItems = 0; int itemsDone = 0; int itemsFailed = 0; static BOOL CALLBACK transcode_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { static convertFileStructW * cfs; switch(uMsg) { case WM_INITDIALOG: { transcoderWnd = hwndDlg; cfs = (convertFileStructW *)calloc(1, sizeof(convertFileStructW)); SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETRANGE32,0,100); SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETRANGE32,0,totalItems*100); SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,0,0); SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,0,0); PostMessage(hwndDlg,WM_TRANSCODE_START,0,0); // show edit info window and restore last position as applicable POINT pt = {GetPrivateProfileInt(L"transcoder", L"convert_x", -1, inifile), GetPrivateProfileInt(L"transcoder", L"convert_y", -1, inifile)}; if (!windowOffScreen(hwndDlg, pt)) SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING); else ShowWindow(hwndDlg, SW_SHOWNA); } break; case WM_TRANSCODE_START: for(;;) { TranscodeItem * t = (TranscodeItem *)transcodeQueue.Peek(); if(!t) { SetDlgItemText(hwndDlg,IDC_ABORT,WASABI_API_LNGSTRINGW(IDS_DONE)); SendMessage(transcoderWnd,WM_TRANSCODE_UPDATEUI,0,0); transcoderIdle=true; return 0; } transcoderIdle=false; RecursiveCreateDirectory(t->outfile); ZeroMemory(cfs,sizeof(*cfs)); cfs->callbackhwnd = hwndDlg; cfs->sourcefile = t->ice.filename; cfs->destfile = t->outfile; cfs->destformat[0] = t->fourcc; //cfs->destformat[1] = 44100; //cfs->destformat[2] = 16; //cfs->destformat[3] = 2; cfs->destformat[6] = mmioFOURCC('I','N','I',' '); cfs->destformat[7] = (intptr_t)inifileA; cfs->error = L""; SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,0,0); wchar_t buf[1024] = {0}; StringCchPrintf(buf, 1024, L"%s - %s", t->ice.artist?t->ice.artist:L"", t->ice.title?t->ice.title:L""); SetDlgItemText(hwndDlg,IDC_CURRENTTRACK,buf); if(SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW)) break; else { if (cfs->error && *cfs->error) { MessageBox(hwndDlg, cfs->error, WASABI_API_LNGSTRINGW(IDS_TRANSCODING_FAILED), MB_ICONWARNING | MB_OK); } delete (TranscodeItem*)transcodeQueue.Poll(); itemsDone++; itemsFailed++; } } case WM_TRANSCODE_ADD: // update totals, stuff added to queue SetWindowPos(hwndDlg,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); if(transcoderIdle) { SetDlgItemText(hwndDlg,IDC_ABORT,WASABI_API_LNGSTRINGW(IDS_ABORT)); SendMessage(hwndDlg,WM_TRANSCODE_START,0,0); return 0; } // else update UI case WM_TRANSCODE_UPDATEUI: { SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETRANGE32,0,totalItems*100); SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,itemsDone*100,0); wchar_t buf[100] = {0}; StringCchPrintfW(buf, 100, WASABI_API_LNGSTRINGW(IDS_TRACKS_DONE_REMAINING_FAILED), itemsDone-itemsFailed, totalItems-itemsDone, itemsFailed); SetDlgItemText(hwndDlg,IDC_TOTALCAPTION,buf); } break; case WM_WA_IPC: switch(lParam) { case IPC_CB_CONVERT_STATUS: if(wParam >= 0 && wParam <= 100) { SendDlgItemMessage(hwndDlg,IDC_TOTALPROGRESS,PBM_SETPOS,(int)wParam + itemsDone*100,0); SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,(int)wParam,0); } break; case IPC_CB_CONVERT_DONE: { SendDlgItemMessage(hwndDlg,IDC_TRACKPROGRESS,PBM_SETPOS,100,0); TranscodeItem * t = (TranscodeItem*)transcodeQueue.Poll(); itemsDone++; cfs->callbackhwnd=NULL; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW_END); copyTags(&t->ice,t->outfile); if (AGAVE_API_STATS) { AGAVE_API_STATS->IncrementStat(api_stats::TRANSCODE_COUNT); AGAVE_API_STATS->SetStat(api_stats::TRANSCODE_FORMAT, t->fourcc); } delete t; PostMessage(hwndDlg,WM_TRANSCODE_START,0,0); } break; } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_ABORT: transcode_dlgproc(hwndDlg,WM_TRANSCODE_ABORT,0,0); break; } break; case WM_CLOSE: case WM_TRANSCODE_ABORT: { transcoding = false; cfs->callbackhwnd = NULL; if(!transcoderIdle) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)cfs,IPC_CONVERTFILEW_END); TranscodeItem * t; while((t = (TranscodeItem*)transcodeQueue.Poll()) != NULL) { // prompt to delete incomplete conversions if(PathFileExists(t->outfile)) { wchar_t title[64] = {0}, prompt[512] = {0}, file[MAX_PATH] = {0}; lstrcpyn(file, t->outfile, MAX_PATH); PathStripPath(file); StringCchPrintf(prompt, 512, WASABI_API_LNGSTRINGW(IDS_REMOVE_PARTIAL_FILE), file); if(MessageBox(hwndDlg, prompt, WASABI_API_LNGSTRINGW_BUF(IDS_TRANSCODING_ABORTED, title, 64), MB_YESNO) == IDYES) { DeleteFile(t->outfile); } } delete t; } RECT rect = {0}; GetWindowRect(hwndDlg, &rect); wchar_t buf[16] = {0}; StringCchPrintf(buf, 16, L"%d", rect.left); WritePrivateProfileString(L"transcoder", L"convert_x", buf, inifile); StringCchPrintf(buf, 16, L"%d", rect.top); WritePrivateProfileString(L"transcoder", L"convert_y", buf, inifile); EndDialog(hwndDlg,0); itemsDone = 0; totalItems = 0; itemsFailed = 0; transcoderWnd = NULL; transcoding = false; transcoderIdle = false; free(cfs); } break; } return 0; } void startTranscoding() { if(transcoding) { totalItems++; SendMessage(transcoderWnd,WM_TRANSCODE_ADD,0,0); } else { transcoding = true; totalItems=1; WASABI_API_CREATEDIALOGW(IDD_TRANSCODE, NULL, transcode_dlgproc); } } void addTrackToTranscodeQueue(const wchar_t * track, unsigned int format, const wchar_t* filepart, const wchar_t* folder) { transcodeQueue.Offer(new TranscodeItem(format, folder, filepart, track)); startTranscoding(); } void addTrackToTranscodeQueue(itemRecordW * track, unsigned int format, const wchar_t* filepart, const wchar_t* folder) { transcodeQueue.Offer(new TranscodeItem(format, folder, filepart, track)); startTranscoding(); } static void fourccToString(unsigned int f, wchar_t * str, int str_len) { char s[4] = {(char)(f&0xFF),(char)((f>>8)&0xFF),(char)((f>>16)&0xFF),0}; StringCchCopy(str, str_len, AutoWide(s)); CharLower(str); } wchar_t* GetDefaultSaveToFolder(wchar_t* path_to_store) { if(FAILED(SHGetFolderPath(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, path_to_store))) { if(FAILED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path_to_store))) { // and if that all fails then do a reasonable default lstrcpyn(path_to_store, L"C:\\My Music", MAX_PATH); } // if there's no valid My Music folder (typically win2k) then default to %my_documents%\my music else { PathCombine(path_to_store, path_to_store, L"My Music"); } } return path_to_store; } DWORD GetPrivateProfileStringUTF8(LPCSTR lpAppName, LPCSTR lpKeyName, LPCSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize, LPCSTR lpFileName) { char utf8_text[2048] = {0}; GetPrivateProfileStringA(lpAppName,lpKeyName,lpDefault,utf8_text, 2048, lpFileName); return MultiByteToWideCharSZ(CP_UTF8, 0, utf8_text, -1, lpReturnedString, nSize); } BOOL WritePrivateProfileStringUTF8(LPCSTR lpAppName, LPCSTR lpKeyName, LPCTSTR lpString, LPCSTR lpFileName) { return WritePrivateProfileStringA(lpAppName, lpKeyName, AutoChar(lpString, CP_UTF8), lpFileName); } class EncodableFormat { public: unsigned int fourcc; wchar_t *desc; EncodableFormat(unsigned int fourcc,wchar_t *desc) : fourcc(fourcc) {this->desc = _wcsdup(desc);} ~EncodableFormat() {free(desc);} }; static void enumProc(intptr_t user_data, const char *desc, int fourcc) { ((C_ItemList*)user_data)->Add(new EncodableFormat((unsigned int)fourcc,AutoWide(desc))); } static void BuildEncodableFormatsList(C_ItemList * list, HWND winampWindow,wchar_t * inifile) { converterEnumFmtStruct e = {enumProc,(intptr_t)list}; SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS); } unsigned int transcodeGatherSettings(wchar_t *format, wchar_t *folder, int format_len, HWND parent) { if(GetPrivateProfileInt(L"transcoder",L"showconf",1,inifile)) if(!transcodeConfig(parent)) return 0; wchar_t tmp[MAX_PATH] = {0}; GetPrivateProfileStringUTF8("transcoder", "fileformat"," - \\## - ",format,1024,inifileA); GetPrivateProfileStringUTF8("transcoder", "fileroot", AutoChar(GetDefaultSaveToFolder(tmp), CP_UTF8), folder, MAX_PATH, inifileA); unsigned int fourcc = GetPrivateProfileInt(L"transcoder",L"format",mmioFOURCC('A','A','C','f'),inifile); char extA[8]="."; convertConfigItem c = {fourcc,"extension",&extA[1],7,inifileA}; if(!SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM)) { // if there was an error, see if it's from an invalid fourcc and try to fallback C_ItemList * formats = new C_ItemList; BuildEncodableFormatsList(formats,plugin.hwndWinampParent,inifile); bool doFail = false; for(int i=0; i < formats->GetSize(); i++) { EncodableFormat * f = (EncodableFormat *)formats->Get(i); // if it exists then abort and fail as prior behaviour if(f->fourcc == fourcc) { doFail = true; break; } } if(!doFail) { fourcc = mmioFOURCC('A','A','C','f'); c.format = fourcc; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM); } } if(extA[1]) StringCchCat(format, format_len, AutoWide(extA)); else { wchar_t ext[8]=L"."; fourccToString(fourcc,&ext[1], 8); StringCchCat(format, format_len, ext); } return fourcc; } void transcode( PtrListWCharPtr &filenames, HWND parent ) { wchar_t format[ 2048 ] = { 0 }, folder[ MAX_PATH ] = { 0 }; unsigned int fourcc = transcodeGatherSettings( format, folder, 2048, parent ); if ( !fourcc ) return; for ( const wchar_t *l_filename : filenames ) addTrackToTranscodeQueue( l_filename, fourcc, format, folder ); } void transcode(const itemRecordListW *ice, HWND parent) { wchar_t format[2048] = {0}, folder[MAX_PATH] = {0}; unsigned int fourcc = transcodeGatherSettings(format, folder, 2048, parent); if(!fourcc) return; for(int i=0; i < ice->Size; i++) { addTrackToTranscodeQueue(&ice->Items[i],fourcc,format,folder); } } static void FreeEncodableFormatsList(C_ItemList * list) { int l = list->GetSize(); while(--l >= 0) { delete ((EncodableFormat*)list->Get(l)); list->Del(l); } } static void doConfigResizeChild(HWND parent, HWND child) { if (child) { RECT r; GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r); ScreenToClient(parent, (LPPOINT)&r); SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); ShowWindow(child, SW_SHOW); } } BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam) { wchar_t cl[32] = {0}; GetClassNameW(hwnd, cl, ARRAYSIZE(cl)); if (!lstrcmpiW(cl, WC_TREEVIEW)) { PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd)); return FALSE; } return TRUE; } int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { if(uMsg == BFFM_INITIALIZED) { wchar_t buf[4096]=L""; GetDlgItemText((HWND)lpData,IDC_ROOTDIR,buf,sizeof(buf)/sizeof(wchar_t)); SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)buf); SetWindowText(hwnd,WASABI_API_LNGSTRINGW(IDS_SELECT_WHERE_TO_SAVE)); // this is not nice but it fixes the selection not working correctly on all OSes EnumChildWindows(hwnd, browseEnumProc, 0); } return 0; } static BOOL CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { static C_ItemList * formats; static convertConfigStruct * ccs; switch (uMsg) { case WM_INITDIALOG: { bool usefn = !!GetPrivateProfileInt(L"transcoder", L"usefilename",0,inifile); if(GetPrivateProfileInt(L"transcoder", L"showconf", 1, inifile)) CheckDlgButton(hwndDlg,IDC_SHOWEVERY,BST_CHECKED); if(usefn) CheckDlgButton(hwndDlg,IDC_USE_FILENAME,BST_CHECKED); EnableWindow(GetDlgItem(hwndDlg, IDC_ROOTDIR),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_NAMING),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_FORMATHELP),!usefn); wchar_t buf[4096]=L"", tmp[MAX_PATH]=L""; GetPrivateProfileStringUTF8("transcoder", "fileformat","<Artist> - <Album>\\## - <Title>",buf,4096,inifileA); SetDlgItemText(hwndDlg,IDC_NAMING,buf); GetPrivateProfileStringUTF8("transcoder", "fileroot", AutoChar(GetDefaultSaveToFolder(tmp), CP_UTF8), buf, 4096, inifileA); SetDlgItemText(hwndDlg,IDC_ROOTDIR,buf); formats = new C_ItemList; BuildEncodableFormatsList(formats,plugin.hwndWinampParent,inifile); ccs = (convertConfigStruct *)calloc(sizeof(convertConfigStruct),1); ccs->extra_data[6] = mmioFOURCC('I','N','I',' '); ccs->extra_data[7] = (int)inifileA; ccs->hwndParent = hwndDlg; ccs->format = GetPrivateProfileInt(L"transcoder",L"format",mmioFOURCC('A','A','C','f'),inifile); for(int i=0; i < formats->GetSize(); i++) { EncodableFormat * f = (EncodableFormat *)formats->Get(i); int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc); SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f); if(f->fourcc == ccs->format) SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0); } // if there is no selection then force things to the correct default if(SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, (WPARAM)0, 0) == CB_ERR) { for(int i=0; i < SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCOUNT, 0, 0); i++) { EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, (WPARAM)i, 0); if(f->fourcc == mmioFOURCC('A','A','C','f')) { ccs->format = f->fourcc; SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)i, 0); break; } } } HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG); doConfigResizeChild(hwndDlg, h); // show config window and restore last position as applicable POINT pt = {GetPrivateProfileInt(L"transcoder", L"showconf_x", -1, inifile), GetPrivateProfileInt(L"transcoder", L"showconf_y", -1, inifile)}; if (!windowOffScreen(hwndDlg, pt)) SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING); } break; case WM_DESTROY: { SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG_END); free(ccs); ccs=0; FreeEncodableFormatsList(formats); delete formats; formats=0; } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_USE_FILENAME: { bool usefn = IsDlgButtonChecked(hwndDlg,IDC_USE_FILENAME)!=0; EnableWindow(GetDlgItem(hwndDlg, IDC_ROOTDIR),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_NAMING),!usefn); EnableWindow(GetDlgItem(hwndDlg, IDC_FORMATHELP),!usefn); } break; case IDC_ENCFORMAT: if (HIWORD(wParam) != CBN_SELCHANGE) return 0; { int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0); if (sel != CB_ERR) { SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG_END); EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0); ccs->format = f->fourcc; HWND h = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)ccs, IPC_CONVERT_CONFIG); doConfigResizeChild(hwndDlg, h); } } break; case IDC_FORMATHELP: { wchar_t titleStr[64] = {0}; MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_FILENAME_FORMAT_HELP), WASABI_API_LNGSTRINGW_BUF(IDS_FILENAME_FORMAT_HELP_TITLE,titleStr,64), MB_OK); break; } case IDC_BROWSE: { BROWSEINFO bi = {0}; LPMALLOC lpm = 0; wchar_t bffFileName[MAX_PATH] = {0}; bi.hwndOwner = hwndDlg; bi.pszDisplayName = bffFileName; bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_CHOOSE_FOLDER); bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_NEWDIALOGSTYLE; bi.lpfn = BrowseCallbackProc; bi.lParam = (LPARAM)hwndDlg; LPITEMIDLIST iil = SHBrowseForFolder(&bi); if(iil) { SHGetPathFromIDList(iil,bffFileName); SHGetMalloc(&lpm); lpm->Free(iil); SetDlgItemText(hwndDlg, IDC_ROOTDIR, bffFileName); } } break; case IDOK: case IDCANCEL: { DWORD fourcc=0; if (LOWORD(wParam) == IDOK) { wchar_t buf[4096]=L""; GetDlgItemText(hwndDlg,IDC_NAMING,buf,sizeof(buf)/sizeof(wchar_t)); WritePrivateProfileStringUTF8("transcoder","fileformat",buf,inifileA); GetDlgItemText(hwndDlg,IDC_ROOTDIR,buf,sizeof(buf)/sizeof(wchar_t)); WritePrivateProfileStringUTF8("transcoder","fileroot",buf,inifileA); WritePrivateProfileString(L"transcoder",L"showconf",IsDlgButtonChecked(hwndDlg,IDC_SHOWEVERY)?L"1":L"0",inifile); WritePrivateProfileString(L"transcoder",L"usefilename",IsDlgButtonChecked(hwndDlg,IDC_USE_FILENAME)?L"1":L"0",inifile); LRESULT esel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0); if (esel!=CB_ERR) { LRESULT data = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, esel, 0); if (data != CB_ERR) { EncodableFormat * f = (EncodableFormat *)data; StringCchPrintf(buf, 4096, L"%d", f->fourcc); WritePrivateProfileString(L"transcoder",L"format",buf,inifile); fourcc=f->fourcc; } } } EndDialog(hwndDlg, (LOWORD(wParam) ? fourcc : 0)); RECT rect = {0}; GetWindowRect(hwndDlg, &rect); wchar_t buf[16] = {0}; StringCchPrintf(buf, 16, L"%d", rect.left); WritePrivateProfileString(L"transcoder", L"showconf_x", buf, inifile); StringCchPrintf(buf, 16, L"%d", rect.top); WritePrivateProfileString(L"transcoder", L"showconf_y", buf, inifile); } break; } break; } return 0; } int transcodeConfig(HWND parent) { // returns fourcc return WASABI_API_DIALOGBOXW(IDD_TRANSCODE_CONFIG, parent, config_dlgproc); } // metadata shit extern wchar_t *guessTitles(const wchar_t *filename, int *tracknum,wchar_t **artist, wchar_t **album,wchar_t **title); #define atoi_NULLOK(s) ((s)?_wtoi(s):0) void filenameToItemRecord(const wchar_t *file, itemRecordW * ice) { int gtrack=0; wchar_t *gartist=NULL,*galbum=NULL,*gtitle=NULL; wchar_t *guessbuf = guessTitles(file,>rack,&gartist,&galbum,>itle); if(!gartist) gartist=L""; if(!galbum) galbum=L""; if(!gtitle) gtitle=L""; wchar_t buf[512] = {0}; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"title", buf, 512); if(buf[0]) { ice->title=_wcsdup(buf); gartist=L""; galbum=L""; gtrack=-1;} else ice->title=_wcsdup(gtitle); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"album", buf, 512); if(buf[0]) ice->album=_wcsdup(buf); else ice->album=_wcsdup(galbum); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"artist", buf, 512); if(buf[0]) ice->artist=_wcsdup(buf); else ice->artist=_wcsdup(gartist); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"albumartist", buf, 512); if(buf[0]) ice->albumartist=_wcsdup(buf); else ice->albumartist=_wcsdup(ice->artist); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"track", buf, 512); if(buf[0]) ice->track=atoi_NULLOK(buf); else ice->track=gtrack; buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"genre", buf, 512); ice->genre=_wcsdup(buf); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"comment", buf, 512); ice->comment=_wcsdup(buf); buf[0]=0; AGAVE_API_METADATA->GetExtendedFileInfo(file, L"year", buf, 512); ice->year=atoi_NULLOK(buf); basicFileInfoStructW b={0}; b.filename=const_cast<wchar_t *>(file); //benski> changed extendedFileInfoStruct but not basicFileInfoStruct, i'll have to do that later so we can get rid of this cast b.quickCheck=0; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW); ice->length=b.length; ice->filename = _wcsdup(file); free(guessbuf); } void copyTags(const itemRecordW *in, const wchar_t *out) { // check if the old file still exists - if it does, we will let Winamp copy metadata for us if (wcscmp(in->filename, out) && PathFileExists(in->filename)) { copyFileInfoStructW copy; copy.dest = out; copy.source = in->filename; if (SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)©, IPC_COPY_EXTENDED_FILE_INFOW) == 0) // 0 indicates success here { AGAVE_API_ALBUMART->CopyAlbumArt(in->filename, out); return; } } wchar_t buf[32] = {0}, file[MAX_PATH] = {0}; StringCchCopy(file, MAX_PATH, out); extendedFileInfoStructW e = {0}; e.filename=file; e.metadata=L"album"; e.ret=in->album; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"artist"; e.ret=in->artist; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"albumartist"; e.ret=in->albumartist; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"title"; e.ret=in->title; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"track"; StringCchPrintf(buf, 32, L"%d", in->track); e.ret=buf; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"genre"; e.ret=in->genre; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); e.metadata=L"comment"; e.ret=in->comment; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); if(in->year > 0) { e.metadata=L"year"; StringCchPrintf(buf, 32, L"%d", in->year); e.ret=buf; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_SET_EXTENDED_FILE_INFOW); } SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&e,IPC_WRITE_EXTENDED_FILE_INFO); } extern "C" { __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin() { return &plugin; } __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) { // prompt to remove our settings with default as no (just incase) wchar_t title[256] = {0}; StringCchPrintf(title, 256, WASABI_API_LNGSTRINGW(IDS_NULLSOFT_FORMAT_CONVERTER), PLUGIN_VERSION); if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS), title,MB_YESNO|MB_DEFBUTTON2) == IDYES) { DeleteFile(inifile); } // if not transcoding then can remove on the fly (5.37+) if(!IsWindow(transcoderWnd)){ return ML_PLUGIN_UNINSTALL_NOW; } // otherwise allow for any prompting/full restart removal (default) return ML_PLUGIN_UNINSTALL_REBOOT; } }; static HWND GetDialogBoxParent() { HWND parent = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT); if (!parent || parent == (HWND)1) return plugin.hwndWinampParent; return parent; }