MD2Video: Genera video da post Hugo in .MD (con qualche compromesso)
Vi è mai capitato di guardare un post del blog e pensare “sarebbe bello poterlo ascoltare mentre faccio altro”? Beh, è esattamente quello che mi è passato per la testa qualche giorno fa. E come ogni sviluppatore che si rispetti, invece di cercare una soluzione esistente, ho pensato “hey, potrei scriverlo io!”. Perché sì, a volte reinventare la ruota può essere divertente (e istruttivo).
L’Architettura del Progetto
Il progetto è strutturato seguendo il principio della separazione delle responsabilità, con tre componenti principali che gestiscono rispettivamente l’input dei post, la generazione degli script e la creazione dei video.
Pipeline di Elaborazione
Post Markdown -> Script XML -> Video MP4
↓ ↓ ↓
BlogProcessor -> ScriptProcessor -> VideoProcessor
Ogni step della pipeline è indipendente e può essere testato e modificato separatamente. Questo approccio modulare permette di:
- Sostituire facilmente i componenti
- Aggiungere nuove funzionalità senza modificare il codice esistente
- Testare ogni componente in isolamento
Il Cuore del Sistema: I Processor
BlogProcessor
Si occupa di leggere i file markdown e estrarre:
- Metadati (frontmatter)
- Struttura del contenuto (headings, paragrafi)
- Elementi speciali (liste, citazioni)
def _parse_content(self, content: str) -> List[Dict]:
"""Parse il contenuto markdown in sezioni strutturate"""
sections = []
current_section = {"level": 0, "title": "", "content": []}
for line in content.split('\n'):
# Matching dei titoli
heading_match = re.match(r'^(#{1,6})\s+(.+)$', line)
if heading_match:
if current_section["content"]:
sections.append(current_section)
level = len(heading_match.group(1))
title = heading_match.group(2).strip()
current_section = {
"level": level,
"title": title,
"content": []
}
ScriptProcessor
Genera uno script XML intermedio che definisce:
- La struttura del video
- Il testo da narrare
- Le transizioni e gli effetti
<?xml version="1.0" encoding="UTF-8"?>
<script version="1.0">
<metadata>
<title>Titolo Post</title>
<date>2024-11-25</date>
</metadata>
<content>
<section level="1" type="intro" animation="fade">
<heading>Introduzione</heading>
<speech pause="0.5">Testo da narrare</speech>
</section>
</content>
</script>
In realtà questo passaggio non è necesario. E’ possibile infatti generare un file XML manualmente nella dir degli script.
Inoltre è possibile aggiungere degli sfondi custom al posto di quelli default generati dallo script, tramite il parametro background di section:
<?xml version="1.0" encoding="UTF-8"?>
<script version="1.0">
<metadata>
<title>Titolo Post 2</title>
<date>2024-11-25</date>
</metadata>
<content>
<section level="1" type="intro" background="intro_bg.png" animation="zoom">
<heading>Introduzione</heading>
<speech pause="0.5">Testo da narrare</speech>
<speech pause="0.7">Ulteriore testo da narrare</speech>
</section>
</content>
</script>
VideoProcessor
Il componente più complesso, che si occupa di:
- Generare le slide con PIL
- Creare l’audio con gTTS
- Applicare effetti e transizioni
- Assemblare il video finale con MoviePy
def _create_slide(self, text: str, output_path: Path):
"""Crea una slide con testo"""
image = Image.new('RGB',
(self.config.VIDEO_WIDTH,
self.config.VIDEO_HEIGHT))
draw = ImageDraw.Draw(image)
# Configurazione font
font = ImageFont.truetype(self.config.FONT_PATH,
self.config.FONT_SIZE)
# Layout del testo
lines = self._wrap_text(text, font,
self.config.MAX_WIDTH)
y_position = self._calculate_vertical_position(lines)
# Rendering del testo
for line in lines:
x = self._center_text(line, font)
draw.text((x, y_position), line,
font=font,
fill=self.config.TEXT_COLOR)
y_position += self.config.LINE_HEIGHT
Sfide Tecniche e Soluzioni
1. Sincronizzazione Audio-Video
Una delle sfide principali è stata sincronizzare perfettamente l’audio con le slide. La soluzione? Utilizzare i metadati dell’audio generato:
def _sync_audio_video(self, audio_clip, video_clip):
"""Sincronizza audio e video considerando le pause"""
audio_duration = audio_clip.duration
video_duration = audio_duration + self.config.PAUSE_DURATION
# Estendi il video per coprire l'audio più la pausa
extended_video = video_clip.set_duration(video_duration)
# Applica l'audio
final_clip = extended_video.set_audio(audio_clip)
return final_clip
2. Gestione della Memoria
La generazione di video può essere memory-intensive. Ho implementato un sistema di pulizia progressiva:
def cleanup(self):
"""Pulisce i file temporanei durante l'elaborazione"""
temp_dir = Path(self.config.TEMP_DIR)
if temp_dir.exists():
for file in temp_dir.glob('*'):
try:
file.unlink()
except Exception as e:
self.logger.error(f"Errore pulizia: {str(e)}")
3. Text-to-Speech: Il Punto Dolente
Attualmente utilizzo gTTS per la sintesi vocale. Non è la soluzione ideale per diversi motivi:
- Qualità audio non eccezionale
- Necessità di connessione internet
- Limiti di utilizzo
- Poca naturalezza nella prosodia
Ho valutato alternative come:
- Amazon Polly (costoso per uso personale)
- Mozilla TTS (complesso da configurare)
- Coqui TTS (promettente ma ancora acerbo)
Per ora gTTS rimane la scelta più pratica per un progetto open source, ma sono aperto a suggerimenti per alternative migliori.
Testing e Qualità del Codice
Ho implementato una suite di test completa utilizzando pytest:
@pytest.fixture
def video_processor(tmp_path):
"""Crea un VideoProcessor configurato per i test"""
processor = VideoProcessor()
processor.config.TEMP_DIR = tmp_path / "temp"
processor.config.OUTPUT_DIR = tmp_path / "output"
return processor
def test_video_generation(video_processor):
"""Verifica la generazione completa del video"""
script_path = "test_script.xml"
output_file = video_processor.process(script_path)
assert Path(output_file).exists()
assert Path(output_file).stat().st_size > 0
Work in Progress
Il progetto è ancora in fase di sviluppo attivo e ci sono diverse aree di miglioramento:
Performance
- Parallelizzazione della generazione delle slide
- Caching dei font e degli elementi grafici comuni
- Ottimizzazione della pipeline video
Features Pianificate
- Supporto per temi personalizzabili
- Più effetti di transizione
- Miglior gestione del layout per codice e tabelle
Documentazione
- Guida completa all’installazione e all’utilizzo
- API reference
- Tutorial per l’estensione
Contribuire al Progetto
Il codice è disponibile su GitHub e sono ben accetti contributi di ogni tipo:
- Bug fix
- Nuove feature
- Miglioramenti alla documentazione
- Suggerimenti per alternative a gTTS
- Report di bug e feedback
Per contribuire:
- Forka il repository
- Crea un branch per la tua feature
- Implementa le modifiche
- Aggiungi o aggiorna i test
- Invia una pull request
Conclusioni
MD2Video è nato come esperimento personale ma ha il potenziale per diventare uno strumento utile per chi vuole rendere i propri contenuti più accessibili. Nonostante le limitazioni attuali (soprattutto legate al text-to-speech), il progetto fornisce una base solida per generare video da contenuti markdown.
Se sei interessato a contribuire o hai suggerimenti, special modo riguardo alternative a gTTS, ogni contributo è benvenuto!