import os import json import shutil import re from pathlib import Path from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QPushButton, QFileDialog, QTableWidget, QTableWidgetItem, QMessageBox ) from PySide6.QtCore import Qt BASE_DIR = Path(__file__).parent ARTISTS_FILE = BASE_DIR / "artists.json" UNDO_FILE = BASE_DIR / "undo_log.json" # ---------- Utils ---------- def load_artists(): if ARTISTS_FILE.exists(): with open(ARTISTS_FILE, "r", encoding="utf-8") as f: return json.load(f) return {} def save_artists(data): with open(ARTISTS_FILE, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) def clean_name(name): name = re.sub(r"[_\-\.]", " ", name) name = re.sub(r"[\"\'`]", "", name) name = re.sub(r"\s+", " ", name) return name.strip() def transliterate_hebrew(text): table = { "א": "a", "ב": "b", "ג": "g", "ד": "d", "ה": "h", "ו": "v", "ז": "z", "ח": "ch", "ט": "t", "י": "i", "כ": "k", "ל": "l", "מ": "m", "נ": "n", "ס": "s", "ע": "a", "פ": "p", "צ": "tz", "ק": "k", "ר": "r", "ש": "sh", "ת": "t", "ך": "k", "ם": "m", "ן": "n", "ף": "f", "ץ": "tz" } return "".join(table.get(c, c) for c in text) # ---------- Main App ---------- class TuneMaster(QWidget): def __init__(self): super().__init__() self.setWindowTitle("TuneMaster – Safe Rename") self.resize(800, 500) self.layout = QVBoxLayout(self) self.btn_select = QPushButton("בחר תיקייה") self.btn_execute = QPushButton("בצע שינויים") self.btn_undo = QPushButton("בטל שינויים") self.table = QTableWidget(0, 3) self.table.setHorizontalHeaderLabels(["מקור", "חדש", "נתיב מלא"]) self.table.horizontalHeader().setStretchLastSection(True) self.layout.addWidget(self.btn_select) self.layout.addWidget(self.table) self.layout.addWidget(self.btn_execute) self.layout.addWidget(self.btn_undo) self.btn_select.clicked.connect(self.select_folder) self.btn_execute.clicked.connect(self.execute) self.btn_undo.clicked.connect(self.undo) self.preview = [] self.artists = load_artists() def select_folder(self): folder = QFileDialog.getExistingDirectory(self, "בחר תיקייה") if not folder: return self.preview.clear() self.table.setRowCount(0) for root, _, files in os.walk(folder): for f in files: path = Path(root) / f stem = path.stem ext = path.suffix clean = clean_name(stem) new_name = None for he, en in self.artists.items(): if he in clean: new_name = clean.replace(he, en) break if not new_name: new_name = transliterate_hebrew(clean) new_full = new_name + ext if new_full != f: self.preview.append((path, path.with_name(new_full))) for old, new in self.preview: row = self.table.rowCount() self.table.insertRow(row) self.table.setItem(row, 0, QTableWidgetItem(old.name)) self.table.setItem(row, 1, QTableWidgetItem(new.name)) self.table.setItem(row, 2, QTableWidgetItem(str(old))) def execute(self): if not self.preview: return undo_data = [] for old, new in self.preview: if new.exists(): continue shutil.move(str(old), str(new)) undo_data.append({"from": str(new), "to": str(old)}) with open(UNDO_FILE, "w", encoding="utf-8") as f: json.dump(undo_data, f, indent=2, ensure_ascii=False) QMessageBox.information(self, "בוצע", "השינויים בוצעו בהצלחה") def undo(self): if not UNDO_FILE.exists(): QMessageBox.warning(self, "אין", "אין מה לבטל") return with open(UNDO_FILE, "r", encoding="utf-8") as f: undo_data = json.load(f) for item in undo_data: if Path(item["from"]).exists(): shutil.move(item["from"], item["to"]) UNDO_FILE.unlink() QMessageBox.information(self, "בוטל", "כל השינויים בוטלו") # ---------- Run ---------- if __name__ == "__main__": app = QApplication([]) win = TuneMaster() win.show() app.exec()