#include "api__ml_pmp.h" #include "transcoder_imp.h" #include "nu/ns_wc.h" #include #include #include extern HWND CreateDummyWindow(); static std::vector transcoders; LRESULT CALLBACK TranscodeMsgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { if ( uMsg == WM_WA_IPC && ( lParam == IPC_CB_CONVERT_STATUS || lParam == IPC_CB_CONVERT_DONE ) ) { for ( TranscoderImp *t : transcoders ) { if ( t->cfs.callbackhwnd == hwnd ) { t->TranscodeProgress( (int)wParam, lParam == IPC_CB_CONVERT_DONE ); break; } } } return 0; } static void fourccToString(unsigned int f, wchar_t * str) { wchar_t s[4] = {(wchar_t)(f&0xFF),(wchar_t)((f>>8)&0xFF),(wchar_t)((f>>16)&0xFF),0}; wcsncpy(str,s,4); CharLower(str); } static unsigned int stringToFourcc(const wchar_t * str) { FOURCC cc = 0; char *ccc = (char *)&cc; // unrolled loop (this function gets called a lot on sync and autofill) if (str[0]) { ccc[0] = (char)str[0]; if (str[1]) { ccc[1] = (char)str[1]; if (str[2]) { ccc[2] = (char)str[2]; if (str[3]) { ccc[3] = (char)str[3]; } } } } CharUpperBuffA(ccc, 4); return cc; } static bool fourccEqual(unsigned int a, unsigned int b) { if((a & 0xFF000000) == 0 || (b & 0xFF000000) == 0) return (a & 0x00FFFFFF) == (b & 0x00FFFFFF); return a == b; } class TranscodeProfileCache { public: unsigned int inputformat; unsigned int outputformat; int outputbitrate; TranscodeProfileCache(unsigned int inputformat,unsigned int outputformat,int outputbitrate) : inputformat(inputformat), outputformat(outputformat),outputbitrate(outputbitrate){} }; static void enumProc(intptr_t user_data, const char *desc, int fourcc) { ((FormatList *)user_data)->push_back(new EncodableFormat((unsigned int)fourcc,AutoWide(desc))); } static void BuildEncodableFormatsList(FormatList &list, HWND winampWindow, Device *device) { converterEnumFmtStruct e = {enumProc,(intptr_t)&list}; SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS); // filter out unacceptable formats int i = list.size(); while (i--) { if (device && device->extraActions(DEVICE_VETO_ENCODER, list[i]->fourcc, 0, 0) == 1) { list.erase(list.begin() + i); } } } static CRITICAL_SECTION csTranscoder; void TranscoderImp::init() { InitializeCriticalSection(&csTranscoder); } void TranscoderImp::quit() { DeleteCriticalSection(&csTranscoder); } TranscoderImp::TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device) : device(device), config(config), winampParent(winampParent), hInst(hInst) { EnterCriticalSection(&csTranscoder); transratethresh = config->ReadInt(L"forcetranscodingbitrate",250); transrate = !!config->ReadInt(L"transrate",0); translossless = !!config->ReadInt(L"translossless",0); TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1); int current_fourcc = config->ReadInt(L"lastusedencoder", 0); if (current_fourcc == 0) { // TODO: ask for default from plugin config->WriteInt(L"lastusedencoder", ' A4M'); } WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_PERCENT,caption,100); ZeroMemory(&cfs,sizeof(convertFileStruct)); this->callback = NULL; transcoders.push_back(this); callbackhwnd = CreateDummyWindow(); StringCchCopy(inifile, ARRAYSIZE(inifile), config->GetIniFile()); WideCharToMultiByteSZ(CP_ACP, 0, inifile, -1, inifileA, MAX_PATH, 0, 0); BuildEncodableFormatsList(formats, winampParent, device); LeaveCriticalSection(&csTranscoder); } TranscoderImp::~TranscoderImp() { EnterCriticalSection(&csTranscoder); //transcoders.eraseObject(this); auto it = std::find(transcoders.begin(), transcoders.end(), this); if (it != transcoders.end()) { transcoders.erase(it); } //formats.deleteAll(); for (auto format : formats) { delete format; } formats.clear(); DestroyWindow(callbackhwnd); LeaveCriticalSection(&csTranscoder); } void TranscoderImp::LoadConfigProfile(wchar_t *profile) { } void TranscoderImp::ReloadConfig() { //formats.deleteAll(); for (auto format : formats) { delete format; } formats.clear(); BuildEncodableFormatsList(formats, winampParent, device); transratethresh = config->ReadInt(L"forcetranscodingbitrate",250); transrate = !!config->ReadInt(L"transrate",0); translossless = !!config->ReadInt(L"translossless",0); TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1); } void TranscoderImp::AddAcceptableFormat(unsigned int format) { outformats.push_back(format); } void TranscoderImp::AddAcceptableFormat(wchar_t *format) { outformats.push_back(stringToFourcc(format)); } static bool FileExists(const wchar_t *file) { return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES; } static int getFileLength(const wchar_t * file, HWND winampParent) { // returns length in seconds basicFileInfoStructW b={0}; b.filename=file; SendMessage(winampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW); return b.length; } static bool isFileLossless(const wchar_t * file) { wchar_t ret[64] = {0}; if (AGAVE_API_METADATA && AGAVE_API_METADATA->GetExtendedFileInfo(file, L"lossless", ret, 64) && ret[0] == '1') return true; return false; } static int getFileBitrate(const wchar_t * file, HWND winampParent) { // returns bitrate in bits per second. int secs = getFileLength(file,winampParent); if(!secs) return 0; FILE * f = _wfopen(file,L"rb"); int len = 0; if(f) { fseek(f,0,2); len=ftell(f); fclose(f); } return (len/secs)*8; } void TranscoderImp::GetTempFilePath(const wchar_t *ext, wchar_t *path) { wchar_t dir[MAX_PATH] = {0}; GetTempPath(MAX_PATH,dir); GetTempFileName(dir,L"transcode",0,path); _wunlink(path); wchar_t *e = wcsrchr(path,L'.'); if(e) *e=0; wcscat(path,ext); _wunlink(path); } void TranscoderImp::TranscodeProgress(int pc, bool done) { if(!done) { wchar_t buf[128] = {0}; StringCchPrintf(buf, ARRAYSIZE(buf), caption,pc); if(callback) callback(callbackContext,buf); } else convertDone = done; } bool TranscoderImp::StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputFile, bool test) { cfs.callbackhwnd = callbackhwnd; cfs.sourcefile = _wcsdup(inputFile); cfs.destfile = _wcsdup(outputFile); cfs.destformat[0] = destformat; cfs.destformat[6] = mmioFOURCC('I','N','I',' '); cfs.destformat[7] = (intptr_t)inifileA; cfs.error = L""; if(!SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILEW)) { if(cfs.error && callback) callback(callbackContext,cfs.error); return false; } if(!test) { convertSetPriorityW csp = {&cfs,THREAD_PRIORITY_NORMAL}; SendMessage(winampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW); TranscodeProgress(0,false); } return true; } void TranscoderImp::EndTranscode() { cfs.callbackhwnd = NULL; SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILE_END); free(cfs.sourcefile); free(cfs.destfile); ZeroMemory(&cfs,sizeof(convertFileStruct)); } bool TranscoderImp::TestTranscode(wchar_t * file, unsigned int destformat) { wchar_t tempfn[MAX_PATH], ext[5]=L"."; fourccToString(destformat,&ext[1]); GetTempFilePath(ext,tempfn); convertFileStructW cfs; cfs.callbackhwnd = callbackhwnd; cfs.sourcefile = file; cfs.destfile = tempfn; cfs.destformat[0] = destformat; cfs.destformat[6] = mmioFOURCC('I','N','I',' '); cfs.destformat[7] = (intptr_t)inifileA; cfs.error = L""; int v = SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERT_TEST); _wunlink(tempfn); return !!v; } bool TranscoderImp::FormatAcceptable(unsigned int format) { int l = outformats.size(); for(int i=0; iReadInt(L"lastusedencoder",' A4M'); if (TestTranscode(file,fourcc)) { char buf[100]="128"; convertConfigItem ccs={fourcc,"bitrate",buf,100, inifileA}; SendMessage(winampParent,WM_WA_IPC,(WPARAM)&ccs,IPC_CONVERT_CONFIG_GET_ITEM); int br = atoi(buf)*1000; if(bitrate) *bitrate = br; return fourcc; } return 0; } bool TranscoderImp::ShouldTranscode(wchar_t * file) { if(TranscoderDisabled) return false; wchar_t * ext = wcsrchr(file,L'.'); if(ext && FormatAcceptable(&ext[1])) { if(transrate && getFileBitrate(file,winampParent) > 1000*transratethresh) return true; else if (translossless && isFileLossless(file)) return true; else return false; } return true; } int TranscoderImp::CanTranscode(wchar_t * file, wchar_t * ext, int length) { if(TranscoderDisabled) return -1; int bitrate; unsigned int fmt = GetOutputFormat(file,&bitrate); if(fmt) { if(ext) { ext[0]=L'.'; ext[1]=0; char extA[8]="."; convertConfigItem c = {fmt,"extension",&extA[1],6,AutoCharDup(config->GetIniFile())}; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM); if(extA[1]) wcsncpy(ext,AutoWide(extA), 10); else fourccToString(fmt,&ext[1]); free(c.configfile); } if (length <= 0) length = getFileLength(file,winampParent); return (bitrate/8) * length; // should transcode } return -1; // transcoding impossible } extern void filenameToItemRecord(wchar_t * file, itemRecordW * ice); extern void copyTags(itemRecordW * in, wchar_t * out); int TranscoderImp::TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callbackFunc)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption) { if(caption) lstrcpyn(this->caption,caption,100); this->callback = callbackFunc; this->callbackContext = callbackContext; convertDone = false; int format = GetOutputFormat(inputFile); if(!format) return -1; if(!StartTranscode(format,inputFile,outputFile)) return -1; while(!convertDone && !(*killswitch)) Sleep(50); EndTranscode(); // copy the tags over itemRecordW ice={0}; filenameToItemRecord(inputFile,&ice); copyTags(&ice,outputFile); freeRecord(&ice); if(convertDone && callback) callback(callbackContext,L"Done"); this->callback = NULL; return convertDone?0:-1; } 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_SHOWNA); } } struct ConfigTranscoderParam { ConfigTranscoderParam() { winampParent=0; configfile=0; memset(&ccs, 0, sizeof(ccs)); config=0; dev=0; } ~ConfigTranscoderParam() { //list.deleteAll(); for (auto l : list) { delete l; } list.clear(); free((char*)ccs.extra_data[7]); free(configfile); } HWND winampParent; wchar_t *configfile; FormatList list; convertConfigStruct ccs; C_Config * config; Device *dev; }; static INT_PTR CALLBACK config_dlgproc_transcode_advanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { static ConfigTranscoderParam *p; switch(uMsg) { case WM_INITDIALOG: p = (ConfigTranscoderParam *)lParam; SetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,p->config->ReadString(L"forcetranscodingbitrate",L"250")); if(p->config->ReadInt(L"transrate",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE,BST_CHECKED); if(p->config->ReadInt(L"translossless",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE_LOSSLESS,BST_CHECKED); break; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: { wchar_t buf[10]=L""; GetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,buf,10); p->config->WriteString(L"forcetranscodingbitrate",buf); p->config->WriteInt(L"transrate",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE)?1:0); p->config->WriteInt(L"translossless",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE_LOSSLESS)?1:0); } case IDCANCEL: EndDialog(hwndDlg,0); break; } break; } return 0; } BOOL TranscoderImp::transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { static ConfigTranscoderParam *p; switch(uMsg) { case WM_INITDIALOG: { p = (ConfigTranscoderParam *)lParam; if(p->config->ReadInt(L"enableTranscoder",1)) CheckDlgButton(hwndDlg,IDC_ENABLETRANSCODER,BST_CHECKED); BuildEncodableFormatsList(p->list, p->winampParent, p->dev); p->ccs.hwndParent = hwndDlg; int encdef = p->config->ReadInt(L"lastusedencoder",0); for(size_t i=0; i < p->list.size(); i++) { EncodableFormat * f = p->list[i]; int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc); SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f); if(i==0 && encdef == 0) encdef = f->fourcc; if(f->fourcc == encdef) { SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0); p->ccs.format = f->fourcc; } } p->ccs.hwndParent = hwndDlg; HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG); doConfigResizeChild(hwndDlg, h); } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_ENABLETRANSCODER: p->config->WriteInt(L"enableTranscoder",IsDlgButtonChecked(hwndDlg,IDC_ENABLETRANSCODER)?1:0); break; case IDC_ADVANCED: return WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG_TRANSCODING_ADVANCED,hwndDlg,config_dlgproc_transcode_advanced,(LPARAM)p); 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(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END); EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0); p->ccs.format = f->fourcc; HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG); doConfigResizeChild(hwndDlg, h); p->config->WriteInt(L"lastusedencoder",p->ccs.format); } } break; } break; case WM_DESTROY: { p->config->WriteInt(L"lastusedencoder",p->ccs.format); SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END); delete p; for( TranscoderImp *l_transcoder : transcoders ) l_transcoder->ReloadConfig(); } break; } return 0; } void* TranscoderImp::ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev) { ConfigTranscoderParam * p = new ConfigTranscoderParam; p->config = config; p->winampParent=winampParent; p->configfile=_wcsdup(config->GetIniFile()); p->ccs.extra_data[6] = mmioFOURCC('I','N','I',' '); p->ccs.extra_data[7] = (int)AutoCharDup(p->configfile); p->dev = dev; return p; }