Simple python example script for Godot Engine projects.
This script allows you to see how big is the project, size of media files, size of scripting files, lines of source code and more:
import sys
import os
import pathlib
import datetime
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout,
QPushButton, QTextEdit, QProgressBar, QFileDialog, QMessageBox
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
def human_size(num_bytes: int) -> str:
units = ["B", "KB", "MB", "GB", "TB"]
size = float(num_bytes)
for unit in units:
if size < 1024.0:
return f"{size:.2f} {unit}"
size /= 1024.0
return f"{size:.2f} PB"
class FolderStatsWorker(QThread):
progress = pyqtSignal(int)
finished = pyqtSignal(str)
def __init__(self, folder_path: str):
super().__init__()
self.folder_path = folder_path
def run(self):
base = pathlib.Path(self.folder_path)
all_files = []
for root, dirs, files in os.walk(base):
for name in files:
all_files.append(pathlib.Path(root) / name)
total_files = len(all_files)
gd_files = []
media_files = []
total_lines_gd = 0
media_exts = {
".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp",
".ogg", ".wav", ".mp3", ".flac",
".mp4", ".mkv", ".avi", ".webm",
".md"
}
total_size = 0
gd_size = 0
media_size = 0
tree_lines = []
# Build tree view
for root, dirs, files in os.walk(base):
rel_root = pathlib.Path(root).relative_to(base)
indent_level = len(rel_root.parts)
indent = " " * indent_level
tree_lines.append(f"{indent}{rel_root if rel_root.parts else base.name}/")
for name in files:
tree_lines.append(f"{indent} {name}")
for idx, f in enumerate(all_files):
try:
stat = f.stat()
total_size += stat.st_size
ext = f.suffix.lower()
if ext == ".gd":
gd_files.append(f)
gd_size += stat.st_size
try:
txt = f.read_text(encoding="utf-8", errors="ignore")
if txt:
total_lines_gd += txt.count("\n") + 1
except Exception:
pass
if ext in media_exts:
media_files.append(f)
media_size += stat.st_size
except Exception:
pass
if total_files > 0:
self.progress.emit(int((idx + 1) * 100 / total_files))
report_lines = []
report_lines.append(f"Folder: {self.folder_path}")
report_lines.append("")
report_lines.append("=== Summary ===")
report_lines.append(f"Total files: {total_files}")
report_lines.append(f"Total .gd files: {len(gd_files)}")
report_lines.append(f"Total media files (incl. .md): {len(media_files)}")
report_lines.append(f"Total lines in .gd files: {total_lines_gd}")
report_lines.append("")
report_lines.append(f"Total size: {human_size(total_size)}")
report_lines.append(f"Total .gd size: {human_size(gd_size)}")
report_lines.append(f"Total media size: {human_size(media_size)}")
report_lines.append("")
report_lines.append("=== Tree ===")
report_lines.extend(tree_lines)
report_text = "\n".join(report_lines)
# Save report
now = datetime.datetime.now()
ts = now.strftime("%Y%m%d_%H%M%S")
folder_name = base.name
report_name = f"{folder_name}_{ts}.txt"
report_path = base / report_name
try:
report_path.write_text(report_text, encoding="utf-8")
except Exception:
pass
self.finished.emit(report_text)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Godot Project Stats")
self.setFixedSize(640, 480)
self.folder_path = None
self.worker = None
central = QWidget()
layout = QVBoxLayout(central)
self.btn_open = QPushButton("Open Folder")
self.btn_process = QPushButton("Process")
self.progress = QProgressBar()
self.progress.setRange(0, 100)
self.text = QTextEdit()
self.text.setReadOnly(True)
layout.addWidget(self.btn_open)
layout.addWidget(self.btn_process)
layout.addWidget(self.progress)
layout.addWidget(self.text)
self.setCentralWidget(central)
self.btn_open.clicked.connect(self.open_folder)
self.btn_process.clicked.connect(self.start_processing)
def open_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Godot Project Folder")
if folder:
self.folder_path = folder
self.text.clear()
self.text.append(f"Selected folder:\n{folder}")
def start_processing(self):
if not self.folder_path:
QMessageBox.warning(self, "No folder", "Please select a folder first.")
return
if self.worker is not None and self.worker.isRunning():
QMessageBox.information(self, "Processing", "Already processing.")
return
self.progress.setValue(0)
self.text.append("\nProcessing...")
self.worker = FolderStatsWorker(self.folder_path)
self.worker.progress.connect(self.progress.setValue)
self.worker.finished.connect(self.on_finished)
self.worker.start()
def on_finished(self, report_text: str):
self.text.clear()
self.text.append(report_text)
self.progress.setValue(100)
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()