mirror of
https://github.com/chidiwilliams/buzz.git
synced 2026-03-14 14:45:46 +01:00
Transcript viewer UX improvements for translations (#1151)
This commit is contained in:
parent
fa6bab952c
commit
806546282d
8 changed files with 111 additions and 49 deletions
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
|
|
@ -15,6 +15,8 @@ concurrency:
|
|||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -95,6 +97,9 @@ jobs:
|
|||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -224,6 +229,8 @@ jobs:
|
|||
|
||||
build_wheels:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -256,6 +263,8 @@ jobs:
|
|||
publish_pypi:
|
||||
needs: [build_wheels, test]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
|
|
@ -274,6 +283,8 @@ jobs:
|
|||
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -315,6 +326,8 @@ jobs:
|
|||
|
||||
deploy_brew_cask:
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
needs: [release]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
|
|
|
|||
4
.github/workflows/manual-build.yml
vendored
4
.github/workflows/manual-build.yml
vendored
|
|
@ -9,6 +9,8 @@ concurrency:
|
|||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
@ -69,6 +71,8 @@ jobs:
|
|||
|
||||
build-snap:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BUZZ_DISABLE_TELEMETRY: true
|
||||
outputs:
|
||||
snap: ${{ steps.snapcraft.outputs.snap }}
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
import platform
|
||||
|
|
@ -180,16 +181,19 @@ class Application(QApplication):
|
|||
|
||||
self.window = MainWindow(transcription_service)
|
||||
|
||||
posthog = Posthog(project_api_key='phc_NqZQUw8NcxfSXsbtk5eCFylmCQpp4FuNnd6ocPAzg2f',
|
||||
host='https://us.i.posthog.com')
|
||||
posthog.capture(distinct_id=self.settings.get_user_identifier(), event="app_launched", properties={
|
||||
"app": VERSION,
|
||||
"locale": locale.getdefaultlocale(),
|
||||
"system": platform.system(),
|
||||
"release": platform.release(),
|
||||
"machine": platform.machine(),
|
||||
"version": platform.version(),
|
||||
})
|
||||
disable_telemetry = os.getenv("BUZZ_DISABLE_TELEMETRY", None)
|
||||
|
||||
if not disable_telemetry:
|
||||
posthog = Posthog(project_api_key='phc_NqZQUw8NcxfSXsbtk5eCFylmCQpp4FuNnd6ocPAzg2f',
|
||||
host='https://us.i.posthog.com')
|
||||
posthog.capture(distinct_id=self.settings.get_user_identifier(), event="app_launched", properties={
|
||||
"app": VERSION,
|
||||
"locale": locale.getdefaultlocale(),
|
||||
"system": platform.system(),
|
||||
"release": platform.release(),
|
||||
"machine": platform.machine(),
|
||||
"version": platform.version(),
|
||||
})
|
||||
|
||||
logging.debug(f"Launching Buzz: {VERSION}, "
|
||||
f"locale: {locale.getdefaultlocale()}, "
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from PyQt6.QtGui import QAction
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QWidget, QMenu, QFileDialog
|
||||
|
||||
from buzz.db.entity.transcription import Transcription
|
||||
|
|
@ -17,45 +18,33 @@ class ExportTranscriptionMenu(QMenu):
|
|||
self,
|
||||
transcription: Transcription,
|
||||
transcription_service: TranscriptionService,
|
||||
has_translation: bool,
|
||||
translation: pyqtSignal,
|
||||
parent: QWidget | None = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.transcription = transcription
|
||||
self.transcription_service = transcription_service
|
||||
self.segments = []
|
||||
self.load_segments()
|
||||
|
||||
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
|
||||
]
|
||||
translation.connect(self.on_translation_available)
|
||||
|
||||
text_label = _("Text")
|
||||
translation_label = _("Translation")
|
||||
self.text_actions = [
|
||||
QAction(text=f"{output_format.value.upper()} - {text_label}", parent=self)
|
||||
for output_format in OutputFormat
|
||||
]
|
||||
self.translation_actions = [
|
||||
QAction(text=f"{output_format.value.upper()} - {translation_label}", parent=self)
|
||||
for output_format in OutputFormat
|
||||
]
|
||||
for action in self.translation_actions:
|
||||
action.setVisible(has_translation)
|
||||
actions = self.text_actions + self.translation_actions
|
||||
self.addActions(actions)
|
||||
self.triggered.connect(self.on_menu_triggered)
|
||||
|
||||
def load_segments(self):
|
||||
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
|
||||
)
|
||||
]
|
||||
@staticmethod
|
||||
def extract_format_and_segment_key(action_text: str):
|
||||
parts = action_text.split('-')
|
||||
|
|
@ -65,9 +54,24 @@ class ExportTranscriptionMenu(QMenu):
|
|||
|
||||
return output_format, segment_key
|
||||
|
||||
def on_translation_available(self):
|
||||
for action in self.translation_actions:
|
||||
action.setVisible(True)
|
||||
|
||||
def on_menu_triggered(self, action: QAction):
|
||||
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
|
||||
)
|
||||
]
|
||||
|
||||
output_format_value, segment_key = self.extract_format_and_segment_key(action.text())
|
||||
output_format = OutputFormat[output_format_value]
|
||||
output_format = OutputFormat(output_format_value.lower())
|
||||
|
||||
default_path = self.transcription.get_output_file_path(
|
||||
output_format=output_format
|
||||
|
|
@ -83,12 +87,9 @@ class ExportTranscriptionMenu(QMenu):
|
|||
if output_file_path == "":
|
||||
return
|
||||
|
||||
# Reload segments in case they were resized
|
||||
self.load_segments()
|
||||
|
||||
write_output(
|
||||
path=output_file_path,
|
||||
segments=self.segments,
|
||||
segments=segments,
|
||||
output_format=output_format,
|
||||
segment_key=segment_key
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -20,7 +21,13 @@ class ViewMode(Enum):
|
|||
class TranscriptionViewModeToolButton(QToolButton):
|
||||
view_mode_changed = pyqtSignal(ViewMode)
|
||||
|
||||
def __init__(self, shortcuts: Shortcuts, parent: Optional[QWidget] = None):
|
||||
def __init__(
|
||||
self,
|
||||
shortcuts: Shortcuts,
|
||||
has_translation: bool,
|
||||
translation: pyqtSignal,
|
||||
parent: Optional[QWidget] = None
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setText(_("View"))
|
||||
|
|
@ -28,6 +35,8 @@ class TranscriptionViewModeToolButton(QToolButton):
|
|||
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
||||
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
|
||||
translation.connect(self.on_translation_available)
|
||||
|
||||
menu = QMenu(self)
|
||||
|
||||
menu.addAction(
|
||||
|
|
@ -36,11 +45,12 @@ class TranscriptionViewModeToolButton(QToolButton):
|
|||
lambda: self.view_mode_changed.emit(ViewMode.TEXT),
|
||||
)
|
||||
|
||||
menu.addAction(
|
||||
self.translation_action = menu.addAction(
|
||||
_("Translation"),
|
||||
QKeySequence(shortcuts.get(Shortcut.VIEW_TRANSCRIPT_TRANSLATION)),
|
||||
lambda: self.view_mode_changed.emit(ViewMode.TRANSLATION)
|
||||
)
|
||||
self.translation_action.setVisible(has_translation)
|
||||
|
||||
menu.addAction(
|
||||
_("Timestamps"),
|
||||
|
|
@ -49,3 +59,6 @@ class TranscriptionViewModeToolButton(QToolButton):
|
|||
)
|
||||
|
||||
self.setMenu(menu)
|
||||
|
||||
def on_translation_available(self):
|
||||
self.translation_action.setVisible(True)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ class TranscriptionViewerWidget(QWidget):
|
|||
self.translator = None
|
||||
self.view_mode = ViewMode.TIMESTAMPS
|
||||
|
||||
# Can't reuse this globally, as transcripts may get translated, so need to get them each time
|
||||
segments = self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
)
|
||||
self.has_translations = any(segment.translation.strip() for segment in segments)
|
||||
|
||||
self.openai_access_token = get_password(Key.OPENAI_API_KEY)
|
||||
|
||||
preferences = self.load_preferences()
|
||||
|
|
@ -138,7 +144,11 @@ class TranscriptionViewerWidget(QWidget):
|
|||
|
||||
toolbar = ToolBar(self)
|
||||
|
||||
view_mode_tool_button = TranscriptionViewModeToolButton(shortcuts, self)
|
||||
view_mode_tool_button = TranscriptionViewModeToolButton(
|
||||
shortcuts,
|
||||
self.has_translations,
|
||||
self.translator.translation,
|
||||
)
|
||||
view_mode_tool_button.view_mode_changed.connect(self.on_view_mode_changed)
|
||||
toolbar.addWidget(view_mode_tool_button)
|
||||
|
||||
|
|
@ -150,7 +160,11 @@ class TranscriptionViewerWidget(QWidget):
|
|||
)
|
||||
|
||||
export_transcription_menu = ExportTranscriptionMenu(
|
||||
transcription, transcription_service, self
|
||||
transcription,
|
||||
transcription_service,
|
||||
self.has_translations,
|
||||
self.translator.translation,
|
||||
self
|
||||
)
|
||||
export_tool_button.setMenu(export_transcription_menu)
|
||||
export_tool_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
|
|
@ -192,6 +206,7 @@ class TranscriptionViewerWidget(QWidget):
|
|||
if self.view_mode == ViewMode.TIMESTAMPS:
|
||||
self.text_display_box.hide()
|
||||
self.table_widget.show()
|
||||
self.audio_player.show()
|
||||
elif self.view_mode == ViewMode.TEXT:
|
||||
segments = self.transcription_service.get_transcription_segments(
|
||||
transcription_id=self.transcription.id_as_uuid
|
||||
|
|
@ -209,8 +224,8 @@ class TranscriptionViewerWidget(QWidget):
|
|||
self.text_display_box.setPlainText(combined_text.strip())
|
||||
self.text_display_box.show()
|
||||
self.table_widget.hide()
|
||||
self.audio_player.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
|
||||
)
|
||||
|
|
@ -219,6 +234,7 @@ class TranscriptionViewerWidget(QWidget):
|
|||
)
|
||||
self.text_display_box.show()
|
||||
self.table_widget.hide()
|
||||
self.audio_player.hide()
|
||||
|
||||
def on_view_mode_changed(self, view_mode: ViewMode) -> None:
|
||||
self.view_mode = view_mode
|
||||
|
|
|
|||
|
|
@ -98,3 +98,5 @@ Defaults to [user_cache_dir](https://pypi.org/project/platformdirs/).
|
|||
**BUZZ_FORCE_CPU** - Will force Buzz to use CPU and not GPU, useful for setups with older GPU if that is slower than GPU or GPU has issues. Example usage `BUZZ_FORCE_CPU=true`. Available since `1.2.1`
|
||||
|
||||
**BUZZ_MERGE_REGROUP_RULE** - Custom regroup merge rule to use when combining transcripts with word-level timings. More information on available options [in stable-ts repo](https://github.com/jianfch/stable-ts?tab=readme-ov-file#regrouping-methods). Available since `1.3.0`
|
||||
|
||||
**BUZZ_DISABLE_TELEMETRY** - Buzz collects basic OS name and architecture usage statistics to better focus development efforts. This variable lets disable collection of these statistics. Example usage `BUZZ_DISABLE_TELEMETRY=true`. Available since `1.3.0`
|
||||
|
|
@ -2,6 +2,7 @@ import pathlib
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
from PyQt6.QtCore import QObject, pyqtSignal
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from buzz.db.entity.transcription import Transcription
|
||||
|
|
@ -14,6 +15,10 @@ from buzz.widgets.transcription_viewer.export_transcription_menu import (
|
|||
from tests.audio import test_audio_path
|
||||
|
||||
|
||||
class TranslationSignal(QObject):
|
||||
translation = pyqtSignal(str, int)
|
||||
|
||||
|
||||
class TestExportTranscriptionMenu:
|
||||
@pytest.fixture()
|
||||
def transcription(
|
||||
|
|
@ -52,9 +57,13 @@ class TestExportTranscriptionMenu:
|
|||
return_value=(str(output_file_path), ""),
|
||||
)
|
||||
|
||||
translation_signal = TranslationSignal()
|
||||
|
||||
widget = ExportTranscriptionMenu(
|
||||
transcription,
|
||||
transcription_service,
|
||||
False,
|
||||
translation_signal.translation
|
||||
)
|
||||
qtbot.add_widget(widget)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue