diff --git a/README.md b/README.md index 32d77e9..c12c451 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,21 @@

ManimStudio

-
+
+ + documentation + + + + pages-build-deployment + + + + release-please + +
+
ManimStudio is a video editor application for editing Python ManimCE video projects. ## Table of Contents diff --git a/docs/conf.py b/docs/conf.py index 9ec4305..553a9df 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,6 +3,7 @@ sys.path.insert(0, os.path.abspath("../src/core")) sys.path.insert(0, os.path.abspath("../src/ui")) +sys.path.insert(0, os.path.abspath("../src/utils")) print("sys.path: ", sys.path) diff --git a/docs/index.rst b/docs/index.rst index 2c1565b..0040865 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Welcome to ManimStudio's documentation! src/core/settings src/core/project_creation_dialog src/core/project_opening_dialog + src/utils/logger_utility src/ui/form_ui src/ui/settings_ui src/ui/videoeditor_ui diff --git a/docs/src/utils/logger_utility.rst b/docs/src/utils/logger_utility.rst new file mode 100644 index 0000000..2a6c38a --- /dev/null +++ b/docs/src/utils/logger_utility.rst @@ -0,0 +1,7 @@ +logger_utility module +===================== + +.. automodule:: logger_utility + :members: + :undoc-members: + :show-inheritance: diff --git a/requirements.txt b/requirements.txt index bace28c..c0f629d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,13 @@ dill==0.3.8 +loguru==0.7.2 +markdown-it-py==3.0.0 +mdurl==0.1.2 +Pygments==2.17.2 PyQt6==6.6.1 PyQt6-Qt6==6.6.1 PyQt6-sip==13.6.0 PySide6==6.6.1 PySide6-Addons==6.6.1 PySide6-Essentials==6.6.1 +rich==13.7.0 shiboken6==6.6.1 diff --git a/src/core/main.py b/src/core/main.py index defc849..132392a 100644 --- a/src/core/main.py +++ b/src/core/main.py @@ -32,6 +32,9 @@ from src.core.project_creation_dialog import ProjectCreationDialog from src.core.project_opening_dialog import ProjectOpeningDialog +# Logger +from src.utils.logger_utility import logger + class Main(QWidget): """Main class for the application""" @@ -39,6 +42,7 @@ class Main(QWidget): styleSheetUpdated = Signal(str) videoEditorOpened = Signal(str) + @logger.catch def __init__(self, parent=None): """Initializer""" super().__init__(parent) @@ -46,9 +50,10 @@ def __init__(self, parent=None): self.settings = load_settings() self.themes = load_themes() self.current_theme = load_current_theme() - + logger.info("Main window initialized") self.load_ui() + @logger.catch def apply_stylesheet(self): """Apply the stylesheet to the main window and update the image based on the theme""" @@ -68,7 +73,9 @@ def apply_stylesheet(self): self.ui.label.setPixmap(QPixmap(image_path)) self.styleSheetUpdated.emit(self.customStyleSheet) + logger.info("Stylesheet applied") + @logger.catch def load_ui(self): """Load the UI from the .ui file""" loader = QUiLoader() @@ -86,6 +93,9 @@ def load_ui(self): self.ui.newProjectBtn.clicked.connect(self.showProjectCreationDialog) self.ui.openProjectBtn.clicked.connect(self.showProjectOpenDialog) + logger.info("UI loaded") + + @logger.catch def open_settings_dialog(self): """Open the settings dialog""" self.settingsDialog = QDialog() @@ -124,6 +134,7 @@ def open_settings_dialog(self): self.settingsDialog.exec() + @logger.catch def update_settings_from_dialog(self): """Update the settings from the dialog, and update the UI""" @@ -159,6 +170,7 @@ def update_settings_from_dialog(self): # Apply the stylesheet self.apply_stylesheet() + @logger.catch def showProjectCreationDialog(self): """Show the project creation dialog""" try: @@ -176,8 +188,9 @@ def showProjectCreationDialog(self): if dialog.exec(): pass except Exception as e: - print(f"Error showing project creation dialog: {e}") + logger.error(f"Error showing project creation dialog: {e}") + @logger.catch def showProjectOpenDialog(self): """Show the project open dialog""" try: @@ -191,8 +204,9 @@ def showProjectOpenDialog(self): ) # Close the main window (WelcomeScreen) as well dialog.exec() except Exception as e: - print(f"Error showing project open dialog: {e}") + logger.error(f"Error showing project open dialog: {e}") + @logger.catch def openVideoEditor(self, projectFilePath): """Open the video editor with the project file""" try: @@ -206,10 +220,9 @@ def openVideoEditor(self, projectFilePath): # Emit the signal after VideoEditor dialog is opened self.videoEditorOpened.emit(projectFilePath) - self.videoEditor.exec() except Exception as e: - print(f"Error opening video editor: {e}") + logger.error(f"Error opening video editor: {e}") pass diff --git a/src/core/project_creation_dialog.py b/src/core/project_creation_dialog.py index 5652871..c1b0820 100644 --- a/src/core/project_creation_dialog.py +++ b/src/core/project_creation_dialog.py @@ -1,7 +1,7 @@ import os import sys -from pathlib import Path import dill +from pathlib import Path from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox from PySide6.QtCore import Signal @@ -17,12 +17,15 @@ getRecentProjectCreationPaths, ) +from src.utils.logger_utility import logger + class ProjectCreationDialog(QDialog): """Dialog for creating a new project""" projectCreated = Signal(str) + @logger.catch def __init__(self, parent=None): """Initializer""" super(ProjectCreationDialog, self).__init__(parent) @@ -32,18 +35,21 @@ def __init__(self, parent=None): self.ui.folderSelectComboBox.mouseDoubleClickEvent = self.selectFolder self.ui.createProjectPushButton.clicked.connect(self.createProject) + @logger.catch def loadRecentProjectPaths(self): """Load the recent project paths into the combo box""" recentPaths = getRecentProjectCreationPaths() for path in recentPaths: self.ui.folderSelectComboBox.addItem(path) + @logger.catch def selectFolder(self, event): """Select a folder for the project""" folder = QFileDialog.getExistingDirectory(self, "Select Project Folder") if folder: self.ui.folderSelectComboBox.addItem(folder) + @logger.catch def createProject(self): """Create a new project and emit the projectCreated signal""" projectName = self.ui.projectNameLineEdit.text() @@ -53,6 +59,7 @@ def createProject(self): QMessageBox.critical( self, "Error", "Please enter a project name and select a folder" ) + logger.error("Project name or folder not entered") return projectPath = os.path.join(projectFolder, projectName) diff --git a/src/core/project_opening_dialog.py b/src/core/project_opening_dialog.py index 0e4488b..b87b8ec 100644 --- a/src/core/project_opening_dialog.py +++ b/src/core/project_opening_dialog.py @@ -1,15 +1,28 @@ import os +import sys +from pathlib import Path from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox from PySide6.QtCore import Signal + + +# Add the parent directory of 'src' to sys.path +current_dir = Path(__file__).resolve().parent +parent_dir = current_dir.parent.parent # Adjust according to your project structure +sys.path.append(str(parent_dir)) + + from src.ui.project_opening_dialog_ui import Ui_Form as Ui_ProjectOpeningDialog from settings import getRecentProjectPaths, addRecentProjectPath +from src.utils.logger_utility import logger + class ProjectOpeningDialog(QDialog): """Dialog for opening an existing project""" projectSelected = Signal(str) + @logger.catch def __init__(self, parent=None): """Initializer""" super().__init__(parent) @@ -20,12 +33,14 @@ def __init__(self, parent=None): self.ui.openProjectPushButton.clicked.connect(self.openProject) self.ui.projectSelectComboBox.mouseDoubleClickEvent = self.browseForProject + @logger.catch def loadRecentProjects(self): """Load the recent projects into the combo box""" recentProjects = getRecentProjectPaths() for projectPath in recentProjects: self.ui.projectSelectComboBox.addItem(projectPath) + @logger.catch def browseForProject(self, event): """Browse for a project""" projectPath, _ = QFileDialog.getOpenFileName( @@ -36,6 +51,7 @@ def browseForProject(self, event): addRecentProjectPath(projectPath) self.accept() + @logger.catch def openProject(self, event): """Open the selected project""" selectedProject = self.ui.projectSelectComboBox.currentText() diff --git a/src/core/settings.py b/src/core/settings.py index a36b3d5..3ac4977 100644 --- a/src/core/settings.py +++ b/src/core/settings.py @@ -4,13 +4,15 @@ import sys import json -"""Settings""" +from src.utils.logger_utility import logger + -# Global static base settings path +# Global base settings and theme path base_settings_path = Path(os.getcwd()) / ".config" / "settings.json" base_theme_path = Path(os.getcwd()) / "src" / "themes" / "themes.json" +@logger.catch def getSettingsPath(): """Get the settings path based on the platform""" homeDir = Path.home() @@ -38,6 +40,7 @@ def getSettingsPath(): json.dump(settings, file, indent=4) +@logger.catch def getThemesPath(): """Get the themes path""" return base_theme_path @@ -45,17 +48,21 @@ def getThemesPath(): themesPath = getThemesPath() +"""Settings""" + +@logger.catch def load_settings(): """Load the settings from the settings.json file""" try: with open(settingsPath, "r") as file: return json.load(file) except Exception as e: - print(e) + logger.error(e) return {} +@logger.catch def update_settings(new_settings): """Overwrite the settings file with the new settings""" try: @@ -63,22 +70,25 @@ def update_settings(new_settings): json.dump(new_settings, file, indent=4) return True except Exception as e: + logger.error(e) return False """Themes""" +@logger.catch def load_themes(): """Load the themes from the themes.json file""" try: with open(themesPath, "r") as file: return json.load(file) except Exception as e: - print(e) + logger.error(e) return {} +@logger.catch def load_current_theme(): """Load the current theme""" settings = load_settings() @@ -91,10 +101,11 @@ def load_current_theme(): ) as file: return json.load(file) except Exception as e: - print(e) + logger.error(e) return {} +@logger.catch def addRecentProjectCreationPath(projectCreationPath): """Add a recent project creation path to the settings file""" settings = load_settings() @@ -107,6 +118,7 @@ def addRecentProjectCreationPath(projectCreationPath): update_settings(settings) +@logger.catch def addRecentProjectPath(projectPath): """Add a recent project path to the settings file""" settings = load_settings() @@ -119,11 +131,13 @@ def addRecentProjectPath(projectPath): update_settings(settings) +@logger.catch def getRecentProjectCreationPaths(): """Get the recent project creation paths""" return load_settings().get("recentProjectCreationPaths", []) +@logger.catch def getRecentProjectPaths(): """Get the recent project paths""" return load_settings().get("recentProjectPaths", []) diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/logger_utility.py b/src/utils/logger_utility.py new file mode 100644 index 0000000..6460b94 --- /dev/null +++ b/src/utils/logger_utility.py @@ -0,0 +1,48 @@ +from loguru import logger +from rich.traceback import Traceback +from rich.console import Console +import io +import sys + + +def rich_formatter(record): + """Rich formatter for loguru logger""" + format_ = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}\n" + if record["exception"] is not None: + output = io.StringIO() + console = Console(file=output, force_terminal=True) + traceback = Traceback.from_exception(*record["exception"]) + console.print(traceback) + record["extra"]["rich_exception"] = output.getvalue() + format_ += "{extra[rich_exception]}" + return format_ + + +def setup_logger(): + """Setup the logger configuration for the application""" + + # Remove default logger configuration + logger.remove() + + # Add a handler for console logging with rich formatting + logger.add(sys.stderr, format=rich_formatter, level="INFO", colorize=True) + + # Add a handler for file logging + logger.add("logs/app_{time}.log", rotation="1 week", level="DEBUG") + + +setup_logger() + +# Example log messages: +# @logger.catch +# def divide(a, b): +# a / b + + +# divide(1, 0) + +# logger.info("This is an info message") +# logger.debug("This is a debug message") +# logger.warning("This is a warning message") +# logger.error("This is an error message") +# logger.critical("This is a critical message")