analitics

Pages

Wednesday, July 8, 2026

Python Qt : Script for Game Engine projects.

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()