codigo0/scripts/normalize-media-filenames.py

101 lines
3.2 KiB
Python
Raw Normal View History

2026-01-19 08:10:16 +00:00
#!/usr/bin/env python3
"""
Normaliza nombres de archivos multimedia a snake_case y actualiza referencias.
Uso:
python3 scripts/normalize-media-filenames.py --apply
python3 scripts/normalize-media-filenames.py --dry-run
"""
import argparse
import os
import re
import unicodedata
from pathlib import Path
MEDIA_EXTS = {'.png', '.jpg', '.jpeg', '.webp', '.svg', '.gif', '.mp4', '.webm', '.mov', '.avi', '.mkv'}
TEXT_EXTS = {'.js', '.jsx', '.ts', '.tsx', '.html', '.css', '.scss', '.sass', '.less', '.md', '.mdx', '.json', '.txt'}
SKIP_DIRS = {'node_modules', '.git', 'dist', 'build', '.next', 'coverage'}
def to_snake_case(filename: str) -> str:
base, ext = os.path.splitext(filename)
base = unicodedata.normalize('NFKD', base).encode('ascii', 'ignore').decode('ascii')
base = base.lower()
base = re.sub(r'[^a-z0-9]+', '_', base).strip('_')
base = re.sub(r'_+', '_', base)
return base + ext.lower()
def iter_files(root: Path):
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for filename in filenames:
yield Path(dirpath) / filename
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--apply', action='store_true', help='Aplicar cambios')
parser.add_argument('--dry-run', action='store_true', help='Solo mostrar cambios')
args = parser.parse_args()
if not args.apply and not args.dry_run:
args.dry_run = True
root = Path(__file__).resolve().parents[1]
media_files = [p for p in iter_files(root) if p.suffix.lower() in MEDIA_EXTS]
rename_map = {}
for path in media_files:
new_name = to_snake_case(path.name)
if new_name != path.name:
new_path = path.with_name(new_name)
if new_path.exists() and new_path.resolve() != path.resolve():
stem, ext = os.path.splitext(new_name)
i = 2
while True:
candidate = path.with_name(f"{stem}_{i}{ext}")
if not candidate.exists():
new_path = candidate
break
i += 1
rename_map[path] = new_path
# Print plan
print(f"Archivos a renombrar: {len(rename_map)}")
for old, new in rename_map.items():
print(f"{old} -> {new}")
if args.dry_run:
return
# Apply renames
for old, new in rename_map.items():
old.rename(new)
# Update references
repl_pairs = []
for old, new in rename_map.items():
repl_pairs.append((str(old), str(new)))
repl_pairs.append((old.name, new.name))
repl_pairs.sort(key=lambda x: len(x[0]), reverse=True)
for path in iter_files(root):
if path.suffix.lower() not in TEXT_EXTS:
continue
try:
text = path.read_text(encoding='utf-8', errors='ignore')
except Exception:
continue
new_text = text
for old, new in repl_pairs:
if old in new_text:
new_text = new_text.replace(old, new)
if new_text != text:
path.write_text(new_text, encoding='utf-8')
if __name__ == '__main__':
main()