Skip to content

Commit

Permalink
Add live transcript export (#784)
Browse files Browse the repository at this point in the history
  • Loading branch information
raivisdejus committed Jun 7, 2024
1 parent 5904f84 commit 8fbaf01
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 49 deletions.
3 changes: 0 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ clean:
rm -rf dist/* || true

COVERAGE_THRESHOLD := 80
ifeq ($(UNAME_S),Linux)
COVERAGE_THRESHOLD := 74
endif

test: buzz/whisper_cpp.py translation_mo
pytest -s -vv --cov=buzz --cov-report=xml --cov-report=html --benchmark-skip --cov-fail-under=${COVERAGE_THRESHOLD}
Expand Down
51 changes: 32 additions & 19 deletions buzz/locale/lv_LV/LC_MESSAGES/buzz.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-26 08:13+0300\n"
"PO-Revision-Date: 2024-05-18 16:28+0300\n"
"POT-Creation-Date: 2024-06-07 08:32+0300\n"
"PO-Revision-Date: 2024-06-07 08:34+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: lv_LV\n"
Expand Down Expand Up @@ -43,7 +43,7 @@ msgid "View Transcript Timestamps"
msgstr "Aplūkot atpazīšanas laikus"

#: buzz/settings/shortcut.py:25 buzz/widgets/main_window_toolbar.py:60
#: buzz/widgets/main_window.py:199
#: buzz/widgets/main_window.py:200
msgid "Clear History"
msgstr "Notīrīt vēsturi"

Expand Down Expand Up @@ -124,23 +124,23 @@ msgstr "Adrese nav derīga"
msgid "The URL you entered is invalid."
msgstr "Jūsu ievadītā URL adrese nav derīga."

#: buzz/widgets/recording_transcriber_widget.py:60
#: buzz/widgets/recording_transcriber_widget.py:61
msgid "Live Recording"
msgstr "Dzīvā ierakstīšana"

#: buzz/widgets/recording_transcriber_widget.py:109
#: buzz/widgets/recording_transcriber_widget.py:110
msgid "Click Record to begin..."
msgstr "Klikšķiniet Ierakstīt, lai sāktu..."

#: buzz/widgets/recording_transcriber_widget.py:121
#: buzz/widgets/recording_transcriber_widget.py:122
msgid "Microphone:"
msgstr "Mikrofons:"

#: buzz/widgets/recording_transcriber_widget.py:278
#: buzz/widgets/recording_transcriber_widget.py:290
msgid "An error occurred while starting a new recording:"
msgstr "Sākot jaunu ierakstu notikusi kļūda:"

#: buzz/widgets/recording_transcriber_widget.py:282
#: buzz/widgets/recording_transcriber_widget.py:294
msgid ""
"Please check your audio devices or check the application logs for more "
"information."
Expand Down Expand Up @@ -168,24 +168,24 @@ msgstr "Fails"
msgid "Help"
msgstr "Palīdzība"

#: buzz/widgets/main_window.py:201
#: buzz/widgets/main_window.py:202
msgid ""
"Are you sure you want to delete the selected transcription(s)? This action "
"cannot be undone."
msgstr ""
"Vai tiešām vēlaties dzēst izvēlētos transkriptus? Šī ir neatgriezeniska "
"darbība."

#: buzz/widgets/main_window.py:221
#: buzz/widgets/main_window.py:222
msgid "Select audio file"
msgstr "Izvēlieties audio failu"

#: buzz/widgets/main_window.py:255
#: buzz/widgets/main_window.py:256
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:191
msgid "Error"
msgstr "Kļūda"

#: buzz/widgets/main_window.py:255
#: buzz/widgets/main_window.py:256
msgid "Unable to save OpenAI API key to keyring"
msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"

Expand Down Expand Up @@ -285,6 +285,7 @@ msgstr "Ieslēgt mapes vērošanu"

#: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:47
#: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:50
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:72
msgid "Browse"
msgstr "Izvēlēties"

Expand All @@ -304,31 +305,43 @@ msgstr "Izvēlieties vērojamo mapi"
msgid "Select Output Folder"
msgstr "Izvēlieties rezultātu mapi"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:31
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:42
msgid "Test"
msgstr "Pārbaudīt"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:37
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:48
msgid "OpenAI API key"
msgstr "OpenAI API atslēga"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:49
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:60
msgid "Default export file name"
msgstr "Eksporta faila nosaukums"
msgstr "Eksporta fails"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:74
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:80
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:66
msgid "Enable live recording transcription export"
msgstr "Eksportēt dzīvā ieraksta transkriptus"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:91
msgid "Export folder"
msgstr "Eksportēt mapē"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:116
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:122
msgid "OpenAI API Key Test"
msgstr "OpenAI API atslēgas pārbaude"

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:75
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:117
msgid ""
"Your API key is valid. Buzz will use this key to perform Whisper API "
"transcriptions."
msgstr ""
"Jūsu API atslēga ir derīga. Buzz izmantos to runas atpazīšanai ar Whisper "
"API."

#: buzz/widgets/preferences_dialog/general_preferences_widget.py:141
msgid "Select Export Folder"
msgstr "Izvēlieties mapi kurā eksportēt"

#: buzz/widgets/preferences_dialog/models_preferences_widget.py:61
msgid "Group"
msgstr "Veids"
Expand Down
1 change: 1 addition & 0 deletions buzz/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def start_recording(self):
)
self.stream.start()
except sounddevice.PortAudioError:
self.stop_recording()
logging.exception("")

def stop_recording(self):
Expand Down
2 changes: 2 additions & 0 deletions buzz/settings/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class Key(enum.Enum):
RECORDING_TRANSCRIBER_LANGUAGE = "recording-transcriber/language"
RECORDING_TRANSCRIBER_TEMPERATURE = "recording-transcriber/temperature"
RECORDING_TRANSCRIBER_INITIAL_PROMPT = "recording-transcriber/initial-prompt"
RECORDING_TRANSCRIBER_EXPORT_ENABLED = "recording-transcriber/export-enabled"
RECORDING_TRANSCRIBER_EXPORT_FOLDER = "recording-transcriber/export-folder"

FILE_TRANSCRIBER_TASK = "file-transcriber/task"
FILE_TRANSCRIBER_MODEL = "file-transcriber/model"
Expand Down
2 changes: 1 addition & 1 deletion buzz/transcriber/recording_transcriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(
self.transcription_options = transcription_options
self.current_stream = None
self.input_device_index = input_device_index
self.sample_rate = sample_rate
self.sample_rate = sample_rate if sample_rate is not None else whisper_audio.SAMPLE_RATE
self.model_path = model_path
self.n_batch_samples = 5 * self.sample_rate # every 5 seconds
# pause queueing if more than 3 batches behind
Expand Down
66 changes: 65 additions & 1 deletion buzz/widgets/preferences_dialog/general_preferences_widget.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import logging
from typing import Optional
from platformdirs import user_documents_dir

from PyQt6.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool
from PyQt6.QtWidgets import QWidget, QFormLayout, QPushButton, QMessageBox
from PyQt6.QtWidgets import (
QWidget,
QFormLayout,
QPushButton,
QMessageBox,
QCheckBox,
QHBoxLayout,
QFileDialog
)
from openai import AuthenticationError, OpenAI

from buzz.settings.settings import Settings
Expand All @@ -10,6 +20,7 @@
from buzz.widgets.openai_api_key_line_edit import OpenAIAPIKeyLineEdit
from buzz.locale import _


class GeneralPreferencesWidget(QWidget):
openai_api_key_changed = pyqtSignal(str)

Expand Down Expand Up @@ -48,6 +59,37 @@ def __init__(
default_export_file_name_line_edit.setMinimumWidth(200)
layout.addRow(_("Default export file name"), default_export_file_name_line_edit)

self.recording_export_enabled = self.settings.value(
key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_ENABLED, default_value=False
)

self.export_enabled_checkbox = QCheckBox(_("Enable live recording transcription export"))
self.export_enabled_checkbox.setChecked(self.recording_export_enabled)
self.export_enabled_checkbox.setObjectName("EnableRecordingExportCheckbox")
self.export_enabled_checkbox.stateChanged.connect(self.on_recording_export_enable_changed)
layout.addRow("", self.export_enabled_checkbox)

self.recording_export_folder_browse_button = QPushButton(_("Browse"))
self.recording_export_folder_browse_button.clicked.connect(self.on_click_browse_export_folder)
self.recording_export_folder_browse_button.setObjectName("RecordingExportFolderBrowseButton")

recording_export_folder = self.settings.value(
key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_FOLDER, default_value=user_documents_dir()
)

recording_export_folder_row = QHBoxLayout()
self.recording_export_folder_line_edit = LineEdit(recording_export_folder, self)
self.recording_export_folder_line_edit.textChanged.connect(self.on_recording_export_folder_changed)
self.recording_export_folder_line_edit.setObjectName("RecordingExportFolderLineEdit")

self.recording_export_folder_line_edit.setEnabled(self.recording_export_enabled)
self.recording_export_folder_browse_button.setEnabled(self.recording_export_enabled)

recording_export_folder_row.addWidget(self.recording_export_folder_line_edit)
recording_export_folder_row.addWidget(self.recording_export_folder_browse_button)

layout.addRow(_("Export folder"), recording_export_folder_row)

self.setLayout(layout)

def on_default_export_file_name_changed(self, text: str):
Expand Down Expand Up @@ -84,6 +126,28 @@ def on_openai_api_key_changed(self, key: str):
self.update_test_openai_api_key_button()
self.openai_api_key_changed.emit(key)

def on_recording_export_enable_changed(self, state: int):
self.recording_export_enabled = state == 2

self.recording_export_folder_line_edit.setEnabled(self.recording_export_enabled)
self.recording_export_folder_browse_button.setEnabled(self.recording_export_enabled)

self.settings.set_value(
Settings.Key.RECORDING_TRANSCRIBER_EXPORT_ENABLED,
self.recording_export_enabled,
)

def on_click_browse_export_folder(self):
folder = QFileDialog.getExistingDirectory(self, _("Select Export Folder"))
self.recording_export_folder_line_edit.setText(folder)
self.on_recording_export_folder_changed(folder)

def on_recording_export_folder_changed(self, folder):
self.settings.set_value(
Settings.Key.RECORDING_TRANSCRIBER_EXPORT_FOLDER,
folder,
)


class TestOpenAIApiKeyJob(QRunnable):
class Signals(QObject):
Expand Down
2 changes: 1 addition & 1 deletion buzz/widgets/preferences_dialog/preferences_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@ def __init__(
self.setLayout(layout)

self.setMinimumHeight(500)
self.setMinimumWidth(500)
self.setMinimumWidth(550)
41 changes: 41 additions & 0 deletions buzz/widgets/recording_transcriber_widget.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import os
import enum
import logging
import datetime
from enum import auto
from typing import Optional, Tuple

Expand Down Expand Up @@ -136,6 +139,37 @@ def __init__(

self.reset_recording_amplitude_listener()

self.export_file = None
self.export_enabled = self.settings.value(
key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_ENABLED,
default_value=False,
)

def setup_for_export(self):
export_folder = self.settings.value(
key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_FOLDER,
default_value="",
)

date_time_now = datetime.datetime.now().strftime("%d-%b-%Y %H-%M-%S")

export_file_name_template = Settings().get_default_export_file_template()

export_file_name = (
export_file_name_template.replace("{{ input_file_name }}", "live recording")
.replace("{{ task }}", self.transcription_options.task.value)
.replace("{{ language }}", self.transcription_options.language or "")
.replace("{{ model_type }}", self.transcription_options.model.model_type.value)
.replace("{{ model_size }}", self.transcription_options.model.whisper_model_size or "")
.replace("{{ date_time }}", date_time_now)
+ ".txt"
)

if not os.path.isdir(export_folder):
self.export_enabled = False

self.export_file = os.path.join(export_folder, export_file_name)

def on_transcription_options_changed(
self, transcription_options: TranscriptionOptions
):
Expand Down Expand Up @@ -180,6 +214,9 @@ def on_record_button_clicked(self):
def start_recording(self):
self.record_button.setDisabled(True)

if self.export_enabled:
self.setup_for_export()

model_path = self.transcription_options.model.get_local_model_path()
if model_path is not None:
self.on_model_loaded(model_path)
Expand Down Expand Up @@ -260,6 +297,10 @@ def on_next_transcription(self, text: str):
self.text_box.insertPlainText(text)
self.text_box.moveCursor(QTextCursor.MoveOperation.End)

if self.export_enabled:
with open(self.export_file, "a") as f:
f.write(text + "\n\n")

def stop_recording(self):
if self.transcriber is not None:
self.transcriber.stop_recording()
Expand Down
1 change: 1 addition & 0 deletions tests/mock_sounddevice.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import time
import logging
from threading import Thread
from typing import Callable, Any
from unittest.mock import MagicMock
Expand Down
Loading

0 comments on commit 8fbaf01

Please sign in to comment.