diff --git a/Sources/pyOpenRPA/Robot/Audio.py b/Sources/pyOpenRPA/Robot/Audio.py index 4ec505ec..5e6c863f 100644 --- a/Sources/pyOpenRPA/Robot/Audio.py +++ b/Sources/pyOpenRPA/Robot/Audio.py @@ -3,12 +3,53 @@ import pyaudio from pydub import AudioSegment import threading import wave +import time +from pyOpenRPA.Utils import Text -def DeviceSystemSoundSearchIndex(): +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 и т.д. """ - pass + 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: 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+: Вернуть список аудио устройст (входящих и исходящих, микрофонов и динамиков). @@ -42,7 +83,7 @@ def DeviceListGet(): class Recorder: mStatusStr = "0_READY" - mAudio = pyaudio.PyAudio() + mAudio = None mCaptureThread = None mStream = None @@ -52,16 +93,22 @@ class Recorder: mRecordedFramesList = [] mUseLoopbackBool = True mSampleRateInt = None - mSampleSizeInt = mAudio.get_sample_size(pyaudio.paInt16) + mSampleSizeInt = None mCaptureBool = True - mFileNameStr = "aux" + mFilePathStr = "out" mFileFormatStr = "mp3" + def __init__(self, inDeviceInt=None): self.mDeviceInt = inDeviceInt - - def CaptureStart(self): + if inDeviceInt == None: self.mDeviceInt = DeviceSystemSoundIndex() + + def CaptureStart(self, inFilePathStr = "out", inFileFormatStr = "mp3", inDoChunkBool = False): + self.mFilePathStr = inFilePathStr + 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"]) @@ -77,32 +124,21 @@ class Recorder: self.mCaptureThread.start() def __Capture__(self): - while self.mCaptureBool == True: + while self.mCaptureBool==True: self.mRecordedFramesList.append(self.mStream.read(self.mFramesInt)) self.mStream.stop_stream() self.mStream.close() #Close module self.mAudio.terminate() - print("done") def CaptureStop(self): self.mCaptureBool=False self.mCaptureThread.join() - print("done2") self.CaptureChunk() - print("done3") - - def CaptureChunk(self): - print("CaptureChunk 1") - waveFile = wave.open(f"{self.mFileNameStr}.{self.mFileFormatStr}", 'wb') - waveFile.setnchannels(self.mChannelCountInt) - waveFile.setsampwidth(self.mSampleSizeInt) - waveFile.setframerate(self.mSampleRateInt) - waveFile.writeframes(b''.join(self.mRecordedFramesList)) - waveFile.close() - lSound = AudioSegment( + # Advanced usage, if you have raw audio data: + sound = AudioSegment( # raw audio data (bytes) data=b''.join(self.mRecordedFramesList), # 2 byte (16 bit) samples @@ -112,11 +148,7 @@ class Recorder: # stereo channels=self.mChannelCountInt ) - print("CaptureChunk 2") - print(len(self.mRecordedFramesList)) - - lSound.export(f"{self.mFileNameStr}.{self.mFileFormatStr}", format=self.mFileFormatStr) - print("CaptureChunk 3") + sound.export(f"{self.mFilePathStr}.{self.mFileFormatStr}", format=f"{self.mFileFormatStr}") self.mRecordedFramesList = [] def FileListGet(self): diff --git a/Sources/pyOpenRPA/Utils/Text.py b/Sources/pyOpenRPA/Utils/Text.py new file mode 100644 index 00000000..90b3c1dc --- /dev/null +++ b/Sources/pyOpenRPA/Utils/Text.py @@ -0,0 +1,7 @@ +import difflib + +def SimilarityNoCase(in1Str, in2Str): + normalized1 = in1Str.lower() + normalized2 = in2Str.lower() + matcher = difflib.SequenceMatcher(None, normalized1, normalized2) + return matcher.ratio() \ No newline at end of file diff --git a/Tools/Jupyter-notebooks/Audio.ipynb b/Tools/Jupyter-notebooks/Audio.ipynb index 2bd0dd32..ead710db 100644 --- a/Tools/Jupyter-notebooks/Audio.ipynb +++ b/Tools/Jupyter-notebooks/Audio.ipynb @@ -15,19 +15,25 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "123\n" + "ename": "AttributeError", + "evalue": "'Recorder' object has no attribute 'test'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[0mpyOpenRPA\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mRobot\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mAudio\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mlRec\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mAudio\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mRecorder\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minDeviceInt\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m10\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mlRec\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mtest\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m: 'Recorder' object has no attribute 'test'" ] } ], "source": [ - "print(123)" + "from pyOpenRPA.Robot import Audio\n", + "lRec = Audio.Recorder(inDeviceInt=10)\n", + "lRec.test()" ] }, { @@ -43,19 +49,9 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "done\n", - "done2\n", - "CaptureChunk 1\n" - ] - } - ], + "outputs": [], "source": [ "lRec.CaptureStop()" ] @@ -197,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -205,7 +201,11 @@ "output_type": "stream", "text": [ "Starting...\n", - "End.\n" + "End.\n", + "2\n", + "2\n", + "48000\n", + "382976\n" ] }, { @@ -214,7 +214,7 @@ "<_io.BufferedRandom name='out.mp3'>" ] }, - "execution_count": 12, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -228,7 +228,7 @@ "#Use module\n", "p = pyaudio.PyAudio()\n", "\n", - "recordtime = 15\n", + "recordtime = 2\n", "device_id = 10\n", "device_info = p.get_device_info_by_index(device_id)\n", "#Open stream\n", @@ -269,12 +269,16 @@ " # stereo\n", " channels=channelcount\n", ")\n", + "print(channelcount)\n", + "print(p.get_sample_size(pyaudio.paInt16))\n", + "print(int(device_info[\"defaultSampleRate\"]))\n", + "print(len(b''.join(recorded_frames)))\n", "sound.export(\"out.mp3\", format=\"mp3\")" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -288,22 +292,123 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.36363636363636365" + "0.647887323943662" ] }, - "execution_count": 8, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "similarity(\"sadsdasd\",\"sadfsdfd \")" + "similarity_no_case(\"Ìèêðîôîí (Realtek High Definiti\",\"Динамики (Realtek High Definition Audio)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'index': 1,\n", + " 'structVersion': 2,\n", + " 'name': 'Ìèêðîôîí (Realtek High Definiti',\n", + " 'hostApi': 0,\n", + " 'maxInputChannels': 2,\n", + " 'maxOutputChannels': 0,\n", + " 'defaultLowInputLatency': 0.09,\n", + " 'defaultLowOutputLatency': 0.09,\n", + " 'defaultHighInputLatency': 0.18,\n", + " 'defaultHighOutputLatency': 0.18,\n", + " 'defaultSampleRate': 44100.0}" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p.get_default_input_device_info()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import pyaudio\n", + "p = pyaudio.PyAudio()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "not (False ^ True)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from pyOpenRPA.Robot import Audio\n", + "Audio.DeviceMicrophoneIndex()" ] }, { diff --git a/changelog.md b/changelog.md index e402e2a2..73e68936 100755 --- a/changelog.md +++ b/changelog.md @@ -19,7 +19,7 @@ AGT - AGENT - ОБЩЕЕ - - Jupyter: запуск из других дисков, отличных от C:// - - Utils: Функции подготовки файлов / директорий -- - Utils: String - similarity +- - Utils: Text - SimilarityNoCase - Сверка двух строк на схождение (коэффициент от 0.0 до 1.0) [1.3.0] - ПОРТИРОВАНО НА LINUX (Ubuntu, Debian, Astra), адаптация функций