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.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: Youtube-Comment-Export
3
+ Version: 0.1.0
4
+ Summary: Export a YouTube channel Comments in a nice Excel format.
5
+ Home-page: https://github.com/Atem83/Youtube-Comment-Export
6
+ Author: Michaël AUGUSTE
7
+ License: MIT
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Description-Content-Type: text/markdown
12
+ License-File: License
13
+ Requires-Dist: yt_dlp
14
+ Requires-Dist: polars
15
+ Requires-Dist: xlsxwriter
16
+ Requires-Dist: colorama
17
+ Requires-Dist: PySide6
18
+ Dynamic: license-file
19
+
20
+ <h1 align="center">Youtube Comment Export</h1>
21
+
22
+ <h3 align="center">Export the comments of a YouTube channel in Excel files.</h3>
23
+
24
+ <div align="center">
25
+
26
+ [![PyPI](https://img.shields.io/pypi/v/Youtube-Comment-Export?style=flat)](https://pypi.org/project/Youtube-Comment-Export)
27
+ <a href="https://opensource.org/license/mit">![License](https://img.shields.io/badge/License-MIT-blue)</a>
28
+ <a href="https://github.com/Atem83/Youtube-Comment-Export/archive/refs/heads/main.zip">![Download](https://img.shields.io/badge/Source_Code-Download-blue)</a>
29
+ ![LOC](https://tokei.rs/b1/github/Atem83/Youtube-Comment-Export?category=lines)
30
+
31
+ </div>
32
+
33
+ <div align="center">
34
+ <div style="display: flex; justify-content: space-around;">
35
+ <img src="https://raw.githubusercontent.com/Atem83/Youtube-Comment-Export/main/images/GUI light theme.png" alt="GUI Light Theme" style="width: 35%;">
36
+ <img src="https://raw.githubusercontent.com/Atem83/Youtube-Comment-Export/main/images/GUI dark theme.png" alt="GUI Dark Theme" style="width: 35%;">
37
+ </div>
38
+ </div>
39
+
40
+ <br>
41
+
42
+ <div align="center">
43
+ <img src="https://raw.githubusercontent.com/Atem83/Youtube-Comment-Export/main/images/GUI Settings.png" alt="GUI Settings" style="width: 35%;">
44
+ </div>
45
+
46
+ <h2 align="center"> Excel Main Sheet </h2>
47
+
48
+ <div align="center">
49
+ <img src="https://raw.githubusercontent.com/Atem83/Youtube-Comment-Export/main/images/Excel videos sheet.png" alt="Excel videos sheet" style="width: 100%;">
50
+ </div>
51
+
52
+ <h2 align="center"> Excel Comments Sheet </h2>
53
+
54
+ <div align="center">
55
+ <img src="https://raw.githubusercontent.com/Atem83/Youtube-Comment-Export/main/images/Excel comments sheet.png" alt="Excel comments sheet" style="width: 100%;">
56
+ </div>
57
+
58
+ <h2 align="center"> Features </h2>
59
+
60
+ - Download data from a YouTube channel
61
+ - Download comments from a YouTube channel
62
+ - Export the data and comments in Excel files in a nice format
63
+ - Light ou Dark theme according to your OS theme
64
+ - Import old saves, this way :
65
+ - you can keep a more precise date of the comments
66
+ - you can keep comments even if they have been deleted
67
+ - sadly, it will not be possible to save downloading time because all the comments needs to be downloaded again
68
+
69
+ <h2 align="center"> Installation </h2>
70
+
71
+ <div align="center">
72
+
73
+ ```
74
+ pip install Youtube-Comment-Export
75
+ ```
76
+
77
+ [<img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/Atem83/Youtube-Comment-Export?&color=green&label=Source%20Code&logo=Python&logoColor=yellow&style=for-the-badge" width="300">](https://github.com/Atem83/Youtube-Comment-Export/archive/refs/heads/main.zip)
78
+
79
+ </div>
80
+
81
+ <h2 align="center"> GUI Usage </h2>
82
+
83
+ ```python
84
+ import ytComments
85
+
86
+ app = ytComments.App()
87
+ app.run()
88
+ ```
89
+ <h2 align="center"> Python Usage </h2>
90
+
91
+ ```python
92
+ import ytComments
93
+
94
+ channel_url = 'https://www.youtube.com/@GabbPiano'
95
+ path_save1 = r'C:\Users\User\Desktop\Save File 1.xlsx'
96
+ path_save2 = r'C:\Users\User\Desktop\Save File 2.xlsx'
97
+ path_save = [path_save1, path_save2]
98
+
99
+ yt = ytComments.yt_manager(channel_url, old_save=path_save)
100
+ yt.import_excel() # Import the old save data
101
+ yt.export_excel() # Export the data in Excel files
102
+
103
+ ```
@@ -0,0 +1,19 @@
1
+ youtube_comment_export-0.1.0.dist-info/licenses/License,sha256=ZxJN3uGdLiUuaBcpc8ZRLNdJMOYjrvsIEzE-XdeHpcs,1094
2
+ ytComments/__init__.py,sha256=Dabtm_kYGtUcEjR9_ijRX9R6xbZfZzS85UFT25YUvXQ,143
3
+ ytComments/metadata.py,sha256=kXNbzQrcUDC_p1SylgM5AWmbLnUpPm6TJ-xpbuPm8SQ,744
4
+ ytComments/settingsManager.py,sha256=KWJ-S0NzDK9435rAPi50z1BhAM05EWcoR4uUgOgIZGc,5746
5
+ ytComments/youtube.py,sha256=ngDLkWwgqRQylH5NIrIwtYerwsqY4RZvEgKMbXFmHKs,23831
6
+ ytComments/gui/__init__.py,sha256=DlncYzZsj9SA99dXl_mnkn0MCW5DyM2qXboHH8sqAJ0,422
7
+ ytComments/gui/about.py,sha256=nF5g5f4rCiouVVucVhrNLFZBR7c8Oln41FTe3Ma4VNI,5100
8
+ ytComments/gui/app.py,sha256=U3zJYD3GM_AXWk5I2z34HKRXUc6rnCbsSVzzgDaYb7Q,3489
9
+ ytComments/gui/error.py,sha256=LnCj2mUOWcJlj46kPEPXlMF6PfwyPhiecDcfqqDRnd4,3134
10
+ ytComments/gui/main.py,sha256=WUolWkpl8b7i0ELdc7LlOD9WxqZE9GMnDXMIDCoKKqI,6381
11
+ ytComments/gui/menu.py,sha256=pgdFzRR4FZRmZ54ePl3gkiy3fitgjGnJTtvbqjpO1SE,938
12
+ ytComments/gui/progressbar.py,sha256=vZnR2XzBRfFESjj1KK-v5wQRQbBD7CvzuaAe_UoA6cI,3775
13
+ ytComments/gui/settings.py,sha256=MgegrkGU6vKshwuTjkeM4wNCEWkekkvLQe_6SLmbiys,7214
14
+ ytComments/gui/update.py,sha256=8RhfKdj-SRso4EwsHRcAZdDv5c9wCtOQxd4fZy95Cc4,5614
15
+ youtube_comment_export-0.1.0.dist-info/METADATA,sha256=tIpJpqaICF-zYIRxGoa-yQke4oE_jgtyiu2MEMPXF_U,3794
16
+ youtube_comment_export-0.1.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
17
+ youtube_comment_export-0.1.0.dist-info/entry_points.txt,sha256=gREMK5n_tans2C7FgvOYXX8DgzxYFWy3OM3ttEeAJw4,58
18
+ youtube_comment_export-0.1.0.dist-info/top_level.txt,sha256=f9W7wZM2h6hpL3Rv_Tyoi5vG6XCe-K54WGyFj48ycok,11
19
+ youtube_comment_export-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (77.0.3)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ytComments-gui = ytComments.gui.App:run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michaël AUGUSTE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ytComments
ytComments/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .youtube import yt_manager
2
+ from .gui.app import App
3
+ from .settingsManager import Settings
4
+
5
+ __all__ = ["yt_manager", "App", "Settings"]
@@ -0,0 +1,19 @@
1
+ from .app import App
2
+ from .main import MainWindow
3
+ from .menu import MenuBar
4
+ from .progressbar import LoadingWindow
5
+ from .settings import SettingsWindow
6
+ from .about import AboutWindow
7
+ from .update import UpdateManager
8
+ from .error import run_error
9
+
10
+ __all__ = [
11
+ "App",
12
+ "MainWindow",
13
+ "MenuBar",
14
+ "LoadingWindow",
15
+ "SettingsWindow",
16
+ "AboutWindow",
17
+ "UpdateManager",
18
+ "run_error",
19
+ ]
@@ -0,0 +1,131 @@
1
+ import re
2
+ import ytComments.metadata as metadata
3
+ from PySide6 import QtWidgets, QtCore, QtGui
4
+ from .update import UpdateManager
5
+ from .error import run_error
6
+
7
+ class AboutWindow(QtWidgets.QDialog):
8
+ """About menu of the application"""
9
+ def __init__(self, parent):
10
+ super().__init__(parent)
11
+ self.menu = parent
12
+ self.setWindowTitle("About")
13
+ layout = QtWidgets.QVBoxLayout()
14
+
15
+ self.init_version()
16
+ layout.addLayout(self.layout_version)
17
+
18
+ self.init_author()
19
+ layout.addLayout(self.layout_author)
20
+
21
+ self.init_license()
22
+ layout.addLayout(self.layout_license)
23
+
24
+ self.init_description()
25
+ layout.addSpacing(10)
26
+ layout.addLayout(self.layout_description)
27
+
28
+ self.setLayout(layout)
29
+
30
+ def init_version(self):
31
+ """Version number and search update button."""
32
+ self.layout_version = QtWidgets.QHBoxLayout()
33
+ version_label = QtWidgets.QLabel("Version : ")
34
+ version_text = QtWidgets.QLabel(metadata.version)
35
+
36
+ # Add the button to check for updates
37
+ self.check_update_button = QtWidgets.QPushButton("Check updates")
38
+ self.check_update_button.setStyleSheet(self.style_link())
39
+ self.check_update_button.clicked.connect(self.check_for_updates)
40
+
41
+ self.layout_version.addWidget(version_label)
42
+ self.layout_version.addWidget(version_text)
43
+ self.layout_version.addWidget(self.check_update_button)
44
+ self.layout_version.setAlignment(QtCore.Qt.AlignLeft)
45
+
46
+ def init_author(self):
47
+ """Program Author."""
48
+ self.layout_author = QtWidgets.QHBoxLayout()
49
+ author_label = QtWidgets.QLabel("Author : ")
50
+ author_text = QtWidgets.QLabel(metadata.author)
51
+
52
+ self.layout_author.addWidget(author_label)
53
+ self.layout_author.addWidget(author_text)
54
+ self.layout_author.setAlignment(QtCore.Qt.AlignLeft)
55
+
56
+ def init_license(self):
57
+ """License and code source url."""
58
+ self.layout_license = QtWidgets.QHBoxLayout()
59
+ license_label = QtWidgets.QLabel("License : ")
60
+ license_text = QtWidgets.QLabel(metadata.license)
61
+
62
+ url_button = QtWidgets.QPushButton("code source")
63
+ url_button.setStyleSheet(self.style_link())
64
+ url_button.clicked.connect(
65
+ lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(metadata.url))
66
+ )
67
+
68
+ self.layout_license.addWidget(license_label)
69
+ self.layout_license.addWidget(license_text)
70
+ self.layout_license.addWidget(url_button)
71
+ self.layout_license.setAlignment(QtCore.Qt.AlignLeft)
72
+
73
+ def init_description(self):
74
+ """Software description."""
75
+ self.layout_description = QtWidgets.QHBoxLayout()
76
+ description_text = QtWidgets.QLabel(metadata.description)
77
+ self.layout_description.addWidget(description_text)
78
+
79
+ def style_link(self):
80
+ """Style for links."""
81
+ css = """
82
+ QPushButton {
83
+ border: none; /* No border */
84
+ background: none; /* No background */
85
+ color: DodgerBlue; /* Text color */
86
+ text-decoration: underline; /* Underlined text to look like a link */
87
+ }
88
+ QPushButton:hover {
89
+ color: RoyalBlue; /* Text color on hover */
90
+ }
91
+ """
92
+ return css
93
+
94
+ def extract_repo_info(self, url: str):
95
+ """Extracts the username and repository name from a GitHub URL."""
96
+ pattern = r'https?://(?:www\.)?github\.com/([^/]+)/([^/]+)'
97
+ match = re.match(pattern, url)
98
+
99
+ if match:
100
+ repo_owner = match.group(1)
101
+ repo_name = match.group(2)
102
+ return repo_owner, repo_name
103
+ else:
104
+ raise ValueError(
105
+ "invalid URL"
106
+ )
107
+
108
+ def check_for_updates(self):
109
+ """Check for update availability."""
110
+ self.check_update_button.setEnabled(False)
111
+ self.check_update_button.setText("Checking for updates...")
112
+ QtWidgets.QApplication.processEvents()
113
+
114
+ repo_owner, repo_name = self.extract_repo_info(metadata.url)
115
+ updater = UpdateManager(repo_owner, repo_name, self)
116
+ if updater.check_updates():
117
+ self.check_update_button.setText("Downloading update...")
118
+ QtWidgets.QApplication.processEvents()
119
+ try:
120
+ updater.update_software()
121
+ self.check_update_button.setText("Update completed")
122
+ updater.show_file_location_message(updater.new_filedir)
123
+ self.menu.app.close()
124
+ except Exception as e:
125
+ msg = "An error occurred during the update"
126
+ self.check_update_button.setText(msg)
127
+ run_error(msg, details = e)
128
+ else:
129
+ self.check_update_button.setText("Software up to date")
130
+
131
+ self.check_update_button.setEnabled(True)
ytComments/gui/app.py ADDED
@@ -0,0 +1,97 @@
1
+ import sys
2
+ import ytComments.metadata as metadata
3
+ from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox
4
+ from ytComments import yt_manager
5
+ from .menu import MenuBar
6
+ from .main import MainWindow
7
+ from .update import UpdateManager
8
+ from .error import run_error
9
+
10
+ class App(QMainWindow):
11
+ """Main application class."""
12
+ def __init__(self):
13
+ # Initialize QApplication
14
+ self.qapp = QApplication(sys.argv)
15
+
16
+ # Initialize QMainWindow
17
+ super().__init__()
18
+ self.setWindowTitle(metadata.name)
19
+ #self.setGeometry(200, 200, 360, 400)
20
+ self.yt = yt_manager()
21
+
22
+ # Create the main window
23
+ self.main_frame = MainWindow(self)
24
+ self.setCentralWidget(self.main_frame)
25
+
26
+ # Create the menu bar
27
+ self.menu_bar = MenuBar(self)
28
+ self.setMenuBar(self.menu_bar)
29
+
30
+ # Connect the window resize event
31
+ self.resizeEvent = self.on_resize
32
+
33
+ # Allow the window to accept drag-and-drop events for files
34
+ self.setAcceptDrops(True)
35
+
36
+ def run(self):
37
+ """Execution of the application."""
38
+ self.show()
39
+ self.check_for_updates()
40
+ sys.exit(self.qapp.exec())
41
+
42
+ def on_resize(self, event):
43
+ """Execute actions when the main window is resized."""
44
+ #print(f"Dimensions of the window : {self.width()}x{self.height()}")
45
+ super().resizeEvent(event)
46
+
47
+ def dragEnterEvent(self, event):
48
+ """Manage the drag-and-drop event.
49
+ Only accept files."""
50
+ if event.mimeData().hasUrls():
51
+ event.accept() # Accept the event
52
+ else:
53
+ event.ignore() # Ignore if it's not a file
54
+
55
+ def dropEvent(self, event):
56
+ """Manage the drop event and retrieve the dropped file."""
57
+ if event.mimeData().hasUrls():
58
+ # Retrieve the URLs from the mimeData
59
+ urls = event.mimeData().urls()
60
+ # Convert the URLs to a local path
61
+ list_urls = []
62
+ for url in urls:
63
+ file_path = url.toLocalFile()
64
+ list_urls.append(file_path)
65
+ # Set the path in the QLineEdit
66
+ self.main_frame.old_save_input.setText(";".join(list_urls))
67
+
68
+ def check_for_updates(self):
69
+ """Check for updates."""
70
+ repo_owner, repo_name = self.menu_bar.about_dialog.extract_repo_info(
71
+ metadata.url
72
+ )
73
+ updater = UpdateManager(repo_owner, repo_name, self)
74
+ reply = None
75
+
76
+ # Check for updates
77
+ if updater.check_updates() and self.yt.settings.auto_update:
78
+ msg = "An update is available,"
79
+ msg += "\nwould you like to update the software ?"
80
+ reply = QMessageBox.question(
81
+ None,
82
+ "Information",
83
+ msg,
84
+ QMessageBox.Yes | QMessageBox.No
85
+ )
86
+
87
+ # Download the update
88
+ if updater.check_updates() and reply == QMessageBox.Yes:
89
+ try:
90
+ updater.update_software()
91
+ updater.show_file_location_message(updater.new_filedir)
92
+ self.close()
93
+ sys.exit(self.qapp.exec())
94
+ except Exception as e:
95
+ msg = "An error occurred during the update"
96
+ run_error(msg, details = e)
97
+
@@ -0,0 +1,83 @@
1
+ import sys
2
+ from PySide6.QtWidgets import (QApplication, QDialog, QLabel, QPushButton,
3
+ QVBoxLayout, QHBoxLayout, QSizePolicy, QWidget,
4
+ QMessageBox)
5
+ from PySide6.QtCore import Qt
6
+
7
+ class ErrorWindow(QDialog):
8
+ def __init__(self, error_message:str = "Une erreur est survenue", error_details:str = ""):
9
+ super().__init__()
10
+
11
+ self.setWindowTitle("Erreur")
12
+ self.setModal(True)
13
+ self.layout = QVBoxLayout()
14
+
15
+ # Définir le drapeau pour rester au premier plan
16
+ self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
17
+
18
+ # Icone d'erreur
19
+ warning_icon = QMessageBox().standardIcon(QMessageBox.Warning)
20
+ icon_label = QLabel()
21
+ icon_label.setPixmap(warning_icon)
22
+ icon_label.setAlignment(Qt.AlignLeft)
23
+
24
+ message_layout = QHBoxLayout()
25
+ message_layout.addWidget(icon_label)
26
+
27
+ # Message d'erreur simplifié
28
+ error_label = QLabel(error_message)
29
+ error_label.setAlignment(Qt.AlignLeft)
30
+ message_layout.addWidget(error_label)
31
+ self.layout.addLayout(message_layout)
32
+
33
+ # Bouton pour afficher le message d'erreur
34
+ self.toggle_button = QPushButton("Afficher le message d'erreur")
35
+ self.toggle_button.clicked.connect(self.toggle_message)
36
+ if error_details != "":
37
+ self.layout.addWidget(self.toggle_button)
38
+
39
+ # Message d'erreur détaillé
40
+ self.error_details_label = QLabel(error_details)
41
+ self.error_details_label.setVisible(False)
42
+ self.layout.addWidget(self.error_details_label)
43
+
44
+ # Permet de dimensionner en petit le bouton "OK"
45
+ button_layout = QHBoxLayout()
46
+ spacer = QWidget()
47
+ spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
48
+ button_layout.addWidget(spacer)
49
+
50
+ # Bouton de fermeture
51
+ close_button = QPushButton("OK")
52
+ close_button.clicked.connect(self.close)
53
+ button_layout.addWidget(close_button)
54
+ self.layout.addLayout(button_layout)
55
+
56
+ self.setLayout(self.layout)
57
+ self.adjustSize()
58
+
59
+ # True = fenêtre détaillée, False = fenêtre simplifiée
60
+ self.expanded = False
61
+
62
+ def toggle_message(self):
63
+ """Affiche ou masque le message d'erreur."""
64
+
65
+ if self.expanded:
66
+ self.toggle_button.setText("Afficher le message d'erreur")
67
+ self.error_details_label.setVisible(False)
68
+ self.adjustSize()
69
+ else:
70
+ self.toggle_button.setText("Masquer le message d'erreur")
71
+ self.error_details_label.setVisible(True)
72
+ self.adjustSize()
73
+ self.expanded = not self.expanded
74
+
75
+ def run_error(message:str = "", details:str = ""):
76
+ """Lance la fenêtre d'erreur."""
77
+ app = QApplication.instance()
78
+ # Créez une nouvelle instance si nécessaire
79
+ if app is None:
80
+ app = QApplication(sys.argv)
81
+
82
+ window = ErrorWindow(str(message), str(details))
83
+ window.exec()
ytComments/gui/main.py ADDED
@@ -0,0 +1,170 @@
1
+ from PySide6 import QtWidgets, QtCore
2
+ from .error import run_error
3
+ from ytComments.gui.progressbar import LoadingWindow
4
+
5
+ class MainWindow(QtWidgets.QFrame):
6
+ """Main Window of the application."""
7
+ def __init__(self, parent: QtWidgets.QMainWindow):
8
+ super().__init__(parent)
9
+ self.app = parent
10
+ layout = QtWidgets.QVBoxLayout(self)
11
+
12
+ # Initialize the layout
13
+ layout.addLayout(self.init_url())
14
+ layout.addLayout(self.init_old_save())
15
+ layout.addLayout(self.init_new_save())
16
+ layout.addLayout(self.init_exe())
17
+
18
+ def init_url(self):
19
+ """Initialize the URL."""
20
+ url_label = QtWidgets.QLabel("URL :")
21
+ self.url_input = QtWidgets.QLineEdit()
22
+ self.url_input.setText(self.app.yt.settings.channel_url)
23
+ layout = QtWidgets.QHBoxLayout()
24
+ layout.addWidget(url_label)
25
+ layout.addWidget(self.url_input)
26
+ return layout
27
+
28
+ def init_old_save(self):
29
+ """Initialize the old save location."""
30
+ old_save_label = QtWidgets.QLabel("Old save :")
31
+ self.old_save_input = QtWidgets.QLineEdit()
32
+ msg = "More than one save can be specified by separating them with a semicolon"
33
+ self.old_save_input.setToolTip(msg)
34
+ old_save_button = QtWidgets.QPushButton("Browse")
35
+ old_save_button.clicked.connect(self.browse_file)
36
+ layout = QtWidgets.QHBoxLayout()
37
+ layout.addWidget(old_save_label)
38
+ layout.addWidget(self.old_save_input)
39
+ layout.addWidget(old_save_button)
40
+ return layout
41
+
42
+ def init_new_save(self):
43
+ """Initialize the new save directory."""
44
+ directory_label = QtWidgets.QLabel("Save Directory :")
45
+ self.directory_input = QtWidgets.QLineEdit()
46
+ self.directory_input.setText(str(self.app.yt.settings.directory))
47
+ directory_button = QtWidgets.QPushButton("Browse")
48
+ directory_button.clicked.connect(self.browse_directory)
49
+ layout = QtWidgets.QHBoxLayout()
50
+ layout.addWidget(directory_label)
51
+ layout.addWidget(self.directory_input)
52
+ layout.addWidget(directory_button)
53
+ return layout
54
+
55
+ def init_exe(self):
56
+ """Initialize the execute button."""
57
+ exe_button = QtWidgets.QPushButton("Execute")
58
+ exe_button.clicked.connect(self.execute)
59
+ layout = QtWidgets.QHBoxLayout()
60
+ layout.addWidget(exe_button)
61
+ return layout
62
+
63
+ def browse_file(self):
64
+ """Open a file dialog to select files."""
65
+ files, _ = QtWidgets.QFileDialog.getOpenFileNames(
66
+ self, "Select the old saves", "", "Excel Files (*.xlsx)")
67
+ # Add them in the old save input
68
+ if files:
69
+ self.old_save_input.setText(";".join(files))
70
+
71
+ def browse_directory(self):
72
+ """Open a file dialog to select a directory."""
73
+ directory = QtWidgets.QFileDialog.getExistingDirectory(
74
+ self, "Select a directory"
75
+ )
76
+ # Add them in the directory input
77
+ if directory:
78
+ self.directory_input.setText(directory)
79
+ self.app.yt.settings.directory = directory
80
+ self.app.yt.settings.save()
81
+
82
+ def execute(self):
83
+ """Execute the program."""
84
+ self.app.yt.settings.channel_url = self.url_input.text()
85
+ old_saves = self.old_save_input.text().split(";")
86
+ directory = self.directory_input.text()
87
+
88
+ # Check the validity of the old saves
89
+ waste = ""
90
+ for save in old_saves.copy():
91
+ file = QtCore.QFile(save)
92
+
93
+ # If the file does not exist, remove it from the list
94
+ if not file.exists():
95
+ old_saves.remove(save)
96
+ waste += save
97
+
98
+ # Check if the waste is a valid file (eg: a file with ";" in its path)
99
+ file = QtCore.QFile(waste)
100
+ if file.exists():
101
+ old_saves.append(waste)
102
+ waste = ""
103
+ else:
104
+ waste += ";"
105
+ file = QtCore.QFile(waste)
106
+ if file.exists():
107
+ old_saves.append(waste)
108
+ waste = ""
109
+
110
+ # Check if the file is a .xlsx file
111
+ if file.exists() and QtCore.QFileInfo(file).suffix() != "xlsx":
112
+ run_error(
113
+ "Invalid file extension detected",
114
+ f"The file {file.fileName()} is not a .xlsx file"
115
+ )
116
+ return
117
+
118
+ # If there isn't any old save, set it to None
119
+ if len(old_saves) == 0:
120
+ old_saves = None
121
+ else:
122
+ self.app.yt.old_save = old_saves
123
+
124
+ # Check if the directory exists
125
+ dir = QtCore.QFile(directory)
126
+ if not dir.exists():
127
+ run_error(
128
+ "Invalid directory",
129
+ f"The directory {directory} does not exist"
130
+ )
131
+ return
132
+ else:
133
+ self.app.yt.settings.directory = directory
134
+
135
+ # Save the settings
136
+ self.app.yt.settings.save()
137
+
138
+ # Reset the finish value
139
+ self.app.yt.finish = False
140
+
141
+ # Show the loading window
142
+ loading_window = LoadingWindow(countdown=True)
143
+ loading_window.show()
144
+ loading_window.update_progress(0)
145
+
146
+ # Launch the program
147
+ if old_saves is not None:
148
+ try:
149
+ self.app.yt.import_excel()
150
+ except:
151
+ run_error("Invalid save, choose a valid save")
152
+ loading_window.close()
153
+ return
154
+ self.app.yt.start() # Execute the thread
155
+
156
+ while self.app.yt.finish == False:
157
+ QtWidgets.QApplication.processEvents()
158
+ loading_window.update_progress(
159
+ self.app.yt.progress,
160
+ self.app.yt.duration
161
+ )
162
+
163
+ # Terminate the download earlier if the loading window is closed
164
+ if loading_window.closing:
165
+ self.app.yt.finish = True
166
+
167
+ loading_window.close()
168
+
169
+
170
+