251 lines
9.7 KiB
Python
251 lines
9.7 KiB
Python
import os
|
|
import sys
|
|
from platform import system
|
|
|
|
from PyQt6.QtCore import QTimer, pyqtSlot
|
|
from PyQt6.QtGui import QAction, QIcon
|
|
from PyQt6.QtWidgets import (QApplication, QCheckBox, QDialog, QFileDialog,
|
|
QFormLayout, QLabel, QLineEdit, QMenu,
|
|
QMessageBox, QPushButton, QSystemTrayIcon,
|
|
QTextEdit, QVBoxLayout, QWidget)
|
|
|
|
from .obs_client import close_obs, is_obs_running, open_obs
|
|
from .playback import Player, get_latest_recording
|
|
from .recorder import Recorder
|
|
from .util import get_recordings_dir, open_file
|
|
|
|
|
|
class TitleDescriptionDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.setWindowTitle("Recording Details")
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.form_layout = QFormLayout()
|
|
|
|
self.title_label = QLabel("Title:")
|
|
self.title_input = QLineEdit(self)
|
|
self.form_layout.addRow(self.title_label, self.title_input)
|
|
|
|
self.description_label = QLabel("Description:")
|
|
self.description_input = QTextEdit(self)
|
|
self.form_layout.addRow(self.description_label, self.description_input)
|
|
|
|
layout.addLayout(self.form_layout)
|
|
|
|
self.submit_button = QPushButton("Save", self)
|
|
self.submit_button.clicked.connect(self.accept)
|
|
layout.addWidget(self.submit_button)
|
|
|
|
def get_values(self):
|
|
return self.title_input.text(), self.description_input.toPlainText()
|
|
|
|
class MainInterface(QWidget):
|
|
def __init__(self, app: QApplication):
|
|
super().__init__()
|
|
self.tray = QSystemTrayIcon(QIcon(resource_path("assets/duck.png")))
|
|
self.tray.show()
|
|
|
|
self.app = app
|
|
|
|
self.init_tray()
|
|
self.init_window()
|
|
|
|
if not is_obs_running():
|
|
self.obs_process = open_obs()
|
|
|
|
def init_window(self):
|
|
self.setWindowTitle("DuckTrack")
|
|
layout = QVBoxLayout(self)
|
|
|
|
self.toggle_record_button = QPushButton("Start Recording", self)
|
|
self.toggle_record_button.clicked.connect(self.toggle_record)
|
|
layout.addWidget(self.toggle_record_button)
|
|
|
|
self.toggle_pause_button = QPushButton("Pause Recording", self)
|
|
self.toggle_pause_button.clicked.connect(self.toggle_pause)
|
|
self.toggle_pause_button.setEnabled(False)
|
|
layout.addWidget(self.toggle_pause_button)
|
|
|
|
self.show_recordings_button = QPushButton("Show Recordings", self)
|
|
self.show_recordings_button.clicked.connect(lambda: open_file(get_recordings_dir()))
|
|
layout.addWidget(self.show_recordings_button)
|
|
|
|
self.play_latest_button = QPushButton("Play Latest Recording", self)
|
|
self.play_latest_button.clicked.connect(self.play_latest_recording)
|
|
layout.addWidget(self.play_latest_button)
|
|
|
|
self.play_custom_button = QPushButton("Play Custom Recording", self)
|
|
self.play_custom_button.clicked.connect(self.play_custom_recording)
|
|
layout.addWidget(self.play_custom_button)
|
|
|
|
self.replay_recording_button = QPushButton("Replay Recording", self)
|
|
self.replay_recording_button.clicked.connect(self.replay_recording)
|
|
self.replay_recording_button.setEnabled(False)
|
|
layout.addWidget(self.replay_recording_button)
|
|
|
|
self.quit_button = QPushButton("Quit", self)
|
|
self.quit_button.clicked.connect(self.quit)
|
|
layout.addWidget(self.quit_button)
|
|
|
|
self.natural_scrolling_checkbox = QCheckBox("Natural Scrolling", self, checked=system() == "Darwin")
|
|
layout.addWidget(self.natural_scrolling_checkbox)
|
|
|
|
self.natural_scrolling_checkbox.stateChanged.connect(self.toggle_natural_scrolling)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def init_tray(self):
|
|
self.menu = QMenu()
|
|
self.tray.setContextMenu(self.menu)
|
|
|
|
self.toggle_record_action = QAction("Start Recording")
|
|
self.toggle_record_action.triggered.connect(self.toggle_record)
|
|
self.menu.addAction(self.toggle_record_action)
|
|
|
|
self.toggle_pause_action = QAction("Pause Recording")
|
|
self.toggle_pause_action.triggered.connect(self.toggle_pause)
|
|
self.toggle_pause_action.setVisible(False)
|
|
self.menu.addAction(self.toggle_pause_action)
|
|
|
|
self.show_recordings_action = QAction("Show Recordings")
|
|
self.show_recordings_action.triggered.connect(lambda: open_file(get_recordings_dir()))
|
|
self.menu.addAction(self.show_recordings_action)
|
|
|
|
self.play_latest_action = QAction("Play Latest Recording")
|
|
self.play_latest_action.triggered.connect(self.play_latest_recording)
|
|
self.menu.addAction(self.play_latest_action)
|
|
|
|
self.play_custom_action = QAction("Play Custom Recording")
|
|
self.play_custom_action.triggered.connect(self.play_custom_recording)
|
|
self.menu.addAction(self.play_custom_action)
|
|
|
|
self.replay_recording_action = QAction("Replay Recording")
|
|
self.replay_recording_action.triggered.connect(self.replay_recording)
|
|
self.menu.addAction(self.replay_recording_action)
|
|
self.replay_recording_action.setVisible(False)
|
|
|
|
self.quit_action = QAction("Quit")
|
|
self.quit_action.triggered.connect(self.quit)
|
|
self.menu.addAction(self.quit_action)
|
|
|
|
self.menu.addSeparator()
|
|
|
|
self.natural_scrolling_option = QAction("Natural Scrolling", checkable=True, checked=system() == "Darwin")
|
|
self.natural_scrolling_option.triggered.connect(self.toggle_natural_scrolling)
|
|
self.menu.addAction(self.natural_scrolling_option)
|
|
|
|
@pyqtSlot()
|
|
def replay_recording(self):
|
|
player = Player()
|
|
if hasattr(self, "last_played_recording_path"):
|
|
player.play(self.last_played_recording_path)
|
|
else:
|
|
self.display_error_message("No recording has been played yet!")
|
|
|
|
@pyqtSlot()
|
|
def play_latest_recording(self):
|
|
player = Player()
|
|
recording_path = get_latest_recording()
|
|
self.last_played_recording_path = recording_path
|
|
self.replay_recording_action.setVisible(True)
|
|
self.replay_recording_button.setEnabled(True)
|
|
player.play(recording_path)
|
|
|
|
@pyqtSlot()
|
|
def play_custom_recording(self):
|
|
player = Player()
|
|
directory = QFileDialog.getExistingDirectory(None, "Select Recording", get_recordings_dir())
|
|
if directory:
|
|
self.last_played_recording_path = directory
|
|
self.replay_recording_button.setEnabled(True)
|
|
self.replay_recording_action.setVisible(True)
|
|
player.play(directory)
|
|
|
|
@pyqtSlot()
|
|
def quit(self):
|
|
if hasattr(self, "recorder_thread"):
|
|
self.toggle_record()
|
|
if hasattr(self, "obs_process"):
|
|
close_obs(self.obs_process)
|
|
self.app.quit()
|
|
|
|
def closeEvent(self, event):
|
|
self.quit()
|
|
|
|
@pyqtSlot()
|
|
def toggle_natural_scrolling(self):
|
|
sender = self.sender()
|
|
|
|
if sender == self.natural_scrolling_checkbox:
|
|
state = self.natural_scrolling_checkbox.isChecked()
|
|
self.natural_scrolling_option.setChecked(state)
|
|
else:
|
|
state = self.natural_scrolling_option.isChecked()
|
|
self.natural_scrolling_checkbox.setChecked(state)
|
|
|
|
@pyqtSlot()
|
|
def toggle_pause(self):
|
|
if self.recorder_thread._is_paused:
|
|
self.recorder_thread.resume_recording()
|
|
self.toggle_pause_action.setText("Pause Recording")
|
|
self.toggle_pause_button.setText("Pause Recording")
|
|
else:
|
|
self.recorder_thread.pause_recording()
|
|
self.toggle_pause_action.setText("Resume Recording")
|
|
self.toggle_pause_button.setText("Resume Recording")
|
|
|
|
@pyqtSlot()
|
|
def toggle_record(self):
|
|
if not hasattr(self, "recorder_thread"):
|
|
self.recorder_thread = Recorder(natural_scrolling=self.natural_scrolling_checkbox.isChecked())
|
|
self.recorder_thread.recording_stopped.connect(self.on_recording_stopped)
|
|
self.recorder_thread.start()
|
|
self.update_menu(True)
|
|
else:
|
|
self.recorder_thread.stop_recording()
|
|
self.recorder_thread.terminate()
|
|
|
|
recording_dir = self.recorder_thread.recording_path
|
|
|
|
del self.recorder_thread
|
|
|
|
dialog = TitleDescriptionDialog()
|
|
QTimer.singleShot(0, dialog.raise_)
|
|
result = dialog.exec()
|
|
|
|
if result == QDialog.DialogCode.Accepted:
|
|
title, description = dialog.get_values()
|
|
|
|
if title:
|
|
renamed_dir = os.path.join(os.path.dirname(recording_dir), title)
|
|
os.rename(recording_dir, renamed_dir)
|
|
|
|
with open(os.path.join(renamed_dir, 'README.md'), 'w') as f:
|
|
f.write(description)
|
|
|
|
self.on_recording_stopped()
|
|
|
|
@pyqtSlot()
|
|
def on_recording_stopped(self):
|
|
self.update_menu(False)
|
|
|
|
def update_menu(self, is_recording: bool):
|
|
self.toggle_record_button.setText("Stop Recording" if is_recording else "Start Recording")
|
|
self.toggle_record_action.setText("Stop Recording" if is_recording else "Start Recording")
|
|
|
|
self.toggle_pause_button.setEnabled(is_recording)
|
|
self.toggle_pause_action.setVisible(is_recording)
|
|
|
|
def display_error_message(self, message):
|
|
QMessageBox.critical(None, "Error", message)
|
|
|
|
def resource_path(relative_path: str) -> str:
|
|
if hasattr(sys, '_MEIPASS'):
|
|
base_path = getattr(sys, "_MEIPASS")
|
|
else:
|
|
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
|
|
|
return os.path.join(base_path, relative_path) |