├── README.md ├── counter.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # AI counter app from my talk at PyCon US 2024 2 | 3 | I built this project for my [Imitation Intelligence](https://simonwillison.net/2024/Jul/14/pycon/) talk. 4 | 5 | This little macOS app listens through the microphone and increments a visible counter any time anyone says "AI" or "Artificial Intelligence". 6 | 7 | You need to download and extract the model file from here: https://alphacephei.com/vosk/models 8 | 9 | Get the `vosk-model-en-us-0.22-lgraph` 128MB zip file and uncompress it. You need to have that `vosk-model-en-us-0.22-lgraph` folder in the same folder as `counter.py` 10 | 11 | Then: 12 | ```bash 13 | python -m venv venv 14 | venv/bin/pip install -r requirements.txt 15 | venv/bin/python counter.py 16 | ``` 17 | Here's [the ChatGPT transcript](https://chatgpt.com/share/58f2352d-1f17-495b-94f1-4eb44cd574b9) I used to help build the tool. 18 | 19 | And a screenshot showing what the counter looks like: 20 | 21 | ![macOS screenshot - a white rectangle in the top right shows the number four](https://github.com/simonw/count-ai/assets/9599/5955465e-2011-4572-8865-85284b7409e7) 22 | -------------------------------------------------------------------------------- /counter.py: -------------------------------------------------------------------------------- 1 | import time 2 | import tkinter as tk 3 | from tkinter import font 4 | import pyaudio 5 | import re 6 | import json 7 | from vosk import Model, KaldiRecognizer 8 | 9 | ai_pattern = re.compile(r'\b(ai|hey hi|a high|a i|artificial intelligence)\b', re.IGNORECASE) 10 | 11 | def count_ai_or_variants(input_string): 12 | matches = ai_pattern.findall(input_string) 13 | return len(matches) 14 | 15 | 16 | class CounterApp: 17 | def __init__(self, root): 18 | self.root = root 19 | self.counter = 0 20 | 21 | # Configure window 22 | self.root.overrideredirect(True) 23 | 24 | window_width = 200 25 | window_height = 100 26 | screen_width = self.root.winfo_screenwidth() 27 | # screen_height = self.root.winfo_screenheight() 28 | 29 | # Calculate position x, y 30 | x = screen_width - window_width 31 | y = 0 32 | 33 | self.root.geometry(f"{window_width}x{window_height}+{x}+{y}") 34 | self.root.attributes("-topmost", True) 35 | \ 36 | # Font configuration 37 | self.custom_font = font.Font(size=48, weight='bold') 38 | 39 | # Label to display counter 40 | self.label = tk.Label(self.root, text=str(self.counter), font=self.custom_font) 41 | self.label.pack(expand=True) 42 | 43 | def update_counter(self, increment=1): 44 | self.counter += increment 45 | self.label.config(text=str(self.counter)) 46 | 47 | 48 | def listen_with_retry(app): 49 | while True: 50 | try: 51 | done = listen_for_ai(app) 52 | if done is stopped: 53 | return 54 | except: 55 | time.sleep(0.2) 56 | 57 | 58 | stopped = object() 59 | 60 | 61 | def listen_for_ai(app): 62 | model_path = "vosk-model-en-us-0.22-lgraph" 63 | model = Model(model_path) 64 | recognizer = KaldiRecognizer(model, 16000) 65 | p = pyaudio.PyAudio() 66 | stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=4096) 67 | stream.start_stream() 68 | 69 | try: 70 | while True: 71 | data = stream.read(4096) 72 | if recognizer.AcceptWaveform(data): 73 | result = json.loads(recognizer.Result()) 74 | print(result) 75 | counts = count_ai_or_variants(result.get('text', '')) 76 | if counts: 77 | app.update_counter(counts) 78 | except KeyboardInterrupt: 79 | print("Stopping...") 80 | finally: 81 | stream.stop_stream() 82 | stream.close() 83 | p.terminate() 84 | return stopped 85 | 86 | if __name__ == "__main__": 87 | root = tk.Tk() 88 | app = CounterApp(root) 89 | 90 | import threading 91 | listener_thread = threading.Thread(target=listen_with_retry, args=(app,)) 92 | listener_thread.daemon = True 93 | listener_thread.start() 94 | 95 | root.mainloop() 96 | 97 | 98 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyaudio 2 | vosk 3 | 4 | --------------------------------------------------------------------------------