mirror of
https://github.com/chidiwilliams/buzz.git
synced 2024-06-03 08:22:12 +02:00
feat: add folder watch (#655)
This commit is contained in:
parent
1120723681
commit
2b839c35cb
|
@ -54,10 +54,10 @@ class Settings:
|
|||
def clear(self):
|
||||
self.settings.clear()
|
||||
|
||||
def begin_group(self, group: Key):
|
||||
def begin_group(self, group: Key) -> None:
|
||||
self.settings.beginGroup(group.value)
|
||||
|
||||
def end_group(self):
|
||||
def end_group(self) -> None:
|
||||
self.settings.endGroup()
|
||||
|
||||
def sync(self):
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import math
|
||||
import multiprocessing
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
@ -96,6 +97,10 @@ class FileTranscriptionTask:
|
|||
FAILED = "failed"
|
||||
CANCELED = "canceled"
|
||||
|
||||
class Source(enum.Enum):
|
||||
FILE_IMPORT = "file_import"
|
||||
FOLDER_WATCH = "folder_watch"
|
||||
|
||||
file_path: str
|
||||
transcription_options: TranscriptionOptions
|
||||
file_transcription_options: FileTranscriptionOptions
|
||||
|
@ -108,6 +113,8 @@ class FileTranscriptionTask:
|
|||
queued_at: Optional[datetime.datetime] = None
|
||||
started_at: Optional[datetime.datetime] = None
|
||||
completed_at: Optional[datetime.datetime] = None
|
||||
output_directory: Optional[str] = None
|
||||
source: Source = Source.FILE_IMPORT
|
||||
|
||||
def status_text(self) -> str:
|
||||
if self.status == FileTranscriptionTask.Status.IN_PROGRESS:
|
||||
|
@ -169,7 +176,7 @@ class FileTranscriber(QObject):
|
|||
for (
|
||||
output_format
|
||||
) in self.transcription_task.file_transcription_options.output_formats:
|
||||
default_path = get_default_output_file_path(
|
||||
default_path = get_output_file_path(
|
||||
task=self.transcription_task, output_format=output_format
|
||||
)
|
||||
|
||||
|
@ -177,6 +184,15 @@ class FileTranscriber(QObject):
|
|||
path=default_path, segments=segments, output_format=output_format
|
||||
)
|
||||
|
||||
if self.transcription_task.source == FileTranscriptionTask.Source.FOLDER_WATCH:
|
||||
shutil.move(
|
||||
self.transcription_task.file_path,
|
||||
os.path.join(
|
||||
self.transcription_task.output_directory,
|
||||
os.path.basename(self.transcription_task.file_path),
|
||||
),
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def transcribe(self) -> List[Segment]:
|
||||
...
|
||||
|
@ -644,24 +660,22 @@ def segments_to_text(segments: List[Segment]) -> str:
|
|||
|
||||
def to_timestamp(ms: float, ms_separator=".") -> str:
|
||||
hr = int(ms / (1000 * 60 * 60))
|
||||
ms = ms - hr * (1000 * 60 * 60)
|
||||
ms -= hr * (1000 * 60 * 60)
|
||||
min = int(ms / (1000 * 60))
|
||||
ms = ms - min * (1000 * 60)
|
||||
ms -= min * (1000 * 60)
|
||||
sec = int(ms / 1000)
|
||||
ms = int(ms - sec * 1000)
|
||||
return f"{hr:02d}:{min:02d}:{sec:02d}{ms_separator}{ms:03d}"
|
||||
|
||||
|
||||
SUPPORTED_OUTPUT_FORMATS = "Audio files (*.mp3 *.wav *.m4a *.ogg);;\
|
||||
SUPPORTED_AUDIO_FORMATS = "Audio files (*.mp3 *.wav *.m4a *.ogg);;\
|
||||
Video files (*.mp4 *.webm *.ogm *.mov);;All files (*.*)"
|
||||
|
||||
|
||||
def get_default_output_file_path(
|
||||
task: FileTranscriptionTask, output_format: OutputFormat
|
||||
):
|
||||
input_file_name = os.path.splitext(task.file_path)[0]
|
||||
def get_output_file_path(task: FileTranscriptionTask, output_format: OutputFormat):
|
||||
input_file_name = os.path.splitext(os.path.basename(task.file_path))[0]
|
||||
date_time_now = datetime.datetime.now().strftime("%d-%b-%Y %H-%M-%S")
|
||||
return (
|
||||
output_file_name = (
|
||||
task.file_transcription_options.default_output_file_name.replace(
|
||||
"{{ input_file_name }}", input_file_name
|
||||
)
|
||||
|
@ -678,6 +692,9 @@ def get_default_output_file_path(
|
|||
+ f".{output_format.value}"
|
||||
)
|
||||
|
||||
output_directory = task.output_directory or os.path.dirname(task.file_path)
|
||||
return os.path.join(output_directory, output_file_name)
|
||||
|
||||
|
||||
def whisper_cpp_params(
|
||||
language: str,
|
||||
|
|
|
@ -53,7 +53,7 @@ class AudioPlayer(QWidget):
|
|||
self.media_player.playbackStateChanged.connect(self.on_playback_state_changed)
|
||||
self.media_player.mediaStatusChanged.connect(self.on_media_status_changed)
|
||||
|
||||
self.update_time_label()
|
||||
self.on_duration_changed(self.media_player.duration())
|
||||
|
||||
def on_duration_changed(self, duration_ms: int):
|
||||
self.scrubber.setRange(0, duration_ms)
|
||||
|
|
|
@ -4,27 +4,48 @@ from PyQt6.QtWidgets import QWidget
|
|||
from buzz.assets import get_asset_path
|
||||
|
||||
|
||||
# TODO: move icons to Qt resources: https://stackoverflow.com/a/52341917/9830227
|
||||
class Icon(QIcon):
|
||||
LIGHT_THEME_BACKGROUND = "#555"
|
||||
DARK_THEME_BACKGROUND = "#EEE"
|
||||
LIGHT_THEME_COLOR = "#555"
|
||||
DARK_THEME_COLOR = "#EEE"
|
||||
|
||||
def __init__(self, path: str, parent: QWidget):
|
||||
# Adapted from https://stackoverflow.com/questions/15123544/change-the-color-of-an-svg-in-qt
|
||||
is_dark_theme = parent.palette().window().color().black() > 127
|
||||
color = self.get_color(is_dark_theme)
|
||||
super().__init__()
|
||||
self.path = path
|
||||
self.parent = parent
|
||||
|
||||
self.color = self.get_color()
|
||||
normal_pixmap = self.create_default_pixmap(self.path, self.color)
|
||||
disabled_pixmap = self.create_disabled_pixmap(normal_pixmap, self.color)
|
||||
self.addPixmap(normal_pixmap, QIcon.Mode.Normal)
|
||||
self.addPixmap(disabled_pixmap, QIcon.Mode.Disabled)
|
||||
|
||||
# https://stackoverflow.com/questions/15123544/change-the-color-of-an-svg-in-qt
|
||||
def create_default_pixmap(self, path, color):
|
||||
pixmap = QPixmap(path)
|
||||
painter = QPainter(pixmap)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
|
||||
painter.fillRect(pixmap.rect(), QColor(color))
|
||||
painter.fillRect(pixmap.rect(), color)
|
||||
painter.end()
|
||||
return pixmap
|
||||
|
||||
super().__init__(pixmap)
|
||||
def create_disabled_pixmap(self, pixmap, color):
|
||||
disabled_pixmap = QPixmap(pixmap.size())
|
||||
disabled_pixmap.fill(QColor(0, 0, 0, 0))
|
||||
|
||||
def get_color(self, is_dark_theme):
|
||||
return (
|
||||
self.DARK_THEME_BACKGROUND if is_dark_theme else self.LIGHT_THEME_BACKGROUND
|
||||
painter = QPainter(disabled_pixmap)
|
||||
painter.setOpacity(0.4)
|
||||
painter.drawPixmap(0, 0, pixmap)
|
||||
painter.setCompositionMode(
|
||||
QPainter.CompositionMode.CompositionMode_DestinationIn
|
||||
)
|
||||
painter.fillRect(disabled_pixmap.rect(), color)
|
||||
painter.end()
|
||||
return disabled_pixmap
|
||||
|
||||
def get_color(self) -> QColor:
|
||||
is_dark_theme = self.parent.palette().window().color().black() > 127
|
||||
return QColor(
|
||||
self.DARK_THEME_COLOR if is_dark_theme else self.LIGHT_THEME_COLOR
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
from typing import Dict, Optional, Tuple, List
|
||||
from typing import Dict, Tuple, List
|
||||
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6.QtCore import pyqtSignal, Qt, QThread, QModelIndex
|
||||
from PyQt6.QtCore import (
|
||||
Qt,
|
||||
QThread,
|
||||
QModelIndex,
|
||||
)
|
||||
from PyQt6.QtGui import QIcon
|
||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox, QFileDialog
|
||||
|
||||
|
@ -15,12 +19,16 @@ from buzz.transcriber import (
|
|||
FileTranscriptionTask,
|
||||
TranscriptionOptions,
|
||||
FileTranscriptionOptions,
|
||||
SUPPORTED_OUTPUT_FORMATS,
|
||||
SUPPORTED_AUDIO_FORMATS,
|
||||
)
|
||||
from buzz.widgets.icon import BUZZ_ICON_PATH
|
||||
from buzz.widgets.main_window_toolbar import MainWindowToolbar
|
||||
from buzz.widgets.menu_bar import MenuBar
|
||||
from buzz.widgets.preferences_dialog.models.preferences import Preferences
|
||||
from buzz.widgets.transcriber.file_transcriber_widget import FileTranscriberWidget
|
||||
from buzz.widgets.transcription_task_folder_watcher import (
|
||||
TranscriptionTaskFolderWatcher,
|
||||
)
|
||||
from buzz.widgets.transcription_tasks_table_widget import TranscriptionTasksTableWidget
|
||||
from buzz.widgets.transcription_viewer.transcription_viewer_widget import (
|
||||
TranscriptionViewerWidget,
|
||||
|
@ -30,8 +38,6 @@ from buzz.widgets.transcription_viewer.transcription_viewer_widget import (
|
|||
class MainWindow(QMainWindow):
|
||||
table_widget: TranscriptionTasksTableWidget
|
||||
tasks: Dict[int, "FileTranscriptionTask"]
|
||||
tasks_changed = pyqtSignal()
|
||||
openai_access_token: Optional[str]
|
||||
|
||||
def __init__(self, tasks_cache=TasksCache()):
|
||||
super().__init__(flags=Qt.WindowType.Window)
|
||||
|
@ -54,7 +60,6 @@ class MainWindow(QMainWindow):
|
|||
)
|
||||
|
||||
self.tasks = {}
|
||||
self.tasks_changed.connect(self.on_tasks_changed)
|
||||
|
||||
self.toolbar = MainWindowToolbar(shortcuts=self.shortcuts, parent=self)
|
||||
self.toolbar.new_transcription_action_triggered.connect(
|
||||
|
@ -72,9 +77,11 @@ class MainWindow(QMainWindow):
|
|||
self.addToolBar(self.toolbar)
|
||||
self.setUnifiedTitleAndToolBarOnMac(True)
|
||||
|
||||
self.preferences = self.load_preferences(settings=self.settings)
|
||||
self.menu_bar = MenuBar(
|
||||
shortcuts=self.shortcuts,
|
||||
default_export_file_name=self.default_export_file_name,
|
||||
preferences=self.preferences,
|
||||
parent=self,
|
||||
)
|
||||
self.menu_bar.import_action_triggered.connect(
|
||||
|
@ -87,6 +94,7 @@ class MainWindow(QMainWindow):
|
|||
self.menu_bar.default_export_file_name_changed.connect(
|
||||
self.default_export_file_name_changed
|
||||
)
|
||||
self.menu_bar.preferences_changed.connect(self.on_preferences_changed)
|
||||
self.setMenuBar(self.menu_bar)
|
||||
|
||||
self.table_widget = TranscriptionTasksTableWidget(self)
|
||||
|
@ -113,6 +121,31 @@ class MainWindow(QMainWindow):
|
|||
|
||||
self.load_geometry()
|
||||
|
||||
self.folder_watcher = TranscriptionTaskFolderWatcher(
|
||||
tasks=self.tasks,
|
||||
preferences=self.preferences.folder_watch,
|
||||
default_export_file_name=self.default_export_file_name,
|
||||
)
|
||||
self.folder_watcher.task_found.connect(self.add_task)
|
||||
self.folder_watcher.find_tasks()
|
||||
|
||||
def on_preferences_changed(self, preferences: Preferences):
|
||||
self.preferences = preferences
|
||||
self.save_preferences(preferences)
|
||||
self.folder_watcher.set_preferences(preferences.folder_watch)
|
||||
self.folder_watcher.find_tasks()
|
||||
|
||||
def save_preferences(self, preferences: Preferences):
|
||||
self.settings.settings.beginGroup("preferences")
|
||||
preferences.save(self.settings.settings)
|
||||
self.settings.settings.endGroup()
|
||||
|
||||
def load_preferences(self, settings: Settings):
|
||||
settings.settings.beginGroup("preferences")
|
||||
preferences = Preferences.load(settings.settings)
|
||||
settings.settings.endGroup()
|
||||
return preferences
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
# Accept file drag events
|
||||
if event.mimeData().hasUrls():
|
||||
|
@ -134,13 +167,13 @@ class MainWindow(QMainWindow):
|
|||
)
|
||||
self.add_task(task)
|
||||
|
||||
def load_task(self, task: FileTranscriptionTask):
|
||||
def upsert_task_in_table(self, task: FileTranscriptionTask):
|
||||
self.table_widget.upsert_task(task)
|
||||
self.tasks[task.id] = task
|
||||
|
||||
def update_task_table_row(self, task: FileTranscriptionTask):
|
||||
self.load_task(task=task)
|
||||
self.tasks_changed.emit()
|
||||
self.upsert_task_in_table(task=task)
|
||||
self.on_tasks_changed()
|
||||
|
||||
@staticmethod
|
||||
def task_completed_or_errored(task: FileTranscriptionTask):
|
||||
|
@ -158,7 +191,8 @@ class MainWindow(QMainWindow):
|
|||
self,
|
||||
_("Clear History"),
|
||||
_(
|
||||
"Are you sure you want to delete the selected transcription(s)? This action cannot be undone."
|
||||
"Are you sure you want to delete the selected transcription(s)? "
|
||||
"This action cannot be undone."
|
||||
),
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
|
@ -169,7 +203,7 @@ class MainWindow(QMainWindow):
|
|||
for task_id in task_ids:
|
||||
self.table_widget.clear_task(task_id)
|
||||
self.tasks.pop(task_id)
|
||||
self.tasks_changed.emit()
|
||||
self.on_tasks_changed()
|
||||
|
||||
def on_stop_transcription_action_triggered(self):
|
||||
selected_rows = self.table_widget.selectionModel().selectedRows()
|
||||
|
@ -178,13 +212,13 @@ class MainWindow(QMainWindow):
|
|||
task = self.tasks[task_id]
|
||||
|
||||
task.status = FileTranscriptionTask.Status.CANCELED
|
||||
self.tasks_changed.emit()
|
||||
self.on_tasks_changed()
|
||||
self.transcriber_worker.cancel_task(task_id)
|
||||
self.table_widget.upsert_task(task)
|
||||
|
||||
def on_new_transcription_action_triggered(self):
|
||||
(file_paths, __) = QFileDialog.getOpenFileNames(
|
||||
self, _("Select audio file"), "", SUPPORTED_OUTPUT_FORMATS
|
||||
self, _("Select audio file"), "", SUPPORTED_AUDIO_FORMATS
|
||||
)
|
||||
if len(file_paths) == 0:
|
||||
return
|
||||
|
@ -213,6 +247,7 @@ class MainWindow(QMainWindow):
|
|||
self.settings.set_value(
|
||||
Settings.Key.DEFAULT_EXPORT_FILE_NAME, default_export_file_name
|
||||
)
|
||||
self.folder_watcher.default_export_file_name = default_export_file_name
|
||||
|
||||
def open_transcript_viewer(self):
|
||||
selected_rows = self.table_widget.selectionModel().selectedRows()
|
||||
|
@ -291,9 +326,9 @@ class MainWindow(QMainWindow):
|
|||
or task.status == FileTranscriptionTask.Status.IN_PROGRESS
|
||||
):
|
||||
task.status = None
|
||||
self.transcriber_worker.add_task(task)
|
||||
self.add_task(task)
|
||||
else:
|
||||
self.load_task(task=task)
|
||||
self.upsert_task_in_table(task=task)
|
||||
|
||||
def save_tasks_to_cache(self):
|
||||
self.tasks_cache.save(list(self.tasks.values()))
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import webbrowser
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtGui import QAction, QKeySequence
|
||||
from PyQt6.QtWidgets import QMenuBar, QWidget
|
||||
|
||||
from buzz.widgets.about_dialog import AboutDialog
|
||||
from buzz.locale import _
|
||||
from buzz.settings.settings import APP_NAME
|
||||
from buzz.settings.shortcut import Shortcut
|
||||
from buzz.widgets.preferences_dialog.preferences_dialog import PreferencesDialog
|
||||
from buzz.widgets.about_dialog import AboutDialog
|
||||
from buzz.widgets.preferences_dialog.models.preferences import Preferences
|
||||
from buzz.widgets.preferences_dialog.preferences_dialog import (
|
||||
PreferencesDialog,
|
||||
)
|
||||
|
||||
|
||||
class MenuBar(QMenuBar):
|
||||
|
@ -17,14 +20,21 @@ class MenuBar(QMenuBar):
|
|||
shortcuts_changed = pyqtSignal(dict)
|
||||
openai_api_key_changed = pyqtSignal(str)
|
||||
default_export_file_name_changed = pyqtSignal(str)
|
||||
preferences_changed = pyqtSignal(Preferences)
|
||||
preferences_dialog: Optional[PreferencesDialog] = None
|
||||
|
||||
def __init__(
|
||||
self, shortcuts: Dict[str, str], default_export_file_name: str, parent: QWidget
|
||||
self,
|
||||
shortcuts: Dict[str, str],
|
||||
default_export_file_name: str,
|
||||
preferences: Preferences,
|
||||
parent: Optional[QWidget] = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.shortcuts = shortcuts
|
||||
self.default_export_file_name = default_export_file_name
|
||||
self.preferences = preferences
|
||||
|
||||
self.import_action = QAction(_("Import Media File..."), self)
|
||||
self.import_action.triggered.connect(self.on_import_action_triggered)
|
||||
|
@ -59,6 +69,7 @@ class MenuBar(QMenuBar):
|
|||
preferences_dialog = PreferencesDialog(
|
||||
shortcuts=self.shortcuts,
|
||||
default_export_file_name=self.default_export_file_name,
|
||||
preferences=self.preferences,
|
||||
parent=self,
|
||||
)
|
||||
preferences_dialog.shortcuts_changed.connect(self.shortcuts_changed)
|
||||
|
@ -66,8 +77,17 @@ class MenuBar(QMenuBar):
|
|||
preferences_dialog.default_export_file_name_changed.connect(
|
||||
self.default_export_file_name_changed
|
||||
)
|
||||
preferences_dialog.finished.connect(self.on_preferences_dialog_finished)
|
||||
preferences_dialog.open()
|
||||
|
||||
self.preferences_dialog = preferences_dialog
|
||||
|
||||
def on_preferences_dialog_finished(self, result):
|
||||
if result == self.preferences_dialog.DialogCode.Accepted:
|
||||
updated_preferences = self.preferences_dialog.updated_preferences
|
||||
self.preferences = updated_preferences
|
||||
self.preferences_changed.emit(updated_preferences)
|
||||
|
||||
def on_help_action_triggered(self):
|
||||
webbrowser.open("https://chidiwilliams.github.io/buzz/docs")
|
||||
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
from typing import Tuple, Optional
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget,
|
||||
QPushButton,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QFileDialog,
|
||||
QCheckBox,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from buzz.store.keyring_store import KeyringStore
|
||||
from buzz.transcriber import (
|
||||
TranscriptionOptions,
|
||||
FileTranscriptionOptions,
|
||||
)
|
||||
from buzz.widgets.line_edit import LineEdit
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
from buzz.widgets.transcriber.file_transcription_form_widget import (
|
||||
FileTranscriptionFormWidget,
|
||||
)
|
||||
|
||||
|
||||
class FolderWatchPreferencesWidget(QWidget):
|
||||
config_changed = pyqtSignal(FolderWatchPreferences)
|
||||
|
||||
def __init__(
|
||||
self, config: FolderWatchPreferences, parent: Optional[QWidget] = None
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.config = config
|
||||
|
||||
checkbox = QCheckBox("Enable folder watch")
|
||||
checkbox.setChecked(config.enabled)
|
||||
checkbox.setObjectName("EnableFolderWatchCheckbox")
|
||||
checkbox.stateChanged.connect(self.on_enable_changed)
|
||||
|
||||
input_folder_browse_button = QPushButton("Browse")
|
||||
input_folder_browse_button.clicked.connect(self.on_click_browse_input_folder)
|
||||
|
||||
output_folder_browse_button = QPushButton("Browse")
|
||||
output_folder_browse_button.clicked.connect(self.on_click_browse_output_folder)
|
||||
|
||||
input_folder_row = QHBoxLayout()
|
||||
self.input_folder_line_edit = LineEdit(config.input_directory, self)
|
||||
self.input_folder_line_edit.setPlaceholderText("/path/to/input/folder")
|
||||
self.input_folder_line_edit.textChanged.connect(self.on_input_folder_changed)
|
||||
self.input_folder_line_edit.setObjectName("InputFolderLineEdit")
|
||||
|
||||
input_folder_row.addWidget(self.input_folder_line_edit)
|
||||
input_folder_row.addWidget(input_folder_browse_button)
|
||||
|
||||
output_folder_row = QHBoxLayout()
|
||||
self.output_folder_line_edit = LineEdit(config.output_directory, self)
|
||||
self.output_folder_line_edit.setPlaceholderText("/path/to/output/folder")
|
||||
self.output_folder_line_edit.textChanged.connect(self.on_output_folder_changed)
|
||||
self.output_folder_line_edit.setObjectName("OutputFolderLineEdit")
|
||||
|
||||
output_folder_row.addWidget(self.output_folder_line_edit)
|
||||
output_folder_row.addWidget(output_folder_browse_button)
|
||||
|
||||
openai_access_token = KeyringStore().get_password(
|
||||
KeyringStore.Key.OPENAI_API_KEY
|
||||
)
|
||||
(
|
||||
transcription_options,
|
||||
file_transcription_options,
|
||||
) = config.file_transcription_options.to_transcription_options(
|
||||
openai_access_token=openai_access_token,
|
||||
file_paths=[],
|
||||
)
|
||||
|
||||
transcription_form_widget = FileTranscriptionFormWidget(
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
parent=self,
|
||||
)
|
||||
transcription_form_widget.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
folders_form_layout = QFormLayout()
|
||||
|
||||
folders_form_layout.addRow("", checkbox)
|
||||
folders_form_layout.addRow("Input folder", input_folder_row)
|
||||
folders_form_layout.addRow("Output folder", output_folder_row)
|
||||
folders_form_layout.addWidget(transcription_form_widget)
|
||||
|
||||
layout.addLayout(folders_form_layout)
|
||||
layout.addWidget(transcription_form_widget)
|
||||
layout.addStretch()
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def on_click_browse_input_folder(self):
|
||||
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
||||
self.input_folder_line_edit.setText(folder)
|
||||
self.on_input_folder_changed(folder)
|
||||
|
||||
def on_input_folder_changed(self, folder):
|
||||
self.config.input_directory = folder
|
||||
self.config_changed.emit(self.config)
|
||||
|
||||
def on_click_browse_output_folder(self):
|
||||
folder = QFileDialog.getExistingDirectory(self, "Select Output Folder")
|
||||
self.output_folder_line_edit.setText(folder)
|
||||
self.on_output_folder_changed(folder)
|
||||
|
||||
def on_output_folder_changed(self, folder):
|
||||
self.config.output_directory = folder
|
||||
self.config_changed.emit(self.config)
|
||||
|
||||
def on_enable_changed(self, state: int):
|
||||
self.config.enabled = state == 2
|
||||
self.config_changed.emit(self.config)
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, options: Tuple[TranscriptionOptions, FileTranscriptionOptions]
|
||||
):
|
||||
transcription_options, file_transcription_options = options
|
||||
self.config.file_transcription_options = (
|
||||
FileTranscriptionPreferences.from_transcription_options(
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
)
|
||||
)
|
||||
self.config_changed.emit(self.config)
|
|
@ -40,7 +40,7 @@ class GeneralPreferencesWidget(QWidget):
|
|||
)
|
||||
self.update_test_openai_api_key_button()
|
||||
|
||||
layout.addRow("OpenAI API Key", self.openai_api_key_line_edit)
|
||||
layout.addRow("OpenAI API key", self.openai_api_key_line_edit)
|
||||
layout.addRow("", self.test_openai_api_key_button)
|
||||
|
||||
default_export_file_name_line_edit = LineEdit(default_export_file_name, self)
|
||||
|
|
0
buzz/widgets/preferences_dialog/models/__init__.py
Normal file
0
buzz/widgets/preferences_dialog/models/__init__.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple, Set, List
|
||||
|
||||
from PyQt6.QtCore import QSettings
|
||||
|
||||
from buzz.model_loader import TranscriptionModel
|
||||
from buzz.transcriber import (
|
||||
Task,
|
||||
OutputFormat,
|
||||
DEFAULT_WHISPER_TEMPERATURE,
|
||||
TranscriptionOptions,
|
||||
FileTranscriptionOptions,
|
||||
)
|
||||
|
||||
|
||||
@dataclass()
|
||||
class FileTranscriptionPreferences:
|
||||
language: Optional[str]
|
||||
task: Task
|
||||
model: TranscriptionModel
|
||||
word_level_timings: bool
|
||||
temperature: Tuple[float, ...]
|
||||
initial_prompt: str
|
||||
output_formats: Set["OutputFormat"]
|
||||
|
||||
def save(self, settings: QSettings) -> None:
|
||||
settings.setValue("language", self.language)
|
||||
settings.setValue("task", self.task)
|
||||
settings.setValue("model", self.model)
|
||||
settings.setValue("word_level_timings", self.word_level_timings)
|
||||
settings.setValue("temperature", self.temperature)
|
||||
settings.setValue("initial_prompt", self.initial_prompt)
|
||||
settings.setValue(
|
||||
"output_formats",
|
||||
[output_format.value for output_format in self.output_formats],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load(cls, settings: QSettings) -> "FileTranscriptionPreferences":
|
||||
language = settings.value("language", None)
|
||||
task = settings.value("task", Task.TRANSCRIBE)
|
||||
model = settings.value("model", TranscriptionModel())
|
||||
word_level_timings = settings.value("word_level_timings", False)
|
||||
temperature = settings.value("temperature", DEFAULT_WHISPER_TEMPERATURE)
|
||||
initial_prompt = settings.value("initial_prompt", "")
|
||||
output_formats = settings.value("output_formats", [])
|
||||
return FileTranscriptionPreferences(
|
||||
language=language,
|
||||
task=task,
|
||||
model=model,
|
||||
word_level_timings=word_level_timings,
|
||||
temperature=temperature,
|
||||
initial_prompt=initial_prompt,
|
||||
output_formats=set(
|
||||
[OutputFormat(output_format) for output_format in output_formats]
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_transcription_options(
|
||||
cls,
|
||||
transcription_options: TranscriptionOptions,
|
||||
file_transcription_options: FileTranscriptionOptions,
|
||||
) -> "FileTranscriptionPreferences":
|
||||
return FileTranscriptionPreferences(
|
||||
task=transcription_options.task,
|
||||
language=transcription_options.language,
|
||||
temperature=transcription_options.temperature,
|
||||
initial_prompt=transcription_options.initial_prompt,
|
||||
word_level_timings=transcription_options.word_level_timings,
|
||||
model=transcription_options.model,
|
||||
output_formats=file_transcription_options.output_formats,
|
||||
)
|
||||
|
||||
def to_transcription_options(
|
||||
self,
|
||||
openai_access_token: Optional[str],
|
||||
file_paths: List[str],
|
||||
default_output_file_name: str = "",
|
||||
) -> Tuple[TranscriptionOptions, FileTranscriptionOptions]:
|
||||
return (
|
||||
TranscriptionOptions(
|
||||
task=self.task,
|
||||
language=self.language,
|
||||
temperature=self.temperature,
|
||||
initial_prompt=self.initial_prompt,
|
||||
word_level_timings=self.word_level_timings,
|
||||
model=self.model,
|
||||
openai_access_token=openai_access_token,
|
||||
),
|
||||
FileTranscriptionOptions(
|
||||
output_formats=self.output_formats,
|
||||
file_paths=file_paths,
|
||||
default_output_file_name=default_output_file_name,
|
||||
),
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from PyQt6.QtCore import QSettings
|
||||
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FolderWatchPreferences:
|
||||
enabled: bool
|
||||
input_directory: str
|
||||
output_directory: str
|
||||
file_transcription_options: FileTranscriptionPreferences
|
||||
|
||||
def save(self, settings: QSettings):
|
||||
settings.setValue("enabled", self.enabled)
|
||||
settings.setValue("input_folder", self.input_directory)
|
||||
settings.setValue("output_directory", self.output_directory)
|
||||
settings.beginGroup("file_transcription_options")
|
||||
self.file_transcription_options.save(settings)
|
||||
settings.endGroup()
|
||||
|
||||
@classmethod
|
||||
def load(cls, settings: QSettings) -> "FolderWatchPreferences":
|
||||
enabled = settings.value("enabled", defaultValue=False, type=bool)
|
||||
input_folder = settings.value("input_folder", defaultValue="", type=str)
|
||||
output_folder = settings.value("output_directory", defaultValue="", type=str)
|
||||
settings.beginGroup("file_transcription_options")
|
||||
file_transcription_options = FileTranscriptionPreferences.load(settings)
|
||||
settings.endGroup()
|
||||
return FolderWatchPreferences(
|
||||
enabled=enabled,
|
||||
input_directory=input_folder,
|
||||
output_directory=output_folder,
|
||||
file_transcription_options=file_transcription_options,
|
||||
)
|
24
buzz/widgets/preferences_dialog/models/preferences.py
Normal file
24
buzz/widgets/preferences_dialog/models/preferences.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from PyQt6.QtCore import QSettings
|
||||
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Preferences:
|
||||
folder_watch: FolderWatchPreferences
|
||||
|
||||
def save(self, settings: QSettings):
|
||||
settings.beginGroup("folder_watch")
|
||||
self.folder_watch.save(settings)
|
||||
settings.endGroup()
|
||||
|
||||
@classmethod
|
||||
def load(cls, settings: QSettings) -> "Preferences":
|
||||
settings.beginGroup("folder_watch")
|
||||
folder_watch = FolderWatchPreferences.load(settings)
|
||||
settings.endGroup()
|
||||
return Preferences(folder_watch=folder_watch)
|
|
@ -1,12 +1,20 @@
|
|||
import copy
|
||||
from typing import Dict, Optional
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QDialog, QWidget, QVBoxLayout, QTabWidget, QDialogButtonBox
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.widgets.preferences_dialog.folder_watch_preferences_widget import (
|
||||
FolderWatchPreferencesWidget,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.general_preferences_widget import (
|
||||
GeneralPreferencesWidget,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.preferences import Preferences
|
||||
from buzz.widgets.preferences_dialog.models_preferences_widget import (
|
||||
ModelsPreferencesWidget,
|
||||
)
|
||||
|
@ -18,16 +26,22 @@ from buzz.widgets.preferences_dialog.shortcuts_editor_preferences_widget import
|
|||
class PreferencesDialog(QDialog):
|
||||
shortcuts_changed = pyqtSignal(dict)
|
||||
openai_api_key_changed = pyqtSignal(str)
|
||||
folder_watch_config_changed = pyqtSignal(FolderWatchPreferences)
|
||||
default_export_file_name_changed = pyqtSignal(str)
|
||||
preferences_changed = pyqtSignal(Preferences)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# TODO: move shortcuts and default export file name into preferences
|
||||
shortcuts: Dict[str, str],
|
||||
default_export_file_name: str,
|
||||
preferences: Preferences,
|
||||
parent: Optional[QWidget] = None,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.updated_preferences = copy.deepcopy(preferences)
|
||||
|
||||
self.setWindowTitle("Preferences")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
@ -49,8 +63,15 @@ class PreferencesDialog(QDialog):
|
|||
shortcuts_table_widget.shortcuts_changed.connect(self.shortcuts_changed)
|
||||
tab_widget.addTab(shortcuts_table_widget, _("Shortcuts"))
|
||||
|
||||
folder_watch_widget = FolderWatchPreferencesWidget(
|
||||
config=self.updated_preferences.folder_watch, parent=self
|
||||
)
|
||||
folder_watch_widget.config_changed.connect(self.folder_watch_config_changed)
|
||||
tab_widget.addTab(folder_watch_widget, _("Folder Watch"))
|
||||
|
||||
button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton(QDialogButtonBox.StandardButton.Ok), self
|
||||
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
|
||||
self,
|
||||
)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
|
@ -60,4 +81,5 @@ class PreferencesDialog(QDialog):
|
|||
|
||||
self.setLayout(layout)
|
||||
|
||||
self.setFixedSize(self.sizeHint())
|
||||
self.setMinimumHeight(500)
|
||||
self.setMinimumWidth(500)
|
||||
|
|
|
@ -5,28 +5,25 @@ from PyQt6.QtCore import pyqtSignal, Qt, QThreadPool
|
|||
from PyQt6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QCheckBox,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
|
||||
from buzz.dialogs import show_model_download_error_dialog
|
||||
from buzz.locale import _
|
||||
from buzz.model_loader import ModelDownloader, TranscriptionModel, ModelType
|
||||
from buzz.model_loader import ModelDownloader
|
||||
from buzz.paths import file_paths_as_title
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.store.keyring_store import KeyringStore
|
||||
from buzz.transcriber import (
|
||||
FileTranscriptionOptions,
|
||||
TranscriptionOptions,
|
||||
Task,
|
||||
DEFAULT_WHISPER_TEMPERATURE,
|
||||
OutputFormat,
|
||||
)
|
||||
from buzz.widgets.model_download_progress_dialog import ModelDownloadProgressDialog
|
||||
from buzz.widgets.transcriber.transcription_options_group_box import (
|
||||
TranscriptionOptionsGroupBox,
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
from buzz.widgets.transcriber.file_transcription_form_widget import (
|
||||
FileTranscriptionFormWidget,
|
||||
)
|
||||
|
||||
|
||||
|
@ -57,89 +54,34 @@ class FileTranscriberWidget(QWidget):
|
|||
)
|
||||
|
||||
self.file_paths = file_paths
|
||||
default_language = self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_LANGUAGE, default_value=""
|
||||
)
|
||||
self.transcription_options = TranscriptionOptions(
|
||||
|
||||
preferences = self.load_preferences()
|
||||
|
||||
(
|
||||
self.transcription_options,
|
||||
self.file_transcription_options,
|
||||
) = preferences.to_transcription_options(
|
||||
openai_access_token=openai_access_token,
|
||||
model=self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_MODEL,
|
||||
default_value=TranscriptionModel(),
|
||||
),
|
||||
task=self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_TASK, default_value=Task.TRANSCRIBE
|
||||
),
|
||||
language=default_language if default_language != "" else None,
|
||||
initial_prompt=self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_INITIAL_PROMPT, default_value=""
|
||||
),
|
||||
temperature=self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_TEMPERATURE,
|
||||
default_value=DEFAULT_WHISPER_TEMPERATURE,
|
||||
),
|
||||
word_level_timings=self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_WORD_LEVEL_TIMINGS,
|
||||
default_value=False,
|
||||
),
|
||||
)
|
||||
default_export_format_states: List[str] = self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_EXPORT_FORMATS, default_value=[]
|
||||
)
|
||||
self.file_transcription_options = FileTranscriptionOptions(
|
||||
file_paths=self.file_paths,
|
||||
output_formats=set(
|
||||
[
|
||||
OutputFormat(output_format)
|
||||
for output_format in default_export_format_states
|
||||
]
|
||||
),
|
||||
default_output_file_name=default_output_file_name,
|
||||
)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
transcription_options_group_box = TranscriptionOptionsGroupBox(
|
||||
default_transcription_options=self.transcription_options, parent=self
|
||||
self.form_widget = FileTranscriptionFormWidget(
|
||||
transcription_options=self.transcription_options,
|
||||
file_transcription_options=self.file_transcription_options,
|
||||
parent=self,
|
||||
)
|
||||
transcription_options_group_box.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
self.form_widget.openai_access_token_changed.connect(
|
||||
self.openai_access_token_changed
|
||||
)
|
||||
|
||||
self.word_level_timings_checkbox = QCheckBox(_("Word-level timings"))
|
||||
self.word_level_timings_checkbox.setChecked(
|
||||
self.settings.value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_WORD_LEVEL_TIMINGS,
|
||||
default_value=False,
|
||||
)
|
||||
)
|
||||
self.word_level_timings_checkbox.stateChanged.connect(
|
||||
self.on_word_level_timings_changed
|
||||
)
|
||||
|
||||
file_transcription_layout = QFormLayout()
|
||||
file_transcription_layout.addRow("", self.word_level_timings_checkbox)
|
||||
|
||||
export_format_layout = QHBoxLayout()
|
||||
for output_format in OutputFormat:
|
||||
export_format_checkbox = QCheckBox(
|
||||
f"{output_format.value.upper()}", parent=self
|
||||
)
|
||||
export_format_checkbox.setChecked(
|
||||
output_format in self.file_transcription_options.output_formats
|
||||
)
|
||||
export_format_checkbox.stateChanged.connect(
|
||||
self.get_on_checkbox_state_changed_callback(output_format)
|
||||
)
|
||||
export_format_layout.addWidget(export_format_checkbox)
|
||||
|
||||
file_transcription_layout.addRow("Export:", export_format_layout)
|
||||
|
||||
self.run_button = QPushButton(_("Run"), self)
|
||||
self.run_button.setDefault(True)
|
||||
self.run_button.clicked.connect(self.on_click_run)
|
||||
|
||||
layout.addWidget(transcription_options_group_box)
|
||||
layout.addLayout(file_transcription_layout)
|
||||
layout.addWidget(self.form_widget)
|
||||
layout.addWidget(self.run_button, 0, Qt.AlignmentFlag.AlignRight)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
@ -147,23 +89,19 @@ class FileTranscriberWidget(QWidget):
|
|||
|
||||
self.reset_transcriber_controls()
|
||||
|
||||
def get_on_checkbox_state_changed_callback(self, output_format: OutputFormat):
|
||||
def on_checkbox_state_changed(state: int):
|
||||
if state == Qt.CheckState.Checked.value:
|
||||
self.file_transcription_options.output_formats.add(output_format)
|
||||
elif state == Qt.CheckState.Unchecked.value:
|
||||
self.file_transcription_options.output_formats.remove(output_format)
|
||||
def load_preferences(self):
|
||||
self.settings.settings.beginGroup("file_transcriber")
|
||||
preferences = FileTranscriptionPreferences.load(settings=self.settings.settings)
|
||||
self.settings.settings.endGroup()
|
||||
return preferences
|
||||
|
||||
return on_checkbox_state_changed
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
):
|
||||
self.transcription_options = transcription_options
|
||||
if self.transcription_options.openai_access_token != "":
|
||||
self.openai_access_token_changed.emit(
|
||||
self.transcription_options.openai_access_token
|
||||
)
|
||||
def save_preferences(self):
|
||||
self.settings.settings.beginGroup("file_transcriber")
|
||||
preferences = FileTranscriptionPreferences.from_transcription_options(
|
||||
self.transcription_options, self.file_transcription_options
|
||||
)
|
||||
preferences.save(settings=self.settings.settings)
|
||||
self.settings.settings.endGroup()
|
||||
|
||||
def on_click_run(self):
|
||||
self.run_button.setDisabled(True)
|
||||
|
@ -210,11 +148,6 @@ class FileTranscriberWidget(QWidget):
|
|||
|
||||
def reset_transcriber_controls(self):
|
||||
self.run_button.setDisabled(False)
|
||||
self.word_level_timings_checkbox.setDisabled(
|
||||
self.transcription_options.model.model_type == ModelType.HUGGING_FACE
|
||||
or self.transcription_options.model.model_type
|
||||
== ModelType.OPEN_AI_WHISPER_API
|
||||
)
|
||||
|
||||
def on_cancel_model_progress_dialog(self):
|
||||
if self.model_loader is not None:
|
||||
|
@ -234,34 +167,5 @@ class FileTranscriberWidget(QWidget):
|
|||
def closeEvent(self, event: QtGui.QCloseEvent) -> None:
|
||||
if self.model_loader is not None:
|
||||
self.model_loader.cancel()
|
||||
|
||||
self.settings.set_value(
|
||||
Settings.Key.FILE_TRANSCRIBER_LANGUAGE, self.transcription_options.language
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.FILE_TRANSCRIBER_TASK, self.transcription_options.task
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.FILE_TRANSCRIBER_TEMPERATURE,
|
||||
self.transcription_options.temperature,
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.FILE_TRANSCRIBER_INITIAL_PROMPT,
|
||||
self.transcription_options.initial_prompt,
|
||||
)
|
||||
self.settings.set_value(
|
||||
Settings.Key.FILE_TRANSCRIBER_MODEL, self.transcription_options.model
|
||||
)
|
||||
self.settings.set_value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_WORD_LEVEL_TIMINGS,
|
||||
value=self.transcription_options.word_level_timings,
|
||||
)
|
||||
self.settings.set_value(
|
||||
key=Settings.Key.FILE_TRANSCRIBER_EXPORT_FORMATS,
|
||||
value=[
|
||||
export_format.value
|
||||
for export_format in self.file_transcription_options.output_formats
|
||||
],
|
||||
)
|
||||
|
||||
self.save_preferences()
|
||||
super().closeEvent(event)
|
||||
|
|
110
buzz/widgets/transcriber/file_transcription_form_widget.py
Normal file
110
buzz/widgets/transcriber/file_transcription_form_widget.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
from typing import Optional
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal, Qt
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QFormLayout, QHBoxLayout
|
||||
|
||||
from buzz.locale import _
|
||||
from buzz.model_loader import ModelType
|
||||
from buzz.transcriber import (
|
||||
TranscriptionOptions,
|
||||
FileTranscriptionOptions,
|
||||
OutputFormat,
|
||||
)
|
||||
from buzz.widgets.transcriber.transcription_options_group_box import (
|
||||
TranscriptionOptionsGroupBox,
|
||||
)
|
||||
|
||||
|
||||
class FileTranscriptionFormWidget(QWidget):
|
||||
openai_access_token_changed = pyqtSignal(str)
|
||||
transcription_options_changed = pyqtSignal(tuple)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transcription_options: TranscriptionOptions,
|
||||
file_transcription_options: FileTranscriptionOptions,
|
||||
parent: Optional[QWidget] = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.transcription_options = transcription_options
|
||||
self.file_transcription_options = file_transcription_options
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
transcription_options_group_box = TranscriptionOptionsGroupBox(
|
||||
default_transcription_options=self.transcription_options, parent=self
|
||||
)
|
||||
transcription_options_group_box.transcription_options_changed.connect(
|
||||
self.on_transcription_options_changed
|
||||
)
|
||||
|
||||
self.word_level_timings_checkbox = QCheckBox(_("Word-level timings"))
|
||||
self.word_level_timings_checkbox.setChecked(
|
||||
self.transcription_options.word_level_timings
|
||||
)
|
||||
self.word_level_timings_checkbox.stateChanged.connect(
|
||||
self.on_word_level_timings_changed
|
||||
)
|
||||
|
||||
file_transcription_layout = QFormLayout()
|
||||
file_transcription_layout.addRow("", self.word_level_timings_checkbox)
|
||||
|
||||
export_format_layout = QHBoxLayout()
|
||||
for output_format in OutputFormat:
|
||||
export_format_checkbox = QCheckBox(
|
||||
f"{output_format.value.upper()}", parent=self
|
||||
)
|
||||
export_format_checkbox.setChecked(
|
||||
output_format in self.file_transcription_options.output_formats
|
||||
)
|
||||
export_format_checkbox.stateChanged.connect(
|
||||
self.get_on_checkbox_state_changed_callback(output_format)
|
||||
)
|
||||
export_format_layout.addWidget(export_format_checkbox)
|
||||
|
||||
file_transcription_layout.addRow("Export:", export_format_layout)
|
||||
|
||||
layout.addWidget(transcription_options_group_box)
|
||||
layout.addLayout(file_transcription_layout)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.reset_word_level_timings()
|
||||
|
||||
def on_transcription_options_changed(
|
||||
self, transcription_options: TranscriptionOptions
|
||||
):
|
||||
self.transcription_options = transcription_options
|
||||
self.reset_word_level_timings()
|
||||
self.transcription_options_changed.emit(
|
||||
(self.transcription_options, self.file_transcription_options)
|
||||
)
|
||||
if self.transcription_options.openai_access_token != "":
|
||||
self.openai_access_token_changed.emit(
|
||||
self.transcription_options.openai_access_token
|
||||
)
|
||||
|
||||
def on_word_level_timings_changed(self, value: int):
|
||||
self.transcription_options.word_level_timings = (
|
||||
value == Qt.CheckState.Checked.value
|
||||
)
|
||||
|
||||
def get_on_checkbox_state_changed_callback(self, output_format: OutputFormat):
|
||||
def on_checkbox_state_changed(state: int):
|
||||
if state == Qt.CheckState.Checked.value:
|
||||
self.file_transcription_options.output_formats.add(output_format)
|
||||
elif state == Qt.CheckState.Unchecked.value:
|
||||
self.file_transcription_options.output_formats.remove(output_format)
|
||||
|
||||
self.transcription_options_changed.emit(
|
||||
(self.transcription_options, self.file_transcription_options)
|
||||
)
|
||||
|
||||
return on_checkbox_state_changed
|
||||
|
||||
def reset_word_level_timings(self):
|
||||
self.word_level_timings_checkbox.setDisabled(
|
||||
self.transcription_options.model.model_type == ModelType.HUGGING_FACE
|
||||
or self.transcription_options.model.model_type
|
||||
== ModelType.OPEN_AI_WHISPER_API
|
||||
)
|
76
buzz/widgets/transcription_task_folder_watcher.py
Normal file
76
buzz/widgets/transcription_task_folder_watcher.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import logging
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
from PyQt6.QtCore import QFileSystemWatcher, pyqtSignal, QObject
|
||||
|
||||
from buzz.store.keyring_store import KeyringStore
|
||||
from buzz.transcriber import FileTranscriptionTask
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
|
||||
|
||||
class TranscriptionTaskFolderWatcher(QFileSystemWatcher):
|
||||
preferences: FolderWatchPreferences
|
||||
task_found = pyqtSignal(FileTranscriptionTask)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tasks: Dict[int, FileTranscriptionTask],
|
||||
preferences: FolderWatchPreferences,
|
||||
default_export_file_name: str,
|
||||
parent: QObject = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
self.tasks = tasks
|
||||
self.default_export_file_name = default_export_file_name
|
||||
self.set_preferences(preferences)
|
||||
self.directoryChanged.connect(self.find_tasks)
|
||||
|
||||
def set_preferences(self, preferences: FolderWatchPreferences):
|
||||
self.preferences = preferences
|
||||
if len(self.directories()) > 0:
|
||||
self.removePaths(self.directories())
|
||||
if preferences.enabled:
|
||||
self.addPath(preferences.input_directory)
|
||||
logging.debug(
|
||||
'Watching for media files in "%s"', preferences.input_directory
|
||||
)
|
||||
|
||||
def find_tasks(self):
|
||||
input_directory = self.preferences.input_directory
|
||||
tasks = {task.file_path: task for task in self.tasks.values()}
|
||||
for dirpath, dirnames, filenames in os.walk(input_directory):
|
||||
for filename in filenames:
|
||||
file_path = os.path.join(dirpath, filename)
|
||||
if (
|
||||
filename.startswith(".") # hidden files
|
||||
or file_path in tasks # file already in tasks
|
||||
):
|
||||
continue
|
||||
|
||||
openai_access_token = KeyringStore().get_password(
|
||||
KeyringStore.Key.OPENAI_API_KEY
|
||||
)
|
||||
(
|
||||
transcription_options,
|
||||
file_transcription_options,
|
||||
) = self.preferences.file_transcription_options.to_transcription_options(
|
||||
openai_access_token=openai_access_token,
|
||||
default_output_file_name=self.default_export_file_name,
|
||||
file_paths=[file_path],
|
||||
)
|
||||
model_path = transcription_options.model.get_local_model_path()
|
||||
task = FileTranscriptionTask(
|
||||
file_path=file_path,
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
model_path=model_path,
|
||||
output_directory=self.preferences.output_directory,
|
||||
source=FileTranscriptionTask.Source.FOLDER_WATCH,
|
||||
)
|
||||
self.task_found.emit(task)
|
||||
|
||||
# Don't traverse into subdirectories
|
||||
break
|
|
@ -5,7 +5,7 @@ from buzz.locale import _
|
|||
from buzz.transcriber import (
|
||||
FileTranscriptionTask,
|
||||
OutputFormat,
|
||||
get_default_output_file_path,
|
||||
get_output_file_path,
|
||||
write_output,
|
||||
)
|
||||
from buzz.widgets.icon import FileDownloadIcon
|
||||
|
@ -30,7 +30,7 @@ class ExportTranscriptionButton(QPushButton):
|
|||
def on_menu_triggered(self, action: QAction):
|
||||
output_format = OutputFormat[action.text()]
|
||||
|
||||
default_path = get_default_output_file_path(
|
||||
default_path = get_output_file_path(
|
||||
task=self.transcription_task, output_format=output_format
|
||||
)
|
||||
|
||||
|
|
|
@ -62,7 +62,6 @@ class ChangeSegmentTextCommand(QUndoCommand):
|
|||
self.task_changed.emit()
|
||||
|
||||
|
||||
# TODO: Fix player duration and add spacer below
|
||||
class TranscriptionViewerWidget(QWidget):
|
||||
transcription_task: FileTranscriptionTask
|
||||
task_changed = pyqtSignal()
|
||||
|
@ -114,6 +113,7 @@ class TranscriptionViewerWidget(QWidget):
|
|||
self.current_segment_label = QLabel()
|
||||
self.current_segment_label.setText("")
|
||||
self.current_segment_label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
self.current_segment_label.setContentsMargins(0, 0, 0, 10)
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
buttons_layout.addStretch()
|
||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
|||
import os
|
||||
import pathlib
|
||||
import platform
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
from typing import List
|
||||
|
@ -21,7 +22,7 @@ from buzz.transcriber import (
|
|||
WhisperCpp,
|
||||
WhisperCppFileTranscriber,
|
||||
WhisperFileTranscriber,
|
||||
get_default_output_file_path,
|
||||
get_output_file_path,
|
||||
to_timestamp,
|
||||
whisper_cpp_params,
|
||||
write_output,
|
||||
|
@ -159,24 +160,34 @@ class TestWhisperCppFileTranscriber:
|
|||
|
||||
class TestWhisperFileTranscriber:
|
||||
@pytest.mark.parametrize(
|
||||
"output_format,expected_file_path,default_output_file_name",
|
||||
"file_path,output_format,expected_file_path,default_output_file_name",
|
||||
[
|
||||
(
|
||||
pytest.param(
|
||||
"/a/b/c.mp4",
|
||||
OutputFormat.SRT,
|
||||
"/a/b/c-translate--Whisper-tiny.srt",
|
||||
"{{ input_file_name }}-{{ task }}-{{ language }}-{{ model_type }}-{{ model_size }}",
|
||||
marks=pytest.mark.skipif(platform.system() == "Windows", reason=""),
|
||||
),
|
||||
pytest.param(
|
||||
"C:\\a\\b\\c.mp4",
|
||||
OutputFormat.SRT,
|
||||
"C:\\a\\b\\c-translate--Whisper-tiny.srt",
|
||||
"{{ input_file_name }}-{{ task }}-{{ language }}-{{ model_type }}-{{ model_size }}",
|
||||
marks=pytest.mark.skipif(platform.system() != "Windows", reason=""),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_output_file2(
|
||||
def test_default_output_file(
|
||||
self,
|
||||
file_path: str,
|
||||
output_format: OutputFormat,
|
||||
expected_file_path: str,
|
||||
default_output_file_name: str,
|
||||
):
|
||||
file_path = get_default_output_file_path(
|
||||
file_path = get_output_file_path(
|
||||
task=FileTranscriptionTask(
|
||||
file_path="/a/b/c.mp4",
|
||||
file_path=file_path,
|
||||
transcription_options=TranscriptionOptions(task=Task.TRANSLATE),
|
||||
file_transcription_options=FileTranscriptionOptions(
|
||||
file_paths=[], default_output_file_name=default_output_file_name
|
||||
|
@ -187,10 +198,27 @@ class TestWhisperFileTranscriber:
|
|||
)
|
||||
assert file_path == expected_file_path
|
||||
|
||||
def test_default_output_file(self):
|
||||
srt = get_default_output_file_path(
|
||||
@pytest.mark.parametrize(
|
||||
"file_path,expected_starts_with",
|
||||
[
|
||||
pytest.param(
|
||||
"/a/b/c.mp4",
|
||||
"/a/b/c (Translated on ",
|
||||
marks=pytest.mark.skipif(platform.system() == "Windows", reason=""),
|
||||
),
|
||||
pytest.param(
|
||||
"C:\\a\\b\\c.mp4",
|
||||
"C:\\a\\b\\c (Translated on ",
|
||||
marks=pytest.mark.skipif(platform.system() != "Windows", reason=""),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_default_output_file_with_date(
|
||||
self, file_path: str, expected_starts_with: str
|
||||
):
|
||||
srt = get_output_file_path(
|
||||
task=FileTranscriptionTask(
|
||||
file_path="/a/b/c.mp4",
|
||||
file_path=file_path,
|
||||
transcription_options=TranscriptionOptions(task=Task.TRANSLATE),
|
||||
file_transcription_options=FileTranscriptionOptions(
|
||||
file_paths=[],
|
||||
|
@ -200,12 +228,13 @@ class TestWhisperFileTranscriber:
|
|||
),
|
||||
output_format=OutputFormat.TXT,
|
||||
)
|
||||
assert srt.startswith("/a/b/c (Translated on ")
|
||||
|
||||
assert srt.startswith(expected_starts_with)
|
||||
assert srt.endswith(".txt")
|
||||
|
||||
srt = get_default_output_file_path(
|
||||
srt = get_output_file_path(
|
||||
task=FileTranscriptionTask(
|
||||
file_path="/a/b/c.mp4",
|
||||
file_path=file_path,
|
||||
transcription_options=TranscriptionOptions(task=Task.TRANSLATE),
|
||||
file_transcription_options=FileTranscriptionOptions(
|
||||
file_paths=[],
|
||||
|
@ -215,7 +244,7 @@ class TestWhisperFileTranscriber:
|
|||
),
|
||||
output_format=OutputFormat.SRT,
|
||||
)
|
||||
assert srt.startswith("/a/b/c (Translated on ")
|
||||
assert srt.startswith(expected_starts_with)
|
||||
assert srt.endswith(".srt")
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -327,6 +356,43 @@ class TestWhisperFileTranscriber:
|
|||
assert len(segments[i].text) > 0
|
||||
logging.debug(f"{segments[i].start} {segments[i].end} {segments[i].text}")
|
||||
|
||||
def test_transcribe_from_folder_watch_source(self, qtbot):
|
||||
file_path = tempfile.mktemp(suffix=".mp3")
|
||||
shutil.copy("testdata/whisper-french.mp3", file_path)
|
||||
|
||||
file_transcription_options = FileTranscriptionOptions(
|
||||
file_paths=[file_path],
|
||||
output_formats={OutputFormat.TXT},
|
||||
default_output_file_name="{{ input_file_name }}",
|
||||
)
|
||||
transcription_options = TranscriptionOptions()
|
||||
model_path = get_model_path(transcription_options.model)
|
||||
|
||||
output_directory = tempfile.mkdtemp()
|
||||
transcriber = WhisperFileTranscriber(
|
||||
task=FileTranscriptionTask(
|
||||
model_path=model_path,
|
||||
transcription_options=transcription_options,
|
||||
file_transcription_options=file_transcription_options,
|
||||
file_path=file_path,
|
||||
output_directory=output_directory,
|
||||
source=FileTranscriptionTask.Source.FOLDER_WATCH,
|
||||
)
|
||||
)
|
||||
with qtbot.wait_signal(transcriber.completed, timeout=10 * 6000):
|
||||
transcriber.run()
|
||||
|
||||
assert not os.path.isfile(file_path)
|
||||
assert os.path.isfile(
|
||||
os.path.join(output_directory, os.path.basename(file_path))
|
||||
)
|
||||
assert os.path.isfile(
|
||||
os.path.join(
|
||||
output_directory,
|
||||
os.path.splitext(os.path.basename(file_path))[0] + ".txt",
|
||||
)
|
||||
)
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_transcribe_stop(self):
|
||||
output_file_path = os.path.join(tempfile.gettempdir(), "whisper.txt")
|
||||
|
|
25
tests/widgets/menu_bar_test.py
Normal file
25
tests/widgets/menu_bar_test.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from PyQt6.QtCore import QSettings
|
||||
|
||||
from buzz.settings.settings import Settings
|
||||
from buzz.settings.shortcut_settings import ShortcutSettings
|
||||
from buzz.widgets.menu_bar import MenuBar
|
||||
from buzz.widgets.preferences_dialog.models.preferences import Preferences
|
||||
from buzz.widgets.preferences_dialog.preferences_dialog import PreferencesDialog
|
||||
|
||||
|
||||
class TestMenuBar:
|
||||
def test_open_preferences_dialog(self, qtbot):
|
||||
menu_bar = MenuBar(
|
||||
shortcuts=ShortcutSettings(Settings()).load(),
|
||||
default_export_file_name="",
|
||||
preferences=Preferences.load(QSettings()),
|
||||
)
|
||||
qtbot.add_widget(menu_bar)
|
||||
|
||||
preferences_dialog = menu_bar.findChild(PreferencesDialog)
|
||||
assert preferences_dialog is None
|
||||
|
||||
menu_bar.preferences_action.trigger()
|
||||
|
||||
preferences_dialog = menu_bar.findChild(PreferencesDialog)
|
||||
assert isinstance(preferences_dialog, PreferencesDialog)
|
|
@ -0,0 +1,55 @@
|
|||
from unittest.mock import Mock
|
||||
|
||||
from PyQt6.QtWidgets import QCheckBox, QLineEdit
|
||||
|
||||
from buzz.model_loader import TranscriptionModel
|
||||
from buzz.transcriber import Task, DEFAULT_WHISPER_TEMPERATURE
|
||||
from buzz.widgets.preferences_dialog.folder_watch_preferences_widget import (
|
||||
FolderWatchPreferencesWidget,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
|
||||
|
||||
class TestFolderWatchPreferencesWidget:
|
||||
def test_edit_folder_watch_preferences(self, qtbot):
|
||||
widget = FolderWatchPreferencesWidget(
|
||||
config=FolderWatchPreferences(
|
||||
enabled=False,
|
||||
input_directory="",
|
||||
output_directory="",
|
||||
file_transcription_options=FileTranscriptionPreferences(
|
||||
language=None,
|
||||
task=Task.TRANSCRIBE,
|
||||
model=TranscriptionModel(),
|
||||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
)
|
||||
mock_config_changed = Mock()
|
||||
widget.config_changed.connect(mock_config_changed)
|
||||
qtbot.add_widget(widget)
|
||||
|
||||
checkbox = widget.findChild(QCheckBox, "EnableFolderWatchCheckbox")
|
||||
input_folder_line_edit = widget.findChild(QLineEdit, "InputFolderLineEdit")
|
||||
output_folder_line_edit = widget.findChild(QLineEdit, "OutputFolderLineEdit")
|
||||
|
||||
assert not checkbox.isChecked()
|
||||
assert input_folder_line_edit.text() == ""
|
||||
assert output_folder_line_edit.text() == ""
|
||||
|
||||
checkbox.setChecked(True)
|
||||
input_folder_line_edit.setText("test/input/folder")
|
||||
output_folder_line_edit.setText("test/output/folder")
|
||||
|
||||
last_config_changed_call = mock_config_changed.call_args_list[-1]
|
||||
assert last_config_changed_call[0][0].enabled
|
||||
assert last_config_changed_call[0][0].input_directory == "test/input/folder"
|
||||
assert last_config_changed_call[0][0].output_directory == "test/output/folder"
|
|
@ -1,19 +1,26 @@
|
|||
from PyQt6.QtCore import QSettings
|
||||
from PyQt6.QtWidgets import QTabWidget
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from buzz.widgets.preferences_dialog.models.preferences import Preferences
|
||||
from buzz.widgets.preferences_dialog.preferences_dialog import PreferencesDialog
|
||||
|
||||
|
||||
class TestPreferencesDialog:
|
||||
def test_create(self, qtbot: QtBot):
|
||||
dialog = PreferencesDialog(shortcuts={}, default_export_file_name="")
|
||||
dialog = PreferencesDialog(
|
||||
shortcuts={},
|
||||
default_export_file_name="",
|
||||
preferences=Preferences.load(QSettings()),
|
||||
)
|
||||
qtbot.add_widget(dialog)
|
||||
|
||||
assert dialog.windowTitle() == "Preferences"
|
||||
|
||||
tab_widget = dialog.findChild(QTabWidget)
|
||||
assert isinstance(tab_widget, QTabWidget)
|
||||
assert tab_widget.count() == 3
|
||||
assert tab_widget.count() == 4
|
||||
assert tab_widget.tabText(0) == "General"
|
||||
assert tab_widget.tabText(1) == "Models"
|
||||
assert tab_widget.tabText(2) == "Shortcuts"
|
||||
assert tab_widget.tabText(3) == "Folder Watch"
|
||||
|
|
105
tests/widgets/transcription_task_folder_watcher_test.py
Normal file
105
tests/widgets/transcription_task_folder_watcher_test.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import shutil
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from buzz.model_loader import TranscriptionModel
|
||||
from buzz.transcriber import (
|
||||
Task,
|
||||
DEFAULT_WHISPER_TEMPERATURE,
|
||||
FileTranscriptionTask,
|
||||
TranscriptionOptions,
|
||||
FileTranscriptionOptions,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.file_transcription_preferences import (
|
||||
FileTranscriptionPreferences,
|
||||
)
|
||||
from buzz.widgets.preferences_dialog.models.folder_watch_preferences import (
|
||||
FolderWatchPreferences,
|
||||
)
|
||||
from buzz.widgets.transcription_task_folder_watcher import (
|
||||
TranscriptionTaskFolderWatcher,
|
||||
)
|
||||
|
||||
|
||||
class TestTranscriptionTaskFolderWatcher:
|
||||
def test_should_add_task_not_in_tasks(self, qtbot: QtBot):
|
||||
input_directory = mkdtemp()
|
||||
watcher = TranscriptionTaskFolderWatcher(
|
||||
tasks={},
|
||||
preferences=FolderWatchPreferences(
|
||||
enabled=True,
|
||||
input_directory=input_directory,
|
||||
output_directory="/path/to/output/folder",
|
||||
file_transcription_options=FileTranscriptionPreferences(
|
||||
language=None,
|
||||
task=Task.TRANSCRIBE,
|
||||
model=TranscriptionModel(),
|
||||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
default_export_file_name="",
|
||||
)
|
||||
|
||||
shutil.copy("testdata/whisper-french.mp3", input_directory)
|
||||
|
||||
with qtbot.wait_signal(watcher.task_found, timeout=10_000) as blocker:
|
||||
pass
|
||||
|
||||
task: FileTranscriptionTask = blocker.args[0]
|
||||
assert task.file_path == os.path.join(input_directory, "whisper-french.mp3")
|
||||
assert task.source == FileTranscriptionTask.Source.FOLDER_WATCH
|
||||
assert task.output_directory == "/path/to/output/folder"
|
||||
|
||||
def test_should_not_add_task_in_tasks(self, qtbot):
|
||||
input_directory = mkdtemp()
|
||||
tasks = {
|
||||
1: FileTranscriptionTask(
|
||||
file_path=os.path.join(input_directory, "whisper-french.mp3"),
|
||||
transcription_options=TranscriptionOptions(),
|
||||
file_transcription_options=FileTranscriptionOptions(file_paths=[]),
|
||||
output_directory="/path/to/output/folder",
|
||||
model_path="",
|
||||
),
|
||||
}
|
||||
|
||||
watcher = TranscriptionTaskFolderWatcher(
|
||||
tasks=tasks,
|
||||
preferences=FolderWatchPreferences(
|
||||
enabled=True,
|
||||
input_directory=input_directory,
|
||||
output_directory="/path/to/output/folder",
|
||||
file_transcription_options=FileTranscriptionPreferences(
|
||||
language=None,
|
||||
task=Task.TRANSCRIBE,
|
||||
model=TranscriptionModel(),
|
||||
word_level_timings=False,
|
||||
temperature=DEFAULT_WHISPER_TEMPERATURE,
|
||||
initial_prompt="",
|
||||
output_formats=set(),
|
||||
),
|
||||
),
|
||||
default_export_file_name="",
|
||||
)
|
||||
|
||||
# Ignored because already in tasks
|
||||
shutil.copy(
|
||||
"testdata/whisper-french.mp3",
|
||||
os.path.join(input_directory, "whisper-french.mp3"),
|
||||
)
|
||||
shutil.copy(
|
||||
"testdata/whisper-french.mp3",
|
||||
os.path.join(input_directory, "whisper-french2.mp3"),
|
||||
)
|
||||
|
||||
with qtbot.wait_signal(watcher.task_found, timeout=10_000) as blocker:
|
||||
pass
|
||||
|
||||
task: FileTranscriptionTask = blocker.args[0]
|
||||
assert task.file_path == os.path.join(input_directory, "whisper-french2.mp3")
|
||||
assert task.source == FileTranscriptionTask.Source.FOLDER_WATCH
|
||||
assert task.output_directory == "/path/to/output/folder"
|
Loading…
Reference in a new issue