import pyaudio from pydub import AudioSegment import threading import wave import time from pyOpenRPA.Utils import Text import os def DeviceMicrophoneIndex(): """L-,W+: Выполнить поиск устройства, с помощью которого можно будет выполнить захват c микрофона. """ p = pyaudio.PyAudio() lDeviceInfoDict = p.get_default_input_device_info() lDefaultIndexInt = lDeviceInfoDict["index"] return lDefaultIndexInt def DeviceSystemSoundIndex(): """L-,W+: Выполнить поиск устройства, с помощью которого можно будет выполнить захват аудио, которое поступает из приложений. Например: аудиоконференции Zoom, whatsapp, telegram и т.д. """ p = pyaudio.PyAudio() inInputBool = True inIsLoopbackBool = True if inInputBool == True: lDeviceInfoDict = p.get_default_output_device_info() lDefaultIndexInt = lDeviceInfoDict["index"] lDefaultNameStr = lDeviceInfoDict["name"] lCatchIndexInt = None lCatchDiffRatioFloat = 0.0 for lItemDict in DeviceListGet(): lCompareBool = False if lItemDict["MaxOutputChannelsInt"]>0: if inIsLoopbackBool==True and lItemDict["HostApiStr"]=="Windows WASAPI": lCompareBool = True elif inIsLoopbackBool==False: lCompareBool = True if lCompareBool == True: lDiffRationFloat = Text.SimilarityNoCase(in1Str=lDefaultNameStr, in2Str=lItemDict["NameStr"]) if lDiffRationFloat> lCatchDiffRatioFloat: lCatchDiffRatioFloat = lDiffRationFloat lCatchIndexInt=lItemDict["IndexInt"] else: lDeviceInfoDict = p.get_default_output_device_info() lDefaultIndexInt = lDeviceInfoDict["index"] lDefaultNameStr = lDeviceInfoDict["name"] lCatchIndexInt = None lCatchDiffRatioFloat = 0.0 for lItemDict in DeviceListGet(): lCompareBool = False if lItemDict["MaxInputChannelsInt"]>0: if inIsLoopbackBool==True and lItemDict["HostApiStr"]=="Windows WASAPI": lCompareBool = True elif inIsLoopbackBool==False: lCompareBool = True if lCompareBool == True: lDiffRationFloat = Text.SimilarityNoCase(in1Str=lDefaultNameStr, in2Str=lItemDict["NameStr"]) if lDiffRationFloat> lCatchDiffRatioFloat: lCatchIndexInt=lItemDict["IndexInt"] return lCatchIndexInt def DeviceListGet(): """L-,W+: Вернуть список аудио устройст (входящих и исходящих, микрофонов и динамиков). from pyOpenRPA.Robot import Audio Audio.DeviceListGet() :return: [{"IndexInt":1, "NameStr": "", "HostApiInt": 0, "HostApiStr": "MME"|"Windows WASAPI"|"Windows WDM-KS", "MaxInputChannelsInt": 0, "MaxOutputChannelsInt": 0, "DefaultSampleRateFloat": 44100.0 },...] :rtype: list """ l_result = [] p = pyaudio.PyAudio() for i in range(0, p.get_device_count()): l_info = p.get_device_info_by_index(i) l_info_dict = { "IndexInt":l_info["index"], "NameStr": l_info["name"], "MaxInputChannelsInt": l_info["maxInputChannels"], "MaxOutputChannelsInt": l_info["maxOutputChannels"], "HostApiInt": l_info["hostApi"], "DefaultSampleRateFloat": l_info["defaultSampleRate"], "HostApiStr": p.get_host_api_info_by_index(l_info["hostApi"])["name"] #"MME"|"Windows WASAPI"|"Windows WDM-KS" } l_result.append(l_info_dict) return l_result class Recorder: mT=[] mStatusStr = "0_READY" # "0_READY", "1_RECORDING" mAudio = None mCaptureThread = None mStream = None mDeviceInt = None mChannelCountInt = None mFramesInt = 512 mRecordedFramesList = [] mSampleRateInt = None mSampleSizeInt = None mCaptureBool = True mFolderPathStr = None mFileNameStr = None mFileFormatStr = None mFileAvailableChunkInt = None mFileNameList=None mChunkSecFloat = None mChunkSilentSecFloat = None mStartSecFloat = None mStartChunkSecFloat = None mDurationSecFloat = None mThresholdInt = 500 mSilentLastCheckTimeFloat = None mIsMicrophoneBool=None def __init__(self, inDeviceInt=None): self.mDeviceInt = inDeviceInt if inDeviceInt == None: inDeviceInt = DeviceSystemSoundIndex() self.mDeviceInt = inDeviceInt if DeviceListGet()[inDeviceInt]["MaxInputChannelsInt"]>0: self.mIsMicrophoneBool = True else: self.mIsMicrophoneBool = False def StatusGet(self): return self.mStatusStr def CaptureStart(self, inFolderPathStr="",inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 300.0, inChunkSilentSecFloat = 10.0): # CHECK AUX.mp3 self.mFileNameList=[] self.mRecordedFramesList=[] self.mStatusStr = "1_RECORDING" if inChunkSecFloat != None or inChunkSilentSecFloat != None: self.mFileAvailableChunkInt = 0 self.mDurationSecFloat = inDurationSecFloat self.mChunkSecFloat = inChunkSecFloat self.mChunkSilentSecFloat = inChunkSilentSecFloat self.mSilentLastCheckTimeFloat=time.time() self.mFolderPathStr = inFolderPathStr self.mFileNameStr = inFileNameStr self.mFileFormatStr = inFileFormatStr self.mAudio = pyaudio.PyAudio() self.mSampleSizeInt = self.mAudio.get_sample_size(pyaudio.paInt16) lDeviceInfoDict = self.mAudio.get_device_info_by_index(self.mDeviceInt) #Open stream self.mSampleRateInt = int(lDeviceInfoDict["defaultSampleRate"]) self.mChannelCountInt = lDeviceInfoDict["maxInputChannels"] if (lDeviceInfoDict["maxOutputChannels"] < lDeviceInfoDict["maxInputChannels"]) else lDeviceInfoDict["maxOutputChannels"] self.mStream = self.mAudio.open(format = pyaudio.paInt16, channels = self.mChannelCountInt, rate = self.mSampleRateInt, input = True, frames_per_buffer = self.mFramesInt, input_device_index = lDeviceInfoDict["index"], as_loopback = not self.mIsMicrophoneBool) self.mCaptureThread = threading.Thread(target=self.__Capture__) self.mStartSecFloat = time.time() self.mStartChunkSecFloat = self.mStartSecFloat self.mCaptureThread.start() def __Capture__(self): while self.mCaptureBool==True: self.mRecordedFramesList.append(self.mStream.read(self.mFramesInt)) self.__TriggerCheck__() def CaptureStop(self, inWaitStream=True): self.mCaptureBool=False if inWaitStream == True: self.mCaptureThread.join() self.mStream.stop_stream() self.mStream.close() #Close module self.mAudio.terminate() self.CaptureChunk() self.mStatusStr = "0_READY" def CaptureChunk(self): lFileNameStr = self.mFileNameStr if self.mFileAvailableChunkInt!=None: lFileNameStr+=f"_{self.mFileAvailableChunkInt:05}" self.mFileAvailableChunkInt = self.mFileAvailableChunkInt + 1 # Advanced usage, if you have raw audio data: sound = AudioSegment( # raw audio data (bytes) data=b''.join(self.mRecordedFramesList), # 2 byte (16 bit) samples sample_width=self.mSampleSizeInt, # 44.1 kHz frame rate frame_rate=self.mSampleRateInt, # stereo channels=self.mChannelCountInt ) sound.export(os.path.join(self.mFolderPathStr,f"{lFileNameStr}.{self.mFileFormatStr}"), format=f"{self.mFileFormatStr}") self.mFileNameList.append(f"{lFileNameStr}.{self.mFileFormatStr}") self.mRecordedFramesList = [] self.mStartChunkSecFloat = time.time() def FileListGet(self): return self.mFileNameList def FileLastGet(self): return self.mFileNameList[-1] def __Callback__(self, inDefList): pass def __CallbackIsSilent__(self): pass def __CallbackIsChunked__(self): pass def __CallbackIsStopped__(self): pass def IsSilent(self, inLastSecFloat=None): "Returns 'True' if below the 'silent' threshold" self.mSilentLastCheckTimeFloat = time.time() if inLastSecFloat == None: inLastSecFloat = self.mChunkSilentSecFloat lFrameLenInt = int(self.mSampleSizeInt*inLastSecFloat) if lFrameLenInt self.mChunkSecFloat: self.CaptureChunk() # Остановка записи по максимальной длине if self.mDurationSecFloat != None and time.time() - self.mStartSecFloat > self.mDurationSecFloat: self.CaptureStop(inWaitStream=False) # Проверка тишины #if self.mChunkSilentSecFloat != None and time.time() - self.mSilentLastCheckTimeFloat and self.IsSilent(): self.mT.append("ТИШИНА!!")