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])

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -12,11 +12,6 @@ generate_product_version(
)
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()

View File

@ -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<IUnknown> 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

View File

@ -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 };

View File

@ -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);
}

View File

@ -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')