Youtube-Comment-Export 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ytComments/gui/menu.py ADDED
@@ -0,0 +1,26 @@
1
+ from PySide6.QtWidgets import QMenuBar, QMainWindow
2
+ from .about import AboutWindow
3
+ from .settings import SettingsWindow
4
+
5
+ class MenuBar(QMenuBar):
6
+ """Menu bar of the application."""
7
+ def __init__(self, parent: QMainWindow):
8
+ super().__init__(parent)
9
+ self.app = parent
10
+
11
+ # Menu Settings
12
+ self.settings_dialog = SettingsWindow(self)
13
+ settings_menu = self.addAction("&Settings")
14
+ settings_menu.triggered.connect(self.show_settings)
15
+
16
+ # Menu About
17
+ self.about_dialog = AboutWindow(self)
18
+ help_menu = self.addAction("&About")
19
+ help_menu.triggered.connect(self.about_dialog.exec)
20
+
21
+ def show_settings(self):
22
+ """Show the Settings window."""
23
+ x = self.app.geometry().x() - 100
24
+ y = self.app.geometry().y() - 100
25
+ self.settings_dialog.setGeometry(x, y, 270, 200)
26
+ self.settings_dialog.exec()
@@ -0,0 +1,101 @@
1
+ import sys
2
+ from PySide6.QtWidgets import (
3
+ QApplication, QDialog, QProgressBar, QLabel, QVBoxLayout, QPushButton
4
+ )
5
+ from PySide6.QtCore import Qt
6
+
7
+ class LoadingWindow(QDialog):
8
+ """Window to show the progress of a long-running operation."""
9
+ def __init__(self, parent=None, countdown:bool = False):
10
+ if QApplication.instance() is None:
11
+ self.qapp = QApplication(sys.argv)
12
+
13
+ super().__init__()
14
+ self.setWindowTitle("Loading...")
15
+ self.countdown = countdown
16
+
17
+ if parent:
18
+ self.parent = parent
19
+ x = self.parent.app.geometry().x()
20
+ y = self.parent.app.geometry().y()
21
+ else:
22
+ x = 100
23
+ y = 100
24
+ self.setGeometry(x, y, 300, 100)
25
+
26
+ # Delete the Windows title buttons
27
+ self.setWindowFlags(Qt.Window | Qt.WindowTitleHint | Qt.WindowStaysOnTopHint)
28
+
29
+ # Initialize a close event
30
+ self.closing = False
31
+
32
+ # Create the progress bar
33
+ self.progress_bar = QProgressBar(self)
34
+ self.progress_bar.setMinimum(0)
35
+ self.progress_bar.setMaximum(100)
36
+ self.progress_bar.setStyleSheet("""
37
+ QProgressBar {
38
+ background-color: #f0f0f0; /* Background color */
39
+ border: 2px solid #8f8f8f; /* Border color */
40
+ border-radius: 5px; /* Rounded corners */
41
+ height: 15px; /* Height of the progress bar */
42
+ text-align: center; /* Center the text */
43
+ color: #000000; /* Text color */
44
+ }
45
+ QProgressBar::chunk {
46
+ background-color: #3c8dbc; /* Background color of the filled chunk */
47
+ border-radius: 5px; /* Rounded corners of the filled chunk */
48
+ }
49
+ """)
50
+
51
+ # Add an explanatory text
52
+ self.label = QLabel("Downloading...", self)
53
+ self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
54
+
55
+ # Add a countdown label
56
+ if self.countdown:
57
+ self.countdown_label = QLabel()
58
+ self.countdown_label.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
59
+
60
+ # Add a cancel button
61
+ msg = "Stop the download earlier, only the downloaded comments will be added to the old save"
62
+ self.cancel_button = QPushButton("Cancel")
63
+ self.cancel_button.clicked.connect(self.terminate)
64
+ self.cancel_button.setToolTip(msg)
65
+
66
+ # Create the layout
67
+ layout = QVBoxLayout()
68
+ layout.addWidget(self.progress_bar)
69
+ layout.addWidget(self.label)
70
+ if self.countdown: layout.addWidget(self.countdown_label)
71
+ layout.addWidget(self.cancel_button)
72
+ layout.setAlignment(Qt.AlignCenter)
73
+ self.setLayout(layout)
74
+
75
+ def update_progress(self, value, duration:int | None = None):
76
+ """Update the progress bar value."""
77
+ self.progress_bar.setValue(value)
78
+
79
+ # Update the countdown label
80
+ if self.countdown and duration is not None:
81
+ hours = int(duration // 3600)
82
+ minutes = int((duration % 3600) // 60)
83
+ seconds = int(duration % 60)
84
+
85
+ self.countdown_label.setText(
86
+ f"Remaining estimated time : {hours:02}h:{minutes:02}m:{seconds:02}s"
87
+ )
88
+
89
+ QApplication.processEvents()
90
+
91
+ def setText(self, text):
92
+ """Update the window title."""
93
+ self.label.setText(text)
94
+
95
+ def terminate(self):
96
+ """Close the window."""
97
+ self.closing = True
98
+ self.close()
99
+
100
+
101
+
@@ -0,0 +1,186 @@
1
+ from PySide6 import QtWidgets, QtGui, QtCore
2
+ from .error import run_error
3
+
4
+ class SettingsWindow(QtWidgets.QDialog):
5
+ """Window for settings."""
6
+ def __init__(self, parent=None):
7
+ super().__init__(parent)
8
+ self.menu = parent
9
+ self.setWindowTitle("Settings")
10
+ self.setModal(True)
11
+
12
+ # Settings Layout
13
+ layout = QtWidgets.QVBoxLayout(self)
14
+ layout.addLayout(self.init_color())
15
+ layout.addLayout(self.init_highlight())
16
+ layout.addLayout(self.init_max_sheets())
17
+ layout.addLayout(self.init_date_format())
18
+ layout.addLayout(self.init_auto_update())
19
+ layout.addSpacing(10)
20
+ layout.addLayout(self.init_buttons())
21
+
22
+ def init_color(self):
23
+ """Initialize Hex color code for the background of the comments on Excel."""
24
+ msg = "Hex color code for the background of the comments on Excel"
25
+ regex = QtCore.QRegularExpression(r'^#[0-9A-Fa-f]{6}$')
26
+ validator = QtGui.QRegularExpressionValidator(regex)
27
+
28
+ # Color Label
29
+ color_label = QtWidgets.QLabel("Background color :")
30
+ color_label.setToolTip(msg)
31
+
32
+ # Color1 input
33
+ self.color1_input = QtWidgets.QLineEdit()
34
+ self.color1_input.setText(self.menu.app.yt.settings.bg_color[0])
35
+ self.color1_input.setPlaceholderText("#C5D9F1")
36
+ self.color1_input.setToolTip(msg)
37
+ self.color1_input.setValidator(validator)
38
+
39
+ # Color2 input
40
+ self.color2_input = QtWidgets.QLineEdit()
41
+ self.color2_input.setText(self.menu.app.yt.settings.bg_color[1])
42
+ self.color2_input.setPlaceholderText("#FDE9D9")
43
+ self.color2_input.setToolTip(msg)
44
+ self.color2_input.setValidator(validator)
45
+
46
+ # Color Layout
47
+ color_layout = QtWidgets.QHBoxLayout()
48
+ color_layout.addWidget(color_label)
49
+ color_layout.addWidget(self.color1_input)
50
+ color_layout.addWidget(self.color2_input)
51
+ return color_layout
52
+
53
+ def init_highlight(self):
54
+ """Initialize the toggle for the background color."""
55
+ msg = "Check to activate the background color"
56
+
57
+ # Highlight label
58
+ highlight_label = QtWidgets.QLabel("Background color :")
59
+ highlight_label.setToolTip(msg)
60
+
61
+ # Highlight toggle
62
+ self.highlight_toggle = QtWidgets.QCheckBox()
63
+ self.highlight_toggle.setChecked(self.menu.app.yt.settings.bg_highlight)
64
+ self.highlight_toggle.setToolTip(msg)
65
+
66
+ # Highlight layout
67
+ highlight_layout = QtWidgets.QHBoxLayout()
68
+ highlight_layout.addWidget(highlight_label)
69
+ highlight_layout.addWidget(self.highlight_toggle)
70
+ return highlight_layout
71
+
72
+ def init_max_sheets(self):
73
+ """Initialize the maximum number of sheets per file to export."""
74
+ msg = "Maximum number of sheets per file to export"
75
+
76
+ # Sheets label
77
+ sheets_label = QtWidgets.QLabel("Sheets per file :")
78
+ sheets_label.setToolTip(msg)
79
+
80
+ # Sheets input
81
+ self.sheets_input = QtWidgets.QLineEdit()
82
+ self.sheets_input.setText(str(self.menu.app.yt.settings.max_sheets))
83
+ self.sheets_input.setToolTip(msg)
84
+
85
+ # Sheets layout
86
+ sheets_layout = QtWidgets.QHBoxLayout()
87
+ sheets_layout.addWidget(sheets_label)
88
+ sheets_layout.addWidget(self.sheets_input)
89
+ return sheets_layout
90
+
91
+ def init_date_format(self):
92
+ """Initialize the date format."""
93
+ msg = "Format of the date in the comments"
94
+
95
+ # Date label
96
+ date_label = QtWidgets.QLabel("Date format :")
97
+ date_label.setToolTip(msg)
98
+
99
+ # Date input
100
+ self.date_input = QtWidgets.QLineEdit()
101
+ self.date_input.setText(self.menu.app.yt.settings.date_format)
102
+ self.date_input.setToolTip(msg)
103
+
104
+ # Date layout
105
+ date_layout = QtWidgets.QHBoxLayout()
106
+ date_layout.addWidget(date_label)
107
+ date_layout.addWidget(self.date_input)
108
+ return date_layout
109
+
110
+ def init_auto_update(self):
111
+ """Initialize the toggle for the automatic update."""
112
+ msg = "Allow or not the program to search for software updates at startup"
113
+
114
+ # Update label
115
+ update_label = QtWidgets.QLabel("Search update at startup :")
116
+ update_label.setToolTip(msg)
117
+
118
+ # Update toggle
119
+ self.update_toggle = QtWidgets.QCheckBox()
120
+ self.update_toggle.setChecked(self.menu.app.yt.settings.auto_update)
121
+ self.update_toggle.setToolTip(msg)
122
+
123
+ # Update layout
124
+ update_layout = QtWidgets.QHBoxLayout()
125
+ update_layout.addWidget(update_label)
126
+ update_layout.addWidget(self.update_toggle)
127
+ return update_layout
128
+
129
+ def init_buttons(self):
130
+ """Initialize the OK and Cancel buttons."""
131
+ # OK button
132
+ ok_button = QtWidgets.QPushButton("OK")
133
+ ok_button.clicked.connect(self.on_ok)
134
+
135
+ # Cancel button
136
+ cancel_button = QtWidgets.QPushButton("Cancel")
137
+ cancel_button.clicked.connect(self.on_cancel)
138
+
139
+ # Buttons layout
140
+ buttons_layout = QtWidgets.QHBoxLayout()
141
+ buttons_layout.addWidget(ok_button)
142
+ buttons_layout.addWidget(cancel_button)
143
+ return buttons_layout
144
+
145
+ def on_ok(self):
146
+ """Manage the click on the OK button."""
147
+
148
+ # Validate the color inputs
149
+ try:
150
+ color1 = str(self.color1_input.text()).upper()
151
+ color2 = str(self.color2_input.text()).upper()
152
+ if color1 == "":
153
+ color1 = "#C5D9F1"
154
+ self.color1_input.setText("#C5D9F1")
155
+ if color2 == "":
156
+ color2 = "#FDE9D9"
157
+ self.color2_input.setText("#FDE9D9")
158
+
159
+ self.menu.app.yt.settings.bg_color = (color1, color2)
160
+ except ValueError as e:
161
+ run_error(e)
162
+ self.color1_input.setText(str(self.menu.app.yt.settings.bg_color[0]))
163
+ self.color2_input.setText(str(self.menu.app.yt.settings.bg_color[1]))
164
+ return
165
+
166
+ # Validate the max sheets inputs
167
+ try:
168
+ max_sheets = int(self.sheets_input.text())
169
+ self.menu.app.yt.settings.max_sheets = max_sheets
170
+ except ValueError as e:
171
+ run_error("The maximum number of sheets must be at least 2")
172
+ self.sheets_input.setText(str(self.menu.app.yt.settings.max_sheets))
173
+ return
174
+
175
+ # Validate the other settings
176
+ self.menu.app.yt.settings.bg_highlight = bool(self.highlight_toggle.isChecked())
177
+ self.menu.app.yt.settings.date_format = str(self.date_input.text())
178
+ self.menu.app.yt.settings.auto_update = bool(self.update_toggle.isChecked())
179
+ self.menu.app.yt.settings.save()
180
+ self.accept()
181
+
182
+ def on_cancel(self):
183
+ """Manage the click on the Cancel button."""
184
+ self.reject()
185
+
186
+
@@ -0,0 +1,158 @@
1
+ import requests
2
+ import os
3
+ import sys
4
+ from PySide6.QtWidgets import QMessageBox
5
+ from packaging.version import Version
6
+ from .progressbar import LoadingWindow
7
+ import ytComments.metadata as metadata
8
+
9
+ class UpdateManager:
10
+ """Update Downloader of the software"""
11
+ def __init__(self, repo_owner, repo_name, parent=None):
12
+ self.about = parent
13
+ self.repo_owner = repo_owner
14
+ self.repo_name = repo_name
15
+ self._file_size = 0
16
+ self._downloaded_size = 0
17
+ self.dir_update = "update"
18
+
19
+ @property
20
+ def file_size(self):
21
+ """Size of the file to download."""
22
+ return self._file_size
23
+
24
+ @file_size.setter
25
+ def file_size(self, value:int):
26
+ if not isinstance(value, int):
27
+ msg = "The file size must be an integer"
28
+ raise TypeError(
29
+ msg
30
+ )
31
+ self._file_size = value
32
+
33
+ @property
34
+ def downloaded_size(self):
35
+ """Size of the downloaded file."""
36
+ return self._downloaded_size
37
+
38
+ @downloaded_size.setter
39
+ def downloaded_size(self, value:int):
40
+ if not isinstance(value, int):
41
+ msg = "The file size must be an integer"
42
+ raise TypeError(
43
+ msg
44
+ )
45
+ self._downloaded_size = value
46
+
47
+ @property
48
+ def progress(self):
49
+ """Percentage of download."""
50
+ if self.file_size == 0:
51
+ return 0
52
+ return int(self.downloaded_size / self.file_size * 100)
53
+
54
+ def get_latest_release_info(self, extension=".exe"):
55
+ """
56
+ Retrieves the latest release information from GitHub,
57
+ including the tag and the URL of the .exe file.
58
+
59
+ Returns:
60
+ tuple: A tuple containing the latest version tag and
61
+ the URL of the .exe file, or (None, None) if an error occurs.
62
+ """
63
+ url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}/releases/latest"
64
+ try:
65
+ response = requests.get(url)
66
+ response.raise_for_status() # Throws an exception for HTTP error codes
67
+ data = response.json()
68
+ tag_name = data.get("tag_name")
69
+ assets = data.get("assets", [])
70
+ exe_url = None
71
+ for asset in assets:
72
+ if asset.get("name", "").lower().endswith(extension):
73
+ exe_url = asset.get("browser_download_url")
74
+ break
75
+ return tag_name, exe_url
76
+ except requests.exceptions.RequestException as e:
77
+ print(f"Error while retrieving the latest version from GitHub : {e}")
78
+ return None, None
79
+
80
+ def check_updates(self):
81
+ """
82
+ Checks if an update is available.
83
+
84
+ Returns:
85
+ bool: True if an update is available, False otherwise.
86
+ """
87
+ latest_tag, exe_url = self.get_latest_release_info()
88
+
89
+ if not latest_tag or not exe_url:
90
+ return False
91
+
92
+ latest_version = Version(latest_tag.lstrip('v'))
93
+ current_version = Version(metadata.version)
94
+
95
+ return latest_version > current_version
96
+
97
+ def update_software(self):
98
+ """Updates the software by downloading the .exe file from GitHub."""
99
+
100
+ print("Updating software...")
101
+ _, exe_url = self.get_latest_release_info()
102
+ if not exe_url:
103
+ print("No .exe file founded in the latest version.")
104
+ return
105
+ print(f"Downloading the new version from : {exe_url}")
106
+
107
+ response = requests.get(exe_url, stream=True)
108
+ response.raise_for_status()
109
+ self.file_size = int(response.headers.get("content-length", 0))
110
+
111
+ # Check if the program is an executable or a python file
112
+ if hasattr(sys, 'frozen'):
113
+ self.old_path = os.path.abspath(sys.executable) # .exe
114
+ else:
115
+ self.old_path = os.path.abspath(__file__) # .py
116
+
117
+ # Define the directory and file path for the update
118
+ new_filename = str(os.path.basename(exe_url))
119
+ self.new_filedir = os.path.join(
120
+ os.path.dirname(self.old_path),
121
+ self.dir_update
122
+ )
123
+ self.new_path = os.path.join(self.new_filedir, new_filename)
124
+
125
+ # Create the directory if it doesn't exist
126
+ os.makedirs(self.new_filedir, exist_ok=True)
127
+
128
+ # Display the progress bar
129
+ loading_window = LoadingWindow(self.about)
130
+ loading_window.show()
131
+
132
+ # Download the updated file
133
+ with open(self.new_path, "wb") as f:
134
+ for chunk in response.iter_content(chunk_size=8192):
135
+ f.write(chunk)
136
+ self.downloaded_size += len(chunk)
137
+ loading_window.update_progress(self.progress)
138
+ print(f"Progress update : {self.progress}%", end="\r")
139
+
140
+ loading_window.close()
141
+ print("Download of the update completed.")
142
+
143
+ def show_file_location_message(self, file_path):
144
+ """Shows a message to the user about the new version."""
145
+ msg_box = QMessageBox()
146
+ txt = f"""
147
+ The update has been downloaded here :\n\n
148
+ {file_path}\n
149
+ You can delete the old version.
150
+ """
151
+ msg_box.setWindowTitle("Update completed")
152
+ msg_box.setText(txt)
153
+ msg_box.setIcon(QMessageBox.Information)
154
+ msg_box.setStandardButtons(QMessageBox.Ok)
155
+
156
+ # Display the message
157
+ msg_box.exec()
158
+
ytComments/metadata.py ADDED
@@ -0,0 +1,22 @@
1
+ from configparser import ConfigParser
2
+ from pathlib import Path
3
+
4
+ def get_project_metadata():
5
+ """Metadata grabber from setup.cfg"""
6
+ config = ConfigParser()
7
+ setup_cfg = Path(__file__).parent.parent / "setup.cfg"
8
+
9
+ with open(setup_cfg, "r", encoding="utf-8") as f:
10
+ config.read_file(f)
11
+
12
+ return {
13
+ "version": config.get("metadata", "version"),
14
+ "author": config.get("metadata", "author"),
15
+ "name": config.get("metadata", "name"),
16
+ "description": config.get("metadata", "description"),
17
+ "url": config.get("metadata", "url"),
18
+ "license": config.get("metadata", "license"),
19
+ }
20
+
21
+ # Initialize the globals variables for an easy import
22
+ globals().update(get_project_metadata())
@@ -0,0 +1,172 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ class Settings():
5
+ """Settings Manager to save and apply settings to the software."""
6
+ def __init__(self):
7
+ self._channel_url = 'https://www.youtube.com/@GabbPiano'
8
+ self._directory = Path.home() / 'Desktop'
9
+ self._bg_color = ('#C5D9F1', '#FDE9D9')
10
+ self._bg_highlight = True
11
+ self._max_sheets = 250
12
+ self._date_format = 'DD/MM/YYYY'
13
+ self._auto_update = True
14
+
15
+ @property
16
+ def channel_url(self):
17
+ """URL of the YouTube channel."""
18
+ return self._channel_url
19
+
20
+ @channel_url.setter
21
+ def channel_url(self, value: str):
22
+ if value == None:
23
+ return
24
+ elif not isinstance(value, str):
25
+ msg = 'The channel URL must be a string'
26
+ raise TypeError(msg)
27
+ self._channel_url = str(value)
28
+
29
+ @property
30
+ def directory(self):
31
+ """Directory where the files will be saved."""
32
+ return self._directory
33
+
34
+ @directory.setter
35
+ def directory(self, value):
36
+ if value == None:
37
+ return
38
+ else:
39
+ path = Path(value)
40
+
41
+ if path.exists() and path.is_dir():
42
+ self._directory = path
43
+ else:
44
+ raise ValueError(
45
+ f"The directory {value} does not exist"
46
+ )
47
+
48
+ @property
49
+ def bg_color(self):
50
+ """Hex color code for the background of the comments on Excel."""
51
+ return self._bg_color
52
+
53
+ @bg_color.setter
54
+ def bg_color(self, value):
55
+ if (len(value) != 2
56
+ or not isinstance(value, tuple)
57
+ or not isinstance(value[0], str)
58
+ or not isinstance(value[1], str)
59
+ ):
60
+ raise ValueError(
61
+ "The value must be a tuple of two strings"
62
+ )
63
+ elif not (value[0].startswith('#') and len(value[0]) == 7):
64
+ raise ValueError(
65
+ "The first value must be a valid hex color code"
66
+ )
67
+ elif not (value[1].startswith('#') and len(value[1]) == 7):
68
+ raise ValueError(
69
+ "The second value must be a valid hex color code"
70
+ )
71
+ self._bg_color = value
72
+
73
+ @property
74
+ def bg_highlight(self):
75
+ """Allow or not the background color to be highlighted."""
76
+ return self._bg_highlight
77
+
78
+ @bg_highlight.setter
79
+ def bg_highlight(self, value):
80
+ if not isinstance(value, bool):
81
+ return
82
+ self._bg_highlight = value
83
+
84
+ @property
85
+ def max_sheets(self):
86
+ """Maximum number of sheets per file to export."""
87
+ return self._max_sheets
88
+
89
+ @max_sheets.setter
90
+ def max_sheets(self, value):
91
+ if not isinstance(value, int):
92
+ raise ValueError(
93
+ "The maximum number of sheets must be an integer"
94
+ )
95
+ elif value < 2:
96
+ raise ValueError(
97
+ "The maximum number of sheets must be at least 2"
98
+ )
99
+ self._max_sheets = value
100
+
101
+ @property
102
+ def date_format(self):
103
+ """Format of the date in the comments."""
104
+ return self._date_format
105
+
106
+ @date_format.setter
107
+ def date_format(self, value):
108
+ if not isinstance(value, str):
109
+ raise ValueError(
110
+ "the date format must be a string. E.g: 'DD/MM/YY' ; 'YYYY-MM-DD'"
111
+ )
112
+ self._date_format = value
113
+
114
+ @property
115
+ def auto_update(self):
116
+ """Allow or not the program to search for software updates at startup."""
117
+ return self._auto_update
118
+
119
+ @auto_update.setter
120
+ def auto_update(self, value):
121
+ if not isinstance(value, bool):
122
+ return
123
+ self._auto_update = value
124
+
125
+ def path_save(self):
126
+ """Path to the saved parameters file."""
127
+ file = "yt_parameters.json"
128
+ folder = "Atem83"
129
+ path_file = Path.home() / "Documents" / folder / file
130
+ return path_file
131
+
132
+ def save(self):
133
+ """Save the parameters in a configuration file."""
134
+ settings = {
135
+ 'channel_url': self.channel_url,
136
+ 'directory': str(self.directory),
137
+ 'bg_color': self.bg_color,
138
+ 'bg_highlight': self.bg_highlight,
139
+ 'max_sheets': self.max_sheets,
140
+ 'date_format': self.date_format,
141
+ 'auto_update': self.auto_update
142
+ }
143
+ path_file = self.path_save()
144
+
145
+ try:
146
+ # Create the folder if it doesn't exist
147
+ path_folder = Path(path_file).parent
148
+ path_folder.mkdir(parents=True, exist_ok=True)
149
+
150
+ # Write my backup
151
+ with open(path_file, 'w', encoding='utf-8') as file:
152
+ json.dump(settings, file, indent=4, ensure_ascii=False)
153
+ except:
154
+ print("Save failed.")
155
+ return
156
+
157
+ def load(self):
158
+ """Load the parameters from the configuration file."""
159
+ path_file = self.path_save()
160
+ try:
161
+ with open(path_file, "r", encoding='utf-8') as file:
162
+ settings = json.load(file)
163
+ self.channel_url = settings["channel_url"]
164
+ self.directory = settings["directory"]
165
+ self.bg_color = tuple(settings["bg_color"])
166
+ self.bg_highlight = settings["bg_highlight"]
167
+ self.max_sheets = settings["max_sheets"]
168
+ self.date_format = settings["date_format"]
169
+ self.auto_update = settings["auto_update"]
170
+ except:
171
+ print("Load failed, the parameters by default will be used.")
172
+ return