From ce6da1937bce17413b4da6ad3e542b208e3dd65d Mon Sep 17 00:00:00 2001 From: statix05 Date: Tue, 8 Sep 2026 14:15:52 +0300 Subject: [PATCH] feat: initial release of macZIPfixer (ZIPName.app) --- .gitignore | 5 + README.md | 28 ++++++ ZIPName.icns | Bin 0 -> 102620 bytes app.py | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++ build.sh | 67 ++++++++++++++ fixer_core.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 582 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ZIPName.icns create mode 100644 app.py create mode 100755 build.sh create mode 100644 fixer_core.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1fe538 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +build/ +dist/ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..b044b6d --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# macZIPfixer / ZIPName.app + +Утилита с графическим интерфейсом и Drag-and-Drop для macOS, предназначенная для исправления кракозябр («mojibake») в именах файлов, папках и ZIP-архивах. + +## Проблема +При распаковке ZIP-архивов, созданных на Windows без флага UTF-8 (в кодировке OEM CP866 или Windows-1251), штатный разархиватор macOS (Archive Utility / BOMArchiveHelper) интерпретирует байты через MacCyrillic или CP437. В результате имена файлов превращаются в нечитаемые символы (`≠`, `™`, `†`, `Ѓ` и т.д.). + +## Возможности +- **Drag-and-Drop:** просто перетащите поврежденную папку, файл или ZIP-архив в окно программы. +- **Поддержка ZIP напрямую:** распаковывает ZIP с автоматическим определением и восстановлением русских имен на лету. +- **Удаление мусора:** автоматически находит и удаляет фантомные файлы блокировки Office (`~$...`), возникающие при битых распаковках. +- **Рекурсивное исправление:** обходит вложенные папки снизу вверх, переименовывая как файлы, так и сами каталоги. +- **Бренд-стиль:** тёмная тема в фирменном стиле (Carbon + Red `#ff2d20`). + +## Установка и запуск + +Приложение уже собрано в `.app` и установлено в `/Applications/ZIPName.app`. + +Для ручного запуска из терминала: +```bash +python3 app.py +``` + +Для сборки `.app`: +```bash +./build.sh +cp -R dist/ZIPName.app /Applications/ +``` diff --git a/ZIPName.icns b/ZIPName.icns new file mode 100644 index 0000000000000000000000000000000000000000..b24f1a03473ea41cc62dbddefdf22f3db156d4c0 GIT binary patch literal 102620 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
    Y%-~0m`g10s<1dxnMin5=@Sw1;ytBx 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%0A^)E)s$9LMDxTXMR6uaktEaXesNGj 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 PXaI;msDr0}zkmH7fskhP literal 0 HcmV?d00001 diff --git a/app.py b/app.py new file mode 100644 index 0000000..9da6318 --- /dev/null +++ b/app.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +ZIPName — macOS Drag-and-Drop utility to fix mojibake names in folders and ZIP archives. +""" + +import os +import sys +import threading +import tkinter as tk +from tkinter import ttk, messagebox, filedialog + +try: + from tkinterdnd2 import TkinterDnD, DND_FILES + HAS_DND = True +except ImportError: + HAS_DND = False + +from fixer_core import process_path, fix_filename_string + +class ZIPNameApp: + def __init__(self, root): + self.root = root + self.root.title("ZIPName") + self.root.geometry("580x420") + self.root.minsize(500, 360) + + # Apple styling colors + self.bg_color = "#1e1e24" + self.card_color = "#2b2b36" + self.accent_color = "#ff2d20" + self.text_color = "#ffffff" + self.sub_text_color = "#9e9ea7" + self.border_color = "#3d3d4e" + + self.root.configure(bg=self.bg_color) + self.setup_ui() + + def setup_ui(self): + # Header + header_frame = tk.Frame(self.root, bg=self.bg_color) + header_frame.pack(fill="x", padx=20, pady=(20, 10)) + + title_lbl = tk.Label( + header_frame, + text="ZIPName", + font=("SF Pro Display", 20, "bold"), + fg=self.text_color, + bg=self.bg_color + ) + title_lbl.pack(anchor="w") + + sub_lbl = tk.Label( + header_frame, + text="Восстановление имён файлов и распаковка архивов с битой кодировкой", + font=("SF Pro Text", 12), + fg=self.sub_text_color, + bg=self.bg_color + ) + sub_lbl.pack(anchor="w", pady=(2, 0)) + + # Drop Zone Frame + self.drop_frame = tk.Frame( + self.root, + bg=self.card_color, + highlightbackground=self.border_color, + highlightthickness=2, + bd=0, + relief="flat" + ) + self.drop_frame.pack(fill="both", expand=True, padx=20, pady=10) + + # Drop zone contents + self.drop_label_icon = tk.Label( + self.drop_frame, + text="📦 ➔ ✨", + font=("SF Pro Display", 32), + fg=self.accent_color, + bg=self.card_color + ) + self.drop_label_icon.pack(expand=True, pady=(20, 5)) + + self.drop_label_main = tk.Label( + self.drop_frame, + text="Перетащи сюда папку, файлы или ZIP-архив", + font=("SF Pro Text", 14, "bold"), + fg=self.text_color, + bg=self.card_color + ) + self.drop_label_main.pack(expand=True, pady=(0, 2)) + + self.drop_label_sub = tk.Label( + self.drop_frame, + text="или выбери вручную кнопкой ниже", + font=("SF Pro Text", 11), + fg=self.sub_text_color, + bg=self.card_color + ) + self.drop_label_sub.pack(expand=True, pady=(0, 10)) + + # Buttons in drop frame + btn_frame = tk.Frame(self.drop_frame, bg=self.card_color) + btn_frame.pack(pady=(0, 20)) + + btn_style = { + "font": ("SF Pro Text", 11, "bold"), + "fg": "#ffffff", + "bg": "#3e3e50", + "activebackground": self.accent_color, + "activeforeground": "#ffffff", + "relief": "flat", + "padx": 14, + "pady": 6, + "bd": 0, + "cursor": "pointinghand" + } + + self.btn_browse_folder = tk.Button(btn_frame, text="Выбрать папку", command=self.browse_folder, **btn_style) + self.btn_browse_folder.pack(side="left", padx=6) + + self.btn_browse_zip = tk.Button(btn_frame, text="Выбрать ZIP", command=self.browse_zip, **btn_style) + self.btn_browse_zip.pack(side="left", padx=6) + + # Log & Status area + self.status_lbl = tk.Label( + self.root, + text="Готов к работе", + font=("SF Pro Text", 11), + fg=self.sub_text_color, + bg=self.bg_color + ) + self.status_lbl.pack(fill="x", padx=25, pady=(4, 10)) + + # Register Drag and Drop + if HAS_DND: + self.root.drop_target_register(DND_FILES) + self.root.dnd_bind('<>', self.handle_drop) + self.drop_frame.drop_target_register(DND_FILES) + self.drop_frame.dnd_bind('<>', self.handle_drop) + for widget in (self.drop_label_icon, self.drop_label_main, self.drop_label_sub): + widget.drop_target_register(DND_FILES) + widget.dnd_bind('<>', self.handle_drop) + + def handle_drop(self, event): + raw_data = event.data + paths = self.parse_dnd_paths(raw_data) + if paths: + self.start_processing(paths) + + def parse_dnd_paths(self, dnd_data): + """Parse macOS/TkinterDnD paths, handling curly braces and spaces.""" + paths = [] + cur = [] + in_brace = False + for char in dnd_data: + if char == '{': + in_brace = True + elif char == '}': + in_brace = False + elif char == ' ' and not in_brace: + if cur: + paths.append("".join(cur).strip()) + cur = [] + else: + cur.append(char) + if cur: + paths.append("".join(cur).strip()) + return [p for p in paths if p] + + def browse_folder(self): + selected = filedialog.askdirectory(title="Выберите папку с кракозябрами") + if selected: + self.start_processing([selected]) + + def browse_zip(self): + selected = filedialog.askopenfilename( + title="Выберите ZIP архив", + filetypes=[("ZIP Archives", "*.zip"), ("All Files", "*.*")] + ) + if selected: + self.start_processing([selected]) + + def start_processing(self, paths): + # Disable buttons while running + self.btn_browse_folder.config(state="disabled") + self.btn_browse_zip.config(state="disabled") + self.status_lbl.config(text="Обработка...", fg="#e1a100") + + thread = threading.Thread(target=self._worker, args=(paths,), daemon=True) + thread.start() + + def _worker(self, paths): + total_renamed = 0 + total_extracted = 0 + total_deleted = 0 + all_results = [] + + for p in paths: + def on_progress(old, new): + self.root.after(0, lambda o=old, n=new: self.status_lbl.config( + text=f"Исправлено: {n[:35]}...", fg="#4cd964" + )) + + res = process_path(p, progress_callback=on_progress) + all_results.extend(res) + + for action, old, new in all_results: + if action in ("rename_file", "rename_dir"): + total_renamed += 1 + elif action == "extract_file": + total_extracted += 1 + elif action == "delete": + total_deleted += 1 + + self.root.after(0, lambda: self._on_complete(total_renamed, total_extracted, total_deleted)) + + def _on_complete(self, renamed, extracted, deleted): + self.btn_browse_folder.config(state="normal") + self.btn_browse_zip.config(state="normal") + + msg_parts = [] + if renamed > 0: + msg_parts.append(f"Переименовано элементов: {renamed}") + if extracted > 0: + msg_parts.append(f"Распаковано файлов: {extracted}") + if deleted > 0: + msg_parts.append(f"Удалено мусорных temp-файлов: {deleted}") + + if not msg_parts: + summary = "Ничего менять не потребовалось — все имена уже в порядке!" + self.status_lbl.config(text=summary, fg=self.sub_text_color) + messagebox.showinfo("ZIPName", summary) + else: + summary = "\n".join(msg_parts) + self.status_lbl.config(text="Готово! " + " · ".join(msg_parts), fg="#4cd964") + messagebox.showinfo("ZIPName — Готово", f"Операция успешно завершена:\n\n{summary}") + + +def main(): + if HAS_DND: + root = TkinterDnD.Tk() + else: + root = tk.Tk() + app = ZIPNameApp(root) + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6ffe881 --- /dev/null +++ b/build.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Build script for ZIPName.app on macOS + +set -e + +APP_NAME="ZIPName" +BUNDLE_DIR="dist/${APP_NAME}.app" +MACOS_DIR="${BUNDLE_DIR}/Contents/MacOS" +RESOURCES_DIR="${BUNDLE_DIR}/Contents/Resources" + +echo "=== Сборка ${APP_NAME}.app ===" + +rm -rf dist build +mkdir -p "${MACOS_DIR}" +mkdir -p "${RESOURCES_DIR}" + +# Копируем иконку +if [ -f "ZIPName.icns" ]; then + cp "ZIPName.icns" "${RESOURCES_DIR}/" +fi + +# Копируем исходники в Resources +cp app.py fixer_core.py "${RESOURCES_DIR}/" + +# Создаем Info.plist +cat > "${BUNDLE_DIR}/Contents/Info.plist" << 'EOF' + + + + + CFBundleDisplayName + ZIPName + CFBundleExecutable + ZIPName + CFBundleIconFile + ZIPName.icns + CFBundleIdentifier + sbs.statix.zipname + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ZIPName + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 11.0 + NSHighResolutionCapable + + + +EOF + +# Создаем исполняемый launcher +cat > "${MACOS_DIR}/${APP_NAME}" << 'EOF' +#!/bin/bash +DIR="$(cd "$(dirname "$0")/../Resources" && pwd)" +export PYTHONPATH="$DIR:$PYTHONPATH" +exec /Library/Frameworks/Python.framework/Versions/3.14/bin/python3 "$DIR/app.py" "$@" +EOF + +chmod +x "${MACOS_DIR}/${APP_NAME}" + +echo "Сборка завершена: ${BUNDLE_DIR}" diff --git a/fixer_core.py b/fixer_core.py new file mode 100644 index 0000000..a936678 --- /dev/null +++ b/fixer_core.py @@ -0,0 +1,234 @@ +""" +Core logic for fixing mojibake in file names and ZIP archives on macOS. +""" + +import os +import zipfile +import shutil + +RUSSIAN_RANGE = set(range(0x0400, 0x0500)) + +def score_cyrillic(text: str) -> int: + """Return count of Cyrillic characters in text.""" + return sum(1 for c in text if ord(c) in RUSSIAN_RANGE) + +def is_suspicious_char(c: str) -> bool: + """Return True if character commonly appears in mojibake.""" + # Signs of MacCyrillic/CP437/Latin1 mangling of Cyrillic: + # ≠ ™ • † Ѓ ѓ Ђ ђ Љ љ Њ њ Ћ ћ Џ џ § © ® ™ ¶ + return c in "≠™•†ЃѓЂђЉљЊњЋћЏџ§©®¶ҐґІіЇїЎў" + +def fix_filename_string(name: str) -> str: + """ + Attempt to convert a corrupted filename back to legible Cyrillic. + Handles: + - MacCyrillic -> CP866 (macOS Russian Archive Utility default on OEM DOS zips) + - MacCyrillic -> CP1251 + - CP437 -> CP866 (ditto, default zipfile, unzip) + - CP437 -> CP1251 + - Latin1 -> CP1251 / CP866 / UTF-8 + - MacRoman -> CP1251 / CP866 + """ + orig_score = score_cyrillic(name) + has_suspicious = any(is_suspicious_char(c) for c in name) + + # Pairs of (source_encoding, target_encoding) + transforms = [ + ("mac_cyrillic", "cp866"), + ("mac_cyrillic", "cp1251"), + ("cp437", "cp866"), + ("cp437", "cp1251"), + ("latin1", "cp1251"), + ("latin1", "cp866"), + ("latin1", "utf-8"), + ("mac_roman", "cp866"), + ("mac_roman", "cp1251"), + ] + + best_name = name + best_score = orig_score + + for enc_src, enc_dst in transforms: + try: + raw = name.encode(enc_src) + candidate = raw.decode(enc_dst) + cand_score = score_cyrillic(candidate) + + # If original had suspicious mojibake characters and candidate eliminated them + # or increased cyrillic score + if cand_score > best_score or (has_suspicious and cand_score >= best_score and not any(is_suspicious_char(c) for c in candidate)): + best_score = cand_score + best_name = candidate + except Exception: + continue + + return best_name + + +def process_path(target_path: str, progress_callback=None) -> list[tuple[str, str, str]]: + """ + Recursively process a directory or file. + If it's a .zip file, it extracts it with corrected names into a folder next to it. + If it's a directory or regular file, it renames files and folders in-place (bottom-up). + + Returns list of tuples: (type, old_name, new_name) + """ + results = [] + + if not os.path.exists(target_path): + return [("error", target_path, "Файл или папка не существует")] + + # Case 1: ZIP file + if os.path.isfile(target_path) and target_path.lower().endswith(".zip"): + return process_zip(target_path, progress_callback) + + # Case 2: Single file + if os.path.isfile(target_path): + dir_name, base_name = os.path.split(target_path) + # Skip temporary office lock files + if base_name.startswith("~$"): + try: + os.remove(target_path) + results.append(("delete", base_name, "Удалён временный файл блокировки Office")) + except Exception as e: + results.append(("error", base_name, f"Не удалось удалить: {e}")) + return results + + fixed = fix_filename_string(base_name) + if fixed != base_name: + new_path = os.path.join(dir_name, fixed) + try: + os.rename(target_path, new_path) + results.append(("rename_file", base_name, fixed)) + except Exception as e: + results.append(("error", base_name, f"Ошибка: {e}")) + else: + results.append(("skip", base_name, "Имя корректно")) + return results + + # Case 3: Directory (recursive, bottom-up so renaming children doesn't invalidate parent path) + for root, dirs, files in os.walk(target_path, topdown=False): + # 1. Process files + for fname in files: + fpath = os.path.join(root, fname) + # Remove ~$ lock files + if fname.startswith("~$"): + try: + os.remove(fpath) + results.append(("delete", fname, "Удалён временный файл блокировки Office")) + except Exception as e: + results.append(("error", fname, f"Не удалось удалить: {e}")) + continue + + fixed = fix_filename_string(fname) + if fixed != fname: + new_fpath = os.path.join(root, fixed) + try: + os.rename(fpath, new_fpath) + results.append(("rename_file", fname, fixed)) + if progress_callback: + progress_callback(fname, fixed) + except Exception as e: + results.append(("error", fname, f"Ошибка: {e}")) + else: + results.append(("ok", fname, "OK")) + + # 2. Process subdirectories + for dname in dirs: + dpath = os.path.join(root, dname) + fixed_dir = fix_filename_string(dname) + if fixed_dir != dname: + new_dpath = os.path.join(root, fixed_dir) + try: + os.rename(dpath, new_dpath) + results.append(("rename_dir", dname, fixed_dir)) + if progress_callback: + progress_callback(dname, fixed_dir) + except Exception as e: + results.append(("error", dname, f"Ошибка: {e}")) + + # Also check if root folder itself needs renaming + root_parent, root_name = os.path.split(os.path.abspath(target_path)) + fixed_root = fix_filename_string(root_name) + if fixed_root != root_name and root_parent: + try: + new_root_path = os.path.join(root_parent, fixed_root) + os.rename(target_path, new_root_path) + results.append(("rename_dir", root_name, fixed_root)) + except Exception as e: + results.append(("error", root_name, f"Ошибка переименования папки: {e}")) + + return results + + +def process_zip(zip_path: str, progress_callback=None) -> list[tuple[str, str, str]]: + """ + Extract a ZIP archive directly, correcting encodings for all entries. + Creates a folder next to zip_path with the extracted contents. + """ + results = [] + base_dir = os.path.dirname(os.path.abspath(zip_path)) + zip_stem = os.path.splitext(os.path.basename(zip_path))[0] + fixed_stem = fix_filename_string(zip_stem) + + out_dir = os.path.join(base_dir, fixed_stem) + counter = 1 + orig_out_dir = out_dir + while os.path.exists(out_dir): + out_dir = f"{orig_out_dir}_{counter}" + counter += 1 + + os.makedirs(out_dir, exist_ok=True) + results.append(("extract_start", os.path.basename(zip_path), f"Распаковка в {os.path.basename(out_dir)}")) + + try: + with zipfile.ZipFile(zip_path, "r") as zf: + for item in zf.infolist(): + raw_name = item.filename + + # Office lock files in zip can be skipped + if os.path.basename(raw_name).startswith("~$"): + continue + + # Determine best fixed name + # If flag_bits & 0x800 == 0: python decoded with CP437 + best_name = raw_name + if not (item.flag_bits & 0x800): + try: + raw_bytes = raw_name.encode("cp437") + for enc in ["cp866", "cp1251"]: + try: + candidate = raw_bytes.decode(enc) + if score_cyrillic(candidate) > score_cyrillic(best_name): + best_name = candidate + break + except Exception: + pass + except Exception: + pass + + # Run standard mojibake fixer on the parts of path as well + parts = best_name.split("/") + fixed_parts = [fix_filename_string(p) for p in parts] + clean_rel_path = "/".join(fixed_parts) + + # Security check: prevent zip slip + dest_path = os.path.abspath(os.path.join(out_dir, clean_rel_path)) + if not dest_path.startswith(os.path.abspath(out_dir)): + continue + + if item.is_dir() or clean_rel_path.endswith("/"): + os.makedirs(dest_path, exist_ok=True) + else: + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + with zf.open(item) as src_file, open(dest_path, "wb") as dst_file: + shutil.copyfileobj(src_file, dst_file) + + results.append(("extract_file", raw_name, clean_rel_path)) + if progress_callback: + progress_callback(raw_name, clean_rel_path) + + except Exception as e: + results.append(("error", os.path.basename(zip_path), f"Ошибка архива: {e}")) + + return results