scribe-cli 0.7.7__tar.gz → 0.7.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {scribe_cli-0.7.7/scribe_cli.egg-info → scribe_cli-0.7.9}/PKG-INFO +3 -2
  2. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/README.md +1 -1
  3. scribe_cli-0.7.9/icon.xcf +0 -0
  4. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/pyproject.toml +1 -0
  5. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/_version.py +2 -2
  6. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/app.py +130 -46
  7. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/models.py +13 -4
  8. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/util.py +3 -41
  9. {scribe_cli-0.7.7 → scribe_cli-0.7.9/scribe_cli.egg-info}/PKG-INFO +3 -2
  10. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_cli.egg-info/SOURCES.txt +4 -1
  11. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_cli.egg-info/requires.txt +1 -0
  12. scribe_cli-0.7.9/scribe_data/share/icon.png +0 -0
  13. scribe_cli-0.7.9/scribe_data/share/icon_recording.png +0 -0
  14. scribe_cli-0.7.9/scribe_data/share/icon_writing.png +0 -0
  15. scribe_cli-0.7.7/scribe_data/share/icon.jpg +0 -0
  16. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/.github/workflows/pypi.yml +0 -0
  17. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/.gitignore +0 -0
  18. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/LICENSE +0 -0
  19. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/__init__.py +0 -0
  20. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/audio.py +0 -0
  21. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/install_desktop.py +0 -0
  22. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/keyboard.py +0 -0
  23. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/models.toml +0 -0
  24. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/saverecording.py +0 -0
  25. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe/testpynput.py +0 -0
  26. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_cli.egg-info/dependency_links.txt +0 -0
  27. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_cli.egg-info/entry_points.txt +0 -0
  28. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_cli.egg-info/top_level.txt +0 -0
  29. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_data/__init__.py +0 -0
  30. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/scribe_data/templates/scribe.desktop +0 -0
  31. {scribe_cli-0.7.7 → scribe_cli-0.7.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: scribe-cli
3
- Version: 0.7.7
3
+ Version: 0.7.9
4
4
  Summary: scribe is a local speech recognition tool that provides real-time transcription using vosk and whisper AI, with the goal of serving as a virtual keyboard on a computer
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Requires-Dist: tqdm
45
45
  Requires-Dist: requests
46
46
  Requires-Dist: pyperclip
47
47
  Requires-Dist: unidecode
48
+ Requires-Dist: termcolor
48
49
  Provides-Extra: keyboard
49
50
  Requires-Dist: pynput; extra == "keyboard"
50
51
  Provides-Extra: whisper
@@ -61,7 +62,7 @@ Requires-Dist: vosk; extra == "all"
61
62
  Requires-Dist: pystray; extra == "all"
62
63
 
63
64
  [![python](https://img.shields.io/badge/python-3.12-blue.svg)]()
64
- [![pypi](https://github.com/perrette/scribe/actions/workflows/pypi.yml/badge.svg)](https://pypi.org/project/scribe-cli)
65
+ [![pypi](https://img.shields.io/pypi/v/scribe-cli)](https://pypi.org/project/scribe-cli)
65
66
 
66
67
  # Scribe
67
68
 
@@ -1,5 +1,5 @@
1
1
  [![python](https://img.shields.io/badge/python-3.12-blue.svg)]()
2
- [![pypi](https://github.com/perrette/scribe/actions/workflows/pypi.yml/badge.svg)](https://pypi.org/project/scribe-cli)
2
+ [![pypi](https://img.shields.io/pypi/v/scribe-cli)](https://pypi.org/project/scribe-cli)
3
3
 
4
4
  # Scribe
5
5
 
Binary file
@@ -19,6 +19,7 @@ dependencies = [
19
19
  "requests",
20
20
  "pyperclip",
21
21
  "unidecode",
22
+ "termcolor",
22
23
  ]
23
24
 
24
25
  classifiers = [
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.7.7'
16
- __version_tuple__ = version_tuple = (0, 7, 7)
15
+ __version__ = version = '0.7.9'
16
+ __version_tuple__ = version_tuple = (0, 7, 9)
@@ -1,9 +1,10 @@
1
1
  from pathlib import Path
2
2
  import tomllib
3
+ import time
3
4
  import argparse
4
5
  from scribe.audio import Microphone
5
6
  from scribe.util import print_partial, clear_line, prompt_choices, check_dependencies, ansi_link, colored
6
- from scribe.models import VoskTranscriber, WhisperTranscriber, StopRecording
7
+ from scribe.models import VoskTranscriber, WhisperTranscriber
7
8
 
8
9
  with open(Path(__file__).parent / "models.toml", "rb") as f:
9
10
  language_config_default = tomllib.load(f)
@@ -37,8 +38,36 @@ def pick_specialist_model(model, language, backend):
37
38
  return model
38
39
 
39
40
 
41
+ class DummyTranscriber:
42
+
43
+ def __init__(self, backend, model_name):
44
+ self.backend = backend
45
+ self.model_name = model_name
46
+
47
+ def start_recording(self, micro, **kwargs):
48
+ while True:
49
+ try:
50
+ yield {"text": input()}
51
+ except KeyboardInterrupt:
52
+ break
53
+
54
+ def __getattr__(self, item):
55
+ return None
56
+
40
57
  def get_transcriber(o, prompt=True):
41
58
 
59
+ whisper_models = ["tiny", "base", "small", "medium", "large", "turbo"]
60
+ whisper_english_models = ["tiny.en", "base.en", "small.en", "medium.en"]
61
+
62
+ if o.dummy:
63
+ return DummyTranscriber("whisper", "dummy")
64
+
65
+ if o.model and not o.backend:
66
+ if o.model.startswith("vosk-"):
67
+ o.backend = "vosk"
68
+ elif o.model in whisper_models + whisper_english_models:
69
+ o.backend = "whisper"
70
+
42
71
  if o.backend:
43
72
  checked_backend = check_dependencies(o.backend)
44
73
  if not checked_backend:
@@ -76,29 +105,25 @@ def get_transcriber(o, prompt=True):
76
105
  print(f"Or pick one of the pre-defined languages: ", " ".join(available_languages))
77
106
  exit(1)
78
107
  choices = [language_config[backend][o.language]["model"]]
79
- default_model = choices[0]
108
+ default_model = choices[0] # this is a string
80
109
 
81
110
  else:
82
111
  available_models = [language_config[backend][lang]["model"] for lang in available_languages]
83
112
  choices = list(zip(available_models, available_languages)) + [f" * [Any model from {ansi_link('https://alphacephei.com/vosk/models')}]"]
84
- default_model = choices[0]
113
+ default_model = choices[0] # this is a tuple !!
85
114
 
86
115
  print(f"For information about vosk models see: {ansi_link('https://alphacephei.com/vosk/models')}")
87
116
  if prompt:
88
- model = prompt_choices(choices, default=default_model, label="model")
117
+ model = prompt_choices(choices, default=default_model, label="model") # this always returns a string
89
118
  else:
90
- model = default_model
119
+ model = default_model[0] if isinstance(default_model, tuple) else default_model # tuple -> string
91
120
 
92
121
  elif backend == "whisper":
93
-
94
- models = ["tiny", "base", "small", "medium", "large", "turbo"]
95
- english_models = ["tiny.en", "base.en", "small.en", "medium.en"]
96
122
  default_model = "small"
97
-
98
123
  print("Some models have a specialized English version (.en) which will be selected as default is `-l en` was requested, but can also be requested explicitly below (option not listed). See [documentation](https://github.com/openai/whisper?tab=readme-ov-file#available-models-and-languages).")
99
124
  if prompt:
100
- model = prompt_choices(models, default=default_model, label="model",
101
- hidden_models=english_models)
125
+ model = prompt_choices(whisper_models, default=default_model, label="model",
126
+ hidden_models=whisper_english_models)
102
127
  else:
103
128
  model = default_model
104
129
 
@@ -143,6 +168,8 @@ def get_parser():
143
168
  parser.add_argument("-l", "--language", choices=list(language_config["vosk"]),
144
169
  help="An alias for preselected models when using the vosk backend, or 'en' for the English version of whisper models.")
145
170
 
171
+ parser.add_argument("--dummy", action="store_true", help=argparse.SUPPRESS)
172
+
146
173
  parser.add_argument("--no-prompt", action="store_false", dest="prompt", help="Disable prompts for backend and model selection and jump to recording")
147
174
  parser.add_argument("--app", action="store_true", help="Start in app mode (relies on pystray)")
148
175
 
@@ -165,7 +192,7 @@ def get_parser():
165
192
 
166
193
 
167
194
  # Commencer l'enregistrement
168
- def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency=0, ascii=False, **greetings):
195
+ def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency=0, ascii=False, callback=None, **greetings):
169
196
 
170
197
  if keyboard:
171
198
  from scribe.keyboard import type_text
@@ -189,7 +216,7 @@ def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency=
189
216
 
190
217
  if clipboard:
191
218
  fulltext += result['text'] + " "
192
- pyperclip.copy(fulltext)
219
+ pyperclip.copy(fulltext.strip())
193
220
 
194
221
  else:
195
222
  print_partial(result.get('partial', ''))
@@ -197,22 +224,8 @@ def start_recording(micro, transcriber, clipboard=True, keyboard=False, latency=
197
224
  if clipboard:
198
225
  print("Copied to clipboard.")
199
226
 
200
-
201
- def interrupt_app_thread(icon):
202
- """Thanks Le Chat for this solution: https://stackoverflow.com/a/325528/2192272
203
- """
204
- import ctypes
205
- thread = icon._recording_thread
206
- # Raise an exception in the thread using ctypes
207
- thread_id = thread.ident
208
- if thread_id is not None:
209
- res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
210
- ctypes.c_long(thread_id),
211
- ctypes.py_object(StopRecording)
212
- )
213
- if res > 1:
214
- ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
215
- print("Failure to raise exception in thread")
227
+ if callback:
228
+ callback()
216
229
 
217
230
 
218
231
  def create_app(micro, transcriber, **kwargs):
@@ -225,7 +238,42 @@ def create_app(micro, transcriber, **kwargs):
225
238
  import threading
226
239
 
227
240
  # Load an image from a file
228
- image = Image.open(Path(scribe_data.__file__).parent / "share" / "icon.jpg")
241
+ image = Image.open(Path(scribe_data.__file__).parent / "share" / "icon.png")
242
+ image_recording = Image.open(Path(scribe_data.__file__).parent / "share" / "icon_recording.png")
243
+ image_writing = Image.open(Path(scribe_data.__file__).parent / "share" / "icon_writing.png")
244
+
245
+ if transcriber.backend == "vosk":
246
+ # Recording and writing happen at the same time in this backend
247
+ # Overlay the writing image on top of the base image
248
+ image_recording = Image.alpha_composite(image_recording.convert("RGBA"), image_writing.convert("RGBA"))
249
+
250
+ def update_icon(icon, force=False):
251
+ if transcriber.recording:
252
+ if force or getattr(icon, "_icon_label", None) != "recording":
253
+ icon.icon = image_recording
254
+ icon._icon_label = "recording"
255
+ icon.update_menu()
256
+
257
+ elif transcriber.busy:
258
+ if force or getattr(icon, "_icon_label", None) != "busy":
259
+ icon.icon = image_writing
260
+ icon._icon_label = "busy"
261
+ icon.update_menu()
262
+
263
+ else:
264
+ if force or getattr(icon, "_icon_label", None) != None:
265
+ icon.icon = image
266
+ icon._icon_label = None
267
+ icon.update_menu()
268
+
269
+ def start_monitoring(icon):
270
+ try:
271
+ while transcriber.busy:
272
+ update_icon(icon)
273
+ time.sleep(0.1)
274
+
275
+ finally:
276
+ update_icon(icon)
229
277
 
230
278
  def callback_quit(icon, item):
231
279
  icon.visible = False
@@ -234,16 +282,34 @@ def create_app(micro, transcriber, **kwargs):
234
282
  icon.stop()
235
283
 
236
284
  def callback_stop_recording(icon, item):
237
- ## Here we need to stop the recording thread
238
- interrupt_app_thread(icon)
239
- icon._recording_thread.join()
285
+ # Here we need to stop the recording thread
286
+
287
+ transcriber.recording = False
288
+ if hasattr(icon, "_recording_thread"):
289
+ icon._recording_thread.join()
290
+ if hasattr(icon, "_monitoring_thread"):
291
+ icon._monitoring_thread.join()
240
292
 
241
293
  def callback_record(icon, item):
294
+ # kwargs["callback"] = icon.update_menu # NOTE: the thread will finish AFTER the callback is complete
295
+ if transcriber.busy:
296
+ print("Still busy recording or transcribing.")
297
+ return
298
+
299
+ if hasattr(icon, "_recording_thread") and icon._recording_thread.is_alive():
300
+ icon._recording_thread.join()
301
+
302
+ if hasattr(icon, "_monitoring_thread") and icon._monitoring_thread.is_alive():
303
+ icon._monitoring_thread.join()
304
+
305
+ transcriber.busy = True # this is a hack to prevent race conditions between the below threads
242
306
  icon._recording_thread = threading.Thread(target=start_recording, args=(micro, transcriber), kwargs=kwargs)
243
307
  icon._recording_thread.start()
308
+ icon._monitoring_thread = threading.Thread(target=start_monitoring, args=(icon,))
309
+ icon._monitoring_thread.start()
244
310
 
245
311
  def is_recording(item):
246
- return hasattr(icon, "_recording_thread") and icon._recording_thread.is_alive()
312
+ return transcriber.busy
247
313
 
248
314
  def is_not_recording(item):
249
315
  return not is_recording(item)
@@ -251,7 +317,6 @@ def create_app(micro, transcriber, **kwargs):
251
317
 
252
318
  # Create a menu
253
319
  menu = pystrayMenu(
254
- # Item('Record', callback_record),
255
320
  Item("Record", callback_record, visible=is_not_recording),
256
321
  Item("Stop", callback_stop_recording, visible=is_recording),
257
322
  Item('Quit', callback_quit),
@@ -279,25 +344,37 @@ def main(args=None):
279
344
  while True:
280
345
  if transcriber is None:
281
346
  transcriber = get_transcriber(o, prompt=o.prompt)
282
- print(f">>> Model {transcriber.model_name} from {transcriber.backend} selected. Keyboard [{'on' if o.keyboard else 'off'}]. Clipboard [{'on' if o.clipboard else 'off'}] <<<")
347
+ print(f"Model [{colored(transcriber.model_name, 'light_blue', attrs=['bold'])}] from [{colored(transcriber.backend, 'light_blue', attrs=['bold'])}] selected.")
283
348
  if o.prompt:
284
- print(f"Choose any of the following actions (or any command-line toggle flag by name)")
285
- print(f"[q] quit")
286
- print(f"[e] change model")
287
- print(f"[x] toggle app [{toggle[o.app]}] -> [{toggle[not o.app]}]")
288
- print(f"[k] toggle keyboard [{toggle[o.keyboard]}] -> [{toggle[not o.keyboard]}]")
289
- print(f"[c] toggle clipboard [{toggle[o.clipboard]}] -> [{toggle[not o.clipboard]}]")
349
+ print(f"Choose any of the following actions")
350
+ print(f"{colored('[q]', 'light_yellow')} quit")
351
+ print(f"{colored('[e]', 'light_yellow')} change model")
352
+ print(f"{colored('[x]', 'light_yellow')} app is {colored(o.app, 'light_blue')} toggle?")
353
+ print(f"{colored('[c]', 'light_yellow')} clipboard is {colored(o.clipboard, 'light_blue')} toggle?")
354
+ print(f"{colored('[k]', 'light_yellow')} keyboard is {colored(o.keyboard, 'light_blue')} toggle?")
355
+ if o.keyboard:
356
+ print(f"{colored('[latency]', 'light_yellow')} between keystrokes is {colored(o.latency, 'light_blue')} s")
290
357
  if transcriber.backend == "whisper":
291
- print(f"[t] change duration (currently {transcriber.timeout}s)")
292
- print(f"[b] change silence duration (currently {transcriber.silence_duration}s)")
293
- print(f"[a] toggle auto-restart after silence [{toggle[transcriber.restart_after_silence]}] -> [{toggle[not transcriber.restart_after_silence]}]")
294
- print(colored(f"Press [Enter] to start recording.", "BOLD"))
358
+ print(f"{colored('[t]', 'light_yellow')} change duration (currently {colored(transcriber.timeout, 'light_blue')} s)")
359
+ print(f"{colored('[b]', 'light_yellow')} change silence duration (currently {colored(transcriber.silence_duration, 'light_blue')} s)")
360
+ print(f"{colored('[a]', 'light_yellow')} auto-restart after silence is {colored(transcriber.restart_after_silence, 'light_blue')} toggle?")
361
+ exclude_flags = ["keyboard", "clipboard", "app", "prompt", "restart_after_silence"]
362
+ display_flags = [a.dest for a in parser._actions if a.help != argparse.SUPPRESS]
363
+ for key, value in vars(o).items():
364
+ if key not in display_flags or key in exclude_flags or not isinstance(value, bool):
365
+ continue
366
+ print(f"{colored(f'[{key}]', 'light_yellow')} is {colored(value, 'light_blue')} toggle?")
367
+
368
+ print(colored(f"Press [Enter] to start recording.", attrs=["bold"]))
295
369
 
296
370
  key = input()
297
371
  if key == "q":
298
372
  exit(0)
299
373
  if key == "e":
300
374
  transcriber = None
375
+ o.model = None
376
+ o.backend = None
377
+ o.language = None
301
378
  continue
302
379
  if key == "k":
303
380
  o.keyboard = not o.keyboard
@@ -318,6 +395,13 @@ def main(args=None):
318
395
  except:
319
396
  print("Invalid duration. Must be an integer.")
320
397
  continue
398
+ if key == "latency":
399
+ ans = input(f"Enter new keyboard latency in seconds (current: {o.latency}): ")
400
+ try:
401
+ o.latency = float(ans)
402
+ except:
403
+ print("Invalid latency. Must be a float.")
404
+ continue
321
405
  if key == "b":
322
406
  ans = input(f"Enter new silence break duration in seconds (current: {transcriber.silence_duration}): ")
323
407
  try:
@@ -32,6 +32,8 @@ class AbstractTranscriber:
32
32
  self.silence_thresh = silence_thresh
33
33
  self.silence_duration = silence_duration
34
34
  self.restart_after_silence = restart_after_silence
35
+ self.recording = False
36
+ self.busy = False
35
37
  self.reset()
36
38
 
37
39
  def get_elapsed(self):
@@ -54,16 +56,18 @@ class AbstractTranscriber:
54
56
 
55
57
  def start_recording(self, microphone,
56
58
  start_message="Recording... Press Ctrl+C to stop.",
57
- stop_message="Stopped recording."):
59
+ stop_message="Done transcribing."):
58
60
 
59
61
  self.reset()
62
+ self.recording = True
63
+ self.busy = True
60
64
 
61
65
  try:
62
66
 
63
67
  with microphone.open_stream():
64
68
  print(start_message)
65
69
 
66
- while True:
70
+ while self.recording:
67
71
  while not microphone.q.empty():
68
72
  data = microphone.q.get()
69
73
 
@@ -78,7 +82,7 @@ class AbstractTranscriber:
78
82
  self.reset()
79
83
  yield result
80
84
  else:
81
- raise KeyboardInterrupt("Silence detected: {:.2f} seconds".format(silence_duration))
85
+ raise StopRecording("Silence detected: {:.2f} seconds".format(silence_duration))
82
86
 
83
87
  else:
84
88
  self.last_sound_time = time.time()
@@ -86,14 +90,18 @@ class AbstractTranscriber:
86
90
  yield self.transcribe_realtime_audio(data)
87
91
 
88
92
  if self.is_overtime():
89
- raise KeyboardInterrupt("Overtime: {:.2f} seconds".format(self.get_elapsed()))
93
+ raise StopRecording("Overtime: {:.2f} seconds".format(self.get_elapsed()))
94
+
95
+ time.sleep(0.1) # avoid overheating
90
96
 
91
97
  except (KeyboardInterrupt, StopRecording):
92
98
  pass
93
99
 
94
100
  finally:
101
+ self.recording = False
95
102
  result = self.finalize()
96
103
  microphone.q.queue.clear()
104
+ self.busy = False
97
105
  yield result
98
106
 
99
107
  print(stop_message)
@@ -102,6 +110,7 @@ class AbstractTranscriber:
102
110
  def get_vosk_model(model, download_root=None, url=None):
103
111
  """Load the Vosk recognizer"""
104
112
  import vosk
113
+ vosk.SetLogLevel(-1)
105
114
  if download_root is None:
106
115
  download_root = VOSK_MODELS_FOLDER
107
116
  model_path = os.path.join(download_root, model)
@@ -3,26 +3,7 @@ import re
3
3
  import tqdm
4
4
  import shutil
5
5
  from functools import partial
6
-
7
-
8
- class bcolors:
9
- # https://stackoverflow.com/a/287944/2192272
10
- HEADER = '\033[95m'
11
- OKBLUE = '\033[94m'
12
- OKGREEN = '\033[92m'
13
- WARNING = '\033[93m'
14
- FAIL = '\033[91m'
15
- ENDC = '\033[0m'
16
- BOLD = '\033[1m'
17
- UNDERLINE = '\033[4m'
18
-
19
- def strip_colors(s):
20
- for name, c in vars(bcolors).items():
21
- if name.startswith("_"):
22
- continue
23
- s = s.replace(c, '')
24
- return s
25
-
6
+ from termcolor import colored
26
7
 
27
8
  def ansi_link(uri, label=None):
28
9
  """https://stackoverflow.com/a/71309268/2192272
@@ -36,25 +17,6 @@ def ansi_link(uri, label=None):
36
17
 
37
18
  return escape_mask.format(parameters, uri, label)
38
19
 
39
- def colored(text, color):
40
- if hasattr(bcolors, color):
41
- color = getattr(bcolors, color)
42
- return f"{color}{text}{bcolors.ENDC}"
43
-
44
-
45
- ANSI_LINK_RE = re.compile(r'(?P<ansi_sequence>\033]8;(?P<parameter>.*?);(?P<uri>.*?)\033\\(?P<label>.*?)\033]8;;\033\\)')
46
-
47
- def strip_ansi_link(s):
48
- for m in ANSI_LINK_RE.findall(s):
49
- s = s.replace(m[0], m[3])
50
- return s
51
-
52
-
53
- def strip_all(s):
54
- s = strip_colors(s)
55
- s = strip_ansi_link(s)
56
- return s
57
-
58
20
 
59
21
  # Function to clear the terminal line
60
22
  def clear_line():
@@ -119,9 +81,9 @@ def format_choice(enum, default=None, unavailable=None):
119
81
  value_str = value
120
82
 
121
83
  if (default is not None and value == default) or (default is None and i == 0):
122
- return f' ' + colored(f'({i+1}) {value_str} [Press Enter]', 'BOLD')
84
+ return f' ' + colored(f'({i+1}) {value_str} [Press Enter]', attrs=['bold'])
123
85
  elif unavailable and value in unavailable:
124
- return f' ' + colored(f'{" "} {value_str} -> unavailable !!', 'FAIL')
86
+ return f' ' + colored(f'{" "} {value_str} -> unavailable !!', attrs=["strike"])
125
87
  else:
126
88
  return f' ({i+1}) {value_str}'
127
89
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: scribe-cli
3
- Version: 0.7.7
3
+ Version: 0.7.9
4
4
  Summary: scribe is a local speech recognition tool that provides real-time transcription using vosk and whisper AI, with the goal of serving as a virtual keyboard on a computer
5
5
  Author-email: Mahé Perrette <mahe.perrette@gmail.com>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Requires-Dist: tqdm
45
45
  Requires-Dist: requests
46
46
  Requires-Dist: pyperclip
47
47
  Requires-Dist: unidecode
48
+ Requires-Dist: termcolor
48
49
  Provides-Extra: keyboard
49
50
  Requires-Dist: pynput; extra == "keyboard"
50
51
  Provides-Extra: whisper
@@ -61,7 +62,7 @@ Requires-Dist: vosk; extra == "all"
61
62
  Requires-Dist: pystray; extra == "all"
62
63
 
63
64
  [![python](https://img.shields.io/badge/python-3.12-blue.svg)]()
64
- [![pypi](https://github.com/perrette/scribe/actions/workflows/pypi.yml/badge.svg)](https://pypi.org/project/scribe-cli)
65
+ [![pypi](https://img.shields.io/pypi/v/scribe-cli)](https://pypi.org/project/scribe-cli)
65
66
 
66
67
  # Scribe
67
68
 
@@ -1,6 +1,7 @@
1
1
  .gitignore
2
2
  LICENSE
3
3
  README.md
4
+ icon.xcf
4
5
  pyproject.toml
5
6
  .github/workflows/pypi.yml
6
7
  scribe/__init__.py
@@ -21,5 +22,7 @@ scribe_cli.egg-info/entry_points.txt
21
22
  scribe_cli.egg-info/requires.txt
22
23
  scribe_cli.egg-info/top_level.txt
23
24
  scribe_data/__init__.py
24
- scribe_data/share/icon.jpg
25
+ scribe_data/share/icon.png
26
+ scribe_data/share/icon_recording.png
27
+ scribe_data/share/icon_writing.png
25
28
  scribe_data/templates/scribe.desktop
@@ -4,6 +4,7 @@ tqdm
4
4
  requests
5
5
  pyperclip
6
6
  unidecode
7
+ termcolor
7
8
 
8
9
  [all]
9
10
  pynput
Binary file
File without changes
File without changes
File without changes
File without changes