mirror of
https://github.com/chidiwilliams/buzz.git
synced 2024-06-29 13:10:26 +02:00
Compare commits
2 commits
42709420af
...
8939447d58
Author | SHA1 | Date | |
---|---|---|---|
|
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
|
import sys
|
||||||
from typing import TextIO
|
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
|
from buzz.assets import APP_BASE_DIR
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ def main():
|
||||||
logging.debug("app_dir: %s", APP_BASE_DIR)
|
logging.debug("app_dir: %s", APP_BASE_DIR)
|
||||||
logging.debug("log_dir: %s", log_dir)
|
logging.debug("log_dir: %s", log_dir)
|
||||||
logging.debug("cache_dir: %s", user_cache_dir("Buzz"))
|
logging.debug("cache_dir: %s", user_cache_dir("Buzz"))
|
||||||
|
logging.debug("data_dir: %s", user_data_dir("Buzz"))
|
||||||
|
|
||||||
app = Application(sys.argv)
|
app = Application(sys.argv)
|
||||||
parse_command_line(app)
|
parse_command_line(app)
|
||||||
|
|
|
@ -24,3 +24,18 @@ class TranscriptionSegmentDAO(DAO[TranscriptionSegment]):
|
||||||
)
|
)
|
||||||
query.bindValue(":transcription_id", str(transcription_id))
|
query.bindValue(":transcription_id", str(transcription_id))
|
||||||
return self._execute_all(query)
|
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
|
start_time: int
|
||||||
end_time: int
|
end_time: int
|
||||||
text: str
|
text: str
|
||||||
|
translation: str
|
||||||
transcription_id: str
|
transcription_id: str
|
||||||
id: int = -1
|
id: int = -1
|
||||||
|
|
|
@ -53,13 +53,14 @@ def copy_transcriptions_from_json_to_sqlite(conn: Connection):
|
||||||
for segment in task.segments:
|
for segment in task.segments:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO transcription_segment (end_time, start_time, text, transcription_id)
|
INSERT INTO transcription_segment (end_time, start_time, text, translation, transcription_id)
|
||||||
VALUES (?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?);
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
segment.end,
|
segment.end,
|
||||||
segment.start,
|
segment.start,
|
||||||
segment.text,
|
segment.text,
|
||||||
|
segment.translation,
|
||||||
transcription_id,
|
transcription_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,9 +39,13 @@ class TranscriptionService:
|
||||||
start_time=segment.start,
|
start_time=segment.start,
|
||||||
end_time=segment.end,
|
end_time=segment.end,
|
||||||
text=segment.text,
|
text=segment.text,
|
||||||
|
translation='',
|
||||||
transcription_id=str(id),
|
transcription_id=str(id),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_transcription_segments(self, transcription_id: UUID):
|
def get_transcription_segments(self, transcription_id: UUID):
|
||||||
return self.transcription_segment_dao.get_segments(transcription_id)
|
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 ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-06-10 21:58+0300\n"
|
"POT-Creation-Date: 2024-06-14 18:59+0300\n"
|
||||||
"PO-Revision-Date: 2024-06-10 21:59+0300\n"
|
"PO-Revision-Date: 2024-06-14 19:01+0300\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: lv_LV\n"
|
"Language: lv_LV\n"
|
||||||
|
@ -39,51 +39,63 @@ msgid "View Transcript Text"
|
||||||
msgstr "Aplūkot atpazīto tekstu"
|
msgstr "Aplūkot atpazīto tekstu"
|
||||||
|
|
||||||
#: buzz/settings/shortcut.py:23
|
#: buzz/settings/shortcut.py:23
|
||||||
|
msgid "View Transcript Translation"
|
||||||
|
msgstr "Aplūkot tulkojumu"
|
||||||
|
|
||||||
|
#: buzz/settings/shortcut.py:24
|
||||||
msgid "View Transcript Timestamps"
|
msgid "View Transcript Timestamps"
|
||||||
msgstr "Aplūkot atpazīšanas laikus"
|
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
|
#: buzz/widgets/main_window.py:222
|
||||||
msgid "Clear History"
|
msgid "Clear History"
|
||||||
msgstr "Notīrīt vēsturi"
|
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"
|
msgid "Cancel Transcription"
|
||||||
msgstr "Atcelt atpazīšanu"
|
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"
|
msgid "Completed"
|
||||||
msgstr "Pabeigts"
|
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"
|
msgid "Canceled"
|
||||||
msgstr "Atcelts"
|
msgstr "Atcelts"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:75
|
#: buzz/widgets/transcription_tasks_table_widget.py:77
|
||||||
msgid "Queued"
|
msgid "Queued"
|
||||||
msgstr "Ierindots"
|
msgstr "Ierindots"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:83
|
#: buzz/widgets/transcription_tasks_table_widget.py:85
|
||||||
msgid "File Name / URL"
|
msgid "File Name / URL"
|
||||||
msgstr "Fails / URL"
|
msgstr "Fails / URL"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:95
|
#: buzz/widgets/transcription_tasks_table_widget.py:97
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Modelis"
|
msgstr "Modelis"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:104
|
#: buzz/widgets/transcription_tasks_table_widget.py:106
|
||||||
msgid "Task"
|
msgid "Task"
|
||||||
msgstr "Uzdevums"
|
msgstr "Uzdevums"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:113
|
#: buzz/widgets/transcription_tasks_table_widget.py:115
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Statuss"
|
msgstr "Statuss"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:121
|
#: buzz/widgets/transcription_tasks_table_widget.py:123
|
||||||
msgid "Date Added"
|
msgid "Date Added"
|
||||||
msgstr "Pievienots"
|
msgstr "Pievienots"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_tasks_table_widget.py:132
|
#: buzz/widgets/transcription_tasks_table_widget.py:134
|
||||||
msgid "Date Completed"
|
msgid "Date Completed"
|
||||||
msgstr "Pabeigts"
|
msgstr "Pabeigts"
|
||||||
|
|
||||||
|
@ -140,11 +152,11 @@ msgstr "Gaida MI tulkojumu..."
|
||||||
msgid "Microphone:"
|
msgid "Microphone:"
|
||||||
msgstr "Mikrofons:"
|
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:"
|
msgid "An error occurred while starting a new recording:"
|
||||||
msgstr "Sākot jaunu ierakstu notikusi kļūda:"
|
msgstr "Sākot jaunu ierakstu notikusi kļūda:"
|
||||||
|
|
||||||
#: buzz/widgets/recording_transcriber_widget.py:386
|
#: buzz/widgets/recording_transcriber_widget.py:395
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please check your audio devices or check the application logs for more "
|
"Please check your audio devices or check the application logs for more "
|
||||||
"information."
|
"information."
|
||||||
|
@ -161,8 +173,8 @@ msgid ""
|
||||||
"Detected missing permissions, please check that snap permissions have been "
|
"Detected missing permissions, please check that snap permissions have been "
|
||||||
"granted"
|
"granted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas "
|
"Ne visi nepieciešamie moduļi darbojas korekti, iespējams nav piešķirtas snap "
|
||||||
"snap atļaujas"
|
"atļaujas"
|
||||||
|
|
||||||
#: buzz/widgets/snap_notice.py:16
|
#: buzz/widgets/snap_notice.py:16
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -205,83 +217,111 @@ msgstr ""
|
||||||
msgid "Select audio file"
|
msgid "Select audio file"
|
||||||
msgstr "Izvēlieties audio failu"
|
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
|
#: buzz/widgets/preferences_dialog/models_preferences_widget.py:191
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Kļūda"
|
msgstr "Kļūda"
|
||||||
|
|
||||||
#: buzz/widgets/main_window.py:278
|
#: buzz/widgets/main_window.py:280
|
||||||
msgid "Unable to save OpenAI API key to keyring"
|
msgid "Unable to save OpenAI API key to keyring"
|
||||||
msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"
|
msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:42
|
#: buzz/widgets/transcription_viewer/export_transcription_menu.py:42
|
||||||
msgid "Save File"
|
#: buzz/widgets/transcription_viewer/transcription_view_mode_tool_button.py:34
|
||||||
msgstr "Saglabāt failu"
|
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:95
|
||||||
|
|
||||||
#: 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
|
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Teksts"
|
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"
|
msgid "Timestamps"
|
||||||
msgstr "Laiks"
|
msgstr "Laiks"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:66
|
#: buzz/widgets/transcription_viewer/transcription_segments_editor_widget.py:93
|
||||||
msgid "Start"
|
msgid "Start"
|
||||||
msgstr "Sākums"
|
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"
|
msgid "End"
|
||||||
msgstr "Beigas"
|
msgstr "Beigas"
|
||||||
|
|
||||||
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:92
|
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:146
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Eksportēt"
|
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
|
#: buzz/widgets/record_button.py:21
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Apturēt"
|
msgstr "Apturēt"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:32
|
#: buzz/widgets/transcriber/advanced_settings_dialog.py:33
|
||||||
msgid "Advanced Settings"
|
msgid "Advanced Settings"
|
||||||
msgstr "Papildu iestatījumi"
|
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\""
|
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\""
|
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:"
|
msgid "Temperature:"
|
||||||
msgstr "Temperatūra:"
|
msgstr "Temperatūra:"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:61
|
#: buzz/widgets/transcriber/advanced_settings_dialog.py:66
|
||||||
msgid "Initial Prompt:"
|
msgid "Initial Prompt:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Sākotnējais\n"
|
"Sākotnējais\n"
|
||||||
"vaicājums:"
|
"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"
|
msgid "Enable AI translation"
|
||||||
msgstr "Tulkot ar MI"
|
msgstr "Tulkot ar MI"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/advanced_settings_dialog.py:75
|
#: buzz/widgets/transcriber/advanced_settings_dialog.py:84
|
||||||
msgid "AI model:"
|
msgid "AI model:"
|
||||||
msgstr "AI modelis:"
|
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..."
|
msgid "Enter instructions for AI on how to translate..."
|
||||||
msgstr "Ievadiet tulkošanas norādes mākslīgajam intelektam..."
|
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:"
|
msgid "Instructions for AI:"
|
||||||
msgstr "Norādes MI:"
|
msgstr "Norādes MI:"
|
||||||
|
|
||||||
|
@ -301,20 +341,20 @@ msgstr "Papildu iestatījumi..."
|
||||||
msgid "Enter prompt..."
|
msgid "Enter prompt..."
|
||||||
msgstr "Ievadiet vaicājumu..."
|
msgstr "Ievadiet vaicājumu..."
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:79
|
#: buzz/widgets/transcriber/transcription_options_group_box.py:86
|
||||||
msgid "Model:"
|
msgid "Model:"
|
||||||
msgstr "Modelis:"
|
msgstr "Modelis:"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:83
|
#: buzz/widgets/transcriber/transcription_options_group_box.py:90
|
||||||
msgid "Task:"
|
msgid "Task:"
|
||||||
msgstr "Uzdevums:"
|
msgstr "Uzdevums:"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/transcription_options_group_box.py:84
|
#: buzz/widgets/transcriber/transcription_options_group_box.py:91
|
||||||
msgid "Language:"
|
msgid "Language:"
|
||||||
msgstr "Valoda:"
|
msgstr "Valoda:"
|
||||||
|
|
||||||
#: buzz/widgets/transcriber/languages_combo_box.py:25
|
#: buzz/widgets/transcriber/languages_combo_box.py:25
|
||||||
#: buzz/transcriber/transcriber.py:152
|
#: buzz/transcriber/transcriber.py:153
|
||||||
msgid "Detect Language"
|
msgid "Detect Language"
|
||||||
msgstr "Noteikt valodu"
|
msgstr "Noteikt valodu"
|
||||||
|
|
||||||
|
@ -380,10 +420,10 @@ msgstr "OpenAI API atslēgas pārbaude"
|
||||||
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:129
|
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:129
|
||||||
msgid ""
|
msgid ""
|
||||||
"Your API key is valid. Buzz will use this key to perform Whisper API "
|
"Your API key is valid. Buzz will use this key to perform Whisper API "
|
||||||
"transcriptions."
|
"transcriptions and AI translations with ChatGPT."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Jūsu API atslēga ir derīga. Buzz izmantos to runas atpazīšanai ar Whisper "
|
"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
|
#: buzz/widgets/preferences_dialog/general_preferences_widget.py:156
|
||||||
msgid "Select Export Folder"
|
msgid "Select Export Folder"
|
||||||
|
|
|
@ -23,6 +23,7 @@ CREATE TABLE transcription_segment (
|
||||||
end_time INT DEFAULT 0,
|
end_time INT DEFAULT 0,
|
||||||
start_time INT DEFAULT 0,
|
start_time INT DEFAULT 0,
|
||||||
text TEXT NOT NULL,
|
text TEXT NOT NULL,
|
||||||
|
translation TEXT DEFAULT '',
|
||||||
transcription_id TEXT,
|
transcription_id TEXT,
|
||||||
FOREIGN KEY (transcription_id) REFERENCES transcription(id) ON DELETE CASCADE
|
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"))
|
OPEN_PREFERENCES_WINDOW = ("Ctrl+,", _("Open Preferences Window"))
|
||||||
|
|
||||||
VIEW_TRANSCRIPT_TEXT = ("Ctrl+E", _("View Transcript Text"))
|
VIEW_TRANSCRIPT_TEXT = ("Ctrl+E", _("View Transcript Text"))
|
||||||
|
VIEW_TRANSCRIPT_TRANSLATION = ("Ctrl+L", _("View Transcript Translation"))
|
||||||
VIEW_TRANSCRIPT_TIMESTAMPS = ("Ctrl+T", _("View Transcript Timestamps"))
|
VIEW_TRANSCRIPT_TIMESTAMPS = ("Ctrl+T", _("View Transcript Timestamps"))
|
||||||
|
|
||||||
CLEAR_HISTORY = ("Ctrl+S", _("Clear History"))
|
CLEAR_HISTORY = ("Ctrl+S", _("Clear History"))
|
||||||
|
|
|
@ -103,7 +103,12 @@ class FileTranscriber(QObject):
|
||||||
|
|
||||||
|
|
||||||
# TODO: Move to transcription service
|
# 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(
|
logging.debug(
|
||||||
"Writing transcription output, path = %s, output format = %s, number of segments = %s",
|
"Writing transcription output, path = %s, output format = %s, number of segments = %s",
|
||||||
path,
|
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:
|
with open(path, "w", encoding="utf-8") as file:
|
||||||
if output_format == OutputFormat.TXT:
|
if output_format == OutputFormat.TXT:
|
||||||
for i, segment in enumerate(segments):
|
for i, segment in enumerate(segments):
|
||||||
file.write(segment.text)
|
file.write(getattr(segment, segment_key))
|
||||||
file.write("\n")
|
file.write("\n")
|
||||||
|
|
||||||
elif output_format == OutputFormat.VTT:
|
elif output_format == OutputFormat.VTT:
|
||||||
|
@ -123,7 +128,7 @@ def write_output(path: str, segments: List[Segment], output_format: OutputFormat
|
||||||
file.write(
|
file.write(
|
||||||
f"{to_timestamp(segment.start)} --> {to_timestamp(segment.end)}\n"
|
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:
|
elif output_format == OutputFormat.SRT:
|
||||||
for i, segment in enumerate(segments):
|
for i, segment in enumerate(segments):
|
||||||
|
@ -131,7 +136,7 @@ def write_output(path: str, segments: List[Segment], output_format: OutputFormat
|
||||||
file.write(
|
file.write(
|
||||||
f'{to_timestamp(segment.start, ms_separator=",")} --> {to_timestamp(segment.end, ms_separator=",")}\n'
|
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")
|
logging.debug("Written transcription output")
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ class RecordingTranscriber(QObject):
|
||||||
whisper_segments, info = model.transcribe(
|
whisper_segments, info = model.transcribe(
|
||||||
audio=samples,
|
audio=samples,
|
||||||
language=self.transcription_options.language
|
language=self.transcription_options.language
|
||||||
if self.transcription_options.language is not ""
|
if self.transcription_options.language != ""
|
||||||
else None,
|
else None,
|
||||||
task=self.transcription_options.task.value,
|
task=self.transcription_options.task.value,
|
||||||
temperature=self.transcription_options.temperature,
|
temperature=self.transcription_options.temperature,
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Segment:
|
||||||
start: int # start time in ms
|
start: int # start time in ms
|
||||||
end: int # end time in ms
|
end: int # end time in ms
|
||||||
text: str
|
text: str
|
||||||
|
translation: str = ""
|
||||||
|
|
||||||
|
|
||||||
LANGUAGES = {
|
LANGUAGES = {
|
||||||
|
|
|
@ -121,6 +121,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=int(segment.get("start") * 1000),
|
start=int(segment.get("start") * 1000),
|
||||||
end=int(segment.get("end") * 1000),
|
end=int(segment.get("end") * 1000),
|
||||||
text=segment.get("text"),
|
text=segment.get("text"),
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
for segment in result.get("segments")
|
for segment in result.get("segments")
|
||||||
]
|
]
|
||||||
|
@ -149,6 +150,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=int(word.start * 1000),
|
start=int(word.start * 1000),
|
||||||
end=int(word.end * 1000),
|
end=int(word.end * 1000),
|
||||||
text=word.word,
|
text=word.word,
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -157,6 +159,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=int(segment.start * 1000),
|
start=int(segment.start * 1000),
|
||||||
end=int(segment.end * 1000),
|
end=int(segment.end * 1000),
|
||||||
text=segment.text,
|
text=segment.text,
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -181,6 +184,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=int(word.start * 1000),
|
start=int(word.start * 1000),
|
||||||
end=int(word.end * 1000),
|
end=int(word.end * 1000),
|
||||||
text=word.word.strip(),
|
text=word.word.strip(),
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
for segment in result.segments
|
for segment in result.segments
|
||||||
for word in segment.words
|
for word in segment.words
|
||||||
|
@ -200,6 +204,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=int(segment.get("start") * 1000),
|
start=int(segment.get("start") * 1000),
|
||||||
end=int(segment.get("end") * 1000),
|
end=int(segment.get("end") * 1000),
|
||||||
text=segment.get("text"),
|
text=segment.get("text"),
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
for segment in segments
|
for segment in segments
|
||||||
]
|
]
|
||||||
|
@ -226,6 +231,7 @@ class WhisperFileTranscriber(FileTranscriber):
|
||||||
start=segment.get("start"),
|
start=segment.get("start"),
|
||||||
end=segment.get("end"),
|
end=segment.get("end"),
|
||||||
text=segment.get("text"),
|
text=segment.get("text"),
|
||||||
|
translation=""
|
||||||
)
|
)
|
||||||
for segment in segments_dict
|
for segment in segments_dict
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,16 +8,18 @@ from PyQt6.QtCore import QObject, pyqtSignal
|
||||||
from buzz.settings.settings import Settings
|
from buzz.settings.settings import Settings
|
||||||
from buzz.store.keyring_store import get_password, Key
|
from buzz.store.keyring_store import get_password, Key
|
||||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||||
|
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
|
||||||
|
|
||||||
|
|
||||||
class Translator(QObject):
|
class Translator(QObject):
|
||||||
translation = pyqtSignal(str)
|
translation = pyqtSignal(str, int)
|
||||||
finished = pyqtSignal()
|
finished = pyqtSignal()
|
||||||
is_running = False
|
is_running = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
transcription_options: TranscriptionOptions,
|
transcription_options: TranscriptionOptions,
|
||||||
|
advanced_settings_dialog: AdvancedSettingsDialog,
|
||||||
parent: Optional[QObject] = None,
|
parent: Optional[QObject] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -25,6 +27,11 @@ class Translator(QObject):
|
||||||
logging.debug(f"Translator init: {transcription_options}")
|
logging.debug(f"Translator init: {transcription_options}")
|
||||||
|
|
||||||
self.transcription_options = 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()
|
self.queue = queue.Queue()
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
@ -44,7 +51,7 @@ class Translator(QObject):
|
||||||
|
|
||||||
while self.is_running:
|
while self.is_running:
|
||||||
try:
|
try:
|
||||||
transcript = self.queue.get(timeout=1)
|
transcript, transcript_id = self.queue.get(timeout=1)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -64,13 +71,17 @@ class Translator(QObject):
|
||||||
logging.error(f"Translation error! Server response: {completion}")
|
logging.error(f"Translation error! Server response: {completion}")
|
||||||
next_translation = "Translation error, see logs!"
|
next_translation = "Translation error, see logs!"
|
||||||
|
|
||||||
self.translation.emit(next_translation)
|
self.translation.emit(next_translation, transcript_id)
|
||||||
|
|
||||||
self.finished.emit()
|
self.finished.emit()
|
||||||
|
|
||||||
def enqueue(self, transcript: str):
|
def on_transcription_options_changed(
|
||||||
if self.is_running:
|
self, transcription_options: TranscriptionOptions
|
||||||
self.queue.put(transcript)
|
):
|
||||||
|
self.transcription_options = transcription_options
|
||||||
|
|
||||||
|
def enqueue(self, transcript: str, transcript_id: Optional[int] = None):
|
||||||
|
self.queue.put((transcript, transcript_id))
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
|
@ -74,6 +74,11 @@ class FileDownloadIcon(Icon):
|
||||||
super().__init__(get_path("assets/file_download_black_24dp.svg"), parent)
|
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):
|
class VisibilityIcon(Icon):
|
||||||
def __init__(self, parent: QWidget):
|
def __init__(self, parent: QWidget):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
|
@ -141,6 +141,8 @@ class MainWindow(QMainWindow):
|
||||||
self.folder_watcher.task_found.connect(self.add_task)
|
self.folder_watcher.task_found.connect(self.add_task)
|
||||||
self.folder_watcher.find_tasks()
|
self.folder_watcher.find_tasks()
|
||||||
|
|
||||||
|
self.transcription_viewer_widget = None
|
||||||
|
|
||||||
if os.environ.get('SNAP_NAME', '') == 'buzz':
|
if os.environ.get('SNAP_NAME', '') == 'buzz':
|
||||||
logging.debug("Running in a snap environment")
|
logging.debug("Running in a snap environment")
|
||||||
self.check_linux_permissions()
|
self.check_linux_permissions()
|
||||||
|
@ -267,6 +269,8 @@ class MainWindow(QMainWindow):
|
||||||
self.on_openai_access_token_changed
|
self.on_openai_access_token_changed
|
||||||
)
|
)
|
||||||
file_transcriber_window.show()
|
file_transcriber_window.show()
|
||||||
|
file_transcriber_window.raise_()
|
||||||
|
file_transcriber_window.activateWindow()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def on_openai_access_token_changed(access_token: str):
|
def on_openai_access_token_changed(access_token: str):
|
||||||
|
@ -347,14 +351,14 @@ class MainWindow(QMainWindow):
|
||||||
self.open_transcription_viewer(transcription)
|
self.open_transcription_viewer(transcription)
|
||||||
|
|
||||||
def open_transcription_viewer(self, transcription: Transcription):
|
def open_transcription_viewer(self, transcription: Transcription):
|
||||||
transcription_viewer_widget = TranscriptionViewerWidget(
|
self.transcription_viewer_widget = TranscriptionViewerWidget(
|
||||||
transcription=transcription,
|
transcription=transcription,
|
||||||
transcription_service=self.transcription_service,
|
transcription_service=self.transcription_service,
|
||||||
shortcuts=self.shortcuts,
|
shortcuts=self.shortcuts,
|
||||||
parent=self,
|
parent=self,
|
||||||
flags=Qt.WindowType.Window,
|
flags=Qt.WindowType.Window,
|
||||||
)
|
)
|
||||||
transcription_viewer_widget.show()
|
self.transcription_viewer_widget.show()
|
||||||
|
|
||||||
def add_task(self, task: FileTranscriptionTask):
|
def add_task(self, task: FileTranscriptionTask):
|
||||||
self.transcription_service.create_transcription(task)
|
self.transcription_service.create_transcription(task)
|
||||||
|
@ -396,6 +400,10 @@ class MainWindow(QMainWindow):
|
||||||
self.transcriber_worker.stop()
|
self.transcriber_worker.stop()
|
||||||
self.transcriber_thread.quit()
|
self.transcriber_thread.quit()
|
||||||
self.transcriber_thread.wait()
|
self.transcriber_thread.wait()
|
||||||
|
|
||||||
|
if self.transcription_viewer_widget is not None:
|
||||||
|
self.transcription_viewer_widget.close()
|
||||||
|
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
def save_geometry(self):
|
def save_geometry(self):
|
||||||
|
|
|
@ -126,7 +126,7 @@ class GeneralPreferencesWidget(QWidget):
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
_("OpenAI API Key Test"),
|
_("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):
|
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 = TextDisplayBox(self)
|
||||||
self.translation_text_box.setPlaceholderText(_("Waiting for AI translation..."))
|
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,
|
default_transcription_options=self.transcription_options,
|
||||||
model_types=model_types,
|
model_types=model_types,
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
transcription_options_group_box.transcription_options_changed.connect(
|
self.transcription_options_group_box.transcription_options_changed.connect(
|
||||||
self.on_transcription_options_changed
|
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.audio_meter_widget)
|
||||||
record_button_layout.addWidget(self.record_button)
|
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(recording_options_layout)
|
||||||
layout.addLayout(record_button_layout)
|
layout.addLayout(record_button_layout)
|
||||||
layout.addWidget(self.transcription_text_box)
|
layout.addWidget(self.transcription_text_box)
|
||||||
|
@ -289,7 +289,10 @@ class RecordingTranscriberWidget(QWidget):
|
||||||
if self.transcription_options.enable_llm_translation:
|
if self.transcription_options.enable_llm_translation:
|
||||||
self.translation_thread = QThread()
|
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)
|
self.translator.moveToThread(self.translation_thread)
|
||||||
|
|
||||||
|
@ -354,7 +357,7 @@ class RecordingTranscriberWidget(QWidget):
|
||||||
with open(self.transcript_export_file, "a") as f:
|
with open(self.transcript_export_file, "a") as f:
|
||||||
f.write(text + "\n\n")
|
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:
|
if len(text) > 0:
|
||||||
self.translation_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
self.translation_text_box.moveCursor(QTextCursor.MoveOperation.End)
|
||||||
if len(self.translation_text_box.toPlainText()) > 0:
|
if len(self.translation_text_box.toPlainText()) > 0:
|
||||||
|
|
|
@ -6,6 +6,7 @@ from PyQt6.QtWidgets import (
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QPlainTextEdit,
|
QPlainTextEdit,
|
||||||
QFormLayout,
|
QFormLayout,
|
||||||
|
QLabel,
|
||||||
)
|
)
|
||||||
|
|
||||||
from buzz.locale import _
|
from buzz.locale import _
|
||||||
|
@ -33,6 +34,10 @@ class AdvancedSettingsDialog(QDialog):
|
||||||
|
|
||||||
layout = QFormLayout(self)
|
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(
|
default_temperature_text = ", ".join(
|
||||||
[str(temp) for temp in transcription_options.temperature]
|
[str(temp) for temp in transcription_options.temperature]
|
||||||
)
|
)
|
||||||
|
@ -60,6 +65,10 @@ class AdvancedSettingsDialog(QDialog):
|
||||||
|
|
||||||
layout.addRow(_("Initial Prompt:"), self.initial_prompt_text_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 = QCheckBox(_("Enable AI translation"))
|
||||||
self.enable_llm_translation_checkbox.setChecked(self.transcription_options.enable_llm_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)
|
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.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 = QComboBox(self)
|
||||||
self.whisper_model_size_combo_box.addItems(
|
self.whisper_model_size_combo_box.addItems(
|
||||||
[size.value.title() for size in WhisperModelSize]
|
[size.value.title() for size in WhisperModelSize]
|
||||||
|
@ -102,13 +109,7 @@ class TranscriptionOptionsGroupBox(QGroupBox):
|
||||||
self.transcription_options_changed.emit(self.transcription_options)
|
self.transcription_options_changed.emit(self.transcription_options)
|
||||||
|
|
||||||
def open_advanced_settings(self):
|
def open_advanced_settings(self):
|
||||||
dialog = AdvancedSettingsDialog(
|
self.advanced_settings_dialog.exec()
|
||||||
transcription_options=self.transcription_options, parent=self
|
|
||||||
)
|
|
||||||
dialog.transcription_options_changed.connect(
|
|
||||||
self.on_transcription_options_changed
|
|
||||||
)
|
|
||||||
dialog.exec()
|
|
||||||
|
|
||||||
def on_transcription_options_changed(
|
def on_transcription_options_changed(
|
||||||
self, transcription_options: TranscriptionOptions
|
self, transcription_options: TranscriptionOptions
|
||||||
|
|
|
@ -59,7 +59,8 @@ def format_record_status_text(record: QSqlRecord) -> str:
|
||||||
status = FileTranscriptionTask.Status(record.value("status"))
|
status = FileTranscriptionTask.Status(record.value("status"))
|
||||||
match status:
|
match status:
|
||||||
case FileTranscriptionTask.Status.IN_PROGRESS:
|
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:
|
case FileTranscriptionTask.Status.COMPLETED:
|
||||||
status = _("Completed")
|
status = _("Completed")
|
||||||
started_at = record.value("time_started")
|
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))})"
|
status += f" ({TranscriptionTasksTableWidget.format_timedelta(datetime.fromisoformat(completed_at) - datetime.fromisoformat(started_at))})"
|
||||||
return status
|
return status
|
||||||
case FileTranscriptionTask.Status.FAILED:
|
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:
|
case FileTranscriptionTask.Status.CANCELED:
|
||||||
return _("Canceled")
|
return _("Canceled")
|
||||||
case FileTranscriptionTask.Status.QUEUED:
|
case FileTranscriptionTask.Status.QUEUED:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from PyQt6.QtGui import QAction
|
from PyQt6.QtGui import QAction
|
||||||
from PyQt6.QtWidgets import QWidget, QMenu, QFileDialog
|
from PyQt6.QtWidgets import QWidget, QMenu, QFileDialog
|
||||||
|
|
||||||
|
@ -23,15 +24,48 @@ class ExportTranscriptionMenu(QMenu):
|
||||||
self.transcription = transcription
|
self.transcription = transcription
|
||||||
self.transcription_service = transcription_service
|
self.transcription_service = transcription_service
|
||||||
|
|
||||||
actions = [
|
self.segments = [
|
||||||
QAction(text=output_format.value.upper(), parent=self)
|
Segment(
|
||||||
for output_format in OutputFormat
|
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.addActions(actions)
|
||||||
self.triggered.connect(self.on_menu_triggered)
|
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):
|
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(
|
default_path = self.transcription.get_output_file_path(
|
||||||
output_format=output_format
|
output_format=output_format
|
||||||
|
@ -47,15 +81,9 @@ class ExportTranscriptionMenu(QMenu):
|
||||||
if output_file_path == "":
|
if output_file_path == "":
|
||||||
return
|
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(
|
write_output(
|
||||||
path=output_file_path,
|
path=output_file_path,
|
||||||
segments=segments,
|
segments=self.segments,
|
||||||
output_format=output_format,
|
output_format=output_format,
|
||||||
|
segment_key=segment_key
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import enum
|
import enum
|
||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from PyQt6.QtCore import pyqtSignal, Qt, QModelIndex, QItemSelection
|
from PyQt6.QtCore import pyqtSignal, Qt, QModelIndex, QItemSelection
|
||||||
from PyQt6.QtSql import QSqlTableModel, QSqlRecord
|
from PyQt6.QtSql import QSqlTableModel, QSqlRecord
|
||||||
from PyQt6.QtGui import QFontMetrics
|
from PyQt6.QtGui import QFontMetrics, QTextOption
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget,
|
QWidget,
|
||||||
QTableView,
|
QTableView,
|
||||||
QStyledItemDelegate,
|
QStyledItemDelegate,
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
|
QTextEdit,
|
||||||
)
|
)
|
||||||
|
|
||||||
from buzz.locale import _
|
from buzz.locale import _
|
||||||
|
from buzz.translator import Translator
|
||||||
from buzz.transcriber.file_transcriber import to_timestamp
|
from buzz.transcriber.file_transcriber import to_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +25,7 @@ class Column(enum.Enum):
|
||||||
END = enum.auto()
|
END = enum.auto()
|
||||||
START = enum.auto()
|
START = enum.auto()
|
||||||
TEXT = enum.auto()
|
TEXT = enum.auto()
|
||||||
|
TRANSLATION = enum.auto()
|
||||||
TRANSCRIPTION_ID = enum.auto()
|
TRANSCRIPTION_ID = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,6 +42,18 @@ class TimeStampDelegate(QStyledItemDelegate):
|
||||||
return to_timestamp(value)
|
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):
|
class TranscriptionSegmentModel(QSqlTableModel):
|
||||||
def __init__(self, transcription_id: UUID):
|
def __init__(self, transcription_id: UUID):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -53,20 +69,31 @@ class TranscriptionSegmentModel(QSqlTableModel):
|
||||||
|
|
||||||
|
|
||||||
class TranscriptionSegmentsEditorWidget(QTableView):
|
class TranscriptionSegmentsEditorWidget(QTableView):
|
||||||
|
PARENT_PADDINGS = 40
|
||||||
segment_selected = pyqtSignal(QSqlRecord)
|
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)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.translator = translator
|
||||||
|
self.translator.translation.connect(self.update_translation)
|
||||||
|
|
||||||
model = TranscriptionSegmentModel(transcription_id=transcription_id)
|
model = TranscriptionSegmentModel(transcription_id=transcription_id)
|
||||||
self.setModel(model)
|
self.setModel(model)
|
||||||
|
|
||||||
timestamp_delegate = TimeStampDelegate()
|
timestamp_delegate = TimeStampDelegate()
|
||||||
|
word_wrap_delegate = WordWrapDelegate()
|
||||||
|
|
||||||
self.column_definitions: list[ColDef] = [
|
self.column_definitions: list[ColDef] = [
|
||||||
ColDef("start", _("Start"), Column.START, delegate=timestamp_delegate),
|
ColDef("start", _("Start"), Column.START, delegate=timestamp_delegate),
|
||||||
ColDef("end", _("End"), Column.END, 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()):
|
for i in range(model.columnCount()):
|
||||||
|
@ -91,17 +118,52 @@ class TranscriptionSegmentsEditorWidget(QTableView):
|
||||||
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
|
self.selectionModel().selectionChanged.connect(self.on_selection_changed)
|
||||||
model.select()
|
model.select()
|
||||||
|
|
||||||
|
self.has_translations = self.has_non_empty_translation()
|
||||||
|
|
||||||
# Show start before end
|
# Show start before end
|
||||||
self.horizontalHeader().swapSections(1, 2)
|
self.horizontalHeader().swapSections(1, 2)
|
||||||
|
|
||||||
font_metrics = QFontMetrics(self.font())
|
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()):
|
for row in range(self.model().rowCount()):
|
||||||
self.setRowHeight(row, max_row_height)
|
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.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(
|
def on_selection_changed(
|
||||||
self, selected: QItemSelection, _deselected: QItemSelection
|
self, selected: QItemSelection, _deselected: QItemSelection
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt6.QtCore import pyqtSignal, Qt
|
from PyQt6.QtCore import pyqtSignal, Qt
|
||||||
|
@ -10,8 +11,14 @@ from buzz.settings.shortcuts import Shortcuts
|
||||||
from buzz.widgets.icon import VisibilityIcon
|
from buzz.widgets.icon import VisibilityIcon
|
||||||
|
|
||||||
|
|
||||||
|
class ViewMode(Enum):
|
||||||
|
TEXT = "Text"
|
||||||
|
TRANSLATION = "Translation"
|
||||||
|
TIMESTAMPS = "Timestamps"
|
||||||
|
|
||||||
|
|
||||||
class TranscriptionViewModeToolButton(QToolButton):
|
class TranscriptionViewModeToolButton(QToolButton):
|
||||||
view_mode_changed = pyqtSignal(bool) # is_timestamps?
|
view_mode_changed = pyqtSignal(ViewMode)
|
||||||
|
|
||||||
def __init__(self, shortcuts: Shortcuts, parent: Optional[QWidget] = None):
|
def __init__(self, shortcuts: Shortcuts, parent: Optional[QWidget] = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -26,12 +33,19 @@ class TranscriptionViewModeToolButton(QToolButton):
|
||||||
menu.addAction(
|
menu.addAction(
|
||||||
_("Text"),
|
_("Text"),
|
||||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_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(
|
menu.addAction(
|
||||||
_("Timestamps"),
|
_("Timestamps"),
|
||||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_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)
|
self.setMenu(menu)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import logging
|
||||||
import platform
|
import platform
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt, QThread
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtMultimedia import QMediaPlayer
|
from PyQt6.QtMultimedia import QMediaPlayer
|
||||||
from PyQt6.QtSql import QSqlRecord
|
from PyQt6.QtSql import QSqlRecord
|
||||||
|
@ -11,6 +12,7 @@ from PyQt6.QtWidgets import (
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QToolButton,
|
QToolButton,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QMessageBox,
|
||||||
)
|
)
|
||||||
|
|
||||||
from buzz.locale import _
|
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.db.service.transcription_service import TranscriptionService
|
||||||
from buzz.paths import file_path_as_title
|
from buzz.paths import file_path_as_title
|
||||||
from buzz.settings.shortcuts import Shortcuts
|
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.audio_player import AudioPlayer
|
||||||
from buzz.widgets.icon import (
|
from buzz.widgets.icon import (
|
||||||
FileDownloadIcon,
|
FileDownloadIcon,
|
||||||
|
TranslateIcon
|
||||||
)
|
)
|
||||||
|
from buzz.translator import Translator
|
||||||
from buzz.widgets.text_display_box import TextDisplayBox
|
from buzz.widgets.text_display_box import TextDisplayBox
|
||||||
from buzz.widgets.toolbar import ToolBar
|
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 (
|
from buzz.widgets.transcription_viewer.export_transcription_menu import (
|
||||||
ExportTranscriptionMenu,
|
ExportTranscriptionMenu,
|
||||||
)
|
)
|
||||||
|
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||||
|
FileTranscriptionPreferences,
|
||||||
|
)
|
||||||
from buzz.widgets.transcription_viewer.transcription_segments_editor_widget import (
|
from buzz.widgets.transcription_viewer.transcription_segments_editor_widget import (
|
||||||
TranscriptionSegmentsEditorWidget,
|
TranscriptionSegmentsEditorWidget,
|
||||||
)
|
)
|
||||||
from buzz.widgets.transcription_viewer.transcription_view_mode_tool_button import (
|
from buzz.widgets.transcription_viewer.transcription_view_mode_tool_button import (
|
||||||
TranscriptionViewModeToolButton,
|
TranscriptionViewModeToolButton,
|
||||||
|
ViewMode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TranscriptionViewerWidget(QWidget):
|
class TranscriptionViewerWidget(QWidget):
|
||||||
transcription: Transcription
|
transcription: Transcription
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -55,10 +68,51 @@ class TranscriptionViewerWidget(QWidget):
|
||||||
|
|
||||||
self.setWindowTitle(file_path_as_title(transcription.file))
|
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(
|
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)
|
self.table_widget.segment_selected.connect(self.on_segment_selected)
|
||||||
|
|
||||||
|
@ -102,6 +156,16 @@ class TranscriptionViewerWidget(QWidget):
|
||||||
export_tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
export_tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||||
toolbar.addWidget(export_tool_button)
|
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.setMenuBar(toolbar)
|
||||||
|
|
||||||
layout.addWidget(self.table_widget)
|
layout.addWidget(self.table_widget)
|
||||||
|
@ -114,10 +178,10 @@ class TranscriptionViewerWidget(QWidget):
|
||||||
self.reset_view()
|
self.reset_view()
|
||||||
|
|
||||||
def reset_view(self):
|
def reset_view(self):
|
||||||
if self.is_showing_timestamps:
|
if self.view_mode == ViewMode.TIMESTAMPS:
|
||||||
self.text_display_box.hide()
|
self.text_display_box.hide()
|
||||||
self.table_widget.show()
|
self.table_widget.show()
|
||||||
else:
|
elif self.view_mode == ViewMode.TEXT:
|
||||||
segments = self.transcription_service.get_transcription_segments(
|
segments = self.transcription_service.get_transcription_segments(
|
||||||
transcription_id=self.transcription.id_as_uuid
|
transcription_id=self.transcription.id_as_uuid
|
||||||
)
|
)
|
||||||
|
@ -126,9 +190,19 @@ class TranscriptionViewerWidget(QWidget):
|
||||||
)
|
)
|
||||||
self.text_display_box.show()
|
self.text_display_box.show()
|
||||||
self.table_widget.hide()
|
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:
|
def on_view_mode_changed(self, view_mode: ViewMode) -> None:
|
||||||
self.is_showing_timestamps = is_timestamps
|
self.view_mode = view_mode
|
||||||
self.reset_view()
|
self.reset_view()
|
||||||
|
|
||||||
def on_segment_selected(self, segment: QSqlRecord):
|
def on_segment_selected(self, segment: QSqlRecord):
|
||||||
|
@ -154,3 +228,46 @@ class TranscriptionViewerWidget(QWidget):
|
||||||
)
|
)
|
||||||
if current_segment is not None:
|
if current_segment is not None:
|
||||||
self.current_segment_label.setText(current_segment.value("text"))
|
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):
|
def test_should_update_advanced_settings(self, qtbot: QtBot):
|
||||||
dialog = AdvancedSettingsDialog(
|
dialog = AdvancedSettingsDialog(
|
||||||
transcription_options=TranscriptionOptions(
|
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)
|
qtbot.add_widget(dialog)
|
||||||
|
@ -122,12 +126,21 @@ class TestAdvancedSettingsDialog:
|
||||||
assert dialog.windowTitle() == _("Advanced Settings")
|
assert dialog.windowTitle() == _("Advanced Settings")
|
||||||
assert dialog.temperature_line_edit.text() == "0.0, 0.8"
|
assert dialog.temperature_line_edit.text() == "0.0, 0.8"
|
||||||
assert dialog.initial_prompt_text_edit.toPlainText() == "prompt"
|
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.temperature_line_edit.setText("0.0, 0.8, 1.0")
|
||||||
dialog.initial_prompt_text_edit.setPlainText("new prompt")
|
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].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].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:
|
class TestTemperatureValidator:
|
||||||
|
|
|
@ -7,6 +7,7 @@ from PyQt6.QtCore import QThread
|
||||||
|
|
||||||
from buzz.translator import Translator
|
from buzz.translator import Translator
|
||||||
from buzz.transcriber.transcriber import TranscriptionOptions
|
from buzz.transcriber.transcriber import TranscriptionOptions
|
||||||
|
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
|
||||||
|
|
||||||
|
|
||||||
class TestTranslator:
|
class TestTranslator:
|
||||||
|
@ -21,7 +22,7 @@ class TestTranslator:
|
||||||
|
|
||||||
if side_effect.call_count < 3:
|
if side_effect.call_count < 3:
|
||||||
raise Empty
|
raise Empty
|
||||||
return 'Hello, how are you?'
|
return "Hello, how are you?", None
|
||||||
|
|
||||||
side_effect.call_count = 0
|
side_effect.call_count = 0
|
||||||
|
|
||||||
|
@ -31,11 +32,18 @@ class TestTranslator:
|
||||||
mock_chat.completions.create.return_value = Mock(
|
mock_chat.completions.create.return_value = Mock(
|
||||||
choices=[Mock(message=Mock(content="AI Translated: Hello, how are you?"))]
|
choices=[Mock(message=Mock(content="AI Translated: Hello, how are you?"))]
|
||||||
)
|
)
|
||||||
translator = Translator(TranscriptionOptions(
|
|
||||||
|
transcription_options = TranscriptionOptions(
|
||||||
enable_llm_translation=False,
|
enable_llm_translation=False,
|
||||||
llm_model="llama3",
|
llm_model="llama3",
|
||||||
llm_prompt="Please translate this text:",
|
llm_prompt="Please translate this text:",
|
||||||
))
|
)
|
||||||
|
translator = Translator(
|
||||||
|
transcription_options,
|
||||||
|
AdvancedSettingsDialog(
|
||||||
|
transcription_options=transcription_options, parent=None
|
||||||
|
)
|
||||||
|
)
|
||||||
translator.queue = mock_queue
|
translator.queue = mock_queue
|
||||||
|
|
||||||
translator.start()
|
translator.start()
|
||||||
|
@ -59,12 +67,18 @@ class TestTranslator:
|
||||||
)
|
)
|
||||||
|
|
||||||
self.translation_thread = QThread()
|
self.translation_thread = QThread()
|
||||||
|
self.transcription_options = TranscriptionOptions(
|
||||||
self.translator = Translator(TranscriptionOptions(
|
|
||||||
enable_llm_translation=False,
|
enable_llm_translation=False,
|
||||||
llm_model="llama3",
|
llm_model="llama3",
|
||||||
llm_prompt="Please translate this text:",
|
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.translator.moveToThread(self.translation_thread)
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ class TestExportTranscriptionMenu:
|
||||||
whisper_model_size=WhisperModelSize.SMALL.value,
|
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(
|
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))
|
return transcription_dao.find_by_id(str(id))
|
||||||
|
|
|
@ -35,6 +35,7 @@ class TestShortcutsEditorWidget:
|
||||||
(_("Import URL"), "Ctrl+U"),
|
(_("Import URL"), "Ctrl+U"),
|
||||||
(_("Open Preferences Window"), "Ctrl+,"),
|
(_("Open Preferences Window"), "Ctrl+,"),
|
||||||
(_("View Transcript Text"), "Ctrl+E"),
|
(_("View Transcript Text"), "Ctrl+E"),
|
||||||
|
(_("View Transcript Translation"), "Ctrl+L"),
|
||||||
(_("View Transcript Timestamps"), "Ctrl+T"),
|
(_("View Transcript Timestamps"), "Ctrl+T"),
|
||||||
(_("Clear History"), "Ctrl+S"),
|
(_("Clear History"), "Ctrl+S"),
|
||||||
(_("Cancel Transcription"), "Ctrl+X"),
|
(_("Cancel Transcription"), "Ctrl+X"),
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import time
|
||||||
import pytest
|
import pytest
|
||||||
from pytestqt.qtbot import QtBot
|
from pytestqt.qtbot import QtBot
|
||||||
|
|
||||||
|
from buzz.locale import _
|
||||||
from buzz.db.entity.transcription import Transcription
|
from buzz.db.entity.transcription import Transcription
|
||||||
from buzz.db.entity.transcription_segment import TranscriptionSegment
|
from buzz.db.entity.transcription_segment import TranscriptionSegment
|
||||||
from buzz.model_loader import ModelType, WhisperModelSize
|
from buzz.model_loader import ModelType, WhisperModelSize
|
||||||
from buzz.transcriber.transcriber import Task
|
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 (
|
from buzz.widgets.transcription_viewer.transcription_segments_editor_widget import (
|
||||||
TranscriptionSegmentsEditorWidget,
|
TranscriptionSegmentsEditorWidget,
|
||||||
)
|
)
|
||||||
|
@ -32,9 +38,9 @@ class TestTranscriptionViewerWidget:
|
||||||
whisper_model_size=WhisperModelSize.SMALL.value,
|
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(
|
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))
|
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, 1).data() == 299
|
||||||
assert editor.model().index(0, 2).data() == 40
|
assert editor.model().index(0, 2).data() == 40
|
||||||
assert editor.model().index(0, 3).data() == "Bien"
|
assert editor.model().index(0, 3).data() == "Bien"
|
||||||
|
widget.close()
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
def test_should_update_segment_text(
|
def test_should_update_segment_text(
|
||||||
self, qtbot, transcription, transcription_service, shortcuts
|
self, qtbot, transcription, transcription_service, shortcuts
|
||||||
|
@ -68,3 +76,27 @@ class TestTranscriptionViewerWidget:
|
||||||
assert isinstance(editor, TranscriptionSegmentsEditorWidget)
|
assert isinstance(editor, TranscriptionSegmentsEditorWidget)
|
||||||
|
|
||||||
editor.model().setData(editor.model().index(0, 3), "Biens")
|
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