ml_rg.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. #include "Main.h"
  2. #include "../nu/AutoWideFn.h"
  3. #include "api__ml_rg.h"
  4. //#include <api/service/waservicefactory.h>
  5. #include "RGFactory.h"
  6. #include "../playlist/ifc_playlistloadercallback.h"
  7. #include <strsafe.h>
  8. // wasabi based services for localisation support
  9. api_language *WASABI_API_LNG = 0;
  10. HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
  11. int uninstalling = 0;
  12. RGFactory rgFactory;
  13. static DWORD ml_rg_config_ipc;
  14. static BOOL ml_rg_open_prefs;
  15. template <class api_T>
  16. void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
  17. {
  18. if (plugin.service)
  19. {
  20. waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
  21. if (factory)
  22. api_t = reinterpret_cast<api_T *>( factory->getInterface() );
  23. }
  24. }
  25. template <class api_T>
  26. void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
  27. {
  28. if (plugin.service && api_t)
  29. {
  30. waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
  31. if (factory)
  32. factory->releaseInterface(api_t);
  33. }
  34. api_t = NULL;
  35. }
  36. static int Init();
  37. static void Quit();
  38. static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
  39. #define PLUG_VER L"1.29"
  40. extern "C" winampMediaLibraryPlugin plugin =
  41. {
  42. MLHDR_VER,
  43. "nullsoft(ml_rg.dll)",
  44. Init,
  45. Quit,
  46. PluginMessageProc,
  47. 0,
  48. 0,
  49. 0,
  50. };
  51. api_decodefile *AGAVE_API_DECODE = 0;
  52. api_application *WASABI_API_APP = 0;
  53. api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
  54. api_stats *AGAVE_API_STATS = 0;
  55. char *iniFile = 0;
  56. int Init()
  57. {
  58. waServiceFactory *sf = 0;
  59. ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
  60. ServiceBuild( AGAVE_API_DECODE, decodeFileGUID );
  61. ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
  62. ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
  63. plugin.service->service_register( &rgFactory );
  64. // loader so that we can get the localisation service api for use
  65. ServiceBuild( WASABI_API_LNG, languageApiGUID );
  66. // need to have this initialised before we try to do anything with localisation features
  67. WASABI_API_START_LANG( plugin.hDllInstance, MlReplayGainLangGUID );
  68. static wchar_t szDescription[ 256 ];
  69. StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_REPLAY_GAIN_ANALYZER ), PLUG_VER );
  70. plugin.description = (char *)szDescription;
  71. ml_rg_config_ipc = (DWORD)SendMessageA( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "ml_rg_config", IPC_REGISTER_WINAMP_IPCMESSAGE );
  72. iniFile = (char *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIFILE );
  73. config_ask = GetPrivateProfileIntA( "ml_rg", "config_ask", config_ask, iniFile );
  74. config_ask_each_album = GetPrivateProfileIntA( "ml_rg", "config_ask_each_album", config_ask_each_album, iniFile );
  75. return ML_INIT_SUCCESS;
  76. }
  77. void Quit()
  78. {
  79. ServiceRelease(decodeFile, decodeFileGUID);
  80. ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
  81. ServiceRelease(WASABI_API_LNG, languageApiGUID);
  82. ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
  83. ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
  84. plugin.service->service_deregister(&rgFactory);
  85. }
  86. void WorkQueue::Add(const wchar_t *filename)
  87. {
  88. wchar_t album[512] = L"";
  89. // if the user wants to ignore tracks already scanned, check and skip appropriately
  90. if (config_ignore_gained_album && GetFileInfo(filename, L"replaygain_album_gain", album, 256) && album[0] != 0)
  91. return;
  92. GetFileInfo(filename, L"album", album, 256);
  93. if (album[0])
  94. {
  95. RGWorkFile workFile;
  96. lstrcpynW(workFile.filename, filename, MAX_PATH);
  97. albums[album].push_back(workFile);
  98. }
  99. else
  100. {
  101. RGWorkFile workFile;
  102. lstrcpynW(workFile.filename, filename, MAX_PATH);
  103. unclassified.push_back(workFile);
  104. }
  105. totalFiles++;
  106. }
  107. void WriteAlbum(WorkQueue::RGWorkQueue &workQueue)
  108. {
  109. for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
  110. {
  111. if (itr->track_gain[0])
  112. SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
  113. if (itr->track_peak[0])
  114. SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
  115. if (itr->album_gain[0])
  116. SetFileInfo(itr->filename, L"replaygain_album_gain", itr->album_gain);
  117. if (itr->album_peak[0])
  118. SetFileInfo(itr->filename, L"replaygain_album_peak", itr->album_peak);
  119. WriteFileInfo();
  120. if (AGAVE_API_STATS)
  121. AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
  122. TagUpdated(itr->filename);
  123. }
  124. }
  125. void WriteTracks(WorkQueue::RGWorkQueue &workQueue)
  126. {
  127. for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
  128. {
  129. if (itr->track_gain[0])
  130. SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
  131. if (itr->track_peak[0])
  132. SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
  133. WriteFileInfo();
  134. if (AGAVE_API_STATS)
  135. AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
  136. TagUpdated(itr->filename);
  137. }
  138. }
  139. void CopyAlbumData(WorkQueue::RGWorkQueue &workQueue, const wchar_t *album_gain, const wchar_t *album_peak)
  140. {
  141. for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
  142. {
  143. if (itr->track_gain && itr->track_gain[0]) // if there's no track gain, it's because there was an error!
  144. {
  145. if (album_gain && album_gain[0])
  146. lstrcpynW(itr->album_gain, album_gain, 64);
  147. if (album_peak && album_peak[0])
  148. lstrcpynW(itr->album_peak, album_peak, 64);
  149. }
  150. }
  151. }
  152. void WorkQueue::Calculate(ProgressCallback *callback, int *killSwitch)
  153. {
  154. void *context = CreateRG();
  155. StartRG(context);
  156. float albumPeak = 0;
  157. for (RGWorkQueue::iterator itr = unclassified.begin();itr != unclassified.end();itr++)
  158. {
  159. if (*killSwitch) {DestroyRG(context); return ;}
  160. CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
  161. callback->FileFinished();
  162. }
  163. if (*killSwitch) {DestroyRG(context); return ;}
  164. callback->AlbumFinished(&unclassified);
  165. for (AlbumMap::iterator mapItr = albums.begin();mapItr != albums.end();mapItr++)
  166. {
  167. albumPeak = 0;
  168. StartRG(context);
  169. for (RGWorkQueue::iterator itr = mapItr->second.begin();itr != mapItr->second.end();itr++)
  170. {
  171. if (*killSwitch) {DestroyRG(context); return ;}
  172. CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
  173. callback->FileFinished();
  174. }
  175. wchar_t album_gain[64] = L"", album_peak[64] = L"";
  176. CalculateAlbumRG(context, album_gain, album_peak, albumPeak);
  177. CopyAlbumData(mapItr->second, album_gain, album_peak);
  178. if (*killSwitch) {DestroyRG(context); return ;}
  179. callback->AlbumFinished(&(mapItr->second));
  180. }
  181. DestroyRG(context);
  182. }
  183. class WorkQueuePlaylistLoader : public ifc_playlistloadercallback
  184. {
  185. public:
  186. WorkQueuePlaylistLoader(WorkQueue *_queue);
  187. void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info);
  188. protected:
  189. WorkQueue *queue;
  190. RECVS_DISPATCH;
  191. };
  192. WorkQueuePlaylistLoader::WorkQueuePlaylistLoader(WorkQueue *_queue)
  193. {
  194. queue = _queue;
  195. }
  196. void WorkQueuePlaylistLoader::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
  197. {
  198. queue->Add(filename);
  199. }
  200. #define CBCLASS WorkQueuePlaylistLoader
  201. START_DISPATCH;
  202. VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
  203. END_DISPATCH;
  204. #undef CBCLASS
  205. INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
  206. {
  207. if (message_type == ML_MSG_ONSENDTOBUILD)
  208. {
  209. if (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
  210. param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_STREAMNAMES ||
  211. param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_STREAMNAMESW ||
  212. (AGAVE_API_PLAYLISTMANAGER && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)))
  213. {
  214. wchar_t description[512] = {0};
  215. WASABI_API_LNGSTRINGW_BUF(IDS_CALCULATE_REPLAY_GAIN, description, 512);
  216. mlAddToSendToStructW s = {0};
  217. s.context = param2;
  218. s.desc = description;
  219. s.user32 = (INT_PTR)PluginMessageProc;
  220. SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&s, ML_IPC_ADDTOSENDTOW);
  221. }
  222. }
  223. else if (message_type == ML_MSG_ONSENDTOSELECT)
  224. {
  225. if (param3 != (INT_PTR)PluginMessageProc) return 0;
  226. INT_PTR type = param1;
  227. INT_PTR data = param2;
  228. if (data)
  229. {
  230. WorkQueue workQueue;
  231. if (type == ML_TYPE_ITEMRECORDLIST)
  232. {
  233. itemRecordList *p = (itemRecordList*)data;
  234. for (int x = 0; x < p->Size; x ++)
  235. {
  236. workQueue.Add(AutoWideFn(p->Items[x].filename));
  237. }
  238. DoProgress(workQueue);
  239. return 1;
  240. }
  241. else if (type == ML_TYPE_ITEMRECORDLISTW)
  242. {
  243. itemRecordListW *p = (itemRecordListW*)data;
  244. for (int x = 0; x < p->Size; x ++)
  245. {
  246. workQueue.Add(p->Items[x].filename);
  247. }
  248. DoProgress(workQueue);
  249. return 1;
  250. }
  251. else if (type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES)
  252. {
  253. char *p = (char*)data;
  254. while (p && *p)
  255. {
  256. workQueue.Add(AutoWideFn(p));
  257. p += strlen(p) + 1;
  258. }
  259. DoProgress(workQueue);
  260. return 1;
  261. }
  262. else if (type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW)
  263. {
  264. wchar_t *p = (wchar_t*)data;
  265. while (p && *p)
  266. {
  267. workQueue.Add(p);
  268. p += wcslen(p) + 1;
  269. }
  270. DoProgress(workQueue);
  271. return 1;
  272. }
  273. else if (type == ML_TYPE_PLAYLIST)
  274. {
  275. mlPlaylist *playlist = (mlPlaylist *)param2;
  276. WorkQueuePlaylistLoader loader(&workQueue);
  277. AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
  278. DoProgress(workQueue);
  279. return 1;
  280. }
  281. else if (type == ML_TYPE_PLAYLISTS)
  282. {
  283. mlPlaylist **playlists = (mlPlaylist **)param2;
  284. WorkQueuePlaylistLoader loader(&workQueue);
  285. while (playlists && *playlists)
  286. {
  287. mlPlaylist *playlist = *playlists;
  288. AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
  289. playlists++;
  290. }
  291. DoProgress(workQueue);
  292. return 1;
  293. }
  294. }
  295. }
  296. else if (message_type == ML_MSG_CONFIG)
  297. {
  298. ml_rg_open_prefs = TRUE;
  299. SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 42, IPC_OPENPREFSTOPAGE);
  300. ml_rg_open_prefs = FALSE;
  301. return 1;
  302. }
  303. // this will be sent if we've opened the playback->replay gain prefs page
  304. // so that we can enable the embedded controls and return the RGConfig proc
  305. else if (message_type == ml_rg_config_ipc)
  306. {
  307. // sanity check by winamp.exe to make sure that we're valid
  308. if(!param1)
  309. {
  310. return 1;
  311. }
  312. // return the config dialog proceedure
  313. else if(param1 == 1)
  314. {
  315. return (INT_PTR)RGConfig;
  316. }
  317. // queried when the playback prefs page is opened to see if we [ml_rg] caused it
  318. else if(param1 == 2)
  319. {
  320. if(ml_rg_open_prefs)
  321. {
  322. return TRUE;
  323. }
  324. }
  325. }
  326. return 0;
  327. }
  328. extern "C" {
  329. __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
  330. {
  331. return &plugin;
  332. }
  333. __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
  334. uninstalling = 1;
  335. // prompt to remove our settings with default as no (just incase)
  336. if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
  337. (wchar_t*)plugin.description,MB_YESNO|MB_DEFBUTTON2) == IDYES)
  338. {
  339. WritePrivateProfileStringA("ml_rg", 0, 0, iniFile);
  340. }
  341. // also attempt to remove the ReplayGainAnalysis.dll so everything is kept cleaner
  342. wchar_t path[MAX_PATH] = {0};
  343. PathCombineW(path, (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"ReplayGainAnalysis.dll");
  344. // if we get a handle then try to lower the handle count so we can delete
  345. HINSTANCE rgLib = GetModuleHandleW(path);
  346. if(rgLib) {
  347. FreeLibrary(rgLib);
  348. }
  349. DeleteFileW(path);
  350. // allow an on-the-fly removal (since we've got to be with a compatible client build)
  351. return ML_PLUGIN_UNINSTALL_NOW;
  352. }
  353. };