86 lines
3.2 KiB
Python
86 lines
3.2 KiB
Python
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()) |