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