winamp/Src/Plugins/Library/ml_pmp/transcoder_imp.cpp
2024-09-24 14:54:57 +02:00

539 lines
15 KiB
C++

#include "api__ml_pmp.h"
#include "transcoder_imp.h"
#include "nu/ns_wc.h"
#include <shlwapi.h>
#include <strsafe.h>
#include <mmiscapi.h>
extern HWND CreateDummyWindow();
static std::vector<TranscoderImp*> transcoders;
LRESULT CALLBACK TranscodeMsgProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
if ( uMsg == WM_WA_IPC && ( lParam == IPC_CB_CONVERT_STATUS || lParam == IPC_CB_CONVERT_DONE ) )
{
for ( TranscoderImp *t : transcoders )
{
if ( t->cfs.callbackhwnd == hwnd )
{
t->TranscodeProgress( (int)wParam, lParam == IPC_CB_CONVERT_DONE );
break;
}
}
}
return 0;
}
static void fourccToString(unsigned int f, wchar_t * str) {
wchar_t s[4] = {(wchar_t)(f&0xFF),(wchar_t)((f>>8)&0xFF),(wchar_t)((f>>16)&0xFF),0};
wcsncpy(str,s,4);
CharLower(str);
}
static unsigned int stringToFourcc(const wchar_t * str) {
FOURCC cc = 0;
char *ccc = (char *)&cc;
// unrolled loop (this function gets called a lot on sync and autofill)
if (str[0])
{
ccc[0] = (char)str[0];
if (str[1])
{
ccc[1] = (char)str[1];
if (str[2])
{
ccc[2] = (char)str[2];
if (str[3])
{
ccc[3] = (char)str[3];
}
}
}
}
CharUpperBuffA(ccc, 4);
return cc;
}
static bool fourccEqual(unsigned int a, unsigned int b) {
if((a & 0xFF000000) == 0 || (b & 0xFF000000) == 0)
return (a & 0x00FFFFFF) == (b & 0x00FFFFFF);
return a == b;
}
class TranscodeProfileCache {
public:
unsigned int inputformat;
unsigned int outputformat;
int outputbitrate;
TranscodeProfileCache(unsigned int inputformat,unsigned int outputformat,int outputbitrate) :
inputformat(inputformat), outputformat(outputformat),outputbitrate(outputbitrate){}
};
static void enumProc(intptr_t user_data, const char *desc, int fourcc)
{
((FormatList *)user_data)->push_back(new EncodableFormat((unsigned int)fourcc,AutoWide(desc)));
}
static void BuildEncodableFormatsList(FormatList &list, HWND winampWindow, Device *device)
{
converterEnumFmtStruct e = {enumProc,(intptr_t)&list};
SendMessage(winampWindow,WM_WA_IPC,(WPARAM)&e,IPC_CONVERT_CONFIG_ENUMFMTS);
// filter out unacceptable formats
int i = list.size();
while (i--)
{
if (device && device->extraActions(DEVICE_VETO_ENCODER, list[i]->fourcc, 0, 0) == 1)
{
list.erase(list.begin() + i);
}
}
}
static CRITICAL_SECTION csTranscoder;
void TranscoderImp::init() {
InitializeCriticalSection(&csTranscoder);
}
void TranscoderImp::quit() {
DeleteCriticalSection(&csTranscoder);
}
TranscoderImp::TranscoderImp(HWND winampParent, HINSTANCE hInst, C_Config * config, Device *device)
: device(device), config(config), winampParent(winampParent), hInst(hInst)
{
EnterCriticalSection(&csTranscoder);
transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
transrate = !!config->ReadInt(L"transrate",0);
translossless = !!config->ReadInt(L"translossless",0);
TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
int current_fourcc = config->ReadInt(L"lastusedencoder", 0);
if (current_fourcc == 0)
{
// TODO: ask for default from plugin
config->WriteInt(L"lastusedencoder", ' A4M');
}
WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_PERCENT,caption,100);
ZeroMemory(&cfs,sizeof(convertFileStruct));
this->callback = NULL;
transcoders.push_back(this);
callbackhwnd = CreateDummyWindow();
StringCchCopy(inifile, ARRAYSIZE(inifile), config->GetIniFile());
WideCharToMultiByteSZ(CP_ACP, 0, inifile, -1, inifileA, MAX_PATH, 0, 0);
BuildEncodableFormatsList(formats, winampParent, device);
LeaveCriticalSection(&csTranscoder);
}
TranscoderImp::~TranscoderImp()
{
EnterCriticalSection(&csTranscoder);
//transcoders.eraseObject(this);
auto it = std::find(transcoders.begin(), transcoders.end(), this);
if (it != transcoders.end())
{
transcoders.erase(it);
}
//formats.deleteAll();
for (auto format : formats)
{
delete format;
}
formats.clear();
DestroyWindow(callbackhwnd);
LeaveCriticalSection(&csTranscoder);
}
void TranscoderImp::LoadConfigProfile(wchar_t *profile) {
}
void TranscoderImp::ReloadConfig()
{
//formats.deleteAll();
for (auto format : formats)
{
delete format;
}
formats.clear();
BuildEncodableFormatsList(formats, winampParent, device);
transratethresh = config->ReadInt(L"forcetranscodingbitrate",250);
transrate = !!config->ReadInt(L"transrate",0);
translossless = !!config->ReadInt(L"translossless",0);
TranscoderDisabled = !config->ReadInt(L"enableTranscoder",1);
}
void TranscoderImp::AddAcceptableFormat(unsigned int format)
{
outformats.push_back(format);
}
void TranscoderImp::AddAcceptableFormat(wchar_t *format)
{
outformats.push_back(stringToFourcc(format));
}
static bool FileExists(const wchar_t *file)
{
return GetFileAttributesW(file) != INVALID_FILE_ATTRIBUTES;
}
static int getFileLength(const wchar_t * file, HWND winampParent) { // returns length in seconds
basicFileInfoStructW b={0};
b.filename=file;
SendMessage(winampParent,WM_WA_IPC,(WPARAM)&b,IPC_GET_BASIC_FILE_INFOW);
return b.length;
}
static bool isFileLossless(const wchar_t * file) {
wchar_t ret[64] = {0};
if (AGAVE_API_METADATA && AGAVE_API_METADATA->GetExtendedFileInfo(file, L"lossless", ret, 64) && ret[0] == '1')
return true;
return false;
}
static int getFileBitrate(const wchar_t * file, HWND winampParent) { // returns bitrate in bits per second.
int secs = getFileLength(file,winampParent);
if(!secs) return 0;
FILE * f = _wfopen(file,L"rb");
int len = 0;
if(f) { fseek(f,0,2); len=ftell(f); fclose(f); }
return (len/secs)*8;
}
void TranscoderImp::GetTempFilePath(const wchar_t *ext, wchar_t *path) {
wchar_t dir[MAX_PATH] = {0};
GetTempPath(MAX_PATH,dir);
GetTempFileName(dir,L"transcode",0,path);
_wunlink(path);
wchar_t *e = wcsrchr(path,L'.');
if(e) *e=0;
wcscat(path,ext);
_wunlink(path);
}
void TranscoderImp::TranscodeProgress(int pc, bool done) {
if(!done) {
wchar_t buf[128] = {0};
StringCchPrintf(buf, ARRAYSIZE(buf), caption,pc);
if(callback) callback(callbackContext,buf);
}
else convertDone = done;
}
bool TranscoderImp::StartTranscode(unsigned int destformat, wchar_t *inputFile, wchar_t *outputFile, bool test) {
cfs.callbackhwnd = callbackhwnd;
cfs.sourcefile = _wcsdup(inputFile);
cfs.destfile = _wcsdup(outputFile);
cfs.destformat[0] = destformat;
cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
cfs.destformat[7] = (intptr_t)inifileA;
cfs.error = L"";
if(!SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILEW))
{
if(cfs.error && callback)
callback(callbackContext,cfs.error);
return false;
}
if(!test)
{
convertSetPriorityW csp = {&cfs,THREAD_PRIORITY_NORMAL};
SendMessage(winampParent, WM_WA_IPC, (WPARAM)&csp, IPC_CONVERT_SET_PRIORITYW);
TranscodeProgress(0,false);
}
return true;
}
void TranscoderImp::EndTranscode()
{
cfs.callbackhwnd = NULL;
SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERTFILE_END);
free(cfs.sourcefile);
free(cfs.destfile);
ZeroMemory(&cfs,sizeof(convertFileStruct));
}
bool TranscoderImp::TestTranscode(wchar_t * file, unsigned int destformat)
{
wchar_t tempfn[MAX_PATH], ext[5]=L".";
fourccToString(destformat,&ext[1]);
GetTempFilePath(ext,tempfn);
convertFileStructW cfs;
cfs.callbackhwnd = callbackhwnd;
cfs.sourcefile = file;
cfs.destfile = tempfn;
cfs.destformat[0] = destformat;
cfs.destformat[6] = mmioFOURCC('I','N','I',' ');
cfs.destformat[7] = (intptr_t)inifileA;
cfs.error = L"";
int v = SendMessage(winampParent,WM_WA_IPC,(WPARAM)&cfs,IPC_CONVERT_TEST);
_wunlink(tempfn);
return !!v;
}
bool TranscoderImp::FormatAcceptable(unsigned int format)
{
int l = outformats.size();
for(int i=0; i<l; i++)
{
if(fourccEqual(outformats[i], format))
return true;
}
return false;
}
bool TranscoderImp::FormatAcceptable(wchar_t * format)
{
return FormatAcceptable(stringToFourcc(format));
}
int TranscoderImp::GetOutputFormat(wchar_t * file, int *bitrate)
{
if (!FileExists(file))
return 0;
int fourcc = config->ReadInt(L"lastusedencoder",' A4M');
if (TestTranscode(file,fourcc))
{
char buf[100]="128";
convertConfigItem ccs={fourcc,"bitrate",buf,100, inifileA};
SendMessage(winampParent,WM_WA_IPC,(WPARAM)&ccs,IPC_CONVERT_CONFIG_GET_ITEM);
int br = atoi(buf)*1000;
if(bitrate) *bitrate = br;
return fourcc;
}
return 0;
}
bool TranscoderImp::ShouldTranscode(wchar_t * file)
{
if(TranscoderDisabled) return false;
wchar_t * ext = wcsrchr(file,L'.');
if(ext && FormatAcceptable(&ext[1])) {
if(transrate && getFileBitrate(file,winampParent) > 1000*transratethresh)
return true;
else if (translossless && isFileLossless(file))
return true;
else return false;
}
return true;
}
int TranscoderImp::CanTranscode(wchar_t * file, wchar_t * ext, int length)
{
if(TranscoderDisabled) return -1;
int bitrate;
unsigned int fmt = GetOutputFormat(file,&bitrate);
if(fmt) {
if(ext) {
ext[0]=L'.'; ext[1]=0;
char extA[8]=".";
convertConfigItem c = {fmt,"extension",&extA[1],6,AutoCharDup(config->GetIniFile())};
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&c,IPC_CONVERT_CONFIG_GET_ITEM);
if(extA[1]) wcsncpy(ext,AutoWide(extA), 10);
else fourccToString(fmt,&ext[1]);
free(c.configfile);
}
if (length <= 0)
length = getFileLength(file,winampParent);
return (bitrate/8) * length; // should transcode
}
return -1; // transcoding impossible
}
extern void filenameToItemRecord(wchar_t * file, itemRecordW * ice);
extern void copyTags(itemRecordW * in, wchar_t * out);
int TranscoderImp::TranscodeFile(wchar_t *inputFile, wchar_t *outputFile, int *killswitch, void (*callbackFunc)(void * callbackContext, wchar_t * status), void* callbackContext, wchar_t * caption)
{
if(caption) lstrcpyn(this->caption,caption,100);
this->callback = callbackFunc;
this->callbackContext = callbackContext;
convertDone = false;
int format = GetOutputFormat(inputFile);
if(!format) return -1;
if(!StartTranscode(format,inputFile,outputFile))
return -1;
while(!convertDone && !(*killswitch))
Sleep(50);
EndTranscode();
// copy the tags over
itemRecordW ice={0};
filenameToItemRecord(inputFile,&ice);
copyTags(&ice,outputFile);
freeRecord(&ice);
if(convertDone && callback)
callback(callbackContext,L"Done");
this->callback = NULL;
return convertDone?0:-1;
}
static void doConfigResizeChild(HWND parent, HWND child)
{
if (child)
{
RECT r;
GetWindowRect(GetDlgItem(parent, IDC_ENC_CONFIG), &r);
ScreenToClient(parent, (LPPOINT)&r);
SetWindowPos(child, 0, r.left, r.top, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
ShowWindow(child, SW_SHOWNA);
}
}
struct ConfigTranscoderParam
{
ConfigTranscoderParam()
{
winampParent=0;
configfile=0;
memset(&ccs, 0, sizeof(ccs));
config=0;
dev=0;
}
~ConfigTranscoderParam()
{
//list.deleteAll();
for (auto l : list)
{
delete l;
}
list.clear();
free((char*)ccs.extra_data[7]);
free(configfile);
}
HWND winampParent;
wchar_t *configfile;
FormatList list;
convertConfigStruct ccs;
C_Config * config;
Device *dev;
};
static INT_PTR CALLBACK config_dlgproc_transcode_advanced(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
static ConfigTranscoderParam *p;
switch(uMsg)
{
case WM_INITDIALOG:
p = (ConfigTranscoderParam *)lParam;
SetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,p->config->ReadString(L"forcetranscodingbitrate",L"250"));
if(p->config->ReadInt(L"transrate",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE,BST_CHECKED);
if(p->config->ReadInt(L"translossless",0)) CheckDlgButton(hwndDlg,IDC_CHECK_FORCE_LOSSLESS,BST_CHECKED);
break;
case WM_COMMAND:
switch(LOWORD(wParam)) {
case IDOK:
{
wchar_t buf[10]=L"";
GetDlgItemText(hwndDlg,IDC_FORCE_BITRATE,buf,10);
p->config->WriteString(L"forcetranscodingbitrate",buf);
p->config->WriteInt(L"transrate",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE)?1:0);
p->config->WriteInt(L"translossless",IsDlgButtonChecked(hwndDlg,IDC_CHECK_FORCE_LOSSLESS)?1:0);
}
case IDCANCEL:
EndDialog(hwndDlg,0);
break;
}
break;
}
return 0;
}
BOOL TranscoderImp::transcodeconfig_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
static ConfigTranscoderParam *p;
switch(uMsg) {
case WM_INITDIALOG:
{
p = (ConfigTranscoderParam *)lParam;
if(p->config->ReadInt(L"enableTranscoder",1)) CheckDlgButton(hwndDlg,IDC_ENABLETRANSCODER,BST_CHECKED);
BuildEncodableFormatsList(p->list, p->winampParent, p->dev);
p->ccs.hwndParent = hwndDlg;
int encdef = p->config->ReadInt(L"lastusedencoder",0);
for(size_t i=0; i < p->list.size(); i++)
{
EncodableFormat * f = p->list[i];
int a = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_ADDSTRING, 0, (LPARAM)f->desc);
SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETITEMDATA, (WPARAM)a, (LPARAM)f);
if(i==0 && encdef == 0) encdef = f->fourcc;
if(f->fourcc == encdef)
{
SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_SETCURSEL, (WPARAM)a, 0);
p->ccs.format = f->fourcc;
}
}
p->ccs.hwndParent = hwndDlg;
HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
doConfigResizeChild(hwndDlg, h);
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_ENABLETRANSCODER:
p->config->WriteInt(L"enableTranscoder",IsDlgButtonChecked(hwndDlg,IDC_ENABLETRANSCODER)?1:0);
break;
case IDC_ADVANCED:
return WASABI_API_DIALOGBOXPARAMW(IDD_CONFIG_TRANSCODING_ADVANCED,hwndDlg,config_dlgproc_transcode_advanced,(LPARAM)p);
case IDC_ENCFORMAT:
if (HIWORD(wParam) != CBN_SELCHANGE) return 0;
{
int sel = SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETCURSEL, 0, 0);
if (sel != CB_ERR)
{
SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
EncodableFormat * f = (EncodableFormat *)SendDlgItemMessage(hwndDlg, IDC_ENCFORMAT, CB_GETITEMDATA, sel, 0);
p->ccs.format = f->fourcc;
HWND h = (HWND)SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG);
doConfigResizeChild(hwndDlg, h);
p->config->WriteInt(L"lastusedencoder",p->ccs.format);
}
}
break;
}
break;
case WM_DESTROY:
{
p->config->WriteInt(L"lastusedencoder",p->ccs.format);
SendMessage(p->winampParent, WM_WA_IPC, (WPARAM)&p->ccs, IPC_CONVERT_CONFIG_END);
delete p;
for( TranscoderImp *l_transcoder : transcoders )
l_transcoder->ReloadConfig();
}
break;
}
return 0;
}
void* TranscoderImp::ConfigureTranscoder(wchar_t * configProfile, HWND winampParent, C_Config * config, Device *dev)
{
ConfigTranscoderParam * p = new ConfigTranscoderParam;
p->config = config;
p->winampParent=winampParent;
p->configfile=_wcsdup(config->GetIniFile());
p->ccs.extra_data[6] = mmioFOURCC('I','N','I',' ');
p->ccs.extra_data[7] = (int)AutoCharDup(p->configfile);
p->dev = dev;
return p;
}