Transcript viewer UX improvements for translations (#1151)

This commit is contained in:
Raivis Dejus 2025-04-25 10:10:52 +03:00 committed by GitHub
commit 806546282d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 111 additions and 49 deletions

View file

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

View file

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

View file

@ -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()}, "

View file

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

View file

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

View file

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

View file

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

View file

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