transcoder_imp.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. #include "api__ml_pmp.h"
  2. #include "transcoder_imp.h"
  3. #include "nu/ns_wc.h"
  4. #include <shlwapi.h>
  5. #include <strsafe.h>
  6. #include <mmiscapi.h>
  7. extern HWND CreateDummyWindow();
  8. static std::vector<TranscoderImp*> transcoders;
  9. LRESULT CALLBACK TranscodeMsgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
  10. {
  11. if ( uMsg == WM_WA_IPC && ( lParam == IPC_CB_CONVERT_STATUS || lParam == IPC_CB_CONVERT_DONE ) )
  12. {
  13. for ( TranscoderImp *t : transcoders )
  14. {
  15. if ( t->cfs.callbackhwnd == hwnd )
  16. {
  17. t->TranscodeProgress( (int)wParam, lParam == IPC_CB_CONVERT_DONE );
  18. break;
  19. }
  20. }
  21. }
  22. return 0;
  23. }
  24. static void fourccToString(unsigned int f, wchar_t * str) {
  25. wchar_t s[4] = {(wchar_t)(f&0xFF),(wchar_t)((f>>8)&0xFF),(wchar_t)((f>>16)&0xFF),0};
  26. wcsncpy(str,s,4);
  27. CharLower(str);
  28. }
  29. static unsigned int stringToFourcc(const wchar_t * str) {
  30. FOURCC cc = 0;
  31. char *ccc = (char *)&cc;
  32. // unrolled loop (this function gets called a lot on sync and autofill)
  33. if (str[0])
  34. {
  35. ccc[0] = (char)str[0];
  36. if (str[1])
  37. {
  38. ccc[1] = (char)str[1];
  39. if (str[2])
  40. {
  41. ccc[2] = (char)str[2];
  42. if (str[3])
  43. {
  44. ccc[3] = (char)str[3];
  45. }
  46. }
  47. }
  48. }
  49. CharUpperBuffA(ccc, 4);
  50. return cc;
  51. }
  52. static bool fourccEqual(unsigned int a, unsigned int b) {
  53. if((a & 0xFF000000) == 0 || (b & 0xFF000000) == 0)
  54. return (a & 0x00FFFFFF) == (b & 0x00FFFFFF);
  55. return a == b;
  56. }
  57. class TranscodeProfileCache {
  58. public:
  59. unsigned int inputformat;
  60. unsigned int outputformat;
  61. int outputbitrate;
  62. TranscodeProfileCache(unsigned int inputformat,unsigned int outputformat,int outputbitrate) :
  63. inputformat(inputformat), outputformat(outputformat),outputbitrate(outputbitrate){}
  64. };
  65. static void enumProc(intptr_t user_data, const char *desc, int fourcc)
  66. {
  67. ((FormatList *)user_data)->push_back(new EncodableFormat((unsigned int)fourcc,AutoWide(desc)));
  68. }
  69. static void BuildEncodableFormatsList(FormatList &list, HWND winampWindow, Device *device)
  70. {
  71. converterEnumFmtStruct e = {enumProc,(intptr_t)&list};
  72. SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS);
  73. // filter out unacceptable formats
  74. int i = list.size();
  75. while (i--)
  76. {
  77. if (device && device->extraActions(DEVICE_VETO_ENCODER, list[i]->fourcc, 0, 0) == 1)
  78. {
  79. list.erase(list.begin() + i);
  80. }
  81. }
  82. }
  83. static CRITICAL_SECTION csTranscoder;
  84. void TranscoderImp::init() {
  85. InitializeCriticalSection(&csTranscoder);
  86. }
  87. void TranscoderImp::quit() {
  88. DeleteCriticalSection(&csTranscoder);
  89. }
  90. TranscoderImp::TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device)
  91. : device(device), config(config), winampParent(winampParent), hInst(hInst)
  92. {
  93. EnterCriticalSection(&csTranscoder);
  94. transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
  95. transrate = !!config->ReadInt(L"transrate",0);
  96. translossless = !!config->ReadInt(L"translossless",0);
  97. TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
  98. int current_fourcc = config->ReadInt(L"lastusedencoder", 0);
  99. if (current_fourcc == 0)
  100. {
  101. // TODO: ask for default from plugin
  102. config->WriteInt(L"lastusedencoder", ' A4M');
  103. }
  104. WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_PERCENT,caption,100);
  105. ZeroMemory(&cfs,sizeof(convertFileStruct));
  106. this->callback = NULL;
  107. transcoders.push_back(this);
  108. callbackhwnd = CreateDummyWindow();
  109. StringCchCopy(inifile, ARRAYSIZE(inifile), config->GetIniFile());
  110. WideCharToMultiByteSZ(CP_ACP, 0, inifile, -1, inifileA, MAX_PATH, 0, 0);
  111. BuildEncodableFormatsList(formats, winampParent, device);
  112. LeaveCriticalSection(&csTranscoder);
  113. }
  114. TranscoderImp::~TranscoderImp()
  115. {
  116. EnterCriticalSection(&csTranscoder);
  117. //transcoders.eraseObject(this);
  118. auto it = std::find(transcoders.begin(), transcoders.end(), this);
  119. if (it != transcoders.end())
  120. {
  121. transcoders.erase(it);
  122. }
  123. //formats.deleteAll();
  124. for (auto format : formats)
  125. {
  126. delete format;
  127. }
  128. formats.clear();
  129. DestroyWindow(callbackhwnd);
  130. LeaveCriticalSection(&csTranscoder);
  131. }
  132. void TranscoderImp::LoadConfigProfile(wchar_t *profile) {
  133. }
  134. void TranscoderImp::ReloadConfig()
  135. {
  136. //formats.deleteAll();
  137. for (auto format : formats)
  138. {
  139. delete format;
  140. }
  141. formats.clear();
  142. BuildEncodableFormatsList(formats, winampParent, device);
  143. transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
  144. transrate = !!config->ReadInt(L"transrate",0);
  145. translossless = !!config->ReadInt(L"translossless",0);
  146. TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
  147. }
  148. void TranscoderImp::AddAcceptableFormat(unsigned int format)
  149. {
  150. outformats.push_back(format);
  151. }
  152. void TranscoderImp::AddAcceptableFormat(wchar_t *format)
  153. {
  154. outformats.push_back(stringToFourcc(format));
  155. }
  156. static bool FileExists(const wchar_t *file)
  157. {
  158. return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES;
  159. }
  160. static int getFileLength(const wchar_t * file, HWND winampParent) { // returns length in seconds
  161. basicFileInfoStructW b={0};
  162. b.filename=file;
  163. SendMessage(winampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
  164. return b.length;
  165. }
  166. static bool isFileLossless(const wchar_t * file) {
  167. wchar_t ret[64] = {0};
  168. if (AGAVE_API_METADATA && AGAVE_API_METADATA->GetExtendedFileInfo(file, L"lossless", ret, 64) && ret[0] == '1')
  169. return true;
  170. return false;
  171. }
  172. static int getFileBitrate(const wchar_t * file, HWND winampParent) { // returns bitrate in bits per second.
  173. int secs = getFileLength(file,winampParent);
  174. if(!secs) return 0;
  175. FILE * f = _wfopen(file,L"rb");
  176. int len = 0;
  177. if(f) { fseek(f,0,2); len=ftell(f); fclose(f); }
  178. return (len/secs)*8;
  179. }
  180. void TranscoderImp::GetTempFilePath(const wchar_t *ext, wchar_t *path) {
  181. wchar_t dir[MAX_PATH] = {0};
  182. GetTempPath(MAX_PATH,dir);
  183. GetTempFileName(dir,L"transcode",0,path);
  184. _wunlink(path);
  185. wchar_t *e = wcsrchr(path,L'.');
  186. if(e) *e=0;
  187. wcscat(path,ext);
  188. _wunlink(path);
  189. }
  190. void TranscoderImp::TranscodeProgress(int pc, bool done) {
  191. if(!done) {
  192. wchar_t buf[128] = {0};
  193. StringCchPrintf(buf, ARRAYSIZE(buf), caption,pc);
  194. if(callback) callback(callbackContext,buf);
  195. }
  196. else convertDone = done;
  197. }
  198. bool TranscoderImp::StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputFile, bool test) {
  199. cfs.callbackhwnd = callbackhwnd;
  200. cfs.sourcefile = _wcsdup(inputFile);
  201. cfs.destfile = _wcsdup(outputFile);
  202. cfs.destformat[0] = destformat;
  203. cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
  204. cfs.destformat[7] = (intptr_t)inifileA;
  205. cfs.error = L"";
  206. if(!SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILEW))
  207. {
  208. if(cfs.error && callback)
  209. callback(callbackContext,cfs.error);
  210. return false;
  211. }
  212. if(!test)
  213. {
  214. convertSetPriorityW csp = {&cfs,THREAD_PRIORITY_NORMAL};
  215. SendMessage(winampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
  216. TranscodeProgress(0,false);
  217. }
  218. return true;
  219. }
  220. void TranscoderImp::EndTranscode()
  221. {
  222. cfs.callbackhwnd = NULL;
  223. SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILE_END);
  224. free(cfs.sourcefile);
  225. free(cfs.destfile);
  226. ZeroMemory(&cfs,sizeof(convertFileStruct));
  227. }
  228. bool TranscoderImp::TestTranscode(wchar_t * file, unsigned int destformat)
  229. {
  230. wchar_t tempfn[MAX_PATH], ext[5]=L".";
  231. fourccToString(destformat,&ext[1]);
  232. GetTempFilePath(ext,tempfn);
  233. convertFileStructW cfs;
  234. cfs.callbackhwnd = callbackhwnd;
  235. cfs.sourcefile = file;
  236. cfs.destfile = tempfn;
  237. cfs.destformat[0] = destformat;
  238. cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
  239. cfs.destformat[7] = (intptr_t)inifileA;
  240. cfs.error = L"";
  241. int v = SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERT_TEST);
  242. _wunlink(tempfn);
  243. return !!v;
  244. }
  245. bool TranscoderImp::FormatAcceptable(unsigned int format)
  246. {
  247. int l = outformats.size();
  248. for(int i=0; i<l; i++)
  249. {
  250. if(fourccEqual(outformats[i], format))
  251. return true;
  252. }
  253. return false;
  254. }
  255. bool TranscoderImp::FormatAcceptable(wchar_t * format)
  256. {
  257. return FormatAcceptable(stringToFourcc(format));
  258. }
  259. int TranscoderImp::GetOutputFormat(wchar_t * file, int *bitrate)
  260. {
  261. if (!FileExists(file))
  262. return 0;
  263. int fourcc = config->ReadInt(L"lastusedencoder",' A4M');
  264. if (TestTranscode(file,fourcc))
  265. {
  266. char buf[100]="128";
  267. convertConfigItem ccs={fourcc,"bitrate",buf,100, inifileA};
  268. SendMessage(winampParent,WM_WA_IPC,(WPARAM)&ccs,IPC_CONVERT_CONFIG_GET_ITEM);
  269. int br = atoi(buf)*1000;
  270. if(bitrate) *bitrate = br;
  271. return fourcc;
  272. }
  273. return 0;
  274. }
  275. bool TranscoderImp::ShouldTranscode(wchar_t * file)
  276. {
  277. if(TranscoderDisabled) return false;
  278. wchar_t * ext = wcsrchr(file,L'.');
  279. if(ext && FormatAcceptable(&ext[1])) {
  280. if(transrate && getFileBitrate(file,winampParent) > 1000*transratethresh)
  281. return true;
  282. else if (translossless && isFileLossless(file))
  283. return true;
  284. else return false;
  285. }
  286. return true;
  287. }
  288. int TranscoderImp::CanTranscode(wchar_t * file, wchar_t * ext, int length)
  289. {
  290. if(TranscoderDisabled) return -1;
  291. int bitrate;
  292. unsigned int fmt = GetOutputFormat(file,&bitrate);
  293. if(fmt) {
  294. if(ext) {
  295. ext[0]=L'.'; ext[1]=0;
  296. char extA[8]=".";
  297. convertConfigItem c = {fmt,"extension",&extA[1],6,AutoCharDup(config->GetIniFile())};
  298. SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM);
  299. if(extA[1]) wcsncpy(ext,AutoWide(extA), 10);
  300. else fourccToString(fmt,&ext[1]);
  301. free(c.configfile);
  302. }
  303. if (length <= 0)
  304. length = getFileLength(file,winampParent);
  305. return (bitrate/8) * length; // should transcode
  306. }
  307. return -1; // transcoding impossible
  308. }
  309. extern void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
  310. extern void copyTags(itemRecordW * in, wchar_t * out);
  311. int TranscoderImp::TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callbackFunc)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption)
  312. {
  313. if(caption) lstrcpyn(this->caption,caption,100);
  314. this->callback = callbackFunc;
  315. this->callbackContext = callbackContext;
  316. convertDone = false;
  317. int format = GetOutputFormat(inputFile);
  318. if(!format) return -1;
  319. if(!StartTranscode(format,inputFile,outputFile))
  320. return -1;
  321. while(!convertDone && !(*killswitch))
  322. Sleep(50);
  323. EndTranscode();
  324. // copy the tags over
  325. itemRecordW ice={0};
  326. filenameToItemRecord(inputFile,&ice);
  327. copyTags(&ice,outputFile);
  328. freeRecord(&ice);
  329. if(convertDone && callback)
  330. callback(callbackContext,L"Done");
  331. this->callback = NULL;
  332. return convertDone?0:-1;
  333. }
  334. static void doConfigResizeChild(HWND parent, HWND child)
  335. {
  336. if (child)
  337. {
  338. RECT r;
  339. GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
  340. ScreenToClient(parent, (LPPOINT)&r);
  341. SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
  342. ShowWindow(child, SW_SHOWNA);
  343. }
  344. }
  345. struct ConfigTranscoderParam
  346. {
  347. ConfigTranscoderParam()
  348. {
  349. winampParent=0;
  350. configfile=0;
  351. memset(&ccs, 0, sizeof(ccs));
  352. config=0;
  353. dev=0;
  354. }
  355. ~ConfigTranscoderParam()
  356. {
  357. //list.deleteAll();
  358. for (auto l : list)
  359. {
  360. delete l;
  361. }
  362. list.clear();
  363. free((char*)ccs.extra_data[7]);
  364. free(configfile);
  365. }
  366. HWND winampParent;
  367. wchar_t *configfile;
  368. FormatList list;
  369. convertConfigStruct ccs;
  370. C_Config * config;
  371. Device *dev;
  372. };
  373. static INT_PTR CALLBACK config_dlgproc_transcode_advanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  374. {
  375. static ConfigTranscoderParam *p;
  376. switch(uMsg)
  377. {
  378. case WM_INITDIALOG:
  379. p = (ConfigTranscoderParam *)lParam;
  380. SetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,p->config->ReadString(L"forcetranscodingbitrate",L"250"));
  381. if(p->config->ReadInt(L"transrate",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE,BST_CHECKED);
  382. if(p->config->ReadInt(L"translossless",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE_LOSSLESS,BST_CHECKED);
  383. break;
  384. case WM_COMMAND:
  385. switch(LOWORD(wParam)) {
  386. case IDOK:
  387. {
  388. wchar_t buf[10]=L"";
  389. GetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,buf,10);
  390. p->config->WriteString(L"forcetranscodingbitrate",buf);
  391. p->config->WriteInt(L"transrate",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE)?1:0);
  392. p->config->WriteInt(L"translossless",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE_LOSSLESS)?1:0);
  393. }
  394. case IDCANCEL:
  395. EndDialog(hwndDlg,0);
  396. break;
  397. }
  398. break;
  399. }
  400. return 0;
  401. }
  402. BOOL TranscoderImp::transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  403. {
  404. static ConfigTranscoderParam *p;
  405. switch(uMsg) {
  406. case WM_INITDIALOG:
  407. {
  408. p = (ConfigTranscoderParam *)lParam;
  409. if(p->config->ReadInt(L"enableTranscoder",1)) CheckDlgButton(hwndDlg,IDC_ENABLETRANSCODER,BST_CHECKED);
  410. BuildEncodableFormatsList(p->list, p->winampParent, p->dev);
  411. p->ccs.hwndParent = hwndDlg;
  412. int encdef = p->config->ReadInt(L"lastusedencoder",0);
  413. for(size_t i=0; i < p->list.size(); i++)
  414. {
  415. EncodableFormat * f = p->list[i];
  416. int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc);
  417. SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f);
  418. if(i==0 && encdef == 0) encdef = f->fourcc;
  419. if(f->fourcc == encdef)
  420. {
  421. SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0);
  422. p->ccs.format = f->fourcc;
  423. }
  424. }
  425. p->ccs.hwndParent = hwndDlg;
  426. HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
  427. doConfigResizeChild(hwndDlg, h);
  428. }
  429. break;
  430. case WM_COMMAND:
  431. switch (LOWORD(wParam))
  432. {
  433. case IDC_ENABLETRANSCODER:
  434. p->config->WriteInt(L"enableTranscoder",IsDlgButtonChecked(hwndDlg,IDC_ENABLETRANSCODER)?1:0);
  435. break;
  436. case IDC_ADVANCED:
  437. return WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG_TRANSCODING_ADVANCED,hwndDlg,config_dlgproc_transcode_advanced,(LPARAM)p);
  438. case IDC_ENCFORMAT:
  439. if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
  440. {
  441. int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
  442. if (sel != CB_ERR)
  443. {
  444. SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
  445. EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
  446. p->ccs.format = f->fourcc;
  447. HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
  448. doConfigResizeChild(hwndDlg, h);
  449. p->config->WriteInt(L"lastusedencoder",p->ccs.format);
  450. }
  451. }
  452. break;
  453. }
  454. break;
  455. case WM_DESTROY:
  456. {
  457. p->config->WriteInt(L"lastusedencoder",p->ccs.format);
  458. SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
  459. delete p;
  460. for( TranscoderImp *l_transcoder : transcoders )
  461. l_transcoder->ReloadConfig();
  462. }
  463. break;
  464. }
  465. return 0;
  466. }
  467. void* TranscoderImp::ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev)
  468. {
  469. ConfigTranscoderParam * p = new ConfigTranscoderParam;
  470. p->config = config;
  471. p->winampParent=winampParent;
  472. p->configfile=_wcsdup(config->GetIniFile());
  473. p->ccs.extra_data[6] = mmioFOURCC('I','N','I',' ');
  474. p->ccs.extra_data[7] = (int)AutoCharDup(p->configfile);
  475. p->dev = dev;
  476. return p;
  477. }