Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

myRecognizer object freezes the script when using .listen method (sometimes) #798

Open
SebasSBM opened this issue Dec 4, 2024 · 10 comments

Comments

@SebasSBM
Copy link

SebasSBM commented Dec 4, 2024

Steps to reproduce

--- (How do you make the issue happen? Does it happen every time you try it?)

Summarizing: I start my computer. I run this code on Terminal. And it works as expected. After some tests, I interrupt the script with Ctrl + C. Then I feel like run it again, it works again, interrupt it again with Ctrl + C. Eventually, after running it several times, when I execute the script again, it always freezes when the r.listen statement is reached, and not even Ctrl + C will interrupt the execution.

When I instantiate a Recognizer object, and then execute the method myRecognizer.listen(source, timeout=5, phrase_time_limit=5) using sr.Microphone() as source, well... sometimes I run it and ev3rything works fine. But some other times I run the script and it freezes when using myRecognizer.listen method... when this happens, not even using Ctrl + C will succeed interrupting the script. This happens despite using timeout parameters to make sure ambient noise doesn't keep it listening forever, and depite testing it in relatively silent places. Trying to catch any exceptions, or even trying multithreading didn't solve the issue. This happens no matter if I use either Google Recognition or Sphinx Recognition

--- (Make sure to go into as much detail as needed to reproduce the issue. Posting your code here can help us resolve the problem much faster!)

# -*- coding: utf-8 -*-
import pyttsx3
import speech_recognition as sr

GOOGLE_RECOGNITION = 0
SPHINX_RECOGNITION = 1

def speech_to_string(language='es-es', recog_mode=GOOGLE_RECOGNITION):
    #It takes microphone input from the user and returns string output

    r = sr.Recognizer()
    with sr.Microphone() as source:
        print("Listening...")
        r.pause_threshold = 1
        r.adjust_for_ambient_noise(source)
        audio = r.listen(source, timeout=5, phrase_time_limit=5)

        query = ""
        try:
            print("Recognizing...")
            if recog_mode == GOOGLE_RECOGNITION:
                query = r.recognize_google(audio, language=language)
            elif recog_mode == SPHINX_RECOGNITION:
                query = r.recognize_sphinx(audio, language=language)
            else: # Default
                query = r.recognize(audio, language=language)
            #print(f"User said: {query}\n")  #User query will be printed.
        except sr.WaitTimeoutError as e:
            print(e)
            print("WAIT TIMEOUT ERROR")
        except sr.UnknownValueError as e:
            print(e)
            print("UNKNOWN VALUE ERROR")
        except sr.RequestError as e:
            print(e)
            print("UNKNOWN VALUE ERROR")
        except Exception as e:
            print(e)
            print("SOME OTHER EXCEPTION...") 
        return query


### MAIN CODE
engine = pyttsx3.init()
while True:
    # XXX Should I run `speech_to_string` in a different thread to not block the main thread? TODO
    #     I might add a variant of this main code  using ThreadpoolExecutor for Multithreading
    #     ...if required. For now I keep the snippet simple. It would freeze the same way anyways
    my_voice_str = speech_to_string('es-es', GOOGLE_RECOGNITION)
    print(f"User said: {my_voice_str}\n")

Expected behaviour

I just expect it to execute without freezing... and if freezing is unevitable due to infinite loops or other issues with the internal algorithm, at least, I'd like to have a chance to capture that as some kind of Exception that I can deal with.

Actual behaviour

As I explained in "Steps to reproduce" (AKA "how to make the issue happen"), sometimes it behaves as expected when you run the script. But some other times, you run the script and the script freezes completely. And when this happens, not even Ctrl + C from the linux Terminal will interrupt the script successfully. When it comes to this, my only option is forcefully closing the Terminal window.

(If the library threw an exception, paste the full stack trace here)

NO, it did throw none Exception at all. BUT, when it runs, I usually see all this output in the Terminal, both the times that the code works, and the times when it freezes. This one is, to be accurate, extracted from one of the executions that lead to freeze the Terminal console... but I honestly think it is very similar to this when it works as well:

ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround40
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround41
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround50
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround51
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.front
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround21
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround40
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround41
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround50
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround51
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.surround71
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.iec958
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2721:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1000:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
Listening...

System information

(Delete all the statements that don't apply.)

My system is <Ubuntu 24.04 LTS x64>

My Python version is <3.12.3>

My Pip version is <24.3.1>.

My SpeechRecognition library version is <3.10.4>. // Confirmed this issue happens with <3.12.x> as well

My PyAudio library version is <0.2.14>

My microphones are: ['sof-essx8336: - (hw:0,5)', 'sof-essx8336: - (hw:0,6)', 'sof-essx8336: - (hw:0,7)', 'pipewire', 'default']
I installed PocketSphinx from --> pip3

@SebasSBM
Copy link
Author

I still have no idea why it behaves this way... shouldn't the software throw an Exception instead of doing this? What should I do about this? Do you need some additional info, or adittional tests, to figure out how to patch this issue? Please let me know

@SebasSBM
Copy link
Author

SebasSBM commented Dec 13, 2024

Well... I just downloaded the whole master branch's file system in a ZIP, and replaced the speech recognition installed with pip3 by the one downloaded from master nowadays... after, I added lots of print statements for the methods Recognizer.listen() and it's inner _listen hoping for finding where the loop got frozen... but now the issue doesn't happen anymore... I wonder why... I will just keep testing it with the snippet above (snippet that, by the way, I created just to isolate this issue), just in case the issue might happen again... I'd like to see the git diff for what changed between "speech_recognition v3.10.4" installed with pip3, VS this "speech_recognition last master commit"... maybe that way I could figure out what went wrong...

EDIT: I have checked out your tag system: so, by intuition I guess the "mentioned master last commit" is v3.12.0 ... I am still not sure that the failure won't happen again in this mentioned last version, that's why I am not closing this ticket... yet

@SebasSBM
Copy link
Author

By the way, the project I was working on with your speech_recognition llibrary goes way beyond the snippet I shown in this #798 issue. The snippet just isolates the failure I reported here with more simple code. I wonder: would you like that I share my project based on your project through a GitHub repository?

@SebasSBM
Copy link
Author

SebasSBM commented Dec 19, 2024

I just confirmed right now, that even the last version (both the 3.12.0 downloaded from pip3 and the one downloaded by zip directly from you last master commit at 13th December 2024, just keep freezing the same way sometimes)... some other times, it just works like normal (until it freezes the first time, then it will probably keep freezing the same way until I reboot my computer)...

What I said in the first post, it doesn't have to be interrupted with Ctrl+C and re-run necessarily to reproduce the bug. You could just run it once, it would print the voice several times as expected, and then freeze as I described in the first post. I empathize that this is not normal behaviour.

By the way, I still feel dissapointed with the fact that you labelled a "bug report ticket" as a "help wanted ticket."

@SebasSBM
Copy link
Author

SebasSBM commented Dec 19, 2024

I have made additional tests to see how exactly the listen() function gets stuck this way, by modifying speech_recognition/__init__.py by just adding print statements in strategic points, which lets me know how your listen function is being executed. This is what I see when the method gets stuck (while I watched the print outputs, I could notice that, actually, Ctrl+C does actually interrupt the script, but it takes way more than usual...). According to the outputs obtained, this is the executing flow I could seewithin this __init__.py file:

1- Line 458 starts executing.
2- Line 486 reached (starts making snowboy configuration, whatever that is)
3- Line 500 reached. Condition is met and it breaks the while True loop where this line is nested.
4- 3- Line 524 reached. When it freezes, it looks like it's because it never breaks this while True loop.

Please notice that it is way too weird that this part of the _listen method gets stuck in an infinite loop, having in mind that I used phrase_time_limit=5 while calling the listen public method of the used Recognizer instance. (you can find full details of my implementation in "Steps to reproduce" section, I left a snippet there that isolates this issue).

Something must be done to prevent this from happening, but I barely started diving in your algorithm, so I have no idea what is actually going on in that while True... I can only try to guess it...

So now we have a hint, lads...

@SebasSBM
Copy link
Author

@ftnext you're wrong. I don't want help. This is a bug report, and I gave you detailed info about it. What I actually want is to fix this bug.

@SebasSBM
Copy link
Author

SebasSBM commented Dec 19, 2024

Well, I digged a little more in this __init__.py file by filling with print statements the while True that I know it never breaks, and where everything freezes the times it freezes (some other times, as I said over and over, everything works as expected). So, these print statements show me the state of the variables that control the loop's flow, giving me some insight of what's going on within. The resulting output while reproducing this bug proves that this while True was iterated 70 times before freezing completely, and then stops printing print statements, like if the script's execution was freezing due to saturating the hardware or something.

Here you have the resulting output:

|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.023219954648526078, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.046439909297052155; -phrase_start_time = 0.023219954648526078
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.046439909297052155, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.06965986394557823; -phrase_start_time = 0.04643990929705215
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.06965986394557823, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.09287981859410431; -phrase_start_time = 0.06965986394557823
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.09287981859410431, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.11609977324263039; -phrase_start_time = 0.09287981859410431
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.11609977324263039, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.13931972789115646; -phrase_start_time = 0.11609977324263038
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.13931972789115646, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.16253968253968254; -phrase_start_time = 0.13931972789115646
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.16253968253968254, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.18575963718820862; -phrase_start_time = 0.16253968253968254
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.18575963718820862, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.2089795918367347; -phrase_start_time = 0.18575963718820862
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 5; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.2089795918367347, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.23219954648526078; -phrase_start_time = 0.2089795918367347
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 6; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.23219954648526078, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.25541950113378686; -phrase_start_time = 0.23219954648526078
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 7; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.25541950113378686, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.2786394557823129; -phrase_start_time = 0.25541950113378686
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 8; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.2786394557823129, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.30185941043083897; -phrase_start_time = 0.2786394557823129
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 9; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.30185941043083897, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.325079365079365; -phrase_start_time = 0.30185941043083897
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 10; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.325079365079365, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.3482993197278911; -phrase_start_time = 0.325079365079365
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 11; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.3482993197278911, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.37151927437641713; -phrase_start_time = 0.3482993197278911
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 12; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.37151927437641713, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.3947392290249432; -phrase_start_time = 0.37151927437641713
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 13; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.3947392290249432, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.41795918367346924; -phrase_start_time = 0.3947392290249432
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 14; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.41795918367346924, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.4411791383219953; -phrase_start_time = 0.41795918367346924
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 15; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.4411791383219953, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.46439909297052134; -phrase_start_time = 0.4411791383219953
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 16; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.46439909297052134, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.4876190476190474; -phrase_start_time = 0.46439909297052134
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 17; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.4876190476190474, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.5108390022675735; -phrase_start_time = 0.48761904761904745
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 18; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.5108390022675735, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.5340589569160996; -phrase_start_time = 0.5108390022675735
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 19; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.5340589569160996, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.5572789115646257; -phrase_start_time = 0.5340589569160996
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 20; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.5572789115646257, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.5804988662131518; -phrase_start_time = 0.5572789115646257
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 21; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.5804988662131518, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.6037188208616779; -phrase_start_time = 0.5804988662131518
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 22; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.6037188208616779, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.626938775510204; -phrase_start_time = 0.6037188208616779
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 23; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.626938775510204, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.6501587301587302; -phrase_start_time = 0.626938775510204
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 24; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.6501587301587302, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.6733786848072563; -phrase_start_time = 0.6501587301587302
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 25; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.6733786848072563, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.6965986394557824; -phrase_start_time = 0.6733786848072563
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.6965986394557824, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.7198185941043085; -phrase_start_time = 0.6965986394557824
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.7198185941043085, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.7430385487528346; -phrase_start_time = 0.7198185941043085
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.7430385487528346, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.7662585034013607; -phrase_start_time = 0.7430385487528346
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.7662585034013607, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.7894784580498868; -phrase_start_time = 0.7662585034013607
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.7894784580498868, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.8126984126984129; -phrase_start_time = 0.7894784580498868
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.8126984126984129, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.835918367346939; -phrase_start_time = 0.8126984126984129
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.835918367346939, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.8591383219954651; -phrase_start_time = 0.835918367346939
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.8591383219954651, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.8823582766439912; -phrase_start_time = 0.8591383219954651
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.8823582766439912, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.9055782312925174; -phrase_start_time = 0.8823582766439912
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.9055782312925174, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.9287981859410435; -phrase_start_time = 0.9055782312925174
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.9287981859410435, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.9520181405895696; -phrase_start_time = 0.9287981859410435
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.9520181405895696, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.9752380952380957; -phrase_start_time = 0.9520181405895696
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.9752380952380957, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 0.9984580498866218; -phrase_start_time = 0.9752380952380957
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 0.9984580498866218, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.021678004535148; -phrase_start_time = 0.9984580498866218
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.021678004535148, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.044897959183674; -phrase_start_time = 1.021678004535148
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.044897959183674, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.0681179138322001; -phrase_start_time = 1.044897959183674
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.0681179138322001, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.0913378684807262; -phrase_start_time = 1.0681179138322001
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.0913378684807262, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.1145578231292523; -phrase_start_time = 1.0913378684807262
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.1145578231292523, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.1377777777777784; -phrase_start_time = 1.1145578231292523
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.1377777777777784, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.1609977324263046; -phrase_start_time = 1.1377777777777784
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.1609977324263046, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.1842176870748307; -phrase_start_time = 1.1609977324263046
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.1842176870748307, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.2074376417233568; -phrase_start_time = 1.1842176870748307
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.2074376417233568, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.2306575963718829; -phrase_start_time = 1.2074376417233568
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 5; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.2306575963718829, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.253877551020409; -phrase_start_time = 1.2306575963718829
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 6; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.253877551020409, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.277097505668935; -phrase_start_time = 1.253877551020409
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 7; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.277097505668935, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.3003174603174612; -phrase_start_time = 1.277097505668935
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.3003174603174612, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.3235374149659873; -phrase_start_time = 1.3003174603174612
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.3235374149659873, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.3467573696145134; -phrase_start_time = 1.3235374149659873
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.3467573696145134, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.3699773242630395; -phrase_start_time = 1.3467573696145134
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.3699773242630395, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.3931972789115656; -phrase_start_time = 1.3699773242630395
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.3931972789115656, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.4164172335600917; -phrase_start_time = 1.3931972789115656
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.4164172335600917, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.4396371882086179; -phrase_start_time = 1.4164172335600917
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.4396371882086179, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.462857142857144; -phrase_start_time = 1.4396371882086179
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.462857142857144, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.48607709750567; -phrase_start_time = 1.462857142857144
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.48607709750567, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.5092970521541962; -phrase_start_time = 1.48607709750567
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.5092970521541962, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.5325170068027223; -phrase_start_time = 1.5092970521541962
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 1; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.5325170068027223, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.5557369614512484; -phrase_start_time = 1.5325170068027223
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 2; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.5557369614512484, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.5789569160997745; -phrase_start_time = 1.5557369614512484
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 3; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.5789569160997745, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.6021768707483006; -phrase_start_time = 1.5789569160997745
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 4; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.6021768707483006, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.6253968253968267; -phrase_start_time = 1.6021768707483006
Before reading stream: buffer's size is [2048]
After reading stream: buffer's size is [2048]
pause_count = 0; pause_buffer_count = 44
self.dynamic_energy_threshold = True
|--> _listen -> 2nd nested while True -__-. CURSED WHILE TRUE
Iteration start: elapsed_time = 1.6253968253968267, seconds_per_buffer = 0.023219954648526078
After sum: elapsed_time = 1.6486167800453528; -phrase_start_time = 1.6253968253968267
Before reading stream: buffer's size is [2048]

...despite using Ctrl+C exactly when the line 524's while True stopped printing my custom print statements within this loop, from the Terminal where I executed the "steps to reproduce snippet" to obtain this output, the script still remains more than 8 minutes without making KeyboardInterrupt effective... and it looks like it could remain like that forever if I don't forcefully close the Terminal window.

I have reproduced this bug several times with these custom print statements, and it's strange that, when the execution freezes, all those time it freezes exactly while trying to execute line 530, and I know that because I surrounded this line with the Before reading stream: and After reading stream: print logs. Just take a look at the fact that the last line in the stdout is the one that prints Before reading stream:, thus proving that, when this bug happens, this is the line that freezes the whole Recognizer.listen method.

@SebasSBM
Copy link
Author

SebasSBM commented Dec 23, 2024

I have gone a little further in the investigation about how to fix this bug... the previously spotted line 530 from __init__.py (within _listen method) looks like this:

buffer = source.stream.read(source.CHUNK)

...source.stream is most probably an instance of MicrophoneStream, and it's read method's body is just the following line:

return self.pyaudio_stream.read(size, exception_on_overflow=False)

...so, I think it would be a good way to go to allow users to obtain an Exception on overflow (being it False is probably what freezes the whole script in my machines)... so, now the method looks like this:

def read(self, size):
    return self.pyaudio_stream.read(size, exception_on_overflow=False)

...I think it should be modified to look like this:

def read(self, size, raise_exceptions=False):
    return self.pyaudio_stream.read(size, exception_on_overflow=raise_exceptions)

...then, we could make the Recognizer's constructor to accept a boolean parameter that would be store into an ad-hoc inner property, so when it reads the microphone's audio stream, the user of the SpeechRecognition library can set if they wanna get exceptions if overflow occurrs or not.

I want to make a Pull Request for this.

@SebasSBM
Copy link
Author

SebasSBM commented Dec 23, 2024

I experimented the approach suggested above: and nothing changed.

Not even flagging exception_on_overflow as True solved the issue.

What a shame. 3 years ago or so I used your library to craft a project that executes commands and responds with text to speech to whatever the user asked. I abandoned my project 3 years ago due to, despite it worked, this issue made it unreliable. "Isn't my machine powerful enough?" "¿is it an issue with my machine?" I wondered...

Nowadays I try to resume the development I abandoned 3 years ago, and find out that this issue happened as well in my new machine (not only in the old one). So this time I dared making a bug report, yet you @ftnext flagged it as "help wanted" --hilarious! Something's wrong in your algorithm, that's obvious. But I can't reach to understand what is it. And your lack of interest to analyze this bug report is pissing me off.

In two weeks I will barely have time to back-engineer your mess, so... good luck with that

SebasSBM added a commit to SebasSBM/speech_recognition that referenced this issue Dec 23, 2024
  I opened the issue Uberi#798 in Uberi/speech_recognition repository (Uberi is,
if I recall correctly, the original creator of speech_recognition library.
More details about issue Uberi#798 here
Uberi#798

  I am being ignored, so I tried to fix it myself... I kept giving feedback
about my resech on this bug in the link above. Last time I could observe that
`MicrophoneStream.read` (located at line Uberi#190) uses a keyword argument named
`exception_on_overflow` which receives `False` value.

  In this commit I tried to reform the methodology, so the class
`Recognizer`'s constructor can this time accept a boolean flag to let the
user choose if they want an exception on overflow or not.

  The issue that `Recognizer.listen` freezing the whole script execution
DIDN'T STOP despite flagging `exception_on_overflow` as `True` via the
Recognizer's constructor. So, this commit is mostly completely useless for
other that just documenting the failed attempt to resolve the issue. :-(

  In my implementation experiments of this commit, even if I instantiate
`Recognizer` using `r = sr.Recognizer(raise_overflow_exceptions=True)`, the
issue keeps happening over and over... so, if it's not an overflow issue,
¿just what the heck is causing the `source.stream.read(source.CHUNK)`
instructions to freeze any script's execution? I have no idea what it
could be...
@SebasSBM
Copy link
Author

...more details about the failed attempt described above in this commit that I uploaded to my fork of this repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants