From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/Plugins/Library/ml_transcode/main.cpp | 1100 +++++++++++++++++++++++++++++ 1 file changed, 1100 insertions(+) create mode 100644 Src/Plugins/Library/ml_transcode/main.cpp (limited to 'Src/Plugins/Library/ml_transcode/main.cpp') diff --git a/Src/Plugins/Library/ml_transcode/main.cpp b/Src/Plugins/Library/ml_transcode/main.cpp new file mode 100644 index 00000000..e0f0c457 --- /dev/null +++ b/Src/Plugins/Library/ml_transcode/main.cpp @@ -0,0 +1,1100 @@ +#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; +} \ No newline at end of file -- cgit