Add text/playlist tags in winamp

This commit is contained in:
bnnm 2018-11-03 19:28:05 +01:00
parent 2eba5abc26
commit 55bff5984f
3 changed files with 206 additions and 35 deletions

View File

@ -12,6 +12,7 @@
#ifdef _MSC_VER
#define _CRT_SECURE_NO_DEPRECATE
#endif
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
@ -21,6 +22,7 @@
#include <ctype.h>
#include "../src/vgmstream.h"
#include "../src/plugins.h"
#include "in2.h"
#include "wa_ipc.h"
#include "ipc_pe.h"
@ -39,13 +41,14 @@
/* ************************************* */
/* plugin main (declared at the bottom of this file) */
/* plugin module (declared at the bottom of this file) */
In_Module input_module;
DWORD WINAPI __stdcall decode(void *arg);
/* Winamp Play extension list, to accept and associate extensions in Windows */
#define EXTENSION_LIST_SIZE (0x2000 * 6)
#define EXT_BUFFER_SIZE 200
/* fixed list to simplify but could also malloc/free on init/close */
char working_extension_list[EXTENSION_LIST_SIZE] = {0};
/* defaults */
@ -58,6 +61,7 @@ typedef struct {
int thread_priority;
int disable_subsongs;
int downmix_channels;
int tagfile_disable;
} winamp_settings;
/* current song settings */
@ -89,6 +93,7 @@ int stream_length_samples = 0;
int fade_samples = 0;
int output_channels = 0;
const char* tagfile_name = "!tags.m3u"; //todo make configurable
in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
@ -121,8 +126,8 @@ in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
#define wa_L(x) x
#endif
/* converts from utf16 to utf8 (if unicode is active) */
static void wa_wchar_to_char(char *dst, size_t dstsize, const in_char *wsrc) {
/* converts from utf16 to utf8 (if unicode is on) */
static void wa_ichar_to_char(char *dst, size_t dstsize, const in_char *wsrc) {
#ifdef UNICODE_INPUT_PLUGIN
/* converto to UTF8 codepage, default separate bytes, source wstr, wstr lenght, */
//int size_needed = WideCharToMultiByte(CP_UTF8,0, src,-1, NULL,0, NULL, NULL);
@ -132,8 +137,8 @@ static void wa_wchar_to_char(char *dst, size_t dstsize, const in_char *wsrc) {
#endif
}
/* converts from utf8 to utf16 (if unicode is active) */
static void wa_char_to_wchar(in_char *wdst, size_t wdstsize, const char *src) {
/* converts from utf8 to utf16 (if unicode is on) */
static void wa_char_to_ichar(in_char *wdst, size_t wdstsize, const char *src) {
#ifdef UNICODE_INPUT_PLUGIN
//int size_needed = MultiByteToWideChar(CP_UTF8,0, src,-1, NULL,0);
MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize);
@ -142,6 +147,24 @@ static void wa_char_to_wchar(in_char *wdst, size_t wdstsize, const char *src) {
#endif
}
/* copies from utf16 to utf16 (if unicode is active) */
static void wa_wchar_to_ichar(in_char *wdst, size_t wdstsize, const wchar_t *src) {
#ifdef UNICODE_INPUT_PLUGIN
wcscpy(wdst,src);
#else
strcpy(wdst,src); //todo ???
#endif
}
/* copies from utf16 to utf16 */
static void wa_char_to_wchar(wchar_t *wdst, size_t wdstsize, const char *src) {
#ifdef UNICODE_INPUT_PLUGIN
MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize);
#else
strcpy(wdst,src); //todo ???
#endif
}
/* opens a utf16 (unicode) path */
static FILE* wa_fopen(const in_char *wpath) {
#ifdef UNICODE_INPUT_PLUGIN
@ -216,7 +239,7 @@ static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const fi
}
/* STREAMFILEs carry char/UTF8 names, convert to wchar for Winamp */
wa_char_to_wchar(wpath,PATH_LIMIT, filename);
wa_char_to_ichar(wpath,PATH_LIMIT, filename);
return open_winamp_streamfile_by_wpath(wpath);
}
@ -265,7 +288,7 @@ static STREAMFILE *open_winamp_streamfile_by_wpath(const in_char *wpath) {
if (!infile) return NULL;
/* convert to UTF-8 if needed for internal use */
wa_wchar_to_char(path,PATH_LIMIT, wpath);
wa_ichar_to_char(path,PATH_LIMIT, wpath);
streamFile = open_winamp_streamfile_by_file(infile,path);
if (!streamFile) {
@ -336,6 +359,7 @@ static void cfg_char_to_wchar(TCHAR *wdst, size_t wdstsize, const char *src) {
#define DEFAULT_IGNORE_LOOP 0
#define DEFAULT_DISABLE_SUBSONGS 0
#define DEFAULT_DOWNMIX_CHANNELS 0
#define DEFAULT_TAGFILE_DISABLE 0
#define INI_ENTRY_FADE_SECONDS TEXT("fade_seconds")
#define INI_ENTRY_FADE_DELAY_SECONDS TEXT("fade_delay")
@ -345,6 +369,7 @@ static void cfg_char_to_wchar(TCHAR *wdst, size_t wdstsize, const char *src) {
#define INI_ENTRY_IGNORE_LOOP TEXT("ignore_loop")
#define INI_ENTRY_DISABLE_SUBSONGS TEXT("disable_subsongs")
#define INI_ENTRY_DOWNMIX_CHANNELS TEXT("downmix_channels")
#define INI_ENTRY_TAGFILE_DISABLE TEXT("tagfile_disable")
TCHAR *priority_strings[] = {
TEXT("Idle"),
@ -458,6 +483,7 @@ static void load_config() {
settings.downmix_channels = DEFAULT_DOWNMIX_CHANNELS;
}
settings.tagfile_disable = GetPrivateProfileInt(CONFIG_APP_NAME,INI_ENTRY_TAGFILE_DISABLE,DEFAULT_TAGFILE_DISABLE,iniFile);
}
/* config dialog handler */
@ -508,6 +534,9 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
cfg_sprintf(buf, TEXT("%d"),settings.downmix_channels);
SetDlgItemText(hDlg,IDC_DOWNMIX_CHANNELS,buf);
if (settings.tagfile_disable)
CheckDlgButton(hDlg,IDC_TAGFILE_DISABLE,BST_CHECKED);
break;
case WM_COMMAND: /* button presses */
@ -598,6 +627,10 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
settings.downmix_channels = temp_downmix_channels;
cfg_sprintf(buf, TEXT("%d"),settings.downmix_channels);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX_CHANNELS,buf,iniFile);
settings.tagfile_disable = (IsDlgButtonChecked(hDlg,IDC_TAGFILE_DISABLE) == BST_CHECKED);
cfg_sprintf(buf, TEXT("%d"),settings.tagfile_disable);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_TAGFILE_DISABLE,buf,iniFile);
}
EndDialog(hDlg,TRUE);
@ -628,6 +661,7 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
CheckDlgButton(hDlg,IDC_DISABLE_SUBSONGS,BST_UNCHECKED);
SetDlgItemText(hDlg,IDC_DOWNMIX_CHANNELS,DEFAULT_DOWNMIX_CHANNELS);
CheckDlgButton(hDlg,IDC_TAGFILE_DISABLE,BST_UNCHECKED);
break;
default:
@ -706,19 +740,17 @@ static int split_subsongs(const in_char * filename, int stream_index, VGMSTREAM
/* remove current file from the playlist */
SendMessage(hPlaylistWindow, WM_WA_IPC, IPC_PE_DELETEINDEX, playlist_index);
/* autoplay doesn't always advance to the first unpacked track, manually fails too */
/* autoplay doesn't always advance to the first unpacked track, but manually fails somehow */
//SendMessage(input_module.hMainWindow,WM_WA_IPC,playlist_index,IPC_SETPLAYLISTPOS);
//SendMessage(input_module.hMainWindow,WM_WA_IPC,0,IPC_STARTPLAY);
return 1;
}
/* parses a modified filename ('fakename') extracting tags parameters (NULL tag for first = filename) */
static int parse_fn_string(const in_char * fn, const in_char * tag, in_char * dst, int dst_size) {
in_char *end;
const in_char *end = wa_strchr(fn,'|');
end = wa_strchr(fn,'|');
if (tag==NULL) {
wa_strcpy(dst,fn);
if (end)
@ -726,14 +758,12 @@ static int parse_fn_string(const in_char * fn, const in_char * tag, in_char * ds
return 1;
}
//todo actually find + read tags
dst[0] = '\0';
return 0;
}
static int parse_fn_int(const in_char * fn, const in_char * tag, int * num) {
in_char * start = wa_strchr(fn,'|');
const in_char * start = wa_strchr(fn,'|');
//todo actually find + read tags
if (start > 0) {
wa_sscanf(start+1, wa_L("$s=%i "), num);
return 1;
@ -767,13 +797,13 @@ static void add_extension(int length, char * dst, const char * ext) {
ext_len = strlen(ext);
/* find end of dst (double \0), saved in i */
for (i=0; i<length-2 && (dst[i] || dst[i+1]); i++)
for (i = 0; i < length-2 && (dst[i] || dst[i+1]); i++)
;
/* check if end reached or not enough room to add */
if (i == length-2 || i + EXT_BUFFER_SIZE+2 > length-2 || ext_len * 3 + 20+2 > EXT_BUFFER_SIZE) {
dst[i]='\0';
dst[i+1]='\0';
dst[i] = '\0';
dst[i+1] = '\0';
return;
}
@ -781,16 +811,17 @@ static void add_extension(int length, char * dst, const char * ext) {
i++;
/* uppercase ext */
for (j=0; j < ext_len; j++)
for (j = 0; j < ext_len; j++)
ext_upp[j] = toupper(ext[j]);
ext_upp[j] = '\0';
/* copy new extension + double null terminate */
written = sprintf(buf, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0'); /*ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
for (j=0; j < written; i++,j++)
/* ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
written = sprintf(buf, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0');
for (j = 0; j < written; i++,j++)
dst[i] = buf[j];
dst[i]='\0';
dst[i+1]='\0';
dst[i] = '\0';
dst[i+1] = '\0';
}
/* Creates Winamp's extension list, a single string that ends with \0\0.
@ -837,7 +868,7 @@ static void get_title(in_char * dst, int dst_size, const in_char * fn, VGMSTREAM
/* show name, but not for the base stream */
if (infostream && infostream->stream_name[0] != '\0' && stream_index > 0) {
in_char stream_name[PATH_LIMIT];
wa_char_to_wchar(stream_name, PATH_LIMIT, infostream->stream_name);
wa_char_to_ichar(stream_name, PATH_LIMIT, infostream->stream_name);
wa_snprintf(buffer,PATH_LIMIT, wa_L(" (%s)"), stream_name);
wa_strcat(dst,buffer);
}
@ -1198,7 +1229,8 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
if (length_in_ms) {
*length_in_ms = -1000;
if (infostream) {
int num_samples = get_vgmstream_play_samples(infoconfig.song_loop_count,infoconfig.song_fade_time,infoconfig.song_fade_delay,infostream);
const int num_samples = get_vgmstream_play_samples(
infoconfig.song_loop_count,infoconfig.song_fade_time,infoconfig.song_fade_delay,infostream);
*length_in_ms = num_samples * 1000LL /infostream->sample_rate;
}
}
@ -1214,8 +1246,8 @@ void winamp_EQSet(int on, char data[10], int preamp) {
/* the decode thread */
DWORD WINAPI __stdcall decode(void *arg) {
int max_buffer_samples = sizeof(sample_buffer) / sizeof(sample_buffer[0]) / 2 / vgmstream->channels;
int max_samples = stream_length_samples;
const int max_buffer_samples = sizeof(sample_buffer) / sizeof(sample_buffer[0]) / 2 / vgmstream->channels;
const int max_samples = stream_length_samples;
while (!decode_abort) {
int samples_to_do;
@ -1284,11 +1316,11 @@ DWORD WINAPI __stdcall decode(void *arg) {
if (vgmstream->loop_flag && fade_samples > 0 && !settings.loop_forever) {
int samples_into_fade = decode_pos_samples - (stream_length_samples - fade_samples);
if (samples_into_fade + samples_to_do > 0) {
int j,k;
for (j=0; j < samples_to_do; j++, samples_into_fade++) {
int j, k;
for (j = 0; j < samples_to_do; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
for (k=0; k < vgmstream->channels; k++) {
const double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
for (k = 0; k < vgmstream->channels; k++) {
sample_buffer[j*vgmstream->channels+k] =
(short)(sample_buffer[j*vgmstream->channels+k]*fadedness);
}
@ -1385,6 +1417,134 @@ In_Module input_module = {
0 /* outMod */
};
__declspec( dllexport ) In_Module * winampGetInModule2() {
__declspec(dllexport) In_Module * winampGetInModule2() {
return &input_module;
}
/* Winamp calls repeatedly calls this for every tag currently used in the Advanced Title Formatting (ATF)
* config (though actual tag names may differ slightly), 'metadata' being the requested key.
* Tags/ret are assumed to be UTF-8 then converted to before output. Returns 0 on failure/tag not found.
* May be called again after certain actions (adding file to playlist, Play, GetFileInfo, etc), and
* doesn't seem the plugin can tell Winamp all tags it supports at once. */
//todo unicode stuff could be improved... probably
static int winampGetExtendedFileInfo_common(in_char* filename, char *metadata, char* ret, int retlen) {
STREAMFILE *streamFile = NULL;
STREAMFILE *tagFile = NULL;
VGMSTREAM_TAGS tag;
char filename_utf8[PATH_LIMIT];
int tag_found = 0;
if (settings.tagfile_disable)
goto fail;
/* always called (value in ms), must return ok so other tags get called */
if (strcasecmp(metadata, "length") == 0) {
strcpy(ret, "0");//todo should export but shows GetFileInfo's ms if not provided
return 1;
}
//todo optimize and only load tagfile/tags once, rather than re-parsing every time
// (filename can be any file in the playlist though, may mix multiple folders)
// maybe call parse_tags and save just tags, since winamp only knows a few and calls them multiple times,
// and save for currentstream_tags and infostream_tags
// (but then again infostream is parsed every time, and this works fast enough in +10y.o. PCs)
streamFile = open_winamp_streamfile_by_wpath(filename);
if (!streamFile) goto fail;
tagFile = open_streamfile_by_filename(streamFile, tagfile_name);
if (!streamFile) goto fail;
#if 0
/* special case to fill WA5's unified dialog */
if (strcasecmp(metadata, "formatinformation") == 0) {
generate_format_string(...);
}
#endif
wa_ichar_to_char(filename_utf8, PATH_LIMIT, filename);
/* find possible tag */
vgmstream_tags_reset(&tag, filename_utf8);
while (vgmstream_tags_next_tag(&tag, tagFile)) {
if (strcasecmp(metadata,tag.key) == 0) {
strncpy(ret, tag.val, retlen); //todo use something better
tag_found = 1;
//break; /* allow later/repeated tags to overwrite */
}
}
if (!tag_found)
goto fail;
close_streamfile(streamFile);
close_streamfile(tagFile);
return 1;
fail:
close_streamfile(streamFile);
close_streamfile(tagFile);
return 0;
}
/* for Winamp 5.24 */
__declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metadata, char *ret, int retlen) {
in_char filename_wchar[PATH_LIMIT];
int ok;
wa_char_to_ichar(filename_wchar, PATH_LIMIT, filename);
ok = winampGetExtendedFileInfo_common(filename_wchar, metadata, ret, retlen);
if (ok == 0)
return 0;
return 1;
}
/* for Winamp 5.3+ */
__declspec (dllexport) int winampGetExtendedFileInfoW(wchar_t *filename, char *metadata, wchar_t *ret, int retlen) {
in_char filename_wchar[PATH_LIMIT];
char ret_utf8[2048];
int ok;
wa_wchar_to_ichar(filename_wchar, PATH_LIMIT, filename);
ok = winampGetExtendedFileInfo_common(filename_wchar, metadata, ret_utf8, sizeof(ret_utf8));
if (ok == 0)
return 0;
wa_char_to_wchar(ret, PATH_LIMIT, ret_utf8);
return 1;
}
/* return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox)
* if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")! */
__declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn) {
return 0;
}
__declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
/* may uninstall without restart as we aren't subclassing */
return IN_PLUGIN_UNINSTALL_NOW;
}
/* winamp sekrit exports: */
/*
EXPORTS
winampGetInModule2 @1
winampGetExtendedFileInfo @2
winampGetExtendedFileInfoW @3
winampAddUnifiedFileInfoPane @4
winampUseUnifiedFileInfoDlg @5
winampGetExtendedRead_close @6
winampGetExtendedRead_getData @7
winampGetExtendedRead_open @8
winampGetExtendedRead_openW @9
winampGetExtendedRead_setTime @10
winampUninstallPlugin @11
*/

View File

@ -10,3 +10,4 @@
#define IDC_DEFAULT_BUTTON 1008
#define IDC_DISABLE_SUBSONGS 1009
#define IDC_DOWNMIX_CHANNELS 1011
#define IDC_TAGFILE_DISABLE 1012

View File

@ -9,25 +9,35 @@ STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "in_vgmstream configuration"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
// right column
DEFPUSHBUTTON "OK",IDOK,129,7,50,14
PUSHBUTTON "Cancel",IDCANCEL,129,24,50,14
PUSHBUTTON "Default",IDC_DEFAULT_BUTTON,129,41,50,14
LTEXT "Thread Priority",IDC_STATIC,110,60,46,8
CONTROL "Slider1",IDC_THREAD_PRIORITY_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,116,76,77,10
CTEXT "DATARIFIC",IDC_THREAD_PRIORITY_TEXT,116,92,77,18
// left column
LTEXT "Loop Count",IDC_STATIC,7,10,44,12
EDITTEXT IDC_LOOP_COUNT,52,7,39,14,ES_AUTOHSCROLL
LTEXT "Fade Length",IDC_STATIC,7,25,41,8
EDITTEXT IDC_FADE_SECONDS,52,23,39,14,ES_AUTOHSCROLL
LTEXT "seconds",IDC_STATIC,93,25,29,11
LTEXT "Fade Delay",IDC_STATIC,7,40,41,8
EDITTEXT IDC_FADE_DELAY_SECONDS,52,38,39,14,ES_AUTOHSCROLL
LTEXT "seconds",IDC_STATIC,93,40,29,11
CONTROL "Loop normally",IDC_LOOP_NORMALLY,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,57,77,10
CONTROL "Loop forever",IDC_LOOP_FOREVER,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,70,77,10
CONTROL "Ignore looping",IDC_IGNORE_LOOP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,83,77,10
CONTROL "Disable subsongs",IDC_DISABLE_SUBSONGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,87,10
LTEXT "Downmix",IDC_STATIC,7,115,48,12
EDITTEXT IDC_DOWNMIX_CHANNELS,52,112,37,14,ES_AUTOHSCROLL
LTEXT "Thread Priority",IDC_STATIC,21,132,46,8
CONTROL "Slider1",IDC_THREAD_PRIORITY_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,7,140,77,10
CTEXT "DATARIFIC",IDC_THREAD_PRIORITY_TEXT,7,153,77,18
CONTROL "Disable tagfile",IDC_TAGFILE_DISABLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,131,87,10
END