'Python: How to throw an exception as soon as the user presses a certain specific key (other than ctrl + c)

I'm using selenium on Python and, I wish I could interrupt the program, catch the exception and do something with it, as soon as the user presses a certain specific key (other than ctrl + c ,because I've already cought this exception). Do you think it is possible ?

As an exemple: I would like to convert a text to speech with selenium by using this website: "https://www.naturalreaders.com/online" (I already did it) and then, when the user presses (for exemple) the space key, I would like to pause the speech (and resume the speech if the user presses this key again)

here is my code:

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.ui import WebDriverWait

from bs4 import BeautifulSoup
from ebooklib import epub
from colorama import Fore, Style
import time, logging, ebooklib, os


if "__main__" == __name__:
    
    def load_site(url, silent= False):
        #those four varables are inside this function
        global driver, r_click, wait, ignored_exceptions
        #close all Chrome windows before starting
        os.system("taskkill /f /im chrome.exe 2> nul")
        
        #this is if I would like to connect to the google account
        chr_options = webdriver.ChromeOptions()
        if silent:
            #to run a site in the background
            chr_options.add_argument("headless")
        driver = webdriver.Chrome(executable_path= <were it is located on your device>, options= chr_options)
        driver.delete_all_cookies()
        driver.get(url)
        #this is a called function which wait until an element is clickable on the driver...
        wait = WebDriverWait(driver, 20,ignored_exceptions=ignored_exceptions)
        driver.maximize_window();time.sleep(1)

    def connect_voice(driver):
        #here I choose the voice on the site inside the driver
        global wait      
        speakers = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[3]"))).click()
        free = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/div[2]/div[2]/div/div/div/app-voice-list/div/mat-tab-group/mat-tab-header/div[2]/div/div/div[1]"))).click(); time.sleep(0.5)
        Paul = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/div[2]/div[2]/div/div/div/app-voice-list/div/mat-tab-group/div/mat-tab-body[1]/div/app-voice/div/mat-selection-list/mat-list-option[6]/div/div[2]/div/div[2]/div/div"))).click()
    
    def say(text, WPM = 180,to_print= False):
        global driver
        nb_words = len(text.split())
        if nb_words == 0: return None
        
        #I replace this character for a good prononciation
        text = list(text.replace("—", "-"))
        #here I delete all the useless numbers of the text
        for i in range(len(text)-1):
            num = 1
            while i + num < len(text) and text[i].isalpha() and text[i+num].isnumeric():          
                text[i+num] = ""
                num += 1
        text = "".join(text)
        
        #here I calculate the good speed for printting a letter
        LPM = (len(text) * WPM) / nb_words
        SPL = (1 / LPM) * 60 - 0.015
        
        #I don't forget to delete the previous text
        clear = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-home/div/app-input/div/div/div[1]/div[2]/button[5]"))); ActionChains(driver).move_to_element(clear).click(clear).perform()
        #and I paste the actual text on the site
        paste = wait.until(expected_conditions.presence_of_element_located((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-home/div/app-input/div/div/div[2]"))).send_keys(text)
        #here I play the text, and he read it  
        play = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[1]"))).click()
        time.sleep(0.6)
        for i, l in enumerate(text):
            #Here I color all the upper characters in upper words
            if l.isupper() and (i == len(text)-1 or text[i+1].isupper() or text[i+1] in [" ", ".", ";",",",":","!","(",")","-"]):
                l = f"{c2}{l}{reset}"
            print(l, end= ""); time.sleep(SPL)
        print()

    logging.captureWarnings(True)
    #colors for the print function
    c1 = Style.BRIGHT + Fore.YELLOW
    c2 = Style.BRIGHT + Fore.CYAN
    reset_color = Style.RESET_ALL
    ignored_exceptions =(NoSuchElementException,StaleElementReferenceException,)
    
    #the site is a text to speech converter
    load_site("https://www.naturalreaders.com/online/", silent= True)
    print("starting reading...")
    connect_voice(driver)
    
    #I read a book which is downloaded on my device
    book = epub.read_epub(<path of the epub book>)
    #get the item objects corresponding to all chapters
    book = list(book.get_items_of_type(ebooklib.ITEM_DOCUMENT))
    #for each item
    for chap in book[1:]:
        try:
            #I get only the text of the html code
            soup = BeautifulSoup(chap.get_content(), "lxml")
            if soup is not None:
                texte = soup.text.splitlines()
                for line in texte:
                    #and I read it line by line
                    say(line + ' ', to_print= True)
        except KeyboardInterrupt:
            #If the user click on ctrl + c, the program read the next chapter
            #(but before, I just pause the actual reading)                         
            play = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[2]"))).click()
            
        print('\n\n')

I would like to add a try/except inside the say function, whose purpose is to stop temporarily the reading when the user click on a specific key (maybe F7, or a key not too accessible) or stop the reading by holding an accesible key(a space). Does anyone is in a situation similar to mine or knows how to raise an exception by clicking or holding a key (other than ctrl + c because already used)?



Solution 1:[1]

I just found how to do it:

import keyboard
for i, l in enumerate(sent):
    if keyboard.is_pressed('p'):
        pause = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[2]"))).click(); time.sleep(0.5)
        while not(keyboard.is_pressed('p')):
             time.sleep(0.05)                
        play = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[1]"))); ActionChains(driver).move_to_element(pause).click(pause).perform()
        id_sent -= 1; break
   if l.isupper() and (i == len(sent)-1 or sent[i+1].isupper() or sent[i+1] in [" ", ".", ";",",",":","!","(",")","-"]):
       l = f"{c2}{l}{reset}"
   print(l, end= ""); time.sleep(SPL)

like you have seen, I added this part of code inside the for loop:

if keyboard.is_pressed('p') #do someting
    pause = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[2]"))).click()
    time.sleep(0.5) #it is important, because if you don't set a sleep, the while loup will be True directly  
    while not(keyboard.is_pressed('p')): #we wait for the user to press this key again, to do an other thing
         time.sleep(0.05)            
    play = wait.until(expected_conditions.element_to_be_clickable((By.XPATH,"/html/body/app-root/div/app-main/mat-sidenav-container/mat-sidenav-content/app-header/mat-toolbar/div[2]/div[2]/app-reader/div/button[1]"))); ActionChains(driver).move_to_element(pause).click(pause).perform()

the condition: if keyboard.is_pressed('p') ; doesn't wait for you to press the 'p' key, so the for loop continue (that's why you need to put this condition inside a for loop, because if you don't, the condition could be true, but already executed and already returned False)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1