1/* in_flac - Winamp2 FLAC input plugin 2 * Copyright (C) 2002,2003,2004,2005,2006,2007 Josh Coalson 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 */ 18 19#if HAVE_CONFIG_H 20# include <config.h> 21#endif 22 23#include <windows.h> 24#include <stdio.h> 25#include "FLAC/all.h" 26#include "share/alloc.h" 27#include "plugin_common/all.h" 28#include "infobox.h" 29#include "configure.h" 30#include "resource.h" 31 32 33typedef struct 34{ 35 char filename[MAX_PATH]; 36 FLAC__StreamMetadata *tags; 37} LOCALDATA; 38 39static char buffer[8192]; 40static char *genres = NULL; 41static DWORD genresSize = 0, genresCount = 0; 42static BOOL genresChanged = FALSE, isNT; 43 44static const char infoTitle[] = "FLAC File Info"; 45 46/* 47 * Genres 48 */ 49 50/* TODO: write genres in utf-8 ? */ 51 52static __inline int GetGenresFileName(char *buffer, int size) 53{ 54 char *c; 55 56 if (!GetModuleFileName(NULL, buffer, size)) 57 return 0; 58 c = strrchr(buffer, '\\'); 59 if (!c) return 0; 60 strcpy(c+1, "genres.txt"); 61 62 return 1; 63} 64 65static void LoadGenres() 66{ 67 HANDLE hFile; 68 DWORD spam; 69 char *c; 70 71 FLAC__ASSERT(0 != genres); 72 73 if (!GetGenresFileName(buffer, sizeof(buffer))) return; 74 /* load file */ 75 hFile = CreateFile(buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 76 if (hFile == INVALID_HANDLE_VALUE) return; 77 genresSize = GetFileSize(hFile, 0); 78 if (genresSize && (genres = (char*)safe_malloc_add_2op_(genresSize, /*+*/2))) 79 { 80 if (!ReadFile(hFile, genres, genresSize, &spam, NULL) || spam!=genresSize) 81 { 82 free(genres); 83 genres = NULL; 84 } 85 else 86 { 87 genres[genresSize] = 0; 88 genres[genresSize+1] = 0; 89 /* replace newlines */ 90 genresChanged = FALSE; 91 genresCount = 1; 92 93 for (c=genres; *c; c++) 94 { 95 if (*c == 10) 96 { 97 *c = 0; 98 if (*(c+1)) 99 genresCount++; 100 else genresSize--; 101 } 102 } 103 } 104 } 105 106 CloseHandle(hFile); 107} 108 109static void SaveGenres(HWND hlist) 110{ 111 HANDLE hFile; 112 DWORD spam; 113 int i, count, len; 114 115 if (!GetGenresFileName(buffer, sizeof(buffer))) return; 116 /* write file */ 117 hFile = CreateFile(buffer, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 118 if (hFile == INVALID_HANDLE_VALUE) return; 119 120 count = SendMessage(hlist, CB_GETCOUNT, 0, 0); 121 for (i=0; i<count; i++) 122 { 123 SendMessage(hlist, CB_GETLBTEXT, i, (LPARAM)buffer); 124 len = strlen(buffer); 125 if (i != count-1) 126 { 127 buffer[len] = 10; 128 len++; 129 } 130 WriteFile(hFile, buffer, len, &spam, NULL); 131 } 132 133 CloseHandle(hFile); 134} 135 136static void AddGenre(HWND hwnd, const char *genre) 137{ 138 HWND hgen = GetDlgItem(hwnd, IDC_GENRE); 139 140 if (SendMessage(hgen, CB_FINDSTRINGEXACT, -1, (LPARAM)genre) == CB_ERR) 141 { 142 genresChanged = TRUE; 143 SendMessage(hgen, CB_ADDSTRING, 0, (LPARAM)genre); 144 } 145} 146 147static void InitGenres(HWND hwnd) 148{ 149 HWND hgen = GetDlgItem(hwnd, IDC_GENRE); 150 char *c; 151 152 /* set text length limit to 64 chars */ 153 SendMessage(hgen, CB_LIMITTEXT, 64, 0); 154 /* try to load genres */ 155 if (!genres) 156 LoadGenres(hgen); 157 /* add the to list */ 158 if (genres) 159 { 160 SendMessage(hgen, CB_INITSTORAGE, genresCount, genresSize); 161 162 for (c = genres; *c; c += strlen(c)+1) 163 SendMessage(hgen, CB_ADDSTRING, 0, (LPARAM)c); 164 } 165} 166 167static void DeinitGenres(HWND hwnd, BOOL final) 168{ 169 if (genresChanged && hwnd) 170 { 171 SaveGenres(GetDlgItem(hwnd, IDC_GENRE)); 172 genresChanged = FALSE; 173 final = TRUE; 174 } 175 if (final) 176 { 177 free(genres); 178 genres = 0; 179 } 180} 181 182static wchar_t *AnsiToWide(const char *src) 183{ 184 int len; 185 wchar_t *dest; 186 187 FLAC__ASSERT(0 != src); 188 189 len = strlen(src) + 1; 190 /* copy */ 191 dest = (wchar_t*)safe_malloc_mul_2op_(len, /*times*/sizeof(wchar_t)); 192 if (dest) mbstowcs(dest, src, len); 193 return dest; 194} 195 196/* 197 * Infobox helpers 198 */ 199 200#define SetText(x,y) ucs2 = FLAC_plugin__tags_get_tag_ucs2(data->tags, y); \ 201 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, ucs2, -1, buffer, sizeof(buffer), NULL, NULL); \ 202 if(ucs2) free(ucs2); \ 203 SetDlgItemText(hwnd, x, buffer) 204 205#define GetText(x,y) GetDlgItemText(hwnd, x, buffer, sizeof(buffer)); \ 206 if (*buffer) { ucs2 = AnsiToWide(buffer); FLAC_plugin__tags_set_tag_ucs2(data->tags, y, ucs2, /*replace_all=*/false); free(ucs2); } \ 207 else FLAC_plugin__tags_delete_tag(data->tags, y) 208 209#define SetTextW(x,y) ucs2 = FLAC_plugin__tags_get_tag_ucs2(data->tags, y); \ 210 SetDlgItemTextW(hwnd, x, ucs2); \ 211 free(ucs2) 212 213#define GetTextW(x,y) GetDlgItemTextW(hwnd, x, (WCHAR*)buffer, sizeof(buffer)/2); \ 214 if (*(WCHAR*)buffer) FLAC_plugin__tags_set_tag_ucs2(data->tags, y, (WCHAR*)buffer, /*replace_all=*/false); \ 215 else FLAC_plugin__tags_delete_tag(data->tags, y) 216 217 218static BOOL InitInfoboxInfo(HWND hwnd, const char *file) 219{ 220 LOCALDATA *data = LocalAlloc(LPTR, sizeof(LOCALDATA)); 221 wchar_t *ucs2; 222 FLAC__StreamMetadata streaminfo; 223 DWORD length, bps, ratio, rg; 224 LONGLONG filesize; 225 226 SetWindowLong(hwnd, GWL_USERDATA, (LONG)data); 227 /* file name */ 228 strncpy(data->filename, file, sizeof(data->filename)); 229 SetDlgItemText(hwnd, IDC_NAME, file); 230 /* stream data and vorbis comment */ 231 filesize = FileSize(file); 232 if (!filesize) return FALSE; 233 if (!FLAC__metadata_get_streaminfo(file, &streaminfo)) 234 return FALSE; 235 ReadTags(file, &data->tags, false); 236 237 length = (DWORD)(streaminfo.data.stream_info.total_samples / streaminfo.data.stream_info.sample_rate); 238 bps = (DWORD)(filesize / (125*streaminfo.data.stream_info.total_samples/streaminfo.data.stream_info.sample_rate)); 239 ratio = bps*1000000 / (streaminfo.data.stream_info.sample_rate*streaminfo.data.stream_info.channels*streaminfo.data.stream_info.bits_per_sample); 240 rg = FLAC_plugin__tags_get_tag_utf8(data->tags, "REPLAYGAIN_TRACK_GAIN") ? 1 : 0; 241 rg |= FLAC_plugin__tags_get_tag_utf8(data->tags, "REPLAYGAIN_ALBUM_GAIN") ? 2 : 0; 242 243 sprintf(buffer, "Sample rate: %d Hz\nChannels: %d\nBits per sample: %d\nMin block size: %d\nMax block size: %d\n" 244 "File size: %I64d bytes\nTotal samples: %I64d\nLength: %d:%02d\nAvg. bitrate: %d\nCompression ratio: %d.%d%%\n" 245 "ReplayGain: %s\n", 246 streaminfo.data.stream_info.sample_rate, streaminfo.data.stream_info.channels, streaminfo.data.stream_info.bits_per_sample, 247 streaminfo.data.stream_info.min_blocksize, streaminfo.data.stream_info.max_blocksize, filesize, streaminfo.data.stream_info.total_samples, 248 length/60, length%60, bps, ratio/10, ratio%10, 249 rg==3 ? "track gain\nReplayGain: album gain" : rg==2 ? "album gain" : rg==1 ? "track gain" : "not present"); 250 251 SetDlgItemText(hwnd, IDC_INFO, buffer); 252 /* tag */ 253 if (isNT) 254 { 255 SetTextW(IDC_TITLE, "TITLE"); 256 SetTextW(IDC_ARTIST, "ARTIST"); 257 SetTextW(IDC_ALBUM, "ALBUM"); 258 SetTextW(IDC_COMMENT, "COMMENT"); 259 SetTextW(IDC_YEAR, "DATE"); 260 SetTextW(IDC_TRACK, "TRACKNUMBER"); 261 SetTextW(IDC_GENRE, "GENRE"); 262 } 263 else 264 { 265 SetText(IDC_TITLE, "TITLE"); 266 SetText(IDC_ARTIST, "ARTIST"); 267 SetText(IDC_ALBUM, "ALBUM"); 268 SetText(IDC_COMMENT, "COMMENT"); 269 SetText(IDC_YEAR, "DATE"); 270 SetText(IDC_TRACK, "TRACKNUMBER"); 271 SetText(IDC_GENRE, "GENRE"); 272 } 273 274 return TRUE; 275} 276 277static void __inline SetTag(HWND hwnd, const char *filename, FLAC__StreamMetadata *tags) 278{ 279 strcpy(buffer, infoTitle); 280 281 if (FLAC_plugin__tags_set(filename, tags)) 282 strcat(buffer, " [Updated]"); 283 else strcat(buffer, " [Failed]"); 284 285 SetWindowText(hwnd, buffer); 286} 287 288static void UpdateTag(HWND hwnd) 289{ 290 LOCALDATA *data = (LOCALDATA*)GetWindowLong(hwnd, GWL_USERDATA); 291 wchar_t *ucs2; 292 293 /* get fields */ 294 if (isNT) 295 { 296 GetTextW(IDC_TITLE, "TITLE"); 297 GetTextW(IDC_ARTIST, "ARTIST"); 298 GetTextW(IDC_ALBUM, "ALBUM"); 299 GetTextW(IDC_COMMENT, "COMMENT"); 300 GetTextW(IDC_YEAR, "DATE"); 301 GetTextW(IDC_TRACK, "TRACKNUMBER"); 302 GetTextW(IDC_GENRE, "GENRE"); 303 304 ucs2 = FLAC_plugin__tags_get_tag_ucs2(data->tags, "GENRE"); 305 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, ucs2, -1, buffer, sizeof(buffer), NULL, NULL); 306 free(ucs2); 307 } 308 else 309 { 310 GetText(IDC_TITLE, "TITLE"); 311 GetText(IDC_ARTIST, "ARTIST"); 312 GetText(IDC_ALBUM, "ALBUM"); 313 GetText(IDC_COMMENT, "COMMENT"); 314 GetText(IDC_YEAR, "DATE"); 315 GetText(IDC_TRACK, "TRACKNUMBER"); 316 GetText(IDC_GENRE, "GENRE"); 317 } 318 319 /* update genres list (buffer should contain genre) */ 320 if (buffer[0]) AddGenre(hwnd, buffer); 321 322 /* write tag */ 323 SetTag(hwnd, data->filename, data->tags); 324} 325 326static void RemoveTag(HWND hwnd) 327{ 328 LOCALDATA *data = (LOCALDATA*)GetWindowLong(hwnd, GWL_USERDATA); 329 FLAC_plugin__tags_delete_all(data->tags); 330 331 SetDlgItemText(hwnd, IDC_TITLE, ""); 332 SetDlgItemText(hwnd, IDC_ARTIST, ""); 333 SetDlgItemText(hwnd, IDC_ALBUM, ""); 334 SetDlgItemText(hwnd, IDC_COMMENT, ""); 335 SetDlgItemText(hwnd, IDC_YEAR, ""); 336 SetDlgItemText(hwnd, IDC_TRACK, ""); 337 SetDlgItemText(hwnd, IDC_GENRE, ""); 338 339 SetTag(hwnd, data->filename, data->tags); 340} 341 342 343static INT_PTR CALLBACK InfoProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 344{ 345 switch (msg) 346 { 347 /* init */ 348 case WM_INITDIALOG: 349 SetWindowText(hwnd, infoTitle); 350 InitGenres(hwnd); 351 /* init fields */ 352 if (!InitInfoboxInfo(hwnd, (const char*)lParam)) 353 PostMessage(hwnd, WM_CLOSE, 0, 0); 354 return TRUE; 355 /* destroy */ 356 case WM_DESTROY: 357 { 358 LOCALDATA *data = (LOCALDATA*)GetWindowLong(hwnd, GWL_USERDATA); 359 FLAC_plugin__tags_destroy(&data->tags); 360 LocalFree(data); 361 DeinitGenres(hwnd, FALSE); 362 } 363 break; 364 /* commands */ 365 case WM_COMMAND: 366 switch (LOWORD(wParam)) 367 { 368 /* ok/cancel */ 369 case IDOK: 370 case IDCANCEL: 371 EndDialog(hwnd, LOWORD(wParam)); 372 return TRUE; 373 /* save */ 374 case IDC_UPDATE: 375 UpdateTag(hwnd); 376 break; 377 /* remove */ 378 case IDC_REMOVE: 379 RemoveTag(hwnd); 380 break; 381 } 382 break; 383 } 384 385 return 0; 386} 387 388/* 389 * Helpers 390 */ 391 392ULONGLONG FileSize(const char *fileName) 393{ 394 LARGE_INTEGER res; 395 HANDLE hFile = CreateFile(fileName, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 396 397 if (hFile == INVALID_HANDLE_VALUE) return 0; 398 res.LowPart = GetFileSize(hFile, &res.HighPart); 399 CloseHandle(hFile); 400 return res.QuadPart; 401} 402 403static __inline char *GetFileName(const char *fullname) 404{ 405 const char *c = fullname + strlen(fullname) - 1; 406 407 while (c > fullname) 408 { 409 if (*c=='\\' || *c=='/') 410 { 411 c++; 412 break; 413 } 414 c--; 415 } 416 417 return (char*)c; 418} 419 420void ReadTags(const char *fileName, FLAC__StreamMetadata **tags, BOOL forDisplay) 421{ 422 if(FLAC_plugin__tags_get(fileName, tags)) { 423 424 /* add file name */ 425 if (forDisplay) 426 { 427 char *c; 428 wchar_t *ucs2; 429 ucs2 = AnsiToWide(fileName); 430 FLAC_plugin__tags_set_tag_ucs2(*tags, "filepath", ucs2, /*replace_all=*/true); 431 free(ucs2); 432 433 strcpy(buffer, GetFileName(fileName)); 434 if (c = strrchr(buffer, '.')) *c = 0; 435 ucs2 = AnsiToWide(buffer); 436 FLAC_plugin__tags_set_tag_ucs2(*tags, "filename", ucs2, /*replace_all=*/true); 437 free(ucs2); 438 } 439 } 440} 441 442/* 443 * Front-end 444 */ 445 446void InitInfobox() 447{ 448 isNT = !(GetVersion() & 0x80000000); 449} 450 451void DeinitInfobox() 452{ 453 DeinitGenres(NULL, true); 454} 455 456void DoInfoBox(HINSTANCE inst, HWND hwnd, const char *filename) 457{ 458 DialogBoxParam(inst, MAKEINTRESOURCE(IDD_INFOBOX), hwnd, InfoProc, (LONG)filename); 459} 460