#!/usr/bin/env python3 import os import shutil import unicodedata import re from pathlib import Path import sys # Copy a directory (~/Masic/RythmBox) to a FAT32 formated # flash drive, named MUSIC, converting all filenames # This was written with GPT-5 Mini via duck.ai as a bash script, # Some manual changes and it was fed into Google Gemini # Gemini then made further optimizations and rewrote it in python # --- CONFIGURATION --- HOME = Path.home() SOURCE_DIR = HOME / "Music/RythmBox" DEST_DIR = Path("/media") / HOME.name / "MUSIC" MAX_BYTES = 255 # --- PROGRESS BAR SETUP --- # Try to import tqdm. If missing, create a dummy fallback. try: from tqdm import tqdm HAS_TQDM = True except ImportError: HAS_TQDM = False print("Tip: Run 'pip3 install tqdm' for a nicer progress bar with ETA.") # Fallback class to mimic tqdm if library is missing class tqdm: def __init__(self, total=0, unit='files', **kwargs): self.total = total self.n = 0 def update(self, n=1): self.n += n sys.stdout.write(f"\rProcessed: {self.n}/{self.total}") sys.stdout.flush() def close(self): print("") def write(self, msg): sys.stdout.write(f"\r{msg}\n") def __enter__(self): return self def __exit__(self, *args): self.close() def sanitize_component(name): """ Makes a filename FAT32 safe. """ # 1. Transliterate Unicode to ASCII norm = unicodedata.normalize('NFKD', name) name = norm.encode('ASCII', 'ignore').decode('ASCII') # 2. Replace forbidden characters and spaces name = re.sub(r'[<>:"/\\|?*]', '-', name) name = re.sub(r'[\000-\037]', '', name) name = re.sub(r'\s+', '_', name) # 3. Trim leading/trailing dots and spaces name = name.strip('. ') # 4. Ensure not empty if not name: name = "_" # 5. Truncate to MAX_BYTES encoded = name.encode('utf-8') if len(encoded) > MAX_BYTES: name = encoded[:MAX_BYTES].decode('utf-8', 'ignore') return name def main(): src_path = Path(SOURCE_DIR) dst_path = Path(DEST_DIR) if not src_path.exists(): print(f"Error: Source directory not found: {src_path}") return print(f"Source: {src_path}") print(f"Dest: {dst_path}") # 1. Count total files for the progress bar print("Scanning source directory to count files...") total_files = sum(len(files) for _, _, files in os.walk(src_path)) print(f"Total files found: {total_files}") print("-" * 40) # 2. Start the copy process with tqdm(total=total_files, unit='file') as pbar: for root, dirs, files in os.walk(src_path): rel_path = Path(root).relative_to(src_path) # Sanitize directory path safe_parts = [sanitize_component(p) for p in rel_path.parts if p != '.'] current_dst_dir = dst_path.joinpath(*safe_parts) if not current_dst_dir.exists(): try: current_dst_dir.mkdir(parents=True, exist_ok=True) except OSError as e: pbar.write(f"Error creating dir {rel_path}: {e}") continue for filename in files: safe_filename = sanitize_component(filename) src_file = Path(root) / filename dst_file = current_dst_dir / safe_filename # Logic: Copy if dest missing OR src is newer should_copy = False if not dst_file.exists(): should_copy = True elif src_file.stat().st_mtime > dst_file.stat().st_mtime: should_copy = True if should_copy: try: # Update description to show current file being copied # (Only do this if using real tqdm to avoid clutter in fallback) if HAS_TQDM: pbar.set_description(f"Copying {safe_filename[:20]}...") shutil.copy2(src_file, dst_file) except Exception as e: pbar.write(f"Failed to copy {filename}: {e}") # Advance progress bar pbar.update(1) print("Operation complete.") if __name__ == "__main__": main()