Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add live transcript export #784

Merged
merged 11 commits into from
Jun 7, 2024
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 @@
)
self.stream.start()
except sounddevice.PortAudioError:
self.stop_recording()

Check warning on line 31 in buzz/recording.py

View check run for this annotation

Codecov / codecov/patch

buzz/recording.py#L31

Added line #L31 was not covered by tests
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 @@

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

Check warning on line 169 in buzz/widgets/recording_transcriber_widget.py

View check run for this annotation

Codecov / codecov/patch

buzz/widgets/recording_transcriber_widget.py#L169

Added line #L169 was not covered by tests

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 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 @@
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
Loading