From 0d97ec4ff2f92a01a8f55c87b3b3d0821ec5391f Mon Sep 17 00:00:00 2001 From: Ivan Maslov Date: Fri, 7 Oct 2022 16:46:25 +0300 Subject: [PATCH] Audio module done! --- Sources/pyOpenRPA/Robot/Audio.py | 59 +++-- Tools/Jupyter-notebooks/Audio.ipynb | 332 +++------------------------- 2 files changed, 72 insertions(+), 319 deletions(-) diff --git a/Sources/pyOpenRPA/Robot/Audio.py b/Sources/pyOpenRPA/Robot/Audio.py index 4eeda69d..b1bab0fe 100644 --- a/Sources/pyOpenRPA/Robot/Audio.py +++ b/Sources/pyOpenRPA/Robot/Audio.py @@ -7,9 +7,6 @@ import time from pyOpenRPA.Utils import Text import os - - - def DeviceMicrophoneIndex(): """L-,W+: Выполнить поиск устройства, с помощью которого можно будет выполнить захват c микрофона. """ @@ -115,8 +112,10 @@ class Recorder: mThresholdInt = 500 mSilentLastCheckTimeFloat = None - mIsMicrophoneBool=None + + mCallbackChunkDef = None + mCallbackStopDef = None def __init__(self, inDeviceInt=None): """L-,W+: Инициализация экземпляра класса записи звука @@ -148,13 +147,17 @@ class Recorder: """ return self.mStatusStr - def CaptureStart(self, inFolderPathStr="",inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 300.0): + def CaptureStart(self, inFolderPathStr="",inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 300.0, inCallbackChunkDef = None, inCallbackStopDef = None): """L-,W+: Начать запись звука .. code-block:: python + + def CallbackChunk(lRec, lFilenameStr): + pass # КОД ОБРАБОТКИ ПОСЛЕ СОХРАНЕНИЯ ЧАСТИ + from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inCallbackChunkDef=CallbackChunk) lRec.CaptureStop() :param inFolderPathStr: Путь к папке, в которую сохранять аудиофайлы захвата , по умолчанию "" @@ -167,6 +170,10 @@ class Recorder: :type inDurationSecFloat: float, опционально :param inChunkSecFloat: Максимальная длина части аудиофайла, по умолчанию 300.0 :type inChunkSecFloat: float, опционально + :param inCallbackChunkDef: Функция, которая будет инициирована в случае выполнения Chunk сохранения (сохранение части). Callback функция должна принимать 2 аргумента: экземпляр класса Recorder и наименование сохраненного файла. Внимание! Функция запускается асинхронно! + :type inCallbackChunkDef: def, опционально + :param inCallbackStopDef: Функция, которая будет инициирована в случае окончания записи. Callback функция должна принимать 2 аргумента: экземпляр класса Recorder и наименование сохраненного файла. Внимание! Функция запускается асинхронно! + :type inCallbackStopDef: def, опционально """ # CHECK AUX.mp3 self.mFileInfoDict = {} @@ -182,6 +189,8 @@ class Recorder: self.mFileFormatStr = inFileFormatStr self.mAudio = pyaudio.PyAudio() self.mSampleSizeInt = self.mAudio.get_sample_size(pyaudio.paInt16) + self.mCallbackChunkDef = inCallbackChunkDef + self.mCallbackStopDef = inCallbackStopDef lDeviceInfoDict = self.mAudio.get_device_info_by_index(self.mDeviceInt) #Open stream self.mSampleRateInt = int(lDeviceInfoDict["defaultSampleRate"]) @@ -209,7 +218,7 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = 10.0, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = 10.0, inChunkSecFloat = 5.0) lRec.CaptureWait() """ self.mCaptureThread.join() @@ -220,7 +229,7 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0) lRec.CaptureStop() :param inWaitStream: True - выполнить ожидание окончания потока захвата перед окончанием, по умолчанию True @@ -234,8 +243,12 @@ class Recorder: self.mStream.close() #Close module self.mAudio.terminate() - self.CaptureChunk(inExtra=inExtra, inForceChunkBool=False) + lFileNameStr = self.CaptureChunk(inExtra=inExtra, inForceChunkBool=False) self.mStatusStr = "0_READY" + if self.mCallbackStopDef != None: + lCallbackThread = threading.Thread(target=self.mCallbackStopDef,args=[self, lFileNameStr]) + lCallbackThread.start() + def CaptureChunk(self, inExtra=None, inForceChunkBool=True): """L-,W+: Зафиксировать захват аудио в виде промежуточного файла вида: <имя файла>_00000.mp3 @@ -243,20 +256,23 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0) lRec.CaptureChunk() :param inExtra: Дополнительный контент, необходимый для идентификации файла. В дальнейшем получить структуру можно с помощью функции FileInfoGet()['Extra'], по умолчанию None :type inExtra: any, опционально :param inForceChunkBool: True - вне зависимости от текущего режима перейти на режим сохранения по частям, по умолчанию True :type inForceChunkBool: bool, опционально + :return: Наименование сохраненного аудиофайла + :rtype: str """ if inForceChunkBool==True and self.mFileAvailableChunkInt==None: self.mFileAvailableChunkInt=0 lFileNameStr = self.mFileNameStr if self.mFileAvailableChunkInt!=None: lFileNameStr+=f"_{self.mFileAvailableChunkInt:05}" self.mFileAvailableChunkInt = self.mFileAvailableChunkInt + 1 - lFilePathStr = os.path.abspath(os.path.join(self.mFolderPathStr,f"{lFileNameStr}.{self.mFileFormatStr}")) + lFileNameWExtStr = f"{lFileNameStr}.{self.mFileFormatStr}" + lFilePathStr = os.path.abspath(os.path.join(self.mFolderPathStr,lFileNameWExtStr)) # Advanced usage, if you have raw audio data: sound = AudioSegment( # raw audio data (bytes) @@ -271,14 +287,18 @@ class Recorder: self.mRecordedFramesList = [] lTimeSecFloat = time.time() sound.export(lFilePathStr, format=f"{self.mFileFormatStr}") - self.mFileNameList.append(f"{lFileNameStr}.{self.mFileFormatStr}") - self.mFileInfoDict[f"{lFileNameStr}.{self.mFileFormatStr}"]= { + self.mFileNameList.append(lFileNameWExtStr) + self.mFileInfoDict[lFileNameWExtStr]= { "StartSecFloat": self.mStartChunkSecFloat, "EndSecFloat": lTimeSecFloat, "Extra": inExtra, "PathStr": lFilePathStr } + if self.mCallbackChunkDef != None and self.mFileAvailableChunkInt!=None: + lCallbackThread = threading.Thread(target=self.mCallbackChunkDef,args=[self, lFileNameWExtStr]) + lCallbackThread.start() self.mStartChunkSecFloat = lTimeSecFloat + return lFileNameWExtStr def FileInfoGet(self, inFileNameStr=None): """L-,W+: Вернуть информацию по аудиофайлу inFileNameStr. Если inFileNameStr == None -> Функция вернет информацию по последнему записанному файлу @@ -286,7 +306,7 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0) lRec.CaptureChunk() lFileInfoDict = lRec.FileInfoGet() @@ -304,7 +324,7 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0) lRec.CaptureChunk() lRec.CaptureChunk() lRec.FileListGet() @@ -320,7 +340,7 @@ class Recorder: .. code-block:: python from pyOpenRPA.Robot import Audio lRec = Audio.Recorder() - lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0) + lRec.CaptureStart(inFileNameStr = "out", inFileFormatStr = "mp3", inDurationSecFloat = None, inChunkSecFloat = 5.0) lRec.CaptureChunk() lRec.CaptureChunk() lRec.FileListGet() @@ -329,13 +349,6 @@ class Recorder: :rtype: list """ return self.mFileNameList[-1] - - def __Callback__(self, inDefList): - pass - def __CallbackIsChunked__(self): - pass - def __CallbackIsStopped__(self): - pass #def IsSilent(self, inLastSecFloat=None): # "Returns 'True' if below the 'silent' threshold" diff --git a/Tools/Jupyter-notebooks/Audio.ipynb b/Tools/Jupyter-notebooks/Audio.ipynb index 34eece79..bd7b3ebb 100644 --- a/Tools/Jupyter-notebooks/Audio.ipynb +++ b/Tools/Jupyter-notebooks/Audio.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -15,36 +15,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2 - NEC LCD1703M (AMD High Definition Audio Device): 0.2926829268292683 _ 0.0\n", - "Динамики (USB PnP Sound Device): 0.7419354838709677 _ 0.2926829268292683\n", - "Динамики (Realtek High Definition Audio): 0.22535211267605634 _ 0.7419354838709677\n", - "9\n" - ] - }, - { - "data": { - "text/plain": [ - "{'IndexInt': 9,\n", - " 'NameStr': 'Динамики (USB PnP Sound Device)',\n", - " 'MaxInputChannelsInt': 0,\n", - " 'MaxOutputChannelsInt': 2,\n", - " 'HostApiInt': 1,\n", - " 'DefaultSampleRateFloat': 44100.0,\n", - " 'HostApiStr': 'Windows WASAPI'}" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from pyOpenRPA.Robot import Audio\n", "print(Audio.DeviceSystemSoundIndex())\n", @@ -53,49 +26,38 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyOpenRPA.Robot import Audio\n", "lRec = Audio.Recorder(inDeviceInt = Audio.DeviceMicrophoneIndex())\n", - "lRec.CaptureStart(inFileNameStr = \"out2\", inFileFormatStr = \"mp3\", inDurationSecFloat = None, inChunkSecFloat = 15.0, inChunkSilentSecFloat = 3.0)" + "lRec.CaptureStart(inFileNameStr = \"out2\", inFileFormatStr = \"mp3\", inDurationSecFloat = None, inChunkSecFloat = 15.0)" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pyOpenRPA.Robot import Audio\n", "lRec = Audio.Recorder()\n", - "lRec.CaptureStart(inFileNameStr = \"out\", inFileFormatStr = \"mp3\", inDurationSecFloat = None, inChunkSecFloat = 5.0, inChunkSilentSecFloat = 10.0)" + "lRec.CaptureStart(inFileNameStr = \"out\", inFileFormatStr = \"mp3\", inDurationSecFloat = None, inChunkSecFloat = 5.0, inCallbackChunkDef=test)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "lRec.FileListGet()" + "lRec.hi" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -104,150 +66,28 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "lRec.mT" + "def test(lRec, lFilenameStr):\n", + " lRec.hi=[lFilenameStr]" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'1_RECORDING'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "lRec.StatusGet()" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Available devices:\n", - "\n", - "0\n", - "0: \t Ïåðåíàçíà÷åíèå çâóêîâûõ óñòð. - Input \n", - " \t MME \n", - "\n", - "0\n", - "1: \t Ìèêðîôîí (Realtek High Definiti \n", - " \t MME \n", - "\n", - "0\n", - "2: \t Ìèêðîôîí (EpocCam Camera Audio) \n", - " \t MME \n", - "\n", - "0\n", - "3: \t Ìèêðîôîí (USB PnP Sound Device) \n", - " \t MME \n", - "\n", - "0\n", - "4: \t Ïåðåíàçíà÷åíèå çâóêîâûõ óñòð. - Output \n", - " \t MME \n", - "\n", - "0\n", - "5: \t Äèíàìèêè (USB PnP Sound Device) \n", - " \t MME \n", - "\n", - "0\n", - "6: \t 2 - NEC LCD1703M (AMD High Defi \n", - " \t MME \n", - "\n", - "0\n", - "7: \t Äèíàìèêè (Realtek High Definiti \n", - " \t MME \n", - "\n", - "1\n", - "8: \t 2 - NEC LCD1703M (AMD High Definition Audio Device) \n", - " \t Windows WASAPI \n", - "\n", - "1\n", - "9: \t Динамики (USB PnP Sound Device) \n", - " \t Windows WASAPI \n", - "\n", - "1\n", - "10: \t Динамики (Realtek High Definition Audio) \n", - " \t Windows WASAPI \n", - "\n", - "1\n", - "11: \t Микрофон (EpocCam Camera Audio) \n", - " \t Windows WASAPI \n", - "\n", - "1\n", - "12: \t Микрофон (Realtek High Definition Audio) \n", - " \t Windows WASAPI \n", - "\n", - "1\n", - "13: \t Микрофон (USB PnP Sound Device) \n", - " \t Windows WASAPI \n", - "\n", - "2\n", - "14: \t Стерео микшер (Realtek HD Audio Stereo input) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "15: \t Микрофон (Realtek HD Audio Mic input) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "16: \t Speakers (Realtek HD Audio output) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "17: \t Лин. вход (Realtek HD Audio Line input) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "18: \t Output (AMD HD Audio HDMI out #1) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "19: \t Динамики (USB PnP Sound Device) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "20: \t Микрофон (USB PnP Sound Device) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "21: \t MIDI (EpocCam Audio) \n", - " \t Windows WDM-KS \n", - "\n", - "2\n", - "22: \t Output (EpocCam Audio) \n", - " \t Windows WDM-KS \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "print (textcolors.blue + \"Available devices:\\n\" + textcolors.end)\n", "for i in range(0, p.get_device_count()):\n", @@ -258,7 +98,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -279,32 +119,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting...\n", - "End.\n", - "2\n", - "2\n", - "48000\n", - "382976\n" - ] - }, - { - "data": { - "text/plain": [ - "<_io.BufferedRandom name='out.mp3'>" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import pyaudio\n", "defaultframes = 512\n", @@ -364,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -378,57 +195,25 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.647887323943662" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "similarity_no_case(\"Ìèêðîôîí (Realtek High Definiti\",\"Динамики (Realtek High Definition Audio)\")" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "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" - } - ], + "outputs": [], "source": [ "p.get_default_input_device_info()" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -438,60 +223,27 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "p" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "not (False ^ True)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from pyOpenRPA.Robot import Audio\n", "Audio.DeviceMicrophoneIndex()" @@ -499,21 +251,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "abspath() takes 1 positional argument but 2 were given", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mTypeError\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;32mimport\u001b[0m \u001b[0mos\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mos\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mabspath\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"\"\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;34m\"\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;31mTypeError\u001b[0m: abspath() takes 1 positional argument but 2 were given" - ] - } - ], + "outputs": [], "source": [ "import os\n", "os.path.abspath(\"\",\"\")"