python-clipboard-speaker

Want your computer to speak a bit? Mark some text and let it rip.
git clone https://kaka.farm/~git/python-clipboard-speaker
Log | Files | Refs | README | LICENSE

clipboard_speaker.py (3371B)


      1 #!/usr/bin/env python3
      2 
      3 import argparse
      4 import os
      5 import sys
      6 from pathlib import Path
      7 from subprocess import Popen, PIPE
      8 
      9 DEFAULT_WORDS_PER_MINUTE = "175"
     10 
     11 CLIPBOARD_SPEAKER_PATH = Path.home() / ".clipboard-speaker"
     12 PID_FILE_PATH = CLIPBOARD_SPEAKER_PATH / "pid"
     13 FIFO_FILE_PATH = CLIPBOARD_SPEAKER_PATH / "fifo"
     14 
     15 WORDS_PER_MINUTE_PATH = CLIPBOARD_SPEAKER_PATH / "words-per-minute"
     16 
     17 
     18 def get_words_per_minute() -> str:
     19     if WORDS_PER_MINUTE_PATH.exists():
     20         with WORDS_PER_MINUTE_PATH.open("r") as words_per_minute_file:
     21             words_per_minute = words_per_minute_file.read().strip()
     22         return words_per_minute
     23     else:
     24         with WORDS_PER_MINUTE_PATH.open("w") as words_per_minute_file:
     25             words_per_minute_file.write(DEFAULT_WORDS_PER_MINUTE)
     26             return DEFAULT_WORDS_PER_MINUTE
     27 
     28 
     29 def get_argparse_options() -> argparse.Namespace:
     30     parser = argparse.ArgumentParser()
     31 
     32     group = parser.add_mutually_exclusive_group(required=True)
     33 
     34     group.add_argument(
     35         "-p",
     36         "--primary",
     37         help="Read the mouse selection kind of clipboard.",
     38         action="store_true",
     39     )
     40 
     41     group.add_argument(
     42         "-b",
     43         "--clipboard",
     44         help="Read the ctrl-c kind of clipboard.",
     45         action="store_true",
     46     )
     47 
     48     args = parser.parse_args()
     49 
     50     return args
     51 
     52 
     53 def main() -> None:
     54     commandline_args = get_argparse_options()
     55 
     56     if not CLIPBOARD_SPEAKER_PATH.exists():
     57         CLIPBOARD_SPEAKER_PATH.mkdir(mode=500)
     58     words_per_minute = get_words_per_minute()
     59     try:
     60         os.mkfifo(FIFO_FILE_PATH, mode=0o600)
     61     except FileExistsError:
     62         pass
     63 
     64     # https://stackoverflow.com/questions/63132778/how-to-use-fifo-named-pipe-as-stdin-in-popen-python
     65     with os.fdopen(
     66         os.open(FIFO_FILE_PATH, os.O_RDONLY | os.O_NONBLOCK), "r"
     67     ) as fifo_read_file, os.fdopen(
     68         os.open(FIFO_FILE_PATH, os.O_WRONLY), "w"
     69     ) as fifo_write_file:
     70 
     71         if commandline_args.primary:
     72             xsel_process = Popen(
     73                 ["xsel", "-p"],
     74                 stdout=PIPE,
     75             )
     76         elif commandline_args.clipboard:
     77             xsel_process = Popen(
     78                 ["xsel", "-b"],
     79                 stdout=PIPE,
     80             )
     81 
     82         # Replace each newline and each consecutive newlines with a single space.
     83         message = b" ".join(
     84             line.strip() for line in xsel_process.stdout for line in line.split(b"\n")
     85         ).decode("utf-8")
     86 
     87         print(message)
     88 
     89         # Write to the fifo with a newline as a good luck token. (it may or may
     90         # not be what will make it show up immediately on the other sideā€¦)
     91         fifo_write_file.write(message + "\n")
     92 
     93         # We make sure that things are written right now by flushing them down.
     94         fifo_write_file.flush()
     95 
     96         if not PID_FILE_PATH.exists():
     97             speak_ng_process = Popen(
     98                 [
     99                     "speak-ng",
    100                     f"-s {words_per_minute}",
    101                 ],
    102                 stdin=fifo_read_file,
    103             )
    104 
    105             with PID_FILE_PATH.open("w") as pid_file:
    106                 pid_file.write(str(speak_ng_process.pid))
    107 
    108             try:
    109                 speak_ng_process.wait()
    110             except KeyboardInterrupt as e:
    111                 os.remove(PID_FILE_PATH)
    112 
    113             os.remove(PID_FILE_PATH)
    114 
    115 
    116 if __name__ == "__main__":
    117     main()