This commit is contained in:
恍兮惚兮 2024-08-11 22:45:33 +08:00
parent 4d2ac7bf10
commit 38f5cd864f
9 changed files with 122 additions and 127 deletions

View File

@ -3066,7 +3066,8 @@ def getalistname(parent, callback, skipid=False, skipidid=None):
}, },
], ],
) )
else: elif len(__uid):
callback(__uid[0]) callback(__uid[0])

View File

@ -347,28 +347,8 @@ class AnkiWindow(QWidget):
@threader @threader
def simulate_key(self, i): def simulate_key(self, i):
def __internal__keystring(i):
try: try:
for _ in (0,): keystring = globalconfig["ankiconnect"]["simulate_key"][i]["keystring"]
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)
except: except:
return return
if not keystring: if not keystring:
@ -387,10 +367,10 @@ class AnkiWindow(QWidget):
for mode in modes: for mode in modes:
windows.keybd_event(mode, 0, windows.KEYEVENTF_KEYUP, 0) 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: if idx == 1:
self.recorder = loopbackrecorder() self.recorder = loopbackrecorder()
self.simulate_key(idx) self.simulate_key(ii)
else: else:
self.recorder.end(callback=target.setText) self.recorder.end(callback=target.setText)
@ -428,11 +408,11 @@ class AnkiWindow(QWidget):
self.remarks = FQPlainTextEdit() self.remarks = FQPlainTextEdit()
recordbtn1 = statusbutton(icons=["fa.microphone", "fa.stop"], colors=["", ""]) recordbtn1 = statusbutton(icons=["fa.microphone", "fa.stop"], colors=["", ""])
recordbtn1.statuschanged.connect( 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 = statusbutton(icons=["fa.microphone", "fa.stop"], colors=["", ""])
recordbtn2.statuschanged.connect( recordbtn2.statuschanged.connect(
functools.partial(self.startorendrecord, self.audiopath_sentence) functools.partial(self.startorendrecord, 2, self.audiopath_sentence)
) )
self.recordbtn1 = recordbtn1 self.recordbtn1 = recordbtn1
self.recordbtn2 = recordbtn2 self.recordbtn2 = recordbtn2

View File

@ -5,6 +5,8 @@ import socket, gobject, uuid, subprocess, functools
import ctypes, importlib, json import ctypes, importlib, json
import ctypes.wintypes import ctypes.wintypes
from qtsymbols import * 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 traceback import print_exc
from myutils.config import ( from myutils.config import (
globalconfig, globalconfig,
@ -759,36 +761,65 @@ def checkmd5reloadmodule(filename, module):
return False, globalcachedmodule.get(key, {}).get("module", None) 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: class loopbackrecorder:
def __init__(self): def __init__(self):
self.file = gobject.gettempdir(str(time.time()) + ".wav")
try: try:
self.waitsignal = str(time.time()) self.capture = audiocapture()
cmd = './files/plugins/loopbackaudio.exe "{}" "{}"'.format(
self.file, self.waitsignal
)
self.engine = subproc_w(cmd, name=str(uuid.uuid4()))
except: except:
print_exc() self.capture = None
@threader @threader
def end(self, callback): def end(self, callback):
windows.SetEvent( if not self.capture:
windows.AutoHandle(windows.CreateEvent(False, False, self.waitsignal)) return callback("")
) wav = self.capture.stop()
self.engine.wait() if not wav:
filewav = self.file return callback("")
if os.path.exists(filewav) == False:
callback("")
return
with open(filewav, "rb") as ff:
wav = ff.read()
mp3 = winsharedutils.encodemp3(wav) mp3 = winsharedutils.encodemp3(wav)
if mp3: if not mp3:
filemp3 = filewav[:-3] + "mp3" file = gobject.gettempdir(str(time.time()) + ".wav")
with open(filemp3, "wb") as ff: with open(file, "wb") as ff:
ff.write(mp3) ff.write(wav)
os.remove(filewav) callback(file)
callback(filemp3)
else: else:
callback(filewav) file = gobject.gettempdir(str(time.time()) + ".mp3")
with open(file, "wb") as ff:
ff.write(mp3)
callback(file)

View File

@ -28,8 +28,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/version)
include(generate_product_version) include(generate_product_version)
set(VERSION_MAJOR 5) set(VERSION_MAJOR 5)
set(VERSION_MINOR 25) set(VERSION_MINOR 26)
set(VERSION_PATCH 1) set(VERSION_PATCH 0)
add_library(pch pch.cpp) add_library(pch pch.cpp)
target_precompile_headers(pch PUBLIC pch.h) target_precompile_headers(pch PUBLIC pch.h)

View File

@ -12,11 +12,6 @@ generate_product_version(
) )
if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) add_library(loopbackaudio MODULE runer.cpp LoopbackCapture.cpp ${versioninfo})
else()
add_executable(loopbackaudio runer.cpp LoopbackCapture.cpp ${versioninfo})
target_precompile_headers(loopbackaudio REUSE_FROM pch) target_precompile_headers(loopbackaudio REUSE_FROM pch)
target_link_libraries(loopbackaudio Mfplat mfuuid ) target_link_libraries(loopbackaudio Mfplat mfuuid )
endif()

View File

@ -52,8 +52,7 @@ typedef HRESULT (STDAPICALLTYPE *ActivateAudioInterfaceAsync_t)(
_In_ REFIID riid, _In_ REFIID riid,
_In_opt_ PROPVARIANT *activationParams, _In_opt_ PROPVARIANT *activationParams,
_In_ IActivateAudioInterfaceCompletionHandler *completionHandler, _In_ IActivateAudioInterfaceCompletionHandler *completionHandler,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation **activationOperation _COM_Outptr_ IActivateAudioInterfaceAsyncOperation **activationOperation);
);
HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includeProcessTree) HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includeProcessTree)
{ {
return SetDeviceStateErrorIfFailed([&]() -> HRESULT return SetDeviceStateErrorIfFailed([&]() -> HRESULT
@ -77,8 +76,7 @@ HRESULT CLoopbackCapture::ActivateAudioInterface(DWORD processId, bool includePr
// Wait for activation completion // Wait for activation completion
m_hActivateCompleted.wait(); m_hActivateCompleted.wait();
return m_activateResult; return m_activateResult; }());
}());
} }
// //
@ -135,8 +133,7 @@ HRESULT CLoopbackCapture::ActivateCompleted(IActivateAudioInterfaceAsyncOperatio
// Everything is ready. // Everything is ready.
m_DeviceState = DeviceState::Initialized; m_DeviceState = DeviceState::Initialized;
return S_OK; return S_OK; }());
}());
// Let ActivateAudioInterface know that m_activateResult has the result of the activation attempt. // Let ActivateAudioInterface know that m_activateResult has the result of the activation attempt.
m_hActivateCompleted.SetEvent(); m_hActivateCompleted.SetEvent();
@ -152,8 +149,6 @@ HRESULT CLoopbackCapture::CreateWAVFile()
{ {
return SetDeviceStateErrorIfFailed([&]() -> HRESULT 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);
// Create and write the WAV header // Create and write the WAV header
@ -166,25 +161,23 @@ HRESULT CLoopbackCapture::CreateWAVFile()
sizeof(m_CaptureFormat) // Size of fmt chunk sizeof(m_CaptureFormat) // Size of fmt chunk
}; };
DWORD dwBytesWritten = 0; DWORD dwBytesWritten = 0;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), header, sizeof(header), &dwBytesWritten, NULL)); std::lock_guard _(bufferlock);
buffer+=std::string((char*)header, sizeof(header));
m_cbHeaderSize += dwBytesWritten; m_cbHeaderSize += sizeof(header);
// 2. The fmt sub-chunk // 2. The fmt sub-chunk
WI_ASSERT(m_CaptureFormat.cbSize == 0); WI_ASSERT(m_CaptureFormat.cbSize == 0);
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_CaptureFormat, sizeof(m_CaptureFormat), &dwBytesWritten, NULL)); buffer+=std::string((char*) &m_CaptureFormat, sizeof(m_CaptureFormat));
m_cbHeaderSize += dwBytesWritten; m_cbHeaderSize += sizeof(m_CaptureFormat);
// 3. The data sub-chunk // 3. The data sub-chunk
DWORD data[] = { FCC('data'), 0 }; // Start of 'data' chunk DWORD data[] = { FCC('data'), 0 }; // Start of 'data' chunk
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), data, sizeof(data), &dwBytesWritten, NULL)); buffer+=std::string((char*) data, sizeof(data));
m_cbHeaderSize += dwBytesWritten; m_cbHeaderSize += sizeof(data);
return S_OK; return S_OK; }());
}());
} }
// //
// FixWAVHeader() // FixWAVHeader()
// //
@ -192,29 +185,24 @@ HRESULT CLoopbackCapture::CreateWAVFile()
// //
HRESULT CLoopbackCapture::FixWAVHeader() HRESULT CLoopbackCapture::FixWAVHeader()
{ {
std::lock_guard _(bufferlock);
// Write the size of the 'data' chunk first // Write the size of the 'data' chunk first
DWORD dwPtr = SetFilePointer(m_hFile.get(), m_cbHeaderSize - sizeof(DWORD), NULL, FILE_BEGIN); auto offset = m_cbHeaderSize - sizeof(DWORD);
RETURN_LAST_ERROR_IF(INVALID_SET_FILE_POINTER == dwPtr); memcpy(buffer.data() + offset, &m_cbDataSize, sizeof(DWORD));
DWORD dwBytesWritten = 0;
RETURN_IF_WIN32_BOOL_FALSE(WriteFile(m_hFile.get(), &m_cbDataSize, sizeof(DWORD), &dwBytesWritten, NULL));
// Write the total file size, minus RIFF chunk and size // Write the total file size, minus RIFF chunk and size
// sizeof(DWORD) == sizeof(FOURCC) // 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; 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; 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(InitializeLoopbackCapture());
RETURN_IF_FAILED(ActivateAudioInterface(processId, includeProcessTree)); RETURN_IF_FAILED(ActivateAudioInterface(processId, includeProcessTree));
@ -244,11 +232,9 @@ HRESULT CLoopbackCapture::OnStartCapture(IMFAsyncResult* pResult)
m_DeviceState = DeviceState::Capturing; m_DeviceState = DeviceState::Capturing;
MFPutWaitingWorkItem(m_SampleReadyEvent.get(), 0, m_SampleReadyAsyncResult.get(), &m_SampleReadyKey); MFPutWaitingWorkItem(m_SampleReadyEvent.get(), 0, m_SampleReadyAsyncResult.get(), &m_SampleReadyKey);
return S_OK; return S_OK; }());
}());
} }
// //
// StopCaptureAsync() // StopCaptureAsync()
// //
@ -401,17 +387,11 @@ HRESULT CLoopbackCapture::OnAudioSampleRequested()
// Get sample buffer // Get sample buffer
RETURN_IF_FAILED(m_AudioCaptureClient->GetBuffer(&Data, &FramesAvailable, &dwCaptureFlags, &u64DevicePosition, &u64QPCPosition)); RETURN_IF_FAILED(m_AudioCaptureClient->GetBuffer(&Data, &FramesAvailable, &dwCaptureFlags, &u64DevicePosition, &u64QPCPosition));
// Write File // Write File
if (m_DeviceState != DeviceState::Stopping) if (m_DeviceState != DeviceState::Stopping)
{ {
DWORD dwBytesWritten = 0; std::lock_guard _(bufferlock);
RETURN_IF_WIN32_BOOL_FALSE(WriteFile( buffer += std::string((char *)Data, cbBytesToCapture);
m_hFile.get(),
Data,
cbBytesToCapture,
&dwBytesWritten,
NULL));
} }
// Release buffer back // Release buffer back

View File

@ -21,7 +21,7 @@ public:
//CLoopbackCapture() = default; //CLoopbackCapture() = default;
~CLoopbackCapture(); ~CLoopbackCapture();
HRESULT StartCaptureAsync(DWORD processId, bool includeProcessTree, PCWSTR outputFileName); HRESULT StartCaptureAsync(DWORD processId, bool includeProcessTree);
HRESULT StopCaptureAsync(); HRESULT StopCaptureAsync();
METHODASYNCCALLBACK(CLoopbackCapture, StartCapture, OnStartCapture); METHODASYNCCALLBACK(CLoopbackCapture, StartCapture, OnStartCapture);
@ -32,6 +32,7 @@ public:
// IActivateAudioInterfaceCompletionHandler // IActivateAudioInterfaceCompletionHandler
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation* operation); STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation* operation);
std::string buffer;
private: private:
// NB: All states >= Initialized will allow some methods // NB: All states >= Initialized will allow some methods
// to be called successfully on the Audio Client // to be called successfully on the Audio Client
@ -69,15 +70,13 @@ private:
wil::unique_event_nothrow m_SampleReadyEvent; wil::unique_event_nothrow m_SampleReadyEvent;
MFWORKITEM_KEY m_SampleReadyKey = 0; MFWORKITEM_KEY m_SampleReadyKey = 0;
wil::unique_hfile m_hFile;
wil::critical_section m_CritSec; wil::critical_section m_CritSec;
DWORD m_dwQueueID = 0; DWORD m_dwQueueID = 0;
DWORD m_cbHeaderSize = 0; DWORD m_cbHeaderSize = 0;
DWORD m_cbDataSize = 0; DWORD m_cbDataSize = 0;
std::mutex bufferlock;
// These two members are used to communicate between the main thread // These two members are used to communicate between the main thread
// and the ActivateCompleted callback. // and the ActivateCompleted callback.
PCWSTR m_outputFileName = nullptr;
HRESULT m_activateResult = E_UNEXPECTED; HRESULT m_activateResult = E_UNEXPECTED;
DeviceState m_DeviceState{ DeviceState::Uninitialized }; DeviceState m_DeviceState{ DeviceState::Uninitialized };

View File

@ -1,12 +1,20 @@
#include "LoopbackCapture.h" #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; CLoopbackCapture loopbackCapture;
loopbackCapture.StartCaptureAsync(GetCurrentProcessId(), false, argv[1]); loopbackCapture.StartCaptureAsync(GetCurrentProcessId(), false);
WaitForSingleObject( WaitForSingleObject(mutex, INFINITE);
CreateEventW(&allAccess, FALSE, FALSE, argv[2]), CloseHandle(mutex);
INFINITE);
loopbackCapture.StopCaptureAsync(); loopbackCapture.StopCaptureAsync();
return 0; datacb(loopbackCapture.buffer.data(), loopbackCapture.buffer.size());
}
DECLARE void StopCaptureAsync(HANDLE m)
{
ReleaseSemaphore(m, 1, NULL);
} }

View File

@ -2,12 +2,13 @@ import shutil,sys
x86=int(sys.argv[1]) x86=int(sys.argv[1])
if x86: if x86:
shutil.copy('../builds/_x86/shareddllproxy32.exe','../../LunaTranslator/files/plugins') 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/winrtutils32.dll','../../LunaTranslator/files/plugins/DLL32')
shutil.copy('../builds/_x86/winsharedutils32.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') shutil.copy('../builds/_x86/wcocr.dll','../../LunaTranslator/files/plugins/DLL32')
else: else:
shutil.copy('../builds/_x64/shareddllproxy64.exe','../../LunaTranslator/files/plugins') 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/hookmagpie.dll','../../LunaTranslator/files/plugins')
shutil.copy('../builds/_x64/winrtutils64.dll','../../LunaTranslator/files/plugins/DLL64') shutil.copy('../builds/_x64/winrtutils64.dll','../../LunaTranslator/files/plugins/DLL64')
shutil.copy('../builds/_x64/winsharedutils64.dll','../../LunaTranslator/files/plugins/DLL64') shutil.copy('../builds/_x64/winsharedutils64.dll','../../LunaTranslator/files/plugins/DLL64')