Skip to content

Commit

Permalink
Adding ability to resize subtitles (#888)
Browse files Browse the repository at this point in the history
  • Loading branch information
raivisdejus authored Aug 17, 2024
1 parent 1c26bec commit 717f855
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 22 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ jobs:
if [ "$RUNNER_OS" == "macOS" ]; then
brew install create-dmg
# kill XProtect to prevent https://github.com/actions/runner-images/issues/7522
sudo pkill -9 XProtect >/dev/null || true;
while pgrep XProtect; do sleep 3; done;
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
Expand Down
5 changes: 5 additions & 0 deletions buzz/assets/resize_black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions buzz/db/dao/transcription_segment_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ def get_segments(self, transcription_id: UUID) -> List[TranscriptionSegment]:
query.bindValue(":transcription_id", str(transcription_id))
return self._execute_all(query)

def delete_segments(self, transcription_id: UUID):
query = self._create_query()
query.prepare(
f"""
DELETE FROM {self.table}
WHERE transcription_id = :transcription_id
"""
)
query.bindValue(":transcription_id", str(transcription_id))
if not query.exec():
raise Exception(query.lastError().text())

def update_segment_translation(self, segment_id: int, translation: str):
query = self._create_query()
query.prepare(
Expand Down
1 change: 1 addition & 0 deletions buzz/db/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ def _setup_db(path: str) -> QSqlDatabase:
db.setDatabaseName(path)
if not db.open():
raise RuntimeError(f"Failed to open database connection: {db.databaseName()}")
db.exec('PRAGMA foreign_keys = ON')
logging.debug("Database connection opened: %s", db.databaseName())
return db
13 changes: 13 additions & 0 deletions buzz/db/service/transcription_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ def update_transcription_as_completed(self, id: UUID, segments: List[Segment]):
)
)

def replace_transcription_segments(self, id: UUID, segments: List[Segment]):
self.transcription_segment_dao.delete_segments(id)
for segment in segments:
self.transcription_segment_dao.insert(
TranscriptionSegment(
start_time=segment.start,
end_time=segment.end,
text=segment.text,
translation='',
transcription_id=str(id),
)
)

def get_transcription_segments(self, transcription_id: UUID):
return self.transcription_segment_dao.get_segments(transcription_id)

Expand Down
36 changes: 25 additions & 11 deletions buzz/locale/lv_LV/LC_MESSAGES/buzz.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-07-11 19:46+0300\n"
"PO-Revision-Date: 2024-07-11 19:48+0300\n"
"POT-Creation-Date: 2024-08-17 11:00+0300\n"
"PO-Revision-Date: 2024-08-17 11:02+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: lv_LV\n"
Expand All @@ -29,12 +29,14 @@ msgstr "https://example.com/audio.mp3"
#: buzz/widgets/import_url_dialog.py:28
#: buzz/widgets/preferences_dialog/preferences_dialog.py:69
#: buzz/widgets/transcriber/advanced_settings_dialog.py:97
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:290
#: buzz/widgets/main_window.py:234
msgid "Ok"
msgstr "Labi"

#: buzz/widgets/import_url_dialog.py:29
#: buzz/widgets/preferences_dialog/preferences_dialog.py:70
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:291
#: buzz/widgets/main_window.py:235
msgid "Cancel"
msgstr "Atcelt"
Expand Down Expand Up @@ -114,7 +116,7 @@ msgid ""
msgstr ""
"OpenAI API atbilde ir nederīga. Lūdzu pārbaudiet API Adresi un savu atslēgu. "
"Atpazīšana un tulkošana joprojām var strādāt, ja API neatbalsta atslēgu "
"pārbaudi. "
"pārbaudi."

#: buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py:42
msgid "Enable folder watch"
Expand Down Expand Up @@ -201,7 +203,7 @@ msgid "Download failed"
msgstr "Lejupielāde neizdevās"

#: buzz/widgets/preferences_dialog/models_preferences_widget.py:259
#: buzz/widgets/main_window.py:291 buzz/model_loader.py:462
#: buzz/widgets/main_window.py:291 buzz/model_loader.py:478
msgid "Error"
msgstr "Kļūda"

Expand Down Expand Up @@ -378,11 +380,11 @@ msgstr "Gaida MI tulkojumu..."
msgid "Microphone:"
msgstr "Mikrofons:"

#: buzz/widgets/recording_transcriber_widget.py:413
#: buzz/widgets/recording_transcriber_widget.py:414
msgid "An error occurred while starting a new recording:"
msgstr "Sākot jaunu ierakstu notikusi kļūda:"

#: buzz/widgets/recording_transcriber_widget.py:417
#: buzz/widgets/recording_transcriber_widget.py:418
msgid ""
"Please check your audio devices or check the application logs for more "
"information."
Expand Down Expand Up @@ -427,23 +429,35 @@ msgstr "Skats"
msgid "Timestamps"
msgstr "Laiks"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:140
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:160
msgid "Export"
msgstr "Eksportēt"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:154
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:174
#: buzz/transcriber/transcriber.py:24
msgid "Translate"
msgstr "Tulkot"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:244
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:184
msgid "Resize"
msgstr "Mainīt garumu"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:274
msgid "API Key Required"
msgstr "API atslēgas kļūda"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:245
#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:275
msgid "Please enter OpenAI API Key in preferences"
msgstr "Lūdzu ievadiet OpenAI API atslēgu iestatījumos"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:292
msgid "Desired subtitle length"
msgstr "Vēlamais teksta garums"

#: buzz/widgets/transcription_viewer/transcription_viewer_widget.py:293
msgid "Enter target characters per subtitle:"
msgstr "Ievadiet vēlamo simbolu skaitu tekstā:"

#: buzz/widgets/transcription_viewer/export_transcription_menu.py:76
msgid "Save File"
msgstr "Saglabāt failu"
Expand Down Expand Up @@ -525,7 +539,7 @@ msgstr "Neizdevās saglabāt OpenAI API atslēgu atslēgu saišķī"
msgid "Transcribe"
msgstr "Atpazīt"

#: buzz/model_loader.py:490
#: buzz/model_loader.py:507
msgid "A connection error occurred"
msgstr "Notika savienojuma kļūda"

Expand Down
3 changes: 3 additions & 0 deletions buzz/widgets/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class TranslateIcon(Icon):
def __init__(self, parent: QWidget):
super().__init__(get_path("assets/translate_black.svg"), parent)

class ResizeIcon(Icon):
def __init__(self, parent: QWidget):
super().__init__(get_path("assets/resize_black.svg"), parent)

class VisibilityIcon(Icon):
def __init__(self, parent: QWidget):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,19 @@ def __init__(
# Show start before end
self.horizontalHeader().swapSections(1, 2)

font_metrics = QFontMetrics(self.font())
max_row_height = font_metrics.height() * 4
for row in range(self.model().rowCount()):
self.setRowHeight(row, max_row_height)
self.init_row_height()

self.setColumnWidth(Column.START.value, 95)
self.setColumnWidth(Column.END.value, 95)

self.setWordWrap(True)

def init_row_height(self):
font_metrics = QFontMetrics(self.font())
max_row_height = font_metrics.height() * 4
for row in range(self.model().rowCount()):
self.setRowHeight(row, max_row_height)

def has_non_empty_translation(self) -> bool:
for i in range(self.model().rowCount()):
if self.model().record(i).value("translation").strip():
Expand Down
94 changes: 89 additions & 5 deletions buzz/widgets/transcription_viewer/transcription_viewer_widget.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging
import platform
from typing import Optional
from uuid import UUID

from PyQt6.QtCore import Qt, QThread
from PyQt6.QtGui import QFont
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QFont, QShowEvent
from PyQt6.QtMultimedia import QMediaPlayer
from PyQt6.QtSql import QSqlRecord
from PyQt6.QtWidgets import (
Expand All @@ -13,8 +12,13 @@
QToolButton,
QLabel,
QMessageBox,
QInputDialog,
QDialogButtonBox,
)

import srt
from srt_equalizer import srt_equalizer

from buzz.locale import _
from buzz.db.entity.transcription import Transcription
from buzz.db.service.transcription_service import TranscriptionService
Expand All @@ -25,12 +29,13 @@
from buzz.widgets.audio_player import AudioPlayer
from buzz.widgets.icon import (
FileDownloadIcon,
TranslateIcon
TranslateIcon,
ResizeIcon,
)
from buzz.translator import Translator
from buzz.widgets.text_display_box import TextDisplayBox
from buzz.widgets.toolbar import ToolBar
from buzz.transcriber.transcriber import TranscriptionOptions
from buzz.transcriber.transcriber import TranscriptionOptions, Segment
from buzz.widgets.transcriber.advanced_settings_dialog import AdvancedSettingsDialog
from buzz.widgets.transcription_viewer.export_transcription_menu import (
ExportTranscriptionMenu,
Expand All @@ -47,7 +52,18 @@
)


class OkEnabledInputDialog(QInputDialog):
def showEvent(self, event: QShowEvent) -> None:
super().showEvent(event)
button_box = self.findChild(QDialogButtonBox)
if button_box:
ok_button = button_box.button(QDialogButtonBox.StandardButton.Ok)
if ok_button:
ok_button.setEnabled(True)


class TranscriptionViewerWidget(QWidget):
resize_button_clicked = pyqtSignal()
transcription: Transcription
settings = Settings()

Expand Down Expand Up @@ -160,6 +176,17 @@ def __init__(

toolbar.addWidget(translate_button)

resize_button = QToolButton()
resize_button.setText(_("Resize"))
resize_button.setObjectName("resize_button")
resize_button.setIcon(ResizeIcon(self))
resize_button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextBesideIcon
)
resize_button.clicked.connect(self.on_resize_button_clicked)

toolbar.addWidget(resize_button)

layout.setMenuBar(toolbar)

layout.addWidget(self.table_widget)
Expand Down Expand Up @@ -255,6 +282,63 @@ def on_translate_button_clicked(self):
for segment in segments:
self.translator.enqueue(segment.value("text"), segment.value("id"))

def on_resize_button_clicked(self):
target_chars_dialog = OkEnabledInputDialog(self)
target_chars_dialog.setOkButtonText(_("Ok"))
target_chars_dialog.setCancelButtonText(_("Cancel"))
target_chars_dialog.setWindowTitle(_("Desired subtitle length"))
target_chars_dialog.setLabelText(_("Enter target characters per subtitle:"))
target_chars_dialog.setIntValue(42)
target_chars_dialog.setIntMaximum(100)
target_chars_dialog.setIntMinimum(1)
target_chars_dialog.setIntStep(1)
target_chars_dialog.setInputMode(QInputDialog.InputMode.IntInput)

if target_chars_dialog.exec() == QInputDialog.DialogCode.Accepted:
target_chars = target_chars_dialog.intValue()
else:
return

segments = self.table_widget.segments()
subs = []
for segment in segments:
subtitle = srt.Subtitle(
index=segment.value("id"),
start=segment.value("start_time"),
end=segment.value("end_time"),
content=segment.value("text")
)
subs.append(subtitle)

resized_subs = []
last_index = 0

# Limit each subtitle to a maximum character length, splitting into
# multiple subtitle items if necessary.
for sub in subs:
new_subs = srt_equalizer.split_subtitle(
sub=sub, target_chars=target_chars, start_from_index=last_index, method="punctuation")
last_index = new_subs[-1].index
resized_subs.extend(new_subs)

segments = [
Segment(
round(sub.start),
round(sub.end),
sub.content
)
for sub in resized_subs
if round(sub.start) != round(sub.end)
]

self.transcription_service.replace_transcription_segments(
UUID(hex=self.transcription.id),
segments
)

self.table_widget.model().select()
self.table_widget.init_row_height()

def closeEvent(self, event):
self.hide()

Expand Down
26 changes: 25 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ faster-whisper = "1.0.1"
openai-whisper = "v20231117"
transformers = "4.39.1"
polib = "^1.2.0"
srt-equalizer = "^0.1.10"

[tool.poetry.group.dev.dependencies]
autopep8 = "^1.7.0"
Expand Down
Loading

0 comments on commit 717f855

Please sign in to comment.