mirror of
https://github.com/chidiwilliams/buzz.git
synced 2026-03-14 14:45:46 +01:00
Add llm translations (#791)
This commit is contained in:
parent
3d44a45476
commit
eb92568381
37 changed files with 916 additions and 129 deletions
23
buzz/assets/translate_black.svg
Normal file
23
buzz/assets/translate_black.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="anna_vital_language_icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
||||
<path d="M62.4,101c-1.5-2.1-2.1-3.4-1.8-3.9c0.2-0.5,1.6-0.7,3.9-0.5c2.3,0.2,4.2,0.5,5.8,0.9c1.5,0.4,2.8,1,3.8,1.7
|
||||
c1,0.7,1.8,1.5,2.3,2.6c0.6,1,1,2.3,1.4,3.7c0.7,2.8,0.5,4.7-0.5,5.7c-1.1,1-2.6,0.8-4.6-0.6c-2.1-1.4-3.9-2.8-5.5-4.2
|
||||
C65.5,105.1,63.9,103.2,62.4,101z M40.7,190.1c4.8-2.1,9-4.2,12.6-6.4c3.5-2.1,6.6-4.4,9.3-6.8c2.6-2.3,5-4.9,7-7.7
|
||||
c2-2.7,3.8-5.8,5.4-9.2c1.3,1.2,2.5,2.4,3.8,3.5c1.2,1.1,2.5,2.2,3.8,3.4c1.3,1.2,2.8,2.4,4.3,3.8c1.5,1.4,3.3,2.8,5.3,4.5
|
||||
c0.7,0.5,1.4,0.9,2.1,1c0.7,0.1,1.7,0,3.1-0.6c1.3-0.5,3-1.4,5.1-2.8c2.1-1.3,4.7-3.1,7.9-5.4c1.6-1.1,2.4-2,2.3-2.7
|
||||
c-0.1-0.7-1-1-2.7-0.9c-3.1,0.1-5.9,0.1-8.3-0.1c-2.5-0.2-5-0.6-7.4-1.4c-2.4-0.8-4.9-1.9-7.5-3.4c-2.6-1.5-5.6-3.6-9.1-6.2
|
||||
c1-3.9,1.8-8,2.4-12.4c0.3-2.5,0.6-4.3,0.8-5.6c0.2-1.2,0.5-2.4,0.9-3.3c0.3-0.8,0.4-1.4,0.5-1.9c0.1-0.5-0.1-1-0.4-1.6
|
||||
c-0.4-0.5-1-1.1-1.9-1.7c-0.9-0.6-2.2-1.4-3.9-2.3c2.4-0.9,5.1-1.7,7.9-2.6c2.7-0.9,5.7-1.8,8.8-2.7c3-0.9,4.5-1.9,4.6-3.1
|
||||
c0.1-1.2-0.9-2.3-3.2-3.5c-1.5-0.8-2.9-1.1-4.3-0.9c-1.4,0.2-3.2,0.9-5.4,2.2c-0.6,0.4-1.8,0.9-3.4,1.6c-1.7,0.7-3.6,1.5-6,2.5
|
||||
c-2.4,1-5,2-7.8,3.1c-2.9,1.1-5.8,2.2-8.7,3.2c-2.9,1.1-5.7,2-8.2,2.8c-2.6,0.8-4.6,1.4-6.1,1.6c-3.8,0.8-5.8,1.6-5.9,2.4
|
||||
c0,0.8,1.5,1.6,4.4,2.4c1.2,0.3,2.3,0.6,3.1,0.6c0.8,0.1,1.7,0.1,2.5,0c0.8-0.1,1.6-0.3,2.4-0.5c0.8-0.3,1.7-0.7,2.8-1.1
|
||||
c1.6-0.8,3.9-1.7,6.9-2.8c2.9-1,6.6-2.4,11.2-4c0.9,2.7,1.4,6,1.4,9.8c0,3.8-0.4,8.1-1.4,13c-1.3-1.1-2.7-2.3-4.2-3.6
|
||||
c-1.5-1.3-2.9-2.6-4.3-3.9c-1.6-1.5-3.2-2.5-4.7-3c-1.6-0.5-3.4-0.5-5.5,0c-3.3,0.9-5,1.9-4.9,3.1c0,1.2,1.3,1.8,3.8,1.9
|
||||
c0.9,0.1,1.8,0.3,2.7,0.6c0.9,0.3,1.9,0.9,3.2,1.8c1.3,0.9,2.9,2.2,4.7,3.8c1.8,1.6,4.2,3.7,7,6.3c-1.2,2.9-2.6,5.6-4.1,8
|
||||
c-1.5,2.5-3.4,5-5.5,7.3c-2.2,2.4-4.7,4.8-7.7,7.2c-3,2.5-6.6,5.1-10.8,7.8c-4.3,2.8-6.5,4.7-6.5,5.6C35,192.1,37,191.7,40.7,190.1z
|
||||
M250.5,81.8v165.3l-111.6-36.4L10.5,253.4V76.1l29.9-10V10.4l81.2,28.7L231.3,2.6v73.1L250.5,81.8z M124.2,50.6L22.3,84.6v152.2
|
||||
l101.9-33.9V50.6L124.2,50.6z M219.4,71.9V19L138.1,46L219.4,71.9z M227,201.9L196.5,92L176,85.6l-30.9,90.8l18.9,5.9l5.8-18.7
|
||||
l31.9,10l5.7,22.3L227,201.9z M174.8,147.7l22.2,6.9l-10.9-42.9L174.8,147.7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -6,7 +6,7 @@ import platform
|
|||
import sys
|
||||
from typing import TextIO
|
||||
|
||||
from platformdirs import user_log_dir, user_cache_dir
|
||||
from platformdirs import user_log_dir, user_cache_dir, user_data_dir
|
||||
|
||||
from buzz.assets import APP_BASE_DIR
|
||||
|
||||
|
|
@ -60,6 +60,7 @@ def main():
|
|||
logging.debug("app_dir: %s", APP_BASE_DIR)
|
||||
logging.debug("log_dir: %s", log_dir)
|
||||
logging.debug("cache_dir: %s", user_cache_dir("Buzz"))
|
||||
logging.debug("data_dir: %s", user_data_dir("Buzz"))
|
||||
|
||||
app = Application(sys.argv)
|
||||
parse_command_line(app)
|
||||
|
|
|
|||
|
|
@ -24,3 +24,18 @@ class TranscriptionSegmentDAO(DAO[TranscriptionSegment]):
|
|||
)
|
||||
query.bindValue(":transcription_id", str(transcription_id))
|
||||
return self._execute_all(query)
|
||||
|
||||
def update_segment_translation(self, segment_id: int, translation: str):
|
||||
query = self._create_query()
|
||||
query.prepare(
|
||||
"""
|
||||
UPDATE transcription_segment
|
||||
SET translation = :translation
|
||||
WHERE id = :id
|
||||
"""
|
||||
)
|
||||
|
||||
query.bindValue(":id", segment_id)
|
||||
query.bindValue(":translation", translation)
|
||||
if not query.exec():
|
||||
raise Exception(query.lastError().text())
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ class TranscriptionSegment(Entity):
|
|||
start_time: int
|
||||
end_time: int
|
||||
text: str
|
||||
translation: str
|
||||
transcription_id: str
|
||||
id: int = -1
|
||||
|
|
|
|||
|
|
@ -53,13 +53,14 @@ def copy_transcriptions_from_json_to_sqlite(conn: Connection):
|
|||
for segment in task.segments:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO transcription_segment (end_time, start_time, text, transcription_id)
|
||||
VALUES (?, ?, ?, ?);
|
||||
INSERT INTO transcription_segment (end_time, start_time, text, translation, transcription_id)
|
||||
VALUES (?, ?, ?, ?, ?);
|
||||
""",
|
||||
(
|
||||
segment.end,
|
||||
segment.start,
|
||||
segment.text,
|
||||
segment.translation,
|
||||
transcription_id,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,13 @@ class TranscriptionService:
|
|||
start_time=segment.start,
|
||||
end_time=segment.end,
|
||||
text=segment.text,
|
||||
translation='',
|
||||
transcription_id=str(id),
|
||||
)
|
||||
)
|
||||
|
||||
def get_transcription_segments(self, transcription_id: UUID):
|
||||
return self.transcription_segment_dao.get_segments(transcription_id)
|
||||
|
||||
def update_segment_translation(self, segment_id: int, translation: str):
|
||||
return self.transcription_segment_dao.update_segment_translation(segment_id, translation)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-06-10 21:58+0300\n"
|
||||
"PO-Revision-Date: 2024-06-10 21:59+0300\n"
|
||||
"POT-Creation-Date: 2024-06-14 18:59+0300\n"
|
||||
"PO-Revision-Date: 2024-06-14 19:01+0300\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: lv_LV\n"
|
||||
|
|
@ -39,51 +39,63 @@ msgid "View Transcript Text"
|
|||
msgstr "Aplūkot atpazīto tekstu"
|
||||
|
||||
#: buzz/settings/shortcut.py:23
|
||||
msgid "View Transcript Translation"
|
||||
msgstr "Aplūkot tulkojumu"
|
||||
|
||||
#: buzz/settings/shortcut.py:24
|
||||
msgid "View Transcript Timestamps"
|
||||
msgstr "Aplūkot atpazīšanas laikus"
|
||||
|
||||
#: buzz/settings/shortcut.py:25 buzz/widgets/main_window_toolbar.py:60
|
||||
#: buzz/settings/shortcut.py:26 buzz/widgets/main_window_toolbar.py:60
|
||||
#: buzz/widgets/main_window.py:222
|
||||
msgid "Clear History"
|
||||
msgstr "Notīrīt vēsturi"
|
||||
|
||||
#: buzz/settings/shortcut.py:26 buzz/widgets/main_window_toolbar.py:52
|
||||
#: buzz/settings/shortcut.py:27 buzz/widgets/main_window_toolbar.py:52
|
||||
msgid "Cancel Transcription"
|
||||
msgstr "Atcelt atpazīšanu"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:64
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:62
|
||||
msgid "In Progress"
|
||||
msgstr "Apstrādā"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:65
|
||||
msgid "Completed"
|
||||
msgstr "Pabeigts"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:73
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:72
|
||||
msgid "Failed"
|
||||
msgstr "Neizdevās"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:75
|
||||
msgid "Canceled"
|
||||
msgstr "Atcelts"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:75
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:77
|
||||
msgid "Queued"
|
||||
msgstr "Ierindots"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:83
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:85
|
||||
msgid "File Name / URL"
|
||||
msgstr "Fails / URL"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:95
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:97
|
||||
msgid "Model"
|
||||
msgstr "Modelis"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:104
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:106
|
||||
msgid "Task"
|
||||
msgstr "Uzdevums"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:113
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:115
|
||||
msgid "Status"
|
||||
msgstr "Statuss"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:121
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:123
|
||||
msgid "Date Added"
|
||||
msgstr "Pievienots"
|
||||
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:132
|
||||
#: buzz/widgets/transcription_tasks_table_widget.py:134
|
||||
msgid "Date Completed"
|
||||
msgstr "Pabeigts"
|
||||
|
||||
|
|
@ -124,23 +136,27 @@ 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:63
|
||||
#: buzz/widgets/recording_transcriber_widget.py:66
|
||||
msgid "Live Recording"
|
||||
msgstr "Dzīvā ierakstīšana"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:112
|
||||
#: buzz/widgets/recording_transcriber_widget.py:125
|
||||
msgid "Click Record to begin..."
|
||||
msgstr "Klikšķiniet Ierakstīt, lai sāktu..."
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:124
|
||||
#: buzz/widgets/recording_transcriber_widget.py:128
|
||||
msgid "Waiting for AI translation..."
|
||||
msgstr "Gaida MI tulkojumu..."
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:140
|
||||
msgid "Microphone:"
|
||||
msgstr "Mikrofons:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:319
|
||||
#: buzz/widgets/recording_transcriber_widget.py:391
|
||||
msgid "An error occurred while starting a new recording:"
|
||||
msgstr "Sākot jaunu ierakstu notikusi kļūda:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:323
|
||||
#: buzz/widgets/recording_transcriber_widget.py:395
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
|
@ -157,8 +173,8 @@ msgid ""
|
|||
"Detected missing permissions, please check that snap permissions have been "
|
||||
"granted"
|
||||
msgstr ""
|
||||
"Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas "
|
||||
"snap atļaujas"
|
||||
"Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas snap "
|
||||
"atļaujas"
|
||||
|
||||
#: buzz/widgets/snap_notice.py:16
|
||||
msgid ""
|
||||
|
|
@ -201,67 +217,113 @@ msgstr ""
|
|||
msgid "Select audio file"
|
||||
msgstr "Izvēlieties audio failu"
|
||||
|
||||
#: buzz/widgets/main_window.py:278
|
||||
#: buzz/widgets/main_window.py:280
|
||||
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:191
|
||||
msgid "Error"
|
||||
msgstr "Kļūda"
|
||||
|
||||
#: buzz/widgets/main_window.py:278
|
||||
#: buzz/widgets/main_window.py:280
|
||||
msgid "Unable to save OpenAI API key to keyring"
|
||||
msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:42
|
||||
msgid "Save File"
|
||||
msgstr "Saglabāt failu"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:44
|
||||
msgid "Text files"
|
||||
msgstr "Teksta faili"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:19
|
||||
msgid "View"
|
||||
msgstr "Skats"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:27
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:68
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:34
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:95
|
||||
msgid "Text"
|
||||
msgstr "Teksts"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:33
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:43
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:65
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:40
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:96
|
||||
msgid "Translation"
|
||||
msgstr "Tulkojums"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:79
|
||||
msgid "Save File"
|
||||
msgstr "Saglabāt failu"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:81
|
||||
msgid "Text files"
|
||||
msgstr "Teksta faili"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:26
|
||||
msgid "View"
|
||||
msgstr "Skats"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:46
|
||||
msgid "Timestamps"
|
||||
msgstr "Laiks"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:66
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:93
|
||||
msgid "Start"
|
||||
msgstr "Sākums"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:67
|
||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:94
|
||||
msgid "End"
|
||||
msgstr "Beigas"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:92
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:146
|
||||
msgid "Export"
|
||||
msgstr "Eksportēt"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:160
|
||||
msgid "Translate"
|
||||
msgstr "Tulkot"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:250
|
||||
msgid "API Key Required"
|
||||
msgstr "API atslēgas kļūda"
|
||||
|
||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:251
|
||||
msgid "Please enter OpenAI API Key in preferences"
|
||||
msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos"
|
||||
|
||||
#: buzz/widgets/record_button.py:21
|
||||
msgid "Stop"
|
||||
msgstr "Apturēt"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:28
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:33
|
||||
msgid "Advanced Settings"
|
||||
msgstr "Papildu iestatījumi"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:42
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:37
|
||||
msgid "Speech recognition settings"
|
||||
msgstr "Runas atpazīšanas iestatījumi"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:46
|
||||
msgid "Comma-separated, e.g. \"0.0, 0.2, 0.4, 0.6, 0.8, 1.0\""
|
||||
msgstr "Atdalīti ar komatu, piemēram, \"0.0, 0.2, 0.4, 0.6, 0.8, 1.0\""
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:60
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:55
|
||||
msgid "Temperature:"
|
||||
msgstr "Temperatūra:"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:61
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:66
|
||||
msgid "Initial Prompt:"
|
||||
msgstr "Sākotnējais vaicājums:"
|
||||
msgstr ""
|
||||
"Sākotnējais\n"
|
||||
"vaicājums:"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:68
|
||||
msgid "Translation settings"
|
||||
msgstr "Tulkojuma iestatījumi"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:72
|
||||
msgid "Enable AI translation"
|
||||
msgstr "Tulkot ar MI"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:84
|
||||
msgid "AI model:"
|
||||
msgstr "AI modelis:"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:88
|
||||
msgid "Enter instructions for AI on how to translate..."
|
||||
msgstr "Ievadiet tulkošanas norādes mākslīgajam intelektam..."
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:92
|
||||
msgid "Instructions for AI:"
|
||||
msgstr "Norādes MI:"
|
||||
|
||||
#: buzz/widgets/transcriber/file_transcription_form_widget.py:42
|
||||
msgid "Word-level timings"
|
||||
|
|
@ -279,20 +341,20 @@ msgstr "Papildu iestatījumi..."
|
|||
msgid "Enter prompt..."
|
||||
msgstr "Ievadiet vaicājumu..."
|
||||
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:79
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:86
|
||||
msgid "Model:"
|
||||
msgstr "Modelis:"
|
||||
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:83
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:90
|
||||
msgid "Task:"
|
||||
msgstr "Uzdevums:"
|
||||
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:84
|
||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:91
|
||||
msgid "Language:"
|
||||
msgstr "Valoda:"
|
||||
|
||||
#: buzz/widgets/transcriber/languages_combo_box.py:25
|
||||
#: buzz/transcriber/transcriber.py:149
|
||||
#: buzz/transcriber/transcriber.py:153
|
||||
msgid "Detect Language"
|
||||
msgstr "Noteikt valodu"
|
||||
|
||||
|
|
@ -358,10 +420,10 @@ msgstr "OpenAI API atslēgas pārbaude"
|
|||
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:129
|
||||
msgid ""
|
||||
"Your API key is valid. Buzz will use this key to perform Whisper API "
|
||||
"transcriptions."
|
||||
"transcriptions and AI translations with ChatGPT."
|
||||
msgstr ""
|
||||
"Jūsu API atslēga ir derīga. Buzz izmantos to runas atpazīšanai ar Whisper "
|
||||
"API."
|
||||
"API un tulkošanai ar ChatGPT."
|
||||
|
||||
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:156
|
||||
msgid "Select Export Folder"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ CREATE TABLE transcription_segment (
|
|||
end_time INT DEFAULT 0,
|
||||
start_time INT DEFAULT 0,
|
||||
text TEXT NOT NULL,
|
||||
translation TEXT DEFAULT '',
|
||||
transcription_id TEXT,
|
||||
FOREIGN KEY (transcription_id) REFERENCES transcription(id) ON DELETE CASCADE
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class Settings:
|
|||
def __init__(self, application=""):
|
||||
self.settings = QSettings(APP_NAME, application)
|
||||
self.settings.sync()
|
||||
logging.debug(f"settings filename: {self.settings.fileName()}")
|
||||
logging.debug(f"Settings filename: {self.settings.fileName()}")
|
||||
|
||||
class Key(enum.Enum):
|
||||
RECORDING_TRANSCRIBER_TASK = "recording-transcriber/task"
|
||||
|
|
@ -19,6 +19,9 @@ class Settings:
|
|||
RECORDING_TRANSCRIBER_LANGUAGE = "recording-transcriber/language"
|
||||
RECORDING_TRANSCRIBER_TEMPERATURE = "recording-transcriber/temperature"
|
||||
RECORDING_TRANSCRIBER_INITIAL_PROMPT = "recording-transcriber/initial-prompt"
|
||||
RECORDING_TRANSCRIBER_ENABLE_LLM_TRANSLATION = "recording-transcriber/enable-llm-translation"
|
||||
RECORDING_TRANSCRIBER_LLM_MODEL = "recording-transcriber/llm-model"
|
||||
RECORDING_TRANSCRIBER_LLM_PROMPT = "recording-transcriber/llm-prompt"
|
||||
RECORDING_TRANSCRIBER_EXPORT_ENABLED = "recording-transcriber/export-enabled"
|
||||
RECORDING_TRANSCRIBER_EXPORT_FOLDER = "recording-transcriber/export-folder"
|
||||
|
||||
|
|
@ -27,6 +30,9 @@ class Settings:
|
|||
FILE_TRANSCRIBER_LANGUAGE = "file-transcriber/language"
|
||||
FILE_TRANSCRIBER_TEMPERATURE = "file-transcriber/temperature"
|
||||
FILE_TRANSCRIBER_INITIAL_PROMPT = "file-transcriber/initial-prompt"
|
||||
FILE_TRANSCRIBER_ENABLE_LLM_TRANSLATION = "file-transcriber/enable-llm-translation"
|
||||
FILE_TRANSCRIBER_LLM_MODEL = "file-transcriber/llm-model"
|
||||
FILE_TRANSCRIBER_LLM_PROMPT = "file-transcriber/llm-prompt"
|
||||
FILE_TRANSCRIBER_WORD_LEVEL_TIMINGS = "file-transcriber/word-level-timings"
|
||||
FILE_TRANSCRIBER_EXPORT_FORMATS = "file-transcriber/export-formats"
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class Shortcut(str, enum.Enum):
|
|||
OPEN_PREFERENCES_WINDOW = ("Ctrl+,", _("Open Preferences Window"))
|
||||
|
||||
VIEW_TRANSCRIPT_TEXT = ("Ctrl+E", _("View Transcript Text"))
|
||||
VIEW_TRANSCRIPT_TRANSLATION = ("Ctrl+L", _("View Transcript Translation"))
|
||||
VIEW_TRANSCRIPT_TIMESTAMPS = ("Ctrl+T", _("View Transcript Timestamps"))
|
||||
|
||||
CLEAR_HISTORY = ("Ctrl+S", _("Clear History"))
|
||||
|
|
|
|||
|
|
@ -103,7 +103,12 @@ class FileTranscriber(QObject):
|
|||
|
||||
|
||||
# TODO: Move to transcription service
|
||||
def write_output(path: str, segments: List[Segment], output_format: OutputFormat):
|
||||
def write_output(
|
||||
path: str,
|
||||
segments: List[Segment],
|
||||
output_format: OutputFormat,
|
||||
segment_key: str = 'text'
|
||||
):
|
||||
logging.debug(
|
||||
"Writing transcription output, path = %s, output format = %s, number of segments = %s",
|
||||
path,
|
||||
|
|
@ -114,7 +119,7 @@ def write_output(path: str, segments: List[Segment], output_format: OutputFormat
|
|||
with open(path, "w", encoding="utf-8") as file:
|
||||
if output_format == OutputFormat.TXT:
|
||||
for i, segment in enumerate(segments):
|
||||
file.write(segment.text)
|
||||
file.write(getattr(segment, segment_key))
|
||||
file.write("\n")
|
||||
|
||||
elif output_format == OutputFormat.VTT:
|
||||
|
|
@ -123,7 +128,7 @@ def write_output(path: str, segments: List[Segment], output_format: OutputFormat
|
|||
file.write(
|
||||
f"{to_timestamp(segment.start)} --> {to_timestamp(segment.end)}\n"
|
||||
)
|
||||
file.write(f"{segment.text}\n\n")
|
||||
file.write(f"{getattr(segment, segment_key)}\n\n")
|
||||
|
||||
elif output_format == OutputFormat.SRT:
|
||||
for i, segment in enumerate(segments):
|
||||
|
|
@ -131,7 +136,7 @@ def write_output(path: str, segments: List[Segment], output_format: OutputFormat
|
|||
file.write(
|
||||
f'{to_timestamp(segment.start, ms_separator=",")} --> {to_timestamp(segment.end, ms_separator=",")}\n'
|
||||
)
|
||||
file.write(f"{segment.text}\n\n")
|
||||
file.write(f"{getattr(segment, segment_key)}\n\n")
|
||||
|
||||
logging.debug("Written transcription output")
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class RecordingTranscriber(QObject):
|
|||
|
||||
def start(self):
|
||||
model_path = self.model_path
|
||||
keep_samples = int(0.15 * self.sample_rate)
|
||||
|
||||
if self.transcription_options.model.model_type == ModelType.WHISPER:
|
||||
model = whisper.load_model(model_path)
|
||||
|
|
@ -82,7 +83,7 @@ class RecordingTranscriber(QObject):
|
|||
self.mutex.acquire()
|
||||
if self.queue.size >= self.n_batch_samples:
|
||||
samples = self.queue[: self.n_batch_samples]
|
||||
self.queue = self.queue[self.n_batch_samples :]
|
||||
self.queue = self.queue[self.n_batch_samples - keep_samples:]
|
||||
self.mutex.release()
|
||||
|
||||
logging.debug(
|
||||
|
|
@ -124,7 +125,7 @@ class RecordingTranscriber(QObject):
|
|||
whisper_segments, info = model.transcribe(
|
||||
audio=samples,
|
||||
language=self.transcription_options.language
|
||||
if self.transcription_options.language is not ""
|
||||
if self.transcription_options.language != ""
|
||||
else None,
|
||||
task=self.transcription_options.task.value,
|
||||
temperature=self.transcription_options.temperature,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class Segment:
|
|||
start: int # start time in ms
|
||||
end: int # end time in ms
|
||||
text: str
|
||||
translation: str = ""
|
||||
|
||||
|
||||
LANGUAGES = {
|
||||
|
|
@ -142,6 +143,9 @@ class TranscriptionOptions:
|
|||
openai_access_token: str = field(
|
||||
default="", metadata=config(exclude=Exclude.ALWAYS)
|
||||
)
|
||||
enable_llm_translation: bool = False
|
||||
llm_prompt: str = ""
|
||||
llm_model: str = ""
|
||||
|
||||
|
||||
def humanize_language(language: str) -> str:
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=int(segment.get("start") * 1000),
|
||||
end=int(segment.get("end") * 1000),
|
||||
text=segment.get("text"),
|
||||
translation=""
|
||||
)
|
||||
for segment in result.get("segments")
|
||||
]
|
||||
|
|
@ -149,6 +150,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=int(word.start * 1000),
|
||||
end=int(word.end * 1000),
|
||||
text=word.word,
|
||||
translation=""
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
|
@ -157,6 +159,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=int(segment.start * 1000),
|
||||
end=int(segment.end * 1000),
|
||||
text=segment.text,
|
||||
translation=""
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -181,6 +184,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=int(word.start * 1000),
|
||||
end=int(word.end * 1000),
|
||||
text=word.word.strip(),
|
||||
translation=""
|
||||
)
|
||||
for segment in result.segments
|
||||
for word in segment.words
|
||||
|
|
@ -200,6 +204,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=int(segment.get("start") * 1000),
|
||||
end=int(segment.get("end") * 1000),
|
||||
text=segment.get("text"),
|
||||
translation=""
|
||||
)
|
||||
for segment in segments
|
||||
]
|
||||
|
|
@ -226,6 +231,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
|||
start=segment.get("start"),
|
||||
end=segment.get("end"),
|
||||
text=segment.get("text"),
|
||||
translation=""
|
||||
)
|
||||
for segment in segments_dict
|
||||
]
|
||||
|
|
|
|||
87
buzz/translator.py
Normal file
87
buzz/translator.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import logging
|
||||
import queue
|
||||
|
||||
from typing import Optional
|
||||
from openai import OpenAI
|
||||
from PyQt6.QtCore import QObject, pyqtSignal
|
||||
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.store.keyring_store import get_password, Key
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
|
||||
|
||||
|
||||
class Translator(QObject):
|
||||
translation = pyqtSignal(str, int)
|
||||
finished = pyqtSignal()
|
||||
is_running = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transcription_options: TranscriptionOptions,
|
||||
advanced_settings_dialog: AdvancedSettingsDialog,
|
||||
parent: Optional[QObject] = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
logging.debug(f"Translator init: {transcription_options}")
|
||||
|
||||
self.transcription_options = transcription_options
|
||||
self.advanced_settings_dialog = advanced_settings_dialog
|
||||
self.advanced_settings_dialog.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
self.queue = queue.Queue()
|
||||
|
||||
settings = Settings()
|
||||
custom_openai_base_url = settings.value(
|
||||
key=Settings.Key.CUSTOM_OPENAI_BASE_URL, default_value=""
|
||||
)
|
||||
openai_api_key = get_password(Key.OPENAI_API_KEY)
|
||||
self.openai_client = OpenAI(
|
||||
api_key=openai_api_key,
|
||||
base_url=custom_openai_base_url if custom_openai_base_url else None
|
||||
)
|
||||
|
||||
def start(self):
|
||||
logging.debug("Starting translation queue")
|
||||
|
||||
self.is_running = True
|
||||
|
||||
while self.is_running:
|
||||
try:
|
||||
transcript, transcript_id = self.queue.get(timeout=1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
completion = self.openai_client.chat.completions.create(
|
||||
model=self.transcription_options.llm_model,
|
||||
messages=[
|
||||
{"role": "system", "content": self.transcription_options.llm_prompt},
|
||||
{"role": "user", "content": transcript}
|
||||
]
|
||||
)
|
||||
|
||||
logging.debug(f"Received translation response: {completion}")
|
||||
|
||||
if completion.choices and completion.choices[0].message:
|
||||
next_translation = completion.choices[0].message.content
|
||||
else:
|
||||
logging.error(f"Translation error! Server response: {completion}")
|
||||
next_translation = "Translation error, see logs!"
|
||||
|
||||
self.translation.emit(next_translation, transcript_id)
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
):
|
||||
self.transcription_options = transcription_options
|
||||
|
||||
def enqueue(self, transcript: str, transcript_id: Optional[int] = None):
|
||||
self.queue.put((transcript, transcript_id))
|
||||
|
||||
def stop(self):
|
||||
self.is_running = False
|
||||
|
|
@ -74,6 +74,11 @@ class FileDownloadIcon(Icon):
|
|||
super().__init__(get_path("assets/file_download_black_24dp.svg"), parent)
|
||||
|
||||
|
||||
class TranslateIcon(Icon):
|
||||
def __init__(self, parent: QWidget):
|
||||
super().__init__(get_path("assets/translate_black.svg"), parent)
|
||||
|
||||
|
||||
class VisibilityIcon(Icon):
|
||||
def __init__(self, parent: QWidget):
|
||||
super().__init__(
|
||||
|
|
|
|||
|
|
@ -141,6 +141,8 @@ class MainWindow(QMainWindow):
|
|||
self.folder_watcher.task_found.connect(self.add_task)
|
||||
self.folder_watcher.find_tasks()
|
||||
|
||||
self.transcription_viewer_widget = None
|
||||
|
||||
if os.environ.get('SNAP_NAME', '') == 'buzz':
|
||||
logging.debug("Running in a snap environment")
|
||||
self.check_linux_permissions()
|
||||
|
|
@ -267,6 +269,8 @@ class MainWindow(QMainWindow):
|
|||
self.on_openai_access_token_changed
|
||||
)
|
||||
file_transcriber_window.show()
|
||||
file_transcriber_window.raise_()
|
||||
file_transcriber_window.activateWindow()
|
||||
|
||||
@staticmethod
|
||||
def on_openai_access_token_changed(access_token: str):
|
||||
|
|
@ -347,14 +351,14 @@ class MainWindow(QMainWindow):
|
|||
self.open_transcription_viewer(transcription)
|
||||
|
||||
def open_transcription_viewer(self, transcription: Transcription):
|
||||
transcription_viewer_widget = TranscriptionViewerWidget(
|
||||
self.transcription_viewer_widget = TranscriptionViewerWidget(
|
||||
transcription=transcription,
|
||||
transcription_service=self.transcription_service,
|
||||
shortcuts=self.shortcuts,
|
||||
parent=self,
|
||||
flags=Qt.WindowType.Window,
|
||||
)
|
||||
transcription_viewer_widget.show()
|
||||
self.transcription_viewer_widget.show()
|
||||
|
||||
def add_task(self, task: FileTranscriptionTask):
|
||||
self.transcription_service.create_transcription(task)
|
||||
|
|
@ -396,6 +400,10 @@ class MainWindow(QMainWindow):
|
|||
self.transcriber_worker.stop()
|
||||
self.transcriber_thread.quit()
|
||||
self.transcriber_thread.wait()
|
||||
|
||||
if self.transcription_viewer_widget is not None:
|
||||
self.transcription_viewer_widget.close()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
||||
def save_geometry(self):
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class GeneralPreferencesWidget(QWidget):
|
|||
QMessageBox.information(
|
||||
self,
|
||||
_("OpenAI API Key Test"),
|
||||
_("Your API key is valid. Buzz will use this key to perform Whisper API transcriptions."),
|
||||
_("Your API key is valid. Buzz will use this key to perform Whisper API transcriptions and AI translations with ChatGPT."),
|
||||
)
|
||||
|
||||
def on_test_openai_api_key_failure(self, error: str):
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ class FileTranscriptionPreferences:
|
|||
word_level_timings: bool
|
||||
temperature: Tuple[float, ...]
|
||||
initial_prompt: str
|
||||
enable_llm_translation: bool
|
||||
llm_prompt: str
|
||||
llm_model: str
|
||||
output_formats: Set["OutputFormat"]
|
||||
|
||||
def save(self, settings: QSettings) -> None:
|
||||
|
|
@ -30,6 +33,9 @@ class FileTranscriptionPreferences:
|
|||
settings.setValue("word_level_timings", self.word_level_timings)
|
||||
settings.setValue("temperature", self.temperature)
|
||||
settings.setValue("initial_prompt", self.initial_prompt)
|
||||
settings.setValue("enable_llm_translation", self.enable_llm_translation)
|
||||
settings.setValue("llm_model", self.llm_model)
|
||||
settings.setValue("llm_prompt", self.llm_prompt)
|
||||
settings.setValue(
|
||||
"output_formats",
|
||||
[output_format.value for output_format in self.output_formats],
|
||||
|
|
@ -44,10 +50,16 @@ class FileTranscriptionPreferences:
|
|||
)
|
||||
|
||||
word_level_timings_value = settings.value("word_level_timings", False)
|
||||
word_level_timings = False if word_level_timings_value == "false" else bool(word_level_timings_value)
|
||||
word_level_timings = False if word_level_timings_value == "false" \
|
||||
else bool(word_level_timings_value)
|
||||
|
||||
temperature = settings.value("temperature", DEFAULT_WHISPER_TEMPERATURE)
|
||||
initial_prompt = settings.value("initial_prompt", "")
|
||||
enable_llm_translation_value = settings.value("enable_llm_translation", False)
|
||||
enable_llm_translation = False if enable_llm_translation_value == "false" \
|
||||
else bool(enable_llm_translation_value)
|
||||
llm_model = settings.value("llm_model", "")
|
||||
llm_prompt = settings.value("llm_prompt", "")
|
||||
output_formats = settings.value("output_formats", []) or []
|
||||
return FileTranscriptionPreferences(
|
||||
language=language,
|
||||
|
|
@ -58,6 +70,9 @@ class FileTranscriptionPreferences:
|
|||
word_level_timings=word_level_timings,
|
||||
temperature=temperature,
|
||||
initial_prompt=initial_prompt,
|
||||
enable_llm_translation=enable_llm_translation,
|
||||
llm_model=llm_model,
|
||||
llm_prompt=llm_prompt,
|
||||
output_formats=set(
|
||||
[OutputFormat(output_format) for output_format in output_formats]
|
||||
),
|
||||
|
|
@ -74,6 +89,9 @@ class FileTranscriptionPreferences:
|
|||
language=transcription_options.language,
|
||||
temperature=transcription_options.temperature,
|
||||
initial_prompt=transcription_options.initial_prompt,
|
||||
enable_llm_translation=transcription_options.enable_llm_translation,
|
||||
llm_model=transcription_options.llm_model,
|
||||
llm_prompt=transcription_options.llm_prompt,
|
||||
word_level_timings=transcription_options.word_level_timings,
|
||||
model=transcription_options.model,
|
||||
output_formats=file_transcription_options.output_formats,
|
||||
|
|
@ -91,6 +109,9 @@ class FileTranscriptionPreferences:
|
|||
language=self.language,
|
||||
temperature=self.temperature,
|
||||
initial_prompt=self.initial_prompt,
|
||||
enable_llm_translation=self.enable_llm_translation,
|
||||
llm_model=self.llm_model,
|
||||
llm_prompt=self.llm_prompt,
|
||||
word_level_timings=self.word_level_timings,
|
||||
model=self.model,
|
||||
openai_access_token=openai_access_token,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from buzz.transcriber.transcriber import (
|
|||
DEFAULT_WHISPER_TEMPERATURE,
|
||||
Task,
|
||||
)
|
||||
from buzz.translator import Translator
|
||||
from buzz.widgets.audio_devices_combo_box import AudioDevicesComboBox
|
||||
from buzz.widgets.audio_meter_widget import AudioMeterWidget
|
||||
from buzz.widgets.model_download_progress_dialog import ModelDownloadProgressDialog
|
||||
|
|
@ -59,6 +60,8 @@ class RecordingTranscriberWidget(QWidget):
|
|||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
self.translation_thread = None
|
||||
self.translator = None
|
||||
self.current_status = self.RecordingStatus.STOPPED
|
||||
self.setWindowTitle(_("Live Recording"))
|
||||
|
||||
|
|
@ -99,6 +102,16 @@ class RecordingTranscriberWidget(QWidget):
|
|||
default_value=DEFAULT_WHISPER_TEMPERATURE,
|
||||
),
|
||||
word_level_timings=False,
|
||||
enable_llm_translation=self.settings.value(
|
||||
key=Settings.Key.RECORDING_TRANSCRIBER_ENABLE_LLM_TRANSLATION,
|
||||
default_value=False,
|
||||
),
|
||||
llm_model=self.settings.value(
|
||||
key=Settings.Key.RECORDING_TRANSCRIBER_LLM_MODEL, default_value=""
|
||||
),
|
||||
llm_prompt=self.settings.value(
|
||||
key=Settings.Key.RECORDING_TRANSCRIBER_LLM_PROMPT, default_value=""
|
||||
),
|
||||
)
|
||||
|
||||
self.audio_devices_combo_box = AudioDevicesComboBox(self)
|
||||
|
|
@ -108,15 +121,18 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.record_button = RecordButton(self)
|
||||
self.record_button.clicked.connect(self.on_record_button_clicked)
|
||||
|
||||
self.text_box = TextDisplayBox(self)
|
||||
self.text_box.setPlaceholderText(_("Click Record to begin..."))
|
||||
self.transcription_text_box = TextDisplayBox(self)
|
||||
self.transcription_text_box.setPlaceholderText(_("Click Record to begin..."))
|
||||
|
||||
transcription_options_group_box = TranscriptionOptionsGroupBox(
|
||||
self.translation_text_box = TextDisplayBox(self)
|
||||
self.translation_text_box.setPlaceholderText(_("Waiting for AI translation..."))
|
||||
|
||||
self.transcription_options_group_box = TranscriptionOptionsGroupBox(
|
||||
default_transcription_options=self.transcription_options,
|
||||
model_types=model_types,
|
||||
parent=self,
|
||||
)
|
||||
transcription_options_group_box.transcription_options_changed.connect(
|
||||
self.transcription_options_group_box.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
|
|
@ -129,17 +145,22 @@ class RecordingTranscriberWidget(QWidget):
|
|||
record_button_layout.addWidget(self.audio_meter_widget)
|
||||
record_button_layout.addWidget(self.record_button)
|
||||
|
||||
layout.addWidget(transcription_options_group_box)
|
||||
layout.addWidget(self.transcription_options_group_box)
|
||||
layout.addLayout(recording_options_layout)
|
||||
layout.addLayout(record_button_layout)
|
||||
layout.addWidget(self.text_box)
|
||||
layout.addWidget(self.transcription_text_box)
|
||||
layout.addWidget(self.translation_text_box)
|
||||
|
||||
if not self.transcription_options.enable_llm_translation:
|
||||
self.translation_text_box.hide()
|
||||
|
||||
self.setLayout(layout)
|
||||
self.setFixedSize(self.sizeHint())
|
||||
self.resize(450, 500)
|
||||
|
||||
self.reset_recording_amplitude_listener()
|
||||
|
||||
self.export_file = None
|
||||
self.transcript_export_file = None
|
||||
self.translation_export_file = None
|
||||
self.export_enabled = self.settings.value(
|
||||
key=Settings.Key.RECORDING_TRANSCRIBER_EXPORT_ENABLED,
|
||||
default_value=False,
|
||||
|
|
@ -168,13 +189,19 @@ class RecordingTranscriberWidget(QWidget):
|
|||
if not os.path.isdir(export_folder):
|
||||
self.export_enabled = False
|
||||
|
||||
self.export_file = os.path.join(export_folder, export_file_name)
|
||||
self.transcript_export_file = os.path.join(export_folder, export_file_name)
|
||||
self.translation_export_file = self.transcript_export_file.replace(".txt", ".translated.txt")
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
):
|
||||
self.transcription_options = transcription_options
|
||||
|
||||
if self.transcription_options.enable_llm_translation:
|
||||
self.translation_text_box.show()
|
||||
else:
|
||||
self.translation_text_box.hide()
|
||||
|
||||
def on_device_changed(self, device_id: int):
|
||||
self.selected_device_id = device_id
|
||||
self.reset_recording_amplitude_listener()
|
||||
|
|
@ -259,6 +286,28 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.transcriber.error.connect(self.transcription_thread.quit)
|
||||
self.transcriber.error.connect(self.transcriber.deleteLater)
|
||||
|
||||
if self.transcription_options.enable_llm_translation:
|
||||
self.translation_thread = QThread()
|
||||
|
||||
self.translator = Translator(
|
||||
self.transcription_options,
|
||||
self.transcription_options_group_box.advanced_settings_dialog,
|
||||
)
|
||||
|
||||
self.translator.moveToThread(self.translation_thread)
|
||||
|
||||
self.translation_thread.started.connect(self.translator.start)
|
||||
self.translation_thread.finished.connect(
|
||||
self.translation_thread.deleteLater
|
||||
)
|
||||
|
||||
self.translator.finished.connect(self.translation_thread.quit)
|
||||
self.translator.finished.connect(self.translator.deleteLater)
|
||||
|
||||
self.translator.translation.connect(self.on_next_translation)
|
||||
|
||||
self.translation_thread.start()
|
||||
|
||||
self.transcription_thread.start()
|
||||
|
||||
def on_download_model_progress(self, progress: Tuple[float, float]):
|
||||
|
|
@ -288,22 +337,45 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.set_recording_status_stopped()
|
||||
self.record_button.setDisabled(False)
|
||||
|
||||
@staticmethod
|
||||
def strip_newlines(text):
|
||||
return text.replace('\r\n', os.linesep).replace('\n', os.linesep)
|
||||
|
||||
def on_next_transcription(self, text: str):
|
||||
text = text.strip()
|
||||
if len(text) > 0:
|
||||
self.text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
if len(self.text_box.toPlainText()) > 0:
|
||||
self.text_box.insertPlainText("\n\n")
|
||||
self.text_box.insertPlainText(text)
|
||||
self.text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
if self.translator is not None:
|
||||
self.translator.enqueue(text)
|
||||
|
||||
self.transcription_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
if len(self.transcription_text_box.toPlainText()) > 0:
|
||||
self.transcription_text_box.insertPlainText("\n\n")
|
||||
self.transcription_text_box.insertPlainText(text)
|
||||
self.transcription_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
|
||||
if self.export_enabled:
|
||||
with open(self.export_file, "a") as f:
|
||||
with open(self.transcript_export_file, "a") as f:
|
||||
f.write(text + "\n\n")
|
||||
|
||||
def on_next_translation(self, text: str, _: Optional[int] = None):
|
||||
if len(text) > 0:
|
||||
self.translation_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
if len(self.translation_text_box.toPlainText()) > 0:
|
||||
self.translation_text_box.insertPlainText("\n\n")
|
||||
self.translation_text_box.insertPlainText(self.strip_newlines(text))
|
||||
self.translation_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||
|
||||
if self.export_enabled:
|
||||
with open(self.translation_export_file, "a") as f:
|
||||
f.write(text + "\n\n")
|
||||
|
||||
def stop_recording(self):
|
||||
if self.transcriber is not None:
|
||||
self.transcriber.stop_recording()
|
||||
|
||||
if self.translator is not None:
|
||||
self.translator.stop()
|
||||
|
||||
# Disable record button until the transcription is actually stopped in the background
|
||||
self.record_button.setDisabled(True)
|
||||
|
||||
|
|
@ -341,7 +413,7 @@ class RecordingTranscriberWidget(QWidget):
|
|||
|
||||
def reset_recording_controls(self):
|
||||
# Clear text box placeholder because the first chunk takes a while to process
|
||||
self.text_box.setPlaceholderText("")
|
||||
self.transcription_text_box.setPlaceholderText("")
|
||||
self.reset_record_button()
|
||||
self.reset_model_download()
|
||||
|
||||
|
|
@ -361,6 +433,9 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.recording_amplitude_listener.deleteLater()
|
||||
self.recording_amplitude_listener = None
|
||||
|
||||
if self.translator is not None:
|
||||
self.translator.stop()
|
||||
|
||||
self.settings.set_value(
|
||||
Settings.Key.RECORDING_TRANSCRIBER_LANGUAGE,
|
||||
self.transcription_options.language,
|
||||
|
|
@ -379,5 +454,17 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.settings.set_value(
|
||||
Settings.Key.RECORDING_TRANSCRIBER_MODEL, self.transcription_options.model
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.RECORDING_TRANSCRIBER_ENABLE_LLM_TRANSLATION,
|
||||
self.transcription_options.enable_llm_translation,
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.RECORDING_TRANSCRIBER_LLM_MODEL,
|
||||
self.transcription_options.llm_model,
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.RECORDING_TRANSCRIBER_LLM_PROMPT,
|
||||
self.transcription_options.llm_prompt,
|
||||
)
|
||||
|
||||
return super().closeEvent(event)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@ from PyQt6.QtWidgets import (
|
|||
QDialog,
|
||||
QWidget,
|
||||
QDialogButtonBox,
|
||||
QCheckBox,
|
||||
QPlainTextEdit,
|
||||
QFormLayout,
|
||||
QLabel,
|
||||
)
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.model_loader import ModelType
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.widgets.line_edit import LineEdit
|
||||
from buzz.widgets.transcriber.initial_prompt_text_edit import InitialPromptTextEdit
|
||||
from buzz.widgets.transcriber.temperature_validator import TemperatureValidator
|
||||
|
|
@ -24,16 +28,16 @@ class AdvancedSettingsDialog(QDialog):
|
|||
super().__init__(parent)
|
||||
|
||||
self.transcription_options = transcription_options
|
||||
self.settings = Settings()
|
||||
|
||||
self.setWindowTitle(_("Advanced Settings"))
|
||||
|
||||
button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton(QDialogButtonBox.StandardButton.Ok), self
|
||||
)
|
||||
button_box.accepted.connect(self.accept)
|
||||
|
||||
layout = QFormLayout(self)
|
||||
|
||||
transcription_settings_title= _("Speech recognition settings")
|
||||
transcription_settings_title_label = QLabel(f"<h4>{transcription_settings_title}</h4>", self)
|
||||
layout.addRow("", transcription_settings_title_label)
|
||||
|
||||
default_temperature_text = ", ".join(
|
||||
[str(temp) for temp in transcription_options.temperature]
|
||||
)
|
||||
|
|
@ -48,6 +52,8 @@ class AdvancedSettingsDialog(QDialog):
|
|||
transcription_options.model.model_type == ModelType.WHISPER
|
||||
)
|
||||
|
||||
layout.addRow(_("Temperature:"), self.temperature_line_edit)
|
||||
|
||||
self.initial_prompt_text_edit = InitialPromptTextEdit(
|
||||
transcription_options.initial_prompt,
|
||||
transcription_options.model.model_type,
|
||||
|
|
@ -57,12 +63,43 @@ class AdvancedSettingsDialog(QDialog):
|
|||
self.on_initial_prompt_changed
|
||||
)
|
||||
|
||||
layout.addRow(_("Temperature:"), self.temperature_line_edit)
|
||||
layout.addRow(_("Initial Prompt:"), self.initial_prompt_text_edit)
|
||||
|
||||
translation_settings_title= _("Translation settings")
|
||||
translation_settings_title_label = QLabel(f"<h4>{translation_settings_title}</h4>", self)
|
||||
layout.addRow("", translation_settings_title_label)
|
||||
|
||||
self.enable_llm_translation_checkbox = QCheckBox(_("Enable AI translation"))
|
||||
self.enable_llm_translation_checkbox.setChecked(self.transcription_options.enable_llm_translation)
|
||||
self.enable_llm_translation_checkbox.stateChanged.connect(self.on_enable_llm_translation_changed)
|
||||
layout.addRow("", self.enable_llm_translation_checkbox)
|
||||
|
||||
self.llm_model_line_edit = LineEdit(self.transcription_options.llm_model, self)
|
||||
self.llm_model_line_edit.textChanged.connect(
|
||||
self.on_llm_model_changed
|
||||
)
|
||||
self.llm_model_line_edit.setMinimumWidth(170)
|
||||
self.llm_model_line_edit.setEnabled(self.transcription_options.enable_llm_translation)
|
||||
self.llm_model_line_edit.setPlaceholderText("gpt-3.5-turbo")
|
||||
layout.addRow(_("AI model:"), self.llm_model_line_edit)
|
||||
|
||||
self.llm_prompt_text_edit = QPlainTextEdit(self.transcription_options.llm_prompt)
|
||||
self.llm_prompt_text_edit.setEnabled(self.transcription_options.enable_llm_translation)
|
||||
self.llm_prompt_text_edit.setPlaceholderText(_("Enter instructions for AI on how to translate..."))
|
||||
self.llm_prompt_text_edit.setMinimumWidth(170)
|
||||
self.llm_prompt_text_edit.setFixedHeight(115)
|
||||
self.llm_prompt_text_edit.textChanged.connect(self.on_llm_prompt_changed)
|
||||
layout.addRow(_("Instructions for AI:"), self.llm_prompt_text_edit)
|
||||
|
||||
button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton(QDialogButtonBox.StandardButton.Ok), self
|
||||
)
|
||||
button_box.accepted.connect(self.accept)
|
||||
|
||||
layout.addWidget(button_box)
|
||||
|
||||
self.setLayout(layout)
|
||||
self.setFixedSize(self.sizeHint())
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def on_temperature_changed(self, text: str):
|
||||
try:
|
||||
|
|
@ -77,3 +114,20 @@ class AdvancedSettingsDialog(QDialog):
|
|||
self.initial_prompt_text_edit.toPlainText()
|
||||
)
|
||||
self.transcription_options_changed.emit(self.transcription_options)
|
||||
|
||||
def on_enable_llm_translation_changed(self, state):
|
||||
self.transcription_options.enable_llm_translation = state == 2
|
||||
self.transcription_options_changed.emit(self.transcription_options)
|
||||
|
||||
self.llm_model_line_edit.setEnabled(self.transcription_options.enable_llm_translation)
|
||||
self.llm_prompt_text_edit.setEnabled(self.transcription_options.enable_llm_translation)
|
||||
|
||||
def on_llm_model_changed(self, text: str):
|
||||
self.transcription_options.llm_model = text
|
||||
self.transcription_options_changed.emit(self.transcription_options)
|
||||
|
||||
def on_llm_prompt_changed(self):
|
||||
self.transcription_options.llm_prompt = (
|
||||
self.llm_prompt_text_edit.toPlainText()
|
||||
)
|
||||
self.transcription_options_changed.emit(self.transcription_options)
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ class InitialPromptTextEdit(QPlainTextEdit):
|
|||
self.setPlaceholderText(_("Enter prompt..."))
|
||||
self.setEnabled(model_type.supports_initial_prompt)
|
||||
self.setMinimumWidth(350)
|
||||
self.setFixedHeight(115)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ class TranscriptionOptionsGroupBox(QGroupBox):
|
|||
)
|
||||
self.model_type_combo_box.changed.connect(self.on_model_type_changed)
|
||||
|
||||
self.advanced_settings_dialog = AdvancedSettingsDialog(
|
||||
transcription_options=self.transcription_options, parent=self
|
||||
)
|
||||
self.advanced_settings_dialog.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
self.whisper_model_size_combo_box = QComboBox(self)
|
||||
self.whisper_model_size_combo_box.addItems(
|
||||
[size.value.title() for size in WhisperModelSize]
|
||||
|
|
@ -102,13 +109,7 @@ class TranscriptionOptionsGroupBox(QGroupBox):
|
|||
self.transcription_options_changed.emit(self.transcription_options)
|
||||
|
||||
def open_advanced_settings(self):
|
||||
dialog = AdvancedSettingsDialog(
|
||||
transcription_options=self.transcription_options, parent=self
|
||||
)
|
||||
dialog.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
dialog.exec()
|
||||
self.advanced_settings_dialog.exec()
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ def format_record_status_text(record: QSqlRecord) -> str:
|
|||
status = FileTranscriptionTask.Status(record.value("status"))
|
||||
match status:
|
||||
case FileTranscriptionTask.Status.IN_PROGRESS:
|
||||
return f'{_("In Progress")} ({record.value("progress") :.0%})'
|
||||
in_progress_label = _("In Progress")
|
||||
return f'{in_progress_label} ({record.value("progress") :.0%})'
|
||||
case FileTranscriptionTask.Status.COMPLETED:
|
||||
status = _("Completed")
|
||||
started_at = record.value("time_started")
|
||||
|
|
@ -68,7 +69,8 @@ def format_record_status_text(record: QSqlRecord) -> str:
|
|||
status += f" ({TranscriptionTasksTableWidget.format_timedelta(datetime.fromisoformat(completed_at) - datetime.fromisoformat(started_at))})"
|
||||
return status
|
||||
case FileTranscriptionTask.Status.FAILED:
|
||||
return f'{_("Failed")} ({record.value("error_message")})'
|
||||
failed_label = _("Failed")
|
||||
return f'{failed_label} ({record.value("error_message")})'
|
||||
case FileTranscriptionTask.Status.CANCELED:
|
||||
return _("Canceled")
|
||||
case FileTranscriptionTask.Status.QUEUED:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtWidgets import QWidget, QMenu, QFileDialog
|
||||
|
||||
|
|
@ -23,15 +24,48 @@ class ExportTranscriptionMenu(QMenu):
|
|||
self.transcription = transcription
|
||||
self.transcription_service = transcription_service
|
||||
|
||||
actions = [
|
||||
QAction(text=output_format.value.upper(), parent=self)
|
||||
for output_format in OutputFormat
|
||||
self.segments = [
|
||||
Segment(
|
||||
start=segment.start_time,
|
||||
end=segment.end_time,
|
||||
text=segment.text,
|
||||
translation=segment.translation)
|
||||
for segment in self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
)
|
||||
]
|
||||
|
||||
if self.segments and len(self.segments[0].translation) > 0:
|
||||
text_label = _("Text")
|
||||
translation_label = _("Translation")
|
||||
actions = [
|
||||
action
|
||||
for output_format in OutputFormat
|
||||
for action in [
|
||||
QAction(text=f"{output_format.value.upper()} - {text_label}", parent=self),
|
||||
QAction(text=f"{output_format.value.upper()} - {translation_label}", parent=self)
|
||||
]
|
||||
]
|
||||
else:
|
||||
actions = [
|
||||
QAction(text=output_format.value.upper(), parent=self)
|
||||
for output_format in OutputFormat
|
||||
]
|
||||
self.addActions(actions)
|
||||
self.triggered.connect(self.on_menu_triggered)
|
||||
|
||||
@staticmethod
|
||||
def extract_format_and_segment_key(action_text: str):
|
||||
parts = action_text.split('-')
|
||||
output_format = parts[0].strip()
|
||||
label = parts[1].strip() if len(parts) > 1 else None
|
||||
segment_key = 'translation' if label == _('Translation') else 'text'
|
||||
|
||||
return output_format, segment_key
|
||||
|
||||
def on_menu_triggered(self, action: QAction):
|
||||
output_format = OutputFormat[action.text()]
|
||||
output_format_value, segment_key = self.extract_format_and_segment_key(action.text())
|
||||
output_format = OutputFormat[output_format_value]
|
||||
|
||||
default_path = self.transcription.get_output_file_path(
|
||||
output_format=output_format
|
||||
|
|
@ -47,15 +81,9 @@ class ExportTranscriptionMenu(QMenu):
|
|||
if output_file_path == "":
|
||||
return
|
||||
|
||||
segments = [
|
||||
Segment(start=segment.start_time, end=segment.end_time, text=segment.text)
|
||||
for segment in self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
)
|
||||
]
|
||||
|
||||
write_output(
|
||||
path=output_file_path,
|
||||
segments=segments,
|
||||
segments=self.segments,
|
||||
output_format=output_format,
|
||||
segment_key=segment_key
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
import enum
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal, Qt, QModelIndex, QItemSelection
|
||||
from PyQt6.QtSql import QSqlTableModel, QSqlRecord
|
||||
from PyQt6.QtGui import QFontMetrics, QTextOption
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget,
|
||||
QTableView,
|
||||
QStyledItemDelegate,
|
||||
QAbstractItemView,
|
||||
QTextEdit,
|
||||
)
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.translator import Translator
|
||||
from buzz.transcriber.file_transcriber import to_timestamp
|
||||
|
||||
|
||||
|
|
@ -21,6 +25,7 @@ class Column(enum.Enum):
|
|||
END = enum.auto()
|
||||
START = enum.auto()
|
||||
TEXT = enum.auto()
|
||||
TRANSLATION = enum.auto()
|
||||
TRANSCRIPTION_ID = enum.auto()
|
||||
|
||||
|
||||
|
|
@ -37,6 +42,18 @@ class TimeStampDelegate(QStyledItemDelegate):
|
|||
return to_timestamp(value)
|
||||
|
||||
|
||||
class WordWrapDelegate(QStyledItemDelegate):
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = QTextEdit(parent)
|
||||
editor.setWordWrapMode(QTextOption.WrapMode.WordWrap)
|
||||
editor.setAcceptRichText(False)
|
||||
|
||||
return editor
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
model.setData(index, editor.toPlainText())
|
||||
|
||||
|
||||
class TranscriptionSegmentModel(QSqlTableModel):
|
||||
def __init__(self, transcription_id: UUID):
|
||||
super().__init__()
|
||||
|
|
@ -52,20 +69,31 @@ class TranscriptionSegmentModel(QSqlTableModel):
|
|||
|
||||
|
||||
class TranscriptionSegmentsEditorWidget(QTableView):
|
||||
PARENT_PADDINGS = 40
|
||||
segment_selected = pyqtSignal(QSqlRecord)
|
||||
|
||||
def __init__(self, transcription_id: UUID, parent: Optional[QWidget]):
|
||||
def __init__(
|
||||
self,
|
||||
transcription_id: UUID,
|
||||
translator: Translator,
|
||||
parent: Optional[QWidget]
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.translator = translator
|
||||
self.translator.translation.connect(self.update_translation)
|
||||
|
||||
model = TranscriptionSegmentModel(transcription_id=transcription_id)
|
||||
self.setModel(model)
|
||||
|
||||
timestamp_delegate = TimeStampDelegate()
|
||||
word_wrap_delegate = WordWrapDelegate()
|
||||
|
||||
self.column_definitions: list[ColDef] = [
|
||||
ColDef("start", _("Start"), Column.START, delegate=timestamp_delegate),
|
||||
ColDef("end", _("End"), Column.END, delegate=timestamp_delegate),
|
||||
ColDef("text", _("Text"), Column.TEXT),
|
||||
ColDef("text", _("Text"), Column.TEXT, delegate=word_wrap_delegate),
|
||||
ColDef("translation", _("Translation"), Column.TRANSLATION, delegate=word_wrap_delegate),
|
||||
]
|
||||
|
||||
for i in range(model.columnCount()):
|
||||
|
|
@ -90,9 +118,52 @@ class TranscriptionSegmentsEditorWidget(QTableView):
|
|||
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
|
||||
model.select()
|
||||
|
||||
self.has_translations = self.has_non_empty_translation()
|
||||
|
||||
# Show start before end
|
||||
self.horizontalHeader().swapSections(1, 2)
|
||||
self.resizeColumnsToContents()
|
||||
|
||||
font_metrics = QFontMetrics(self.font())
|
||||
max_row_height = font_metrics.height() * 4
|
||||
for row in range(self.model().rowCount()):
|
||||
self.setRowHeight(row, max_row_height)
|
||||
|
||||
self.setColumnWidth(Column.START.value, 95)
|
||||
self.setColumnWidth(Column.END.value, 95)
|
||||
|
||||
self.setWordWrap(True)
|
||||
|
||||
def has_non_empty_translation(self) -> bool:
|
||||
for i in range(self.model().rowCount()):
|
||||
if self.model().record(i).value("translation").strip():
|
||||
return True
|
||||
return False
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
|
||||
if not self.has_translations:
|
||||
self.hideColumn(Column.TRANSLATION.value)
|
||||
else:
|
||||
self.showColumn(Column.TRANSLATION.value)
|
||||
|
||||
text_column_count = 2 if self.has_translations else 1
|
||||
|
||||
time_column_widths = self.columnWidth(Column.START.value) + self.columnWidth(Column.END.value)
|
||||
text_column_width = (
|
||||
int((self.parent().width() - self.PARENT_PADDINGS - time_column_widths) / text_column_count))
|
||||
|
||||
self.setColumnWidth(Column.TEXT.value, text_column_width)
|
||||
self.setColumnWidth(Column.TRANSLATION.value, text_column_width)
|
||||
|
||||
def update_translation(self, translation: str, segment_id: Optional[int] = None):
|
||||
self.has_translations = True
|
||||
self.resizeEvent(None)
|
||||
|
||||
for row in range(self.model().rowCount()):
|
||||
if self.model().record(row).value("id") == segment_id:
|
||||
self.model().setData(self.model().index(row, Column.TRANSLATION.value), translation)
|
||||
break
|
||||
|
||||
def on_selection_changed(
|
||||
self, selected: QItemSelection, _deselected: QItemSelection
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal, Qt
|
||||
|
|
@ -10,8 +11,14 @@ from buzz.settings.shortcuts import Shortcuts
|
|||
from buzz.widgets.icon import VisibilityIcon
|
||||
|
||||
|
||||
class ViewMode(Enum):
|
||||
TEXT = "Text"
|
||||
TRANSLATION = "Translation"
|
||||
TIMESTAMPS = "Timestamps"
|
||||
|
||||
|
||||
class TranscriptionViewModeToolButton(QToolButton):
|
||||
view_mode_changed = pyqtSignal(bool) # is_timestamps?
|
||||
view_mode_changed = pyqtSignal(ViewMode)
|
||||
|
||||
def __init__(self, shortcuts: Shortcuts, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
|
|
@ -26,12 +33,19 @@ class TranscriptionViewModeToolButton(QToolButton):
|
|||
menu.addAction(
|
||||
_("Text"),
|
||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_TEXT)),
|
||||
lambda: self.view_mode_changed.emit(False),
|
||||
lambda: self.view_mode_changed.emit(ViewMode.TEXT),
|
||||
)
|
||||
|
||||
menu.addAction(
|
||||
_("Translation"),
|
||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_TRANSLATION)),
|
||||
lambda: self.view_mode_changed.emit(ViewMode.TRANSLATION)
|
||||
)
|
||||
|
||||
menu.addAction(
|
||||
_("Timestamps"),
|
||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_TIMESTAMPS)),
|
||||
lambda: self.view_mode_changed.emit(True),
|
||||
lambda: self.view_mode_changed.emit(ViewMode.TIMESTAMPS),
|
||||
)
|
||||
|
||||
self.setMenu(menu)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import logging
|
||||
import platform
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtCore import Qt, QThread
|
||||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtMultimedia import QMediaPlayer
|
||||
from PyQt6.QtSql import QSqlRecord
|
||||
|
|
@ -11,6 +12,7 @@ from PyQt6.QtWidgets import (
|
|||
QVBoxLayout,
|
||||
QToolButton,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
)
|
||||
|
||||
from buzz.locale import _
|
||||
|
|
@ -18,25 +20,36 @@ from buzz.db.entity.transcription import Transcription
|
|||
from buzz.db.service.transcription_service import TranscriptionService
|
||||
from buzz.paths import file_path_as_title
|
||||
from buzz.settings.shortcuts import Shortcuts
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.store.keyring_store import get_password, Key
|
||||
from buzz.widgets.audio_player import AudioPlayer
|
||||
from buzz.widgets.icon import (
|
||||
FileDownloadIcon,
|
||||
TranslateIcon
|
||||
)
|
||||
from buzz.translator import Translator
|
||||
from buzz.widgets.text_display_box import TextDisplayBox
|
||||
from buzz.widgets.toolbar import ToolBar
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
|
||||
from buzz.widgets.transcription_viewer.export_transcription_menu import (
|
||||
ExportTranscriptionMenu,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
from buzz.widgets.transcription_viewer.transcription_segments_editor_widget import (
|
||||
TranscriptionSegmentsEditorWidget,
|
||||
)
|
||||
from buzz.widgets.transcription_viewer.transcription_view_mode_tool_button import (
|
||||
TranscriptionViewModeToolButton,
|
||||
ViewMode
|
||||
)
|
||||
|
||||
|
||||
class TranscriptionViewerWidget(QWidget):
|
||||
transcription: Transcription
|
||||
settings = Settings()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -55,10 +68,45 @@ class TranscriptionViewerWidget(QWidget):
|
|||
|
||||
self.setWindowTitle(file_path_as_title(transcription.file))
|
||||
|
||||
self.is_showing_timestamps = True
|
||||
self.translation_thread = None
|
||||
self.translator = None
|
||||
self.view_mode = ViewMode.TIMESTAMPS
|
||||
|
||||
self.openai_access_token = get_password(Key.OPENAI_API_KEY)
|
||||
|
||||
preferences = self.load_preferences()
|
||||
|
||||
(
|
||||
self.transcription_options,
|
||||
self.file_transcription_options,
|
||||
) = preferences.to_transcription_options(
|
||||
openai_access_token=self.openai_access_token,
|
||||
)
|
||||
|
||||
self.transcription_options_dialog = AdvancedSettingsDialog(
|
||||
transcription_options=self.transcription_options, parent=self
|
||||
)
|
||||
self.transcription_options_dialog.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
self.translator = Translator(
|
||||
self.transcription_options,
|
||||
self.transcription_options_dialog,
|
||||
)
|
||||
|
||||
self.translation_thread = QThread()
|
||||
self.translator.moveToThread(self.translation_thread)
|
||||
|
||||
self.translation_thread.started.connect(self.translator.start)
|
||||
|
||||
self.translation_thread.start()
|
||||
|
||||
self.table_widget = TranscriptionSegmentsEditorWidget(
|
||||
transcription_id=UUID(hex=transcription.id), parent=self
|
||||
transcription_id=UUID(hex=transcription.id),
|
||||
translator=self.translator,
|
||||
|
||||
parent=self
|
||||
)
|
||||
self.table_widget.segment_selected.connect(self.on_segment_selected)
|
||||
|
||||
|
|
@ -102,6 +150,16 @@ class TranscriptionViewerWidget(QWidget):
|
|||
export_tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
toolbar.addWidget(export_tool_button)
|
||||
|
||||
translate_button = QToolButton()
|
||||
translate_button.setText(_("Translate"))
|
||||
translate_button.setIcon(TranslateIcon(self))
|
||||
translate_button.setToolButtonStyle(
|
||||
Qt.ToolButtonStyle.ToolButtonTextBesideIcon
|
||||
)
|
||||
translate_button.clicked.connect(self.on_translate_button_clicked)
|
||||
|
||||
toolbar.addWidget(translate_button)
|
||||
|
||||
layout.setMenuBar(toolbar)
|
||||
|
||||
layout.addWidget(self.table_widget)
|
||||
|
|
@ -114,10 +172,10 @@ class TranscriptionViewerWidget(QWidget):
|
|||
self.reset_view()
|
||||
|
||||
def reset_view(self):
|
||||
if self.is_showing_timestamps:
|
||||
if self.view_mode == ViewMode.TIMESTAMPS:
|
||||
self.text_display_box.hide()
|
||||
self.table_widget.show()
|
||||
else:
|
||||
elif self.view_mode == ViewMode.TEXT:
|
||||
segments = self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
)
|
||||
|
|
@ -126,9 +184,19 @@ class TranscriptionViewerWidget(QWidget):
|
|||
)
|
||||
self.text_display_box.show()
|
||||
self.table_widget.hide()
|
||||
else: # ViewMode.TRANSLATION
|
||||
# TODO add check for if translation exists
|
||||
segments = self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
)
|
||||
self.text_display_box.setPlainText(
|
||||
" ".join(segment.translation.strip() for segment in segments)
|
||||
)
|
||||
self.text_display_box.show()
|
||||
self.table_widget.hide()
|
||||
|
||||
def on_view_mode_changed(self, is_timestamps: bool) -> None:
|
||||
self.is_showing_timestamps = is_timestamps
|
||||
def on_view_mode_changed(self, view_mode: ViewMode) -> None:
|
||||
self.view_mode = view_mode
|
||||
self.reset_view()
|
||||
|
||||
def on_segment_selected(self, segment: QSqlRecord):
|
||||
|
|
@ -154,3 +222,44 @@ class TranscriptionViewerWidget(QWidget):
|
|||
)
|
||||
if current_segment is not None:
|
||||
self.current_segment_label.setText(current_segment.value("text"))
|
||||
|
||||
def load_preferences(self):
|
||||
self.settings.settings.beginGroup("file_transcriber")
|
||||
preferences = FileTranscriptionPreferences.load(settings=self.settings.settings)
|
||||
self.settings.settings.endGroup()
|
||||
return preferences
|
||||
|
||||
def open_advanced_settings(self):
|
||||
self.transcription_options_dialog.show()
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
):
|
||||
self.transcription_options = transcription_options
|
||||
|
||||
def on_translate_button_clicked(self):
|
||||
if len(self.openai_access_token) == 0:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
_("API Key Required"),
|
||||
_("Please enter OpenAI API Key in preferences")
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if self.transcription_options.llm_model == "" or self.transcription_options.llm_prompt == "":
|
||||
self.transcription_options_dialog.show()
|
||||
return
|
||||
|
||||
segments = self.table_widget.segments()
|
||||
for segment in segments:
|
||||
self.translator.enqueue(segment.value("text"), segment.value("id"))
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.hide()
|
||||
|
||||
self.translator.stop()
|
||||
self.translation_thread.quit()
|
||||
self.translation_thread.wait()
|
||||
|
||||
super().closeEvent(event)
|
||||
|
|
|
|||
|
|
@ -111,7 +111,11 @@ class TestAdvancedSettingsDialog:
|
|||
def test_should_update_advanced_settings(self, qtbot: QtBot):
|
||||
dialog = AdvancedSettingsDialog(
|
||||
transcription_options=TranscriptionOptions(
|
||||
temperature=(0.0, 0.8), initial_prompt="prompt"
|
||||
temperature=(0.0, 0.8),
|
||||
initial_prompt="prompt",
|
||||
enable_llm_translation=False,
|
||||
llm_model="",
|
||||
llm_prompt=""
|
||||
)
|
||||
)
|
||||
qtbot.add_widget(dialog)
|
||||
|
|
@ -122,12 +126,21 @@ class TestAdvancedSettingsDialog:
|
|||
assert dialog.windowTitle() == _("Advanced Settings")
|
||||
assert dialog.temperature_line_edit.text() == "0.0, 0.8"
|
||||
assert dialog.initial_prompt_text_edit.toPlainText() == "prompt"
|
||||
assert dialog.enable_llm_translation_checkbox.isChecked() is False
|
||||
assert dialog.llm_model_line_edit.text() == ""
|
||||
assert dialog.llm_prompt_text_edit.toPlainText() == ""
|
||||
|
||||
dialog.temperature_line_edit.setText("0.0, 0.8, 1.0")
|
||||
dialog.initial_prompt_text_edit.setPlainText("new prompt")
|
||||
dialog.enable_llm_translation_checkbox.setChecked(True)
|
||||
dialog.llm_model_line_edit.setText("model")
|
||||
dialog.llm_prompt_text_edit.setPlainText("Please translate this text")
|
||||
|
||||
assert transcription_options_mock.call_args[0][0].temperature == (0.0, 0.8, 1.0)
|
||||
assert transcription_options_mock.call_args[0][0].initial_prompt == "new prompt"
|
||||
assert transcription_options_mock.call_args[0][0].enable_llm_translation is True
|
||||
assert transcription_options_mock.call_args[0][0].llm_model == "model"
|
||||
assert transcription_options_mock.call_args[0][0].llm_prompt == "Please translate this text"
|
||||
|
||||
|
||||
class TestTemperatureValidator:
|
||||
|
|
|
|||
116
tests/translator_test.py
Normal file
116
tests/translator_test.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import time
|
||||
import pytest
|
||||
from queue import Empty
|
||||
from unittest.mock import Mock, patch, create_autospec
|
||||
|
||||
from PyQt6.QtCore import QThread
|
||||
|
||||
from buzz.translator import Translator
|
||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
|
||||
|
||||
|
||||
class TestTranslator:
|
||||
@patch('buzz.translator.OpenAI', autospec=True)
|
||||
@patch('buzz.translator.queue.Queue', autospec=True)
|
||||
def test_start(self, mock_queue, mock_openai):
|
||||
def side_effect(*args, **kwargs):
|
||||
side_effect.call_count += 1
|
||||
|
||||
if side_effect.call_count >= 5:
|
||||
translator.is_running = False
|
||||
|
||||
if side_effect.call_count < 3:
|
||||
raise Empty
|
||||
return "Hello, how are you?", None
|
||||
|
||||
side_effect.call_count = 0
|
||||
|
||||
mock_queue.get.side_effect = side_effect
|
||||
mock_chat = Mock()
|
||||
mock_openai.return_value.chat = mock_chat
|
||||
mock_chat.completions.create.return_value = Mock(
|
||||
choices=[Mock(message=Mock(content="AI Translated: Hello, how are you?"))]
|
||||
)
|
||||
|
||||
transcription_options = TranscriptionOptions(
|
||||
enable_llm_translation=False,
|
||||
llm_model="llama3",
|
||||
llm_prompt="Please translate this text:",
|
||||
)
|
||||
translator = Translator(
|
||||
transcription_options,
|
||||
AdvancedSettingsDialog(
|
||||
transcription_options=transcription_options, parent=None
|
||||
)
|
||||
)
|
||||
translator.queue = mock_queue
|
||||
|
||||
translator.start()
|
||||
|
||||
mock_queue.get.assert_called()
|
||||
mock_chat.completions.create.assert_called()
|
||||
|
||||
@patch('buzz.translator.OpenAI', autospec=True)
|
||||
def test_translator(self, mock_openai, qtbot):
|
||||
|
||||
self.on_next_translation_called = False
|
||||
|
||||
def on_next_translation(text: str):
|
||||
self.on_next_translation_called = True
|
||||
assert text.startswith("AI Translated:")
|
||||
|
||||
mock_chat = Mock()
|
||||
mock_openai.return_value.chat = mock_chat
|
||||
mock_chat.completions.create.return_value = Mock(
|
||||
choices=[Mock(message=Mock(content="AI Translated: Hello, how are you?"))]
|
||||
)
|
||||
|
||||
self.translation_thread = QThread()
|
||||
self.transcription_options = TranscriptionOptions(
|
||||
enable_llm_translation=False,
|
||||
llm_model="llama3",
|
||||
llm_prompt="Please translate this text:",
|
||||
)
|
||||
|
||||
self.translator = Translator(
|
||||
self.transcription_options,
|
||||
AdvancedSettingsDialog(
|
||||
transcription_options=self.transcription_options, parent=None
|
||||
)
|
||||
)
|
||||
|
||||
self.translator.moveToThread(self.translation_thread)
|
||||
|
||||
self.translation_thread.started.connect(self.translator.start)
|
||||
self.translation_thread.finished.connect(
|
||||
self.translation_thread.deleteLater
|
||||
)
|
||||
|
||||
self.translator.finished.connect(self.translation_thread.quit)
|
||||
self.translator.finished.connect(self.translator.deleteLater)
|
||||
|
||||
self.translator.translation.connect(on_next_translation)
|
||||
|
||||
self.translation_thread.start()
|
||||
|
||||
time.sleep(3)
|
||||
assert self.translator.is_running
|
||||
|
||||
self.translator.enqueue("Hello, how are you?")
|
||||
|
||||
def translation_signal_received():
|
||||
assert self.on_next_translation_called
|
||||
|
||||
qtbot.wait_until(translation_signal_received, timeout=60 * 1000)
|
||||
|
||||
if self.translator is not None:
|
||||
self.translator.stop()
|
||||
self.translator.deleteLater()
|
||||
|
||||
if self.translation_thread is not None:
|
||||
self.translation_thread.quit()
|
||||
self.translation_thread.deleteLater()
|
||||
|
||||
# Wait to clean-up threads
|
||||
time.sleep(3)
|
||||
|
|
@ -30,9 +30,9 @@ class TestExportTranscriptionMenu:
|
|||
whisper_model_size=WhisperModelSize.SMALL.value,
|
||||
)
|
||||
)
|
||||
transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", str(id)))
|
||||
transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", "", str(id)))
|
||||
transcription_segment_dao.insert(
|
||||
TranscriptionSegment(299, 329, "venue dans", str(id))
|
||||
TranscriptionSegment(299, 329, "venue dans", "", str(id))
|
||||
)
|
||||
|
||||
return transcription_dao.find_by_id(str(id))
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ class TestFolderWatchPreferencesWidget:
|
|||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
enable_llm_translation=False,
|
||||
llm_model="",
|
||||
llm_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -101,10 +101,10 @@ class TestGeneralPreferencesWidget:
|
|||
assert openai_base_url == ""
|
||||
assert widget.custom_openai_base_url_line_edit.text() == ""
|
||||
|
||||
widget.custom_openai_base_url_line_edit.setText("https://localhost:8000/v1")
|
||||
widget.custom_openai_base_url_line_edit.setText("http://localhost:11434/v1")
|
||||
|
||||
updated_openai_base_url = settings.value(
|
||||
key=Settings.Key.CUSTOM_OPENAI_BASE_URL, default_value=""
|
||||
)
|
||||
|
||||
assert updated_openai_base_url == "https://localhost:8000/v1"
|
||||
assert updated_openai_base_url == "http://localhost:11434/v1"
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ class TestRecordingTranscriberWidget:
|
|||
widget.device_sample_rate = 16_000
|
||||
qtbot.add_widget(widget)
|
||||
|
||||
assert len(widget.text_box.toPlainText()) == 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) == 0
|
||||
|
||||
def assert_text_box_contains_text():
|
||||
assert len(widget.text_box.toPlainText()) > 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) > 0
|
||||
|
||||
widget.record_button.click()
|
||||
qtbot.wait_until(callback=assert_text_box_contains_text, timeout=60 * 1000)
|
||||
|
|
@ -56,7 +56,7 @@ class TestRecordingTranscriberWidget:
|
|||
with qtbot.wait_signal(widget.transcription_thread.finished, timeout=60 * 1000):
|
||||
widget.stop_recording()
|
||||
|
||||
assert len(widget.text_box.toPlainText()) > 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) > 0
|
||||
widget.close()
|
||||
|
||||
# on CI transcribed output is garbage, so we check if there is anything
|
||||
|
|
@ -90,10 +90,10 @@ class TestRecordingTranscriberWidget:
|
|||
widget.export_enabled = True
|
||||
qtbot.add_widget(widget)
|
||||
|
||||
assert len(widget.text_box.toPlainText()) == 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) == 0
|
||||
|
||||
def assert_text_box_contains_text():
|
||||
assert len(widget.text_box.toPlainText()) > 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) > 0
|
||||
|
||||
widget.record_button.click()
|
||||
qtbot.wait_until(callback=assert_text_box_contains_text, timeout=60 * 1000)
|
||||
|
|
@ -101,9 +101,9 @@ class TestRecordingTranscriberWidget:
|
|||
with qtbot.wait_signal(widget.transcription_thread.finished, timeout=60 * 1000):
|
||||
widget.stop_recording()
|
||||
|
||||
assert len(widget.text_box.toPlainText()) > 0
|
||||
assert len(widget.transcription_text_box.toPlainText()) > 0
|
||||
|
||||
with open(widget.export_file, 'r') as file:
|
||||
with open(widget.transcript_export_file, 'r') as file:
|
||||
contents = file.read()
|
||||
assert len(contents) > 0
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class TestShortcutsEditorWidget:
|
|||
(_("Import URL"), "Ctrl+U"),
|
||||
(_("Open Preferences Window"), "Ctrl+,"),
|
||||
(_("View Transcript Text"), "Ctrl+E"),
|
||||
(_("View Transcript Translation"), "Ctrl+L"),
|
||||
(_("View Transcript Timestamps"), "Ctrl+T"),
|
||||
(_("Clear History"), "Ctrl+S"),
|
||||
(_("Cancel Transcription"), "Ctrl+X"),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ class TestTranscriptionTaskFolderWatcher:
|
|||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
enable_llm_translation=False,
|
||||
llm_model="",
|
||||
llm_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
|
|
@ -86,6 +89,9 @@ class TestTranscriptionTaskFolderWatcher:
|
|||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
enable_llm_translation=False,
|
||||
llm_model="",
|
||||
llm_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import uuid
|
||||
|
||||
import time
|
||||
import pytest
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.db.entity.transcription import Transcription
|
||||
from buzz.db.entity.transcription_segment import TranscriptionSegment
|
||||
from buzz.model_loader import ModelType, WhisperModelSize
|
||||
from buzz.transcriber.transcriber import Task
|
||||
from buzz.widgets.transcription_viewer.transcription_view_mode_tool_button import (
|
||||
TranscriptionViewModeToolButton,
|
||||
ViewMode
|
||||
)
|
||||
from buzz.widgets.transcription_viewer.transcription_segments_editor_widget import (
|
||||
TranscriptionSegmentsEditorWidget,
|
||||
)
|
||||
|
|
@ -32,9 +38,9 @@ class TestTranscriptionViewerWidget:
|
|||
whisper_model_size=WhisperModelSize.SMALL.value,
|
||||
)
|
||||
)
|
||||
transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", str(id)))
|
||||
transcription_segment_dao.insert(TranscriptionSegment(40, 299, "Bien", "", str(id)))
|
||||
transcription_segment_dao.insert(
|
||||
TranscriptionSegment(299, 329, "venue dans", str(id))
|
||||
TranscriptionSegment(299, 329, "venue dans", "", str(id))
|
||||
)
|
||||
|
||||
return transcription_dao.find_by_id(str(id))
|
||||
|
|
@ -55,6 +61,7 @@ class TestTranscriptionViewerWidget:
|
|||
assert editor.model().index(0, 1).data() == 299
|
||||
assert editor.model().index(0, 2).data() == 40
|
||||
assert editor.model().index(0, 3).data() == "Bien"
|
||||
widget.close()
|
||||
|
||||
def test_should_update_segment_text(
|
||||
self, qtbot, transcription, transcription_service, shortcuts
|
||||
|
|
@ -68,3 +75,25 @@ class TestTranscriptionViewerWidget:
|
|||
assert isinstance(editor, TranscriptionSegmentsEditorWidget)
|
||||
|
||||
editor.model().setData(editor.model().index(0, 3), "Biens")
|
||||
widget.close()
|
||||
|
||||
def test_text_button_changes_view_mode(
|
||||
self, qtbot, transcription, transcription_service, shortcuts
|
||||
):
|
||||
widget = TranscriptionViewerWidget(
|
||||
transcription, transcription_service, shortcuts
|
||||
)
|
||||
qtbot.add_widget(widget)
|
||||
|
||||
view_mode_tool_button = widget.findChild(TranscriptionViewModeToolButton)
|
||||
menu = view_mode_tool_button.menu()
|
||||
|
||||
text_action = next(action for action in menu.actions() if action.text() == _("Text"))
|
||||
text_action.trigger()
|
||||
assert widget.view_mode == ViewMode.TEXT
|
||||
|
||||
text_action = next(action for action in menu.actions() if action.text() == _("Translation"))
|
||||
text_action.trigger()
|
||||
assert widget.view_mode == ViewMode.TRANSLATION
|
||||
|
||||
widget.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue