import asyncio import edge_tts import re import os from pathlib import Path def limpiar_markdown(texto): texto = re.sub(r'```.*?```', ' [código] ', texto, flags=re.DOTALL) texto = re.sub(r'\|.*?\|', '', texto) texto = re.sub(r'[#*_~`>]', '', texto) return ' '.join(texto.split()) def cargar_titulos_readme(readme_path): """Extrae títulos de temas del README: {(bloque_num, tema_num): titulo}""" titulos = {} patron = re.compile(r'\[([^\]]+)\]\(bloque(\d+)/tema(\d+)\.md\)') texto = Path(readme_path).read_text(encoding="utf-8") for match in patron.finditer(texto): titulo, bloque_num, tema_num = match.group(1), int(match.group(2)), int(match.group(3)) titulos[(bloque_num, tema_num)] = titulo return titulos async def convertir_archivo(path_md, bloque_num, titulos, mp3_dir): temp_output = path_md.with_suffix('.temp.mp3') final_output = mp3_dir / path_md.with_suffix('.mp3').name # Saltar si el .mp3 ya existe y es más reciente que el .md if final_output.exists() and final_output.stat().st_mtime >= path_md.stat().st_mtime: print(f"⏭️ Sin cambios, omitiendo: {path_md.name}") return # Extraer número de tema del nombre del fichero (ej: tema6_audio -> 6) match_tema = re.match(r'tema(\d+)', path_md.stem) if match_tema: tema_num = int(match_tema.group(1)) titulo_tema = titulos.get((bloque_num, tema_num), '') if titulo_tema: nombre_meta = f"Bloque {bloque_num} - Tema {tema_num}: {titulo_tema}" else: nombre_meta = f"Bloque {bloque_num} - Tema {tema_num}" else: nombre_meta = path_md.stem.replace('_', ' ').capitalize() # Escapar comillas para el comando shell nombre_meta_escaped = nombre_meta.replace('"', '\\"') texto = path_md.read_text(encoding="utf-8") texto_limpio = limpiar_markdown(texto) # 1. Generar audio comunicar = edge_tts.Communicate(texto_limpio, "es-ES-AlvaroNeural") await comunicar.save(temp_output) # 2. Normalización agresiva para Spotify (CBR 192k + Título) comando = ( f'ffmpeg -i "{temp_output}" -codec:a libmp3lame -b:a 192k -ar 44100 ' f'-metadata title="{nombre_meta_escaped}" -id3v2_version 3 -write_id3v1 1 ' f'-y "{final_output}" > /dev/null 2>&1' ) os.system(comando) if temp_output.exists(): temp_output.unlink() print(f"✅ Listo para el coche: {final_output} (Título: {nombre_meta})") async def main(): base_dir = Path(__file__).parent.parent readme_path = base_dir / "README.md" audios_dir = base_dir / "audios" titulos = cargar_titulos_readme(readme_path) for bloque_num in range(1, 5): md_dir = audios_dir / "md" / f"bloque{bloque_num}" mp3_dir = audios_dir / "mp3" / f"bloque{bloque_num}" if not md_dir.exists(): continue mp3_dir.mkdir(parents=True, exist_ok=True) print(f"\nProcesando bloque{bloque_num}...") archivos = sorted(md_dir.glob('*_audio.md')) for md in archivos: print(f"Procesando: {md.name}...") await convertir_archivo(md, bloque_num, titulos, mp3_dir) if __name__ == "__main__": asyncio.run(main())