diff --git a/LunaTranslator/LunaTranslator/gui/dialog_savedgame.py b/LunaTranslator/LunaTranslator/gui/dialog_savedgame.py index 22944533..c19b59b4 100644 --- a/LunaTranslator/LunaTranslator/gui/dialog_savedgame.py +++ b/LunaTranslator/LunaTranslator/gui/dialog_savedgame.py @@ -3066,7 +3066,8 @@ def getalistname(parent, callback, skipid=False, skipidid=None): }, ], ) - else: + elif len(__uid): + callback(__uid[0]) diff --git a/LunaTranslator/LunaTranslator/gui/showword.py b/LunaTranslator/LunaTranslator/gui/showword.py index 9debceb5..7894b980 100644 --- a/LunaTranslator/LunaTranslator/gui/showword.py +++ b/LunaTranslator/LunaTranslator/gui/showword.py @@ -347,28 +347,8 @@ class AnkiWindow(QWidget): @threader def simulate_key(self, i): - def __internal__keystring(i): - try: - for _ in (0,): - - if not gobject.baseobject.textsource: - break - - gameuid = gobject.baseobject.textsource.gameuid - if not gameuid: - break - if savehook_new_data[gameuid]["follow_default_ankisettings"]: - break - if not savehook_new_data[gameuid][f"anki_simulate_key_{i}_use"]: - return None - return savehook_new_data[gameuid][ - f"anki_simulate_key_{i}_keystring" - ] - except: - pass - return globalconfig["ankiconnect"]["simulate_key"][i]["keystring"] try: - keystring = __internal__keystring(i) + keystring = globalconfig["ankiconnect"]["simulate_key"][i]["keystring"] except: return if not keystring: @@ -387,10 +367,10 @@ class AnkiWindow(QWidget): for mode in modes: windows.keybd_event(mode, 0, windows.KEYEVENTF_KEYUP, 0) - def startorendrecord(self, target: QLineEdit, idx): + def startorendrecord(self, ii, target: QLineEdit, idx): if idx == 1: self.recorder = loopbackrecorder() - self.simulate_key(idx) + self.simulate_key(ii) else: self.recorder.end(callback=target.setText) @@ -428,11 +408,11 @@ class AnkiWindow(QWidget): self.remarks = FQPlainTextEdit() recordbtn1 = statusbutton(icons=["fa.microphone", "fa.stop"], colors=["", ""]) recordbtn1.statuschanged.connect( - functools.partial(self.startorendrecord, self.audiopath) + functools.partial(self.startorendrecord, 1, self.audiopath) ) recordbtn2 = statusbutton(icons=["fa.microphone", "fa.stop"], colors=["", ""]) recordbtn2.statuschanged.connect( - functools.partial(self.startorendrecord, self.audiopath_sentence) + functools.partial(self.startorendrecord, 2, self.audiopath_sentence) ) self.recordbtn1 = recordbtn1 self.recordbtn2 = recordbtn2 diff --git a/LunaTranslator/LunaTranslator/myutils/utils.py b/LunaTranslator/LunaTranslator/myutils/utils.py index 2fb519b9..2cd9ebc0 100644 --- a/LunaTranslator/LunaTranslator/myutils/utils.py +++ b/LunaTranslator/LunaTranslator/myutils/utils.py @@ -5,6 +5,8 @@ import socket, gobject, uuid, subprocess, functools import ctypes, importlib, json import ctypes.wintypes from qtsymbols import * +from ctypes import CDLL, c_void_p, CFUNCTYPE, c_size_t, cast, c_char, POINTER +from ctypes.wintypes import HANDLE from traceback import print_exc from myutils.config import ( globalconfig, @@ -759,36 +761,65 @@ def checkmd5reloadmodule(filename, module): return False, globalcachedmodule.get(key, {}).get("module", None) +class audiocapture: + def __datacollect(self, ptr, size): + self.data = cast(ptr, POINTER(c_char))[:size] + self.stoped.release() + + def __mutexcb(self, mutex): + self.mutex = mutex + + def stop(self): + _ = self.mutex + if _: + self.mutex = None + self.StopCaptureAsync(_) + self.stoped.acquire() + return self.data + + def __del__(self): + self.stop() + + def __init__(self) -> None: + + loopbackaudio = CDLL(gobject.GetDllpath("loopbackaudio.dll")) + StartCaptureAsync = loopbackaudio.StartCaptureAsync + StartCaptureAsync.argtypes = c_void_p, c_void_p + StartCaptureAsync.restype = HANDLE + StopCaptureAsync = loopbackaudio.StopCaptureAsync + StopCaptureAsync.argtypes = (HANDLE,) + self.StopCaptureAsync = StopCaptureAsync + self.mutex = None + self.stoped = threading.Lock() + self.stoped.acquire() + self.data = None + self.cb1 = CFUNCTYPE(None, c_void_p, c_size_t)(self.__datacollect) + self.cb2 = CFUNCTYPE(None, c_void_p)(self.__mutexcb) + threading.Thread(target=StartCaptureAsync, args=(self.cb1, self.cb2)).start() + + class loopbackrecorder: def __init__(self): - self.file = gobject.gettempdir(str(time.time()) + ".wav") try: - self.waitsignal = str(time.time()) - cmd = './files/plugins/loopbackaudio.exe "{}" "{}"'.format( - self.file, self.waitsignal - ) - self.engine = subproc_w(cmd, name=str(uuid.uuid4())) + self.capture = audiocapture() except: - print_exc() + self.capture = None @threader def end(self, callback): - windows.SetEvent( - windows.AutoHandle(windows.CreateEvent(False, False, self.waitsignal)) - ) - self.engine.wait() - filewav = self.file - if os.path.exists(filewav) == False: - callback("") - return - with open(filewav, "rb") as ff: - wav = ff.read() + if not self.capture: + return callback("") + wav = self.capture.stop() + if not wav: + return callback("") mp3 = winsharedutils.encodemp3(wav) - if mp3: - filemp3 = filewav[:-3] + "mp3" - with open(filemp3, "wb") as ff: - ff.write(mp3) - os.remove(filewav) - callback(filemp3) + if not mp3: + file = gobject.gettempdir(str(time.time()) + ".wav") + with open(file, "wb") as ff: + ff.write(wav) + callback(file) else: - callback(filewav) + file = gobject.gettempdir(str(time.time()) + ".mp3") + with open(file, "wb") as ff: + ff.write(mp3) + callback(file) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 44dd5105..daaa0232 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -28,8 +28,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/version) include(generate_product_version) set(VERSION_MAJOR 5) -set(VERSION_MINOR 25) -set(VERSION_PATCH 1) +set(VERSION_MINOR 26) +set(VERSION_PATCH 0) add_library(pch pch.cpp) target_precompile_headers(pch PUBLIC pch.h) diff --git a/plugins/applicationloopbackaudio/CMakeLists.txt b/plugins/applicationloopbackaudio/CMakeLists.txt index b344a417..f26077fa 100644 --- a/plugins/applicationloopbackaudio/CMakeLists.txt +++ b/plugins/applicationloopbackaudio/CMakeLists.txt @@ -10,13 +10,8 @@ generate_product_version( VERSION_MINOR ${VERSION_MINOR} VERSION_PATCH ${VERSION_PATCH} ) - - -if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) + -else() - - add_executable(loopbackaudio runer.cpp LoopbackCapture.cpp ${versioninfo}) + add_library(loopbackaudio MODULE runer.cpp LoopbackCapture.cpp ${versioninfo}) target_precompile_headers(loopbackaudio REUSE_FROM pch) - target_link_libraries(loopbackaudio Mfplat mfuuid ) -endif() + target_link_libraries(loopbackaudio Mfplat mfuuid ) \ No newline at end of file diff --git a/plugins/applicationloopbackaudio/LoopbackCapture.cpp b/plugins/applicationloopbackaudio/LoopbackCapture.cpp index 3465340f..5bd8a785 100644 --- a/plugins/applicationloopbackaudio/LoopbackCapture.cpp +++ b/plugins/applicationloopbackaudio/LoopbackCapture.cpp @@ -47,17 +47,16 @@ CLoopbackCapture::~CLoopbackCapture() MFUnlockWorkQueue(m_dwQueueID); } } -typedef HRESULT (STDAPICALLTYPE *ActivateAudioInterfaceAsync_t)( +typedef HRESULT(STDAPICALLTYPE *ActivateAudioInterfaceAsync_t)( _In_ LPCWSTR deviceInterfacePath, _In_ REFIID riid, _In_opt_ PROPVARIANT *activationParams, _In_ IActivateAudioInterfaceCompletionHandler *completionHandler, - _COM_Outptr_ IActivateAudioInterfaceAsyncOperation **activationOperation - ); + _COM_Outptr_ IActivateAudioInterfaceAsyncOperation **activationOperation); HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includeProcessTree) { return SetDeviceStateErrorIfFailed([&]() -> HRESULT - { + { AUDIOCLIENT_ACTIVATION_PARAMS audioclientActivationParams = {}; audioclientActivationParams.ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK; audioclientActivationParams.ProcessLoopbackParams.ProcessLoopbackMode = includeProcessTree ? @@ -77,8 +76,7 @@ HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includePr // Wait for activation completion m_hActivateCompleted.wait(); - return m_activateResult; - }()); + return m_activateResult; }()); } // @@ -87,10 +85,10 @@ HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includePr // Callback implementation of ActivateAudioInterfaceAsync function. This will be called on MTA thread // when results of the activation are available. // -HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperation* operation) +HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *operation) { - m_activateResult = SetDeviceStateErrorIfFailed([&]()->HRESULT - { + m_activateResult = SetDeviceStateErrorIfFailed([&]() -> HRESULT + { // Check for a successful activation result HRESULT hrActivateResult = E_UNEXPECTED; wil::com_ptr_nothrow punkAudioInterface; @@ -135,8 +133,7 @@ HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperatio // Everything is ready. m_DeviceState = DeviceState::Initialized; - return S_OK; - }()); + return S_OK; }()); // Let ActivateAudioInterface know that m_activateResult has the result of the activation attempt. m_hActivateCompleted.SetEvent(); @@ -150,10 +147,8 @@ HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperatio // HRESULT CLoopbackCapture::CreateWAVFile() { - return SetDeviceStateErrorIfFailed([&]()->HRESULT - { - m_hFile.reset(CreateFile(m_outputFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); - RETURN_LAST_ERROR_IF(!m_hFile); + return SetDeviceStateErrorIfFailed([&]() -> HRESULT + { // Create and write the WAV header @@ -166,25 +161,23 @@ HRESULT CLoopbackCapture::CreateWAVFile() sizeof(m_CaptureFormat) // Size of fmt chunk }; DWORD dwBytesWritten = 0; - RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), header, sizeof(header), &dwBytesWritten, NULL)); - - m_cbHeaderSize += dwBytesWritten; + std::lock_guard _(bufferlock); + buffer+=std::string((char*)header, sizeof(header)); + m_cbHeaderSize += sizeof(header); // 2. The fmt sub-chunk WI_ASSERT(m_CaptureFormat.cbSize == 0); - RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_CaptureFormat, sizeof(m_CaptureFormat), &dwBytesWritten, NULL)); - m_cbHeaderSize += dwBytesWritten; + buffer+=std::string((char*) &m_CaptureFormat, sizeof(m_CaptureFormat)); + m_cbHeaderSize += sizeof(m_CaptureFormat); // 3. The data sub-chunk DWORD data[] = { FCC('data'), 0 }; // Start of 'data' chunk - RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), data, sizeof(data), &dwBytesWritten, NULL)); - m_cbHeaderSize += dwBytesWritten; + buffer+=std::string((char*) data, sizeof(data)); + m_cbHeaderSize += sizeof(data); - return S_OK; - }()); + return S_OK; }()); } - // // FixWAVHeader() // @@ -192,29 +185,24 @@ HRESULT CLoopbackCapture::CreateWAVFile() // HRESULT CLoopbackCapture::FixWAVHeader() { + + std::lock_guard _(bufferlock); // Write the size of the 'data' chunk first - DWORD dwPtr = SetFilePointer(m_hFile.get(), m_cbHeaderSize - sizeof(DWORD), NULL, FILE_BEGIN); - RETURN_LAST_ERROR_IF(INVALID_SET_FILE_POINTER == dwPtr); - - DWORD dwBytesWritten = 0; - RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_cbDataSize, sizeof(DWORD), &dwBytesWritten, NULL)); - + auto offset = m_cbHeaderSize - sizeof(DWORD); + memcpy(buffer.data() + offset, &m_cbDataSize, sizeof(DWORD)); // Write the total file size, minus RIFF chunk and size // sizeof(DWORD) == sizeof(FOURCC) - RETURN_LAST_ERROR_IF(INVALID_SET_FILE_POINTER == SetFilePointer(m_hFile.get(), sizeof(DWORD), NULL, FILE_BEGIN)); DWORD cbTotalSize = m_cbDataSize + m_cbHeaderSize - 8; - RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &cbTotalSize, sizeof(DWORD), &dwBytesWritten, NULL)); - RETURN_IF_WIN32_BOOL_FALSE(FlushFileBuffers(m_hFile.get())); + offset = sizeof(DWORD); + memcpy(buffer.data() + offset, &cbTotalSize, sizeof(DWORD)); return S_OK; } -HRESULT CLoopbackCapture::StartCaptureAsync(DWORD processId, bool includeProcessTree, PCWSTR outputFileName) +HRESULT CLoopbackCapture::StartCaptureAsync(DWORD processId, bool includeProcessTree) { - m_outputFileName = outputFileName; - auto resetOutputFileName = wil::scope_exit([&] { m_outputFileName = nullptr; }); RETURN_IF_FAILED(InitializeLoopbackCapture()); RETURN_IF_FAILED(ActivateAudioInterface(processId, includeProcessTree)); @@ -234,21 +222,19 @@ HRESULT CLoopbackCapture::StartCaptureAsync(DWORD processId, bool includeProcess // // Callback method to start capture // -HRESULT CLoopbackCapture::OnStartCapture(IMFAsyncResult* pResult) +HRESULT CLoopbackCapture::OnStartCapture(IMFAsyncResult *pResult) { - return SetDeviceStateErrorIfFailed([&]()->HRESULT - { + return SetDeviceStateErrorIfFailed([&]() -> HRESULT + { // Start the capture RETURN_IF_FAILED(m_AudioClient->Start()); m_DeviceState = DeviceState::Capturing; MFPutWaitingWorkItem(m_SampleReadyEvent.get(), 0, m_SampleReadyAsyncResult.get(), &m_SampleReadyKey); - return S_OK; - }()); + return S_OK; }()); } - // // StopCaptureAsync() // @@ -257,7 +243,7 @@ HRESULT CLoopbackCapture::OnStartCapture(IMFAsyncResult* pResult) HRESULT CLoopbackCapture::StopCaptureAsync() { RETURN_HR_IF(E_NOT_VALID_STATE, (m_DeviceState != DeviceState::Capturing) && - (m_DeviceState != DeviceState::Error)); + (m_DeviceState != DeviceState::Error)); m_DeviceState = DeviceState::Stopping; @@ -274,7 +260,7 @@ HRESULT CLoopbackCapture::StopCaptureAsync() // // Callback method to stop capture // -HRESULT CLoopbackCapture::OnStopCapture(IMFAsyncResult* pResult) +HRESULT CLoopbackCapture::OnStopCapture(IMFAsyncResult *pResult) { // Stop capture by cancelling Work Item // Cancel the queued work item (if any) @@ -307,7 +293,7 @@ HRESULT CLoopbackCapture::FinishCaptureAsync() // Because of the asynchronous nature of the MF Work Queues and the DataWriter, there could still be // a sample processing. So this will get called to finalize the WAV header. // -HRESULT CLoopbackCapture::OnFinishCapture(IMFAsyncResult* pResult) +HRESULT CLoopbackCapture::OnFinishCapture(IMFAsyncResult *pResult) { // FixWAVHeader will set the DeviceStateStopped when all async tasks are complete HRESULT hr = FixWAVHeader(); @@ -324,7 +310,7 @@ HRESULT CLoopbackCapture::OnFinishCapture(IMFAsyncResult* pResult) // // Callback method when ready to fill sample buffer // -HRESULT CLoopbackCapture::OnSampleReady(IMFAsyncResult* pResult) +HRESULT CLoopbackCapture::OnSampleReady(IMFAsyncResult *pResult) { if (SUCCEEDED(OnAudioSampleRequested())) { @@ -351,7 +337,7 @@ HRESULT CLoopbackCapture::OnSampleReady(IMFAsyncResult* pResult) HRESULT CLoopbackCapture::OnAudioSampleRequested() { UINT32 FramesAvailable = 0; - BYTE* Data = nullptr; + BYTE *Data = nullptr; DWORD dwCaptureFlags; UINT64 u64DevicePosition = 0; UINT64 u64QPCPosition = 0; @@ -401,17 +387,11 @@ HRESULT CLoopbackCapture::OnAudioSampleRequested() // Get sample buffer RETURN_IF_FAILED(m_AudioCaptureClient->GetBuffer(&Data, &FramesAvailable, &dwCaptureFlags, &u64DevicePosition, &u64QPCPosition)); - // Write File if (m_DeviceState != DeviceState::Stopping) { - DWORD dwBytesWritten = 0; - RETURN_IF_WIN32_BOOL_FALSE(WriteFile( - m_hFile.get(), - Data, - cbBytesToCapture, - &dwBytesWritten, - NULL)); + std::lock_guard _(bufferlock); + buffer += std::string((char *)Data, cbBytesToCapture); } // Release buffer back diff --git a/plugins/applicationloopbackaudio/LoopbackCapture.h b/plugins/applicationloopbackaudio/LoopbackCapture.h index ec1aabef..42ca657c 100644 --- a/plugins/applicationloopbackaudio/LoopbackCapture.h +++ b/plugins/applicationloopbackaudio/LoopbackCapture.h @@ -21,7 +21,7 @@ public: //CLoopbackCapture() = default; ~CLoopbackCapture(); - HRESULT StartCaptureAsync(DWORD processId, bool includeProcessTree, PCWSTR outputFileName); + HRESULT StartCaptureAsync(DWORD processId, bool includeProcessTree); HRESULT StopCaptureAsync(); METHODASYNCCALLBACK(CLoopbackCapture, StartCapture, OnStartCapture); @@ -32,6 +32,7 @@ public: // IActivateAudioInterfaceCompletionHandler STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation* operation); + std::string buffer; private: // NB: All states >= Initialized will allow some methods // to be called successfully on the Audio Client @@ -69,15 +70,13 @@ private: wil::unique_event_nothrow m_SampleReadyEvent; MFWORKITEM_KEY m_SampleReadyKey = 0; - wil::unique_hfile m_hFile; wil::critical_section m_CritSec; DWORD m_dwQueueID = 0; DWORD m_cbHeaderSize = 0; DWORD m_cbDataSize = 0; - + std::mutex bufferlock; // These two members are used to communicate between the main thread // and the ActivateCompleted callback. - PCWSTR m_outputFileName = nullptr; HRESULT m_activateResult = E_UNEXPECTED; DeviceState m_DeviceState{ DeviceState::Uninitialized }; diff --git a/plugins/applicationloopbackaudio/runer.cpp b/plugins/applicationloopbackaudio/runer.cpp index e1ebeb02..e0daa989 100644 --- a/plugins/applicationloopbackaudio/runer.cpp +++ b/plugins/applicationloopbackaudio/runer.cpp @@ -1,12 +1,20 @@ #include "LoopbackCapture.h" -int wmain(int argc, wchar_t *argv[]) +#define DECLARE extern "C" __declspec(dllexport) + +DECLARE void StartCaptureAsync(void (*datacb)(void *ptr, size_t size), void (*handlecb)(HANDLE)) { + auto mutex = CreateSemaphoreW(NULL, 0, 1, NULL); + handlecb(mutex); CLoopbackCapture loopbackCapture; - loopbackCapture.StartCaptureAsync(GetCurrentProcessId(), false, argv[1]); - WaitForSingleObject( - CreateEventW(&allAccess, FALSE, FALSE, argv[2]), - INFINITE); + loopbackCapture.StartCaptureAsync(GetCurrentProcessId(), false); + WaitForSingleObject(mutex, INFINITE); + CloseHandle(mutex); loopbackCapture.StopCaptureAsync(); - return 0; + datacb(loopbackCapture.buffer.data(), loopbackCapture.buffer.size()); +} + +DECLARE void StopCaptureAsync(HANDLE m) +{ + ReleaseSemaphore(m, 1, NULL); } \ No newline at end of file diff --git a/plugins/scripts/copytarget.py b/plugins/scripts/copytarget.py index 884100c8..d0b9b065 100644 --- a/plugins/scripts/copytarget.py +++ b/plugins/scripts/copytarget.py @@ -2,12 +2,13 @@ import shutil,sys x86=int(sys.argv[1]) if x86: shutil.copy('../builds/_x86/shareddllproxy32.exe','../../LunaTranslator/files/plugins') - shutil.copy('../builds/_x86/loopbackaudio.exe','../../LunaTranslator/files/plugins') + shutil.copy('../builds/_x86/loopbackaudio.dll','../../LunaTranslator/files/plugins/DLL32') shutil.copy('../builds/_x86/winrtutils32.dll','../../LunaTranslator/files/plugins/DLL32') shutil.copy('../builds/_x86/winsharedutils32.dll','../../LunaTranslator/files/plugins/DLL32') shutil.copy('../builds/_x86/wcocr.dll','../../LunaTranslator/files/plugins/DLL32') else: shutil.copy('../builds/_x64/shareddllproxy64.exe','../../LunaTranslator/files/plugins') + shutil.copy('../builds/_x64/loopbackaudio.dll','../../LunaTranslator/files/plugins/DLL64') shutil.copy('../builds/_x64/hookmagpie.dll','../../LunaTranslator/files/plugins') shutil.copy('../builds/_x64/winrtutils64.dll','../../LunaTranslator/files/plugins/DLL64') shutil.copy('../builds/_x64/winsharedutils64.dll','../../LunaTranslator/files/plugins/DLL64')