Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
ryshaal authored Nov 4, 2024
1 parent 90b42b1 commit 4f4da7e
Showing 1 changed file with 357 additions and 0 deletions.
357 changes: 357 additions & 0 deletions mtz_packing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
import os
import zipfile
import shutil
import logging
import time
import threading
import sys
import random
from typing import Optional, List
from pathlib import Path
from itertools import cycle
import contextlib
from datetime import datetime


class ColorText:
"""Class untuk menangani warna text di terminal"""

HEADER = "\033[95m"
BLUE = "\033[94m"
CYAN = "\033[96m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
END = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"

@staticmethod
def blue(text: str) -> str:
return f"{ColorText.BLUE}{text}{ColorText.END}"

@staticmethod
def green(text: str) -> str:
return f"{ColorText.GREEN}{text}{ColorText.END}"

@staticmethod
def red(text: str) -> str:
return f"{ColorText.RED}{text}{ColorText.END}"

@staticmethod
def yellow(text: str) -> str:
return f"{ColorText.YELLOW}{text}{ColorText.END}"

@staticmethod
def cyan(text: str) -> str:
return f"{ColorText.CYAN}{text}{ColorText.END}"

@staticmethod
def bold(text: str) -> str:
return f"{ColorText.BOLD}{text}{ColorText.END}"


class LoadingAnimation:
"""Class untuk menangani animasi loading di terminal dengan efek smooth"""

def __init__(self, description: str = "Processing"):
self.description = description
self.is_running = False
self.animation_thread = None
self.progress = 0
self.animations = {
"smooth_bar": [
"▰▱▱▱▱▱▱▱▱▱",
"▰▰▱▱▱▱▱▱▱▱",
"▰▰▰▱▱▱▱▱▱▱",
"▰▰▰▰▱▱▱▱▱▱",
"▰▰▰▰▰▱▱▱▱▱",
"▰▰▰▰▰▰▱▱▱▱",
"▰▰▰▰▰▰▰▱▱▱",
"▰▰▰▰▰▰▰▰▱▱",
"▰▰▰▰▰▰▰▰▰▱",
"▰▰▰▰▰▰▰▰▰▰",
],
"wave": ["≋", "≈", "≋", "≈", "≋"],
"spinner": ["◜", "◠", "◝", "◞", "◡", "◟"],
"pulse": ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
}
self.colors = [
ColorText.BLUE,
ColorText.CYAN,
ColorText.GREEN,
ColorText.YELLOW,
]
self.current_color = random.choice(self.colors)
self.animation_chars = self.animations["smooth_bar"]

def start(self):
"""Memulai animasi loading"""
self.is_running = True
self.animation_thread = threading.Thread(target=self._animate)
self.animation_thread.daemon = True
self.animation_thread.start()

def stop(self):
"""Menghentikan animasi loading"""
self.is_running = False
if self.animation_thread:
self.animation_thread.join()
sys.stdout.write("\r" + " " * (len(self.description) + 30) + "\r")
sys.stdout.flush()

def _animate(self):
"""Fungsi internal untuk menjalankan animasi dengan efek smooth"""
while self.is_running:
for frame in self.animation_chars:
if not self.is_running:
break
animation = f"{self.current_color}{frame}{ColorText.END}"
sys.stdout.write(f"\r{animation} {self.description} ")
sys.stdout.flush()
time.sleep(0.1)


@contextlib.contextmanager
def loading_animation(description: str = "Processing"):
"""Context manager untuk animasi loading"""
spinner = LoadingAnimation(description)
spinner.start()
try:
yield spinner
finally:
spinner.stop()


class MTZCompressor:
"""Class untuk menangani kompresi file MTZ"""

def __init__(self):
self.setup_logging()
self.stats = {
"start_time": None,
"total_files": 0,
"total_size": 0,
"compressed_size": 0,
}

def setup_logging(self) -> None:
log_folder = Path("logs")
log_folder.mkdir(exist_ok=True)

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_file = log_folder / f"compression_{timestamp}.log"

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file, mode="w"),
],
)

def print_banner(self):
"""Menampilkan banner aplikasi"""
banner = f"""
{ColorText.cyan('╔═════════════════════════════════════════╗')}
{ColorText.cyan('║')} {ColorText.bold(' MTZ Packing v2.0 ')} {ColorText.cyan('║')}
{ColorText.cyan('║')} {ColorText.yellow(' BY Ryhshall ')} {ColorText.cyan('║')}
{ColorText.cyan('╚═════════════════════════════════════════╝')}
"""
print(banner)

def validate_folder(self, folder_path: str) -> bool:
if not os.path.exists(folder_path):
print(f"\n{ColorText.red('❌ Folder tidak ditemukan!')}")
return False

if not os.path.isdir(folder_path):
print(f"\n{ColorText.red('❌ Path bukan folder!')}")
return False

return True

def calculate_size(self, path: str) -> int:
"""Menghitung ukuran folder/file"""
if os.path.isfile(path):
return os.path.getsize(path)
total = 0
for dirpath, _, filenames in os.walk(path):
for f in filenames:
fp = os.path.join(dirpath, f)
total += os.path.getsize(fp)
return total

def format_size(self, size: int) -> str:
"""Format ukuran file dalam bentuk yang mudah dibaca"""
for unit in ["B", "KB", "MB", "GB"]:
if size < 1024:
return f"{size:.2f} {unit}"
size /= 1024
return f"{size:.2f} TB"

def zip_folder(self, folder_path: str) -> Optional[str]:
try:
folder_size = self.calculate_size(folder_path)
with loading_animation(
f"Mengompres {ColorText.yellow(os.path.basename(folder_path))} ({self.format_size(folder_size)})"
):
zip_path = f"{folder_path}.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_STORED) as zipf:
for root, dirs, files in os.walk(folder_path):
if os.path.basename(root) in ["wallpaper", "preview"]:
continue

if not files and not dirs:
arcname = os.path.relpath(root, folder_path) + "/"
zipf.write(root, arcname)
else:
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, folder_path)
zipf.write(file_path, arcname)
self.stats["total_files"] += 1
self.stats["total_size"] += os.path.getsize(file_path)

return zip_path
except Exception as e:
logging.error(f"Error compressing folder: {str(e)}")
return None

def verify_zip(self, zip_path: str) -> bool:
with loading_animation(
f"Memverifikasi {ColorText.yellow(os.path.basename(zip_path))}"
):
try:
with zipfile.ZipFile(zip_path, "r") as zipf:
corrupt_file = zipf.testzip()
if corrupt_file is not None:
logging.error(f"File corrupt: {corrupt_file}")
return False
return True
except Exception as e:
logging.error(f"Error verifying ZIP: {str(e)}")
return False

def remove_zip_extension(self, folder_path: str) -> None:
with loading_animation(
f"Menghapus ekstensi ZIP dari {ColorText.yellow(os.path.basename(folder_path))}"
):
try:
folder_path = os.path.abspath(folder_path)
for filename in os.listdir(folder_path):
if filename.endswith(".zip"):
old_path = os.path.join(folder_path, filename)
new_path = os.path.join(folder_path, filename[:-4])
os.rename(old_path, new_path)
logging.info(f"Renamed: {old_path} to {new_path}")
except Exception as e:
logging.error(f"Error removing ZIP extensions: {str(e)}")

def create_mtz(self, folder_path: str) -> bool:
with loading_animation(f"Membuat file {ColorText.yellow('MTZ')}"):
try:
folder_path = os.path.abspath(folder_path)
mtz_path = folder_path + ".mtz"

with zipfile.ZipFile(mtz_path, "w", zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(folder_path):
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, folder_path)
zf.write(full_path, rel_path)
logging.info(f"Added to MTZ: {rel_path}")

if os.path.exists(mtz_path):
self.stats["compressed_size"] = os.path.getsize(mtz_path)
shutil.rmtree(folder_path)
return True
return False
except Exception as e:
logging.error(f"Error creating MTZ: {str(e)}")
return False

def show_completion(self, folder_path: str):
"""Menampilkan pesan selesai dengan statistik"""
os.system("cls" if os.name == "nt" else "clear")
mtz_path = folder_path + ".mtz"

completion_time = time.time() - self.stats["start_time"]
compression_ratio = (
(self.stats["compressed_size"] / self.stats["total_size"]) * 100
if self.stats["total_size"] > 0
else 0
)

print(f"\n{ColorText.green('✨ Packing Berhasil! ✨')}")
print(f"\n{ColorText.cyan('📊 Statistik Packing:')}")
print(f"├─ Total file: {ColorText.yellow(str(self.stats['total_files']))}")
print(
f"├─ Ukuran awal: {ColorText.yellow(self.format_size(self.stats['total_size']))}"
)
print(
f"├─ Ukuran akhir: {ColorText.yellow(self.format_size(self.stats['compressed_size']))}"
)
print(f"├─ Rasio kompresi: {ColorText.yellow(f'{compression_ratio:.1f}%')}")
print(f"├─ Waktu proses: {ColorText.yellow(f'{completion_time:.1f} detik')}")
print(f"└─ File MTZ: {ColorText.yellow(mtz_path)}\n")


def get_user_input() -> str:
"""Fungsi untuk mendapatkan input dari user"""
return input(
f"{ColorText.cyan('📂')} Masukkan alamat folder yang akan dipacking: "
).strip()


def main():
"""Fungsi utama"""
os.system("cls" if os.name == "nt" else "clear")
compressor = MTZCompressor()
compressor.print_banner()

try:
main_folder = get_user_input()

if not compressor.validate_folder(main_folder):
sys.exit(1)

print(f"\n{ColorText.cyan('🔄')} Memulai proses kompresi...\n")
compressor.stats["start_time"] = time.time()

# Step 1: Kompresi folder ke ZIP
for folder in os.listdir(main_folder):
folder_path = os.path.join(main_folder, folder)

if folder in ["wallpaper", "preview"]:
logging.info(f"Mengabaikan folder: {folder}")
continue

if os.path.isdir(folder_path):
zip_path = compressor.zip_folder(folder_path)
if zip_path and compressor.verify_zip(zip_path):
shutil.rmtree(folder_path)
print(f"{ColorText.green('✓')} {folder}")
logging.info(f"Folder berhasil dikompres: {folder}")

# Step 2: Hapus ekstensi ZIP
compressor.remove_zip_extension(main_folder)

# Step 3: Buat file MTZ
if compressor.create_mtz(main_folder):
compressor.show_completion(main_folder)
else:
print(f"\n{ColorText.red('❌ Gagal membuat file MTZ!')}\n")
sys.exit(1)

except KeyboardInterrupt:
os.system("cls" if os.name == "nt" else "clear")
print(f"\n{ColorText.yellow('⚠️ Dibatalkan!')}\n")
sys.exit(0)
except Exception as e:
print(f"\n{ColorText.red('❌ Error:')} {str(e)}\n")
sys.exit(1)


if __name__ == "__main__":
main()

0 comments on commit 4f4da7e

Please sign in to comment.