diff --git a/README.md b/README.md
index 32d77e9..c12c451 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,21 @@
ManimStudio
-
+
+
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")