mirror of
https://github.com/chidiwilliams/buzz.git
synced 2024-06-27 04:00:37 +02:00
Compare commits
3 commits
44958c7018
...
91b1775088
Author | SHA1 | Date | |
---|---|---|---|
|
91b1775088 | ||
|
8939447d58 | ||
|
99e8dd20a7 |
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"
|
||||
|
||||
|
@ -140,11 +152,11 @@ msgstr "Gaida MI tulkojumu..."
|
|||
msgid "Microphone:"
|
||||
msgstr "Mikrofons:"
|
||||
|
||||
#: buzz/widgets/recording_transcriber_widget.py:382
|
||||
#: 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:386
|
||||
#: buzz/widgets/recording_transcriber_widget.py:395
|
||||
msgid ""
|
||||
"Please check your audio devices or check the application logs for more "
|
||||
"information."
|
||||
|
@ -161,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 ""
|
||||
|
@ -205,83 +217,111 @@ 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:32
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:33
|
||||
msgid "Advanced Settings"
|
||||
msgstr "Papildu iestatījumi"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:41
|
||||
#: 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:50
|
||||
#: 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\n"
|
||||
"vaicājums:"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:63
|
||||
#: 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:75
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:84
|
||||
msgid "AI model:"
|
||||
msgstr "AI modelis:"
|
||||
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:79
|
||||
#: 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:83
|
||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:92
|
||||
msgid "Instructions for AI:"
|
||||
msgstr "Norādes MI:"
|
||||
|
||||
|
@ -301,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:152
|
||||
#: buzz/transcriber/transcriber.py:153
|
||||
msgid "Detect Language"
|
||||
msgstr "Noteikt valodu"
|
||||
|
||||
|
@ -380,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
|
||||
);
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -125,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 = {
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -8,16 +8,18 @@ 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)
|
||||
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)
|
||||
|
@ -25,6 +27,11 @@ class Translator(QObject):
|
|||
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()
|
||||
|
@ -44,7 +51,7 @@ class Translator(QObject):
|
|||
|
||||
while self.is_running:
|
||||
try:
|
||||
transcript = self.queue.get(timeout=1)
|
||||
transcript, transcript_id = self.queue.get(timeout=1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
|
@ -64,13 +71,17 @@ class Translator(QObject):
|
|||
logging.error(f"Translation error! Server response: {completion}")
|
||||
next_translation = "Translation error, see logs!"
|
||||
|
||||
self.translation.emit(next_translation)
|
||||
self.translation.emit(next_translation, transcript_id)
|
||||
|
||||
self.finished.emit()
|
||||
|
||||
def enqueue(self, transcript: str):
|
||||
if self.is_running:
|
||||
self.queue.put(transcript)
|
||||
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):
|
||||
|
|
|
@ -127,12 +127,12 @@ class RecordingTranscriberWidget(QWidget):
|
|||
self.translation_text_box = TextDisplayBox(self)
|
||||
self.translation_text_box.setPlaceholderText(_("Waiting for AI translation..."))
|
||||
|
||||
transcription_options_group_box = TranscriptionOptionsGroupBox(
|
||||
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
|
||||
)
|
||||
|
||||
|
@ -145,7 +145,7 @@ 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.transcription_text_box)
|
||||
|
@ -289,7 +289,10 @@ class RecordingTranscriberWidget(QWidget):
|
|||
if self.transcription_options.enable_llm_translation:
|
||||
self.translation_thread = QThread()
|
||||
|
||||
self.translator = Translator(self.transcription_options)
|
||||
self.translator = Translator(
|
||||
self.transcription_options,
|
||||
self.transcription_options_group_box.advanced_settings_dialog,
|
||||
)
|
||||
|
||||
self.translator.moveToThread(self.translation_thread)
|
||||
|
||||
|
@ -354,7 +357,7 @@ class RecordingTranscriberWidget(QWidget):
|
|||
with open(self.transcript_export_file, "a") as f:
|
||||
f.write(text + "\n\n")
|
||||
|
||||
def on_next_translation(self, text: str):
|
||||
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:
|
||||
|
|
|
@ -6,6 +6,7 @@ from PyQt6.QtWidgets import (
|
|||
QCheckBox,
|
||||
QPlainTextEdit,
|
||||
QFormLayout,
|
||||
QLabel,
|
||||
)
|
||||
|
||||
from buzz.locale import _
|
||||
|
@ -33,6 +34,10 @@ class AdvancedSettingsDialog(QDialog):
|
|||
|
||||
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]
|
||||
)
|
||||
|
@ -60,6 +65,10 @@ class AdvancedSettingsDialog(QDialog):
|
|||
|
||||
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)
|
||||
|
|
|
@ -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,19 +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
|
||||
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
|
||||
|
||||
|
||||
|
@ -22,6 +25,7 @@ class Column(enum.Enum):
|
|||
END = enum.auto()
|
||||
START = enum.auto()
|
||||
TEXT = enum.auto()
|
||||
TRANSLATION = enum.auto()
|
||||
TRANSCRIPTION_ID = enum.auto()
|
||||
|
||||
|
||||
|
@ -38,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__()
|
||||
|
@ -53,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()):
|
||||
|
@ -91,17 +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)
|
||||
|
||||
font_metrics = QFontMetrics(self.font())
|
||||
max_row_height = font_metrics.height() * 3
|
||||
max_row_height = font_metrics.height() * 4
|
||||
for row in range(self.model().rowCount()):
|
||||
self.setRowHeight(row, max_row_height)
|
||||
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
self.setColumnWidth(Column.START.value, 95)
|
||||
self.setColumnWidth(Column.END.value, 95)
|
||||
|
||||
self.setWordWrap(True)
|
||||
self.resizeColumnsToContents()
|
||||
|
||||
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,51 @@ 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.finished.connect(
|
||||
self.translation_thread.deleteLater
|
||||
)
|
||||
|
||||
self.translator.finished.connect(self.translation_thread.quit)
|
||||
self.translator.finished.connect(self.translator.deleteLater)
|
||||
|
||||
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 +156,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 +178,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 +190,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 +228,46 @@ 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()
|
||||
|
||||
if self.translation_thread.isRunning():
|
||||
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:
|
||||
|
|
|
@ -7,6 +7,7 @@ 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:
|
||||
|
@ -21,7 +22,7 @@ class TestTranslator:
|
|||
|
||||
if side_effect.call_count < 3:
|
||||
raise Empty
|
||||
return 'Hello, how are you?'
|
||||
return "Hello, how are you?", None
|
||||
|
||||
side_effect.call_count = 0
|
||||
|
||||
|
@ -31,11 +32,18 @@ class TestTranslator:
|
|||
mock_chat.completions.create.return_value = Mock(
|
||||
choices=[Mock(message=Mock(content="AI Translated: Hello, how are you?"))]
|
||||
)
|
||||
translator = Translator(TranscriptionOptions(
|
||||
|
||||
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()
|
||||
|
@ -59,12 +67,18 @@ class TestTranslator:
|
|||
)
|
||||
|
||||
self.translation_thread = QThread()
|
||||
|
||||
self.translator = Translator(TranscriptionOptions(
|
||||
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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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,8 @@ 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()
|
||||
time.sleep(3)
|
||||
|
||||
def test_should_update_segment_text(
|
||||
self, qtbot, transcription, transcription_service, shortcuts
|
||||
|
@ -68,3 +76,27 @@ class TestTranscriptionViewerWidget:
|
|||
assert isinstance(editor, TranscriptionSegmentsEditorWidget)
|
||||
|
||||
editor.model().setData(editor.model().index(0, 3), "Biens")
|
||||
widget.close()
|
||||
time.sleep(3)
|
||||
|
||||
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()
|
||||
time.sleep(3)
|
||||
|
|
Loading…
Reference in a new issue