Synchronisation Audio-Video (Passerelle)

Module : 04-Audio-Applications
Niveau : Applications
Technologies : OpenAI TTS, moviepy, pydub, ffmpeg-python
VRAM estimee : ~10 GB
Duree estimee : 55 minutes

Objectifs d’Apprentissage

Prerequis

  • Notebooks Foundation (01-1 TTS, 01-3 Audio Opérations) completes
  • Notebook 04-2 (Pipeline de Transcription) pour les sous-titres
  • Cle API OpenAI configuree (OPENAI_API_KEY dans .env)
  • moviepy, pydub, ffmpeg installes

Note : Ce notebook est la passerelle entre la serie Audio et la serie Video. Il utilise des techniques audio pour enrichir du contenu video. La serie Video (Video/01-Foundation/) approfondit la generation et l’edition video.

Navigation : Index | << Précédent | Serie Video >>

# Parametres Papermill - JAMAIS modifier ce commentaire

# Configuration notebook
notebook_mode = "interactive"        # "interactive" ou "batch"
skip_widgets = False               # True pour mode batch MCP
debug_level = "INFO"

# Parametres TTS narration
tts_model = "tts-1"               # "tts-1" ou "tts-1-hd"
narrator_voice = "nova"            # Voix de narration

# Parametres video
video_resolution = (640, 480)      # Resolution de la video de test
video_fps = 24                     # Images par seconde
video_duration = 15                # Duree de la video de test (secondes)

# Configuration pipeline
generate_audio = True
save_output_files = True
background_music_volume = -20      # Volume musique de fond en dB (relatif)
# Parameters
BATCH_MODE = "true"

Configuration du pipeline

Ce notebook utilise des paramètres configurables pour s’adapter a différents scénarios d’exécution.

Modes d’exécution - interactive : Mode normal avec widgets et affichages - batch : Mode automatise (Papermill) sans interaction

Paramètres TTS - tts_model : Modèle de synthese vocale (tts-1 ou tts-1-hd) - narrator_voice : Voix utilisee pour la narration (nova, alloy, echo, fable, onyx, shimmer)

Paramètres video - video_resolution : Taille de la video (640x480 pour les tests) - video_fps : Images par seconde (24 standard, 30 pour video fluide) - video_duration : Duree totale en secondes (15s pour demo rapide)

Paramètres de mixage - background_music_volume : Volume relatif de la musique (-20dB standard)

Flags de contrôle - generate_audio : Active/desactive la generation TTS (pour tests rapides) - save_output_files : Sauvegarde les fichiers audio generes - skip_widgets : Desactive les widgets interactifs en mode batch

Ces paramètres permettent d’adapter le notebook a différents cas d’usage : - Prototype rapide : Duree courte, resolution basse - Production : Haute resolution, tts-1-hd, musique personnalisee - Test : generate_audio=False pour verifier le code sans appels API

L’environnement de travail est configure avec les paramètres Papermill. La cellule suivante charge les bibliotheques Python necessaires : traitement video, synthese audio TTS et utilitaires de synchronisation.

# Setup environnement et imports
import os
import sys
import json
import time
from pathlib import Path
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple
from io import BytesIO
import logging

import numpy as np
from IPython.display import Audio, display, HTML, Video as IPVideo

# Resolution GENAI_ROOT
GENAI_ROOT = Path.cwd()
while GENAI_ROOT.name != 'GenAI' and len(GENAI_ROOT.parts) > 1:
    GENAI_ROOT = GENAI_ROOT.parent

HELPERS_PATH = GENAI_ROOT / 'shared' / 'helpers'
if HELPERS_PATH.exists():
    sys.path.insert(0, str(HELPERS_PATH.parent))
    try:
        from helpers.audio_helpers import (
            synthesize_openai, play_audio_bytes, display_audio_bundle,
            estimate_audio_duration, get_audio_info
        )
        print("Helpers audio importes")
    except ImportError as e:
        print(f"Helpers audio non disponibles : {e}")

# Repertoires de sortie
OUTPUT_DIR = GENAI_ROOT / 'outputs' / 'audio' / 'av_sync'
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

logging.basicConfig(level=getattr(logging, debug_level))
logger = logging.getLogger('av_sync')

print(f"Synchronisation Audio-Video")
print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Mode : {notebook_mode}, TTS : {tts_model}")
print(f"Sortie : {OUTPUT_DIR.relative_to(GENAI_ROOT)}")
Helpers audio importes
Synchronisation Audio-Video
Date : 2026-08-16 01:50:59
Mode : interactive, TTS : tts-1
Sortie : outputs\audio\av_sync

Verification de l’environnement

Le notebook necessite plusieurs dependances pour fonctionner correctement. Verifions leur disponibilite.

Bibliotheques requises

Bibliotheque Usage Installation
moviepy Manipulation video pip install moviepy
pydub Manipulation audio pip install pydub
ffmpeg-python Encodage multimedia pip install ffmpeg-python
openai API TTS pip install openai
dotenv Configuration .env pip install python-dotenv

Système externe - ffmpeg : Encodeur video (requis par moviepy et pydub) - Windows : choco install ffmpeg - Linux : sudo apt install ffmpeg - macOS : brew install ffmpeg

Verification API La cle OpenAI est chargee depuis le fichier .env a la racine du dossier GenAI. Sans cette cle, la generation TTS ne fonctionnera pas.

Helpers audio Des fonctions utilitaires sont importees depuis shared/helpers/audio_helpers : - synthesize_openai() : Generation TTS via API OpenAI - play_audio_bytes() : Lecture audio dans le notebook - estimate_audio_duration() : Estimation de la duree - get_audio_info() : Metadonnees audio

Si les helpers ne sont pas disponibles, le notebook fonctionnera quand même avec des implementations de secours.

Les modules Python sont importes. Cette cellule charge les cles API depuis le fichier .env en remontant l’arborescence de repertoires pour garantir la compatibilite avec les différents environnements d’exécution.

# Chargement robuste de la configuration .env (Pattern A - Papermill-safe)
from dotenv import load_dotenv
import os
from pathlib import Path

current_path = Path.cwd()
genai_path = None

# Remonter jusqu'au dossier GenAI
while len(current_path.parts) > 1:
    if current_path.name == "GenAI":
        genai_path = current_path
        break
    current_path = current_path.parent

if genai_path:
    env_path = genai_path / ".env"
    if env_path.exists():
        load_dotenv(env_path)
        print(f".env charge depuis: {env_path.name}")

# Configuration API OpenAI (pattern canonique de la serie : TTS ne passe pas par OpenRouter)
openai_key = os.environ.get('OPENAI_DIRECT_API_KEY') or os.environ.get('OPENAI_API_KEY', '')
openai_available = bool(openai_key and not openai_key.startswith('sk-or-'))

if openai_available:
    print("Cle OpenAI directe valide - API disponible")
    from openai import OpenAI
    client = OpenAI(api_key=openai_key, base_url="https://api.openai.com/v1")
else:
    print("OPENAI_DIRECT_API_KEY non disponible ou invalide (cle OpenRouter)")
    openai_key = "dummy_key_for_validation"
    client = None

# Verification des dependances video
moviepy_available = False
try:
    from moviepy import VideoFileClip, AudioFileClip, concatenate_videoclips, ColorClip, TextClip, CompositeVideoClip
    moviepy_available = True
    print("moviepy disponible")
except ImportError:
    print("moviepy non installe - pip install moviepy")

# pydub pour manipulation audio
try:
    from pydub import AudioSegment
    print("pydub disponible")
except ImportError:
    print("pydub non installe")

print("\nConfiguration prete")
.env charge depuis: .env
Cle OpenAI directe valide - API disponible
moviepy disponible
pydub disponible

Configuration prete

Section 1 : Creation d’une video de test et extraction audio

Avant de travailler sur la synchronisation, nous creons une video de test simple et montrons comment extraire sa piste audio.

Opération Outil Entree Sortie
Creation video moviepy Paramètres Fichier .mp4
Extraction audio moviepy / ffmpeg Video .mp4 Audio .wav/.mp3
Analyse audio pydub Audio Metadonnees

Creation de contenu video avec moviepy

moviepy est une bibliotheque Python pour la manipulation video qui simplifie les opérations courantes : - Creation de clips a partir d’images ou de couleurs - Concatenation de plusieurs segments - Ajout de textes et titres - Export vers des formats standards (MP4, AVI, GIF)

Structure d’une video Une video est composee de : 1. Visuels : Images, couleurs, animations, textes 2. Audio : Voix, musique, effets sonores 3. Metadonnees : Resolution, FPS, codec, duree

Workflow de creation

1. Définir les segments (label, couleur, duree)
2. Créer un clip par segment (ColorClip + TextClip)
3. Concatener les segments (concatenate_videoclips)
4. Configurer les paramètres (FPS, resolution)
5. Exporter (write_videofile)

Extraction audio L’extraction de la piste audio d’une video existante se fait en une ligne :

from moviepy import VideoFileClip
video = VideoFileClip("video.mp4")
audio = video.audio  # Extraire la piste audio
audio.write_audiofile("audio.wav")  # Sauvegarder

Cette opération est essentielle pour : - Transcrire une video (Whisper) - Remplacer l’audio (doublage) - Analyser la bande sonore

Le code suivant créé une video de test avec 3 segments colores.

# Creation d'une video de test et extraction audio
print("CREATION VIDEO DE TEST ET EXTRACTION AUDIO")
print("=" * 50)

test_video_path = OUTPUT_DIR / "test_video.mp4"
extracted_audio_path = OUTPUT_DIR / "extracted_audio.wav"

if generate_audio and moviepy_available:
    # Creer une video de test avec des scenes colorees
    print("Creation de la video de test...")

    # Definir les segments de la video
    video_segments = [
        {"label": "Introduction", "color": (30, 60, 120), "duration": 5},
        {"label": "Concepts cles", "color": (60, 120, 30), "duration": 5},
        {"label": "Conclusion", "color": (120, 30, 60), "duration": 5}
    ]

    clips = []
    for seg in video_segments:
        # Clip de couleur unie
        color_clip = ColorClip(
            size=video_resolution,
            color=seg['color'],
            duration=seg['duration']
        )

        # Ajouter un titre
        try:
            txt_clip = TextClip(
                text=seg['label'],
                font_size=40,
                color='white'
            ).with_position('center').with_duration(seg['duration'])
            clip = CompositeVideoClip([color_clip, txt_clip])
        except Exception:
            # Si TextClip echoue (ImageMagick non disponible), utiliser le clip brut
            clip = color_clip

        clips.append(clip)

    # Concatener les clips
    final_video = concatenate_videoclips(clips)
    final_video = final_video.with_fps(video_fps)

    # Sauvegarder la video
    final_video.write_videofile(
        str(test_video_path),
        fps=video_fps,
        codec='libx264',
        audio=False,
        logger=None
    )
    final_video.close()

    print(f"Video creee : {test_video_path.name}")
    print(f"Resolution : {video_resolution[0]}x{video_resolution[1]}")
    print(f"Duree : {video_duration}s | FPS : {video_fps}")
    print(f"Segments : {len(video_segments)}")

    for i, seg in enumerate(video_segments):
        start = sum(s['duration'] for s in video_segments[:i])
        end = start + seg['duration']
        print(f"  [{start:2.0f}s - {end:2.0f}s] {seg['label']}")

    # Demonstration de l'extraction audio
    print(f"\n(La video de test n'a pas de piste audio - nous en ajouterons une)")

elif not moviepy_available:
    print("moviepy non disponible - pip install moviepy")
else:
    print("Generation desactivee")
CREATION VIDEO DE TEST ET EXTRACTION AUDIO
==================================================
Creation de la video de test...
Video creee : test_video.mp4
Resolution : 640x480
Duree : 15s | FPS : 24
Segments : 3
  [ 0s -  5s] Introduction
  [ 5s - 10s] Concepts cles
  [10s - 15s] Conclusion

(La video de test n'a pas de piste audio - nous en ajouterons une)

Interpretation : Video de test

Segment Debut Fin Contenu
Introduction 0s 5s Fond bleu
Concepts cles 5s 10s Fond vert
Conclusion 10s 15s Fond rouge

Points cles : 1. moviepy permet de créer et manipuler des videos en Python 2. L’extraction audio utilise la même API (video.audio) 3. La video de test servira de base pour la synchronisation


Exercice : Planificateur de segments video

Duree estimee : 15-20 minutes

Objectif

Créer une fonction qui planifie automatiquement les segments video et calcule la duree optimale du texte de narration pour chaque segment.

Contexte

Pour synchroniser audio et video, il faut que le texte de narration tienne dans la duree du segment. A 130 mots/min (vitesse standard), un segment de 5 secondes ne peut contenir que ~11 mots. Cette fonction aide a calibrer les scripts.

Instructions

  1. Définir une liste de segments video avec duree et label
  2. Calculer le nombre maximal de mots par segment
  3. Generer des suggestions de texte de narration adaptees
  4. Verifier que chaque texte respecte la contrainte de duree

Indices : - # Étape 1 : Vitesse standard = 2.17 mots/seconde, mots_max = duree * 2.17 * 0.85 (marge) - # Étape 2 : Pour chaque segment, calculer le budget de mots - # Indice : Prevoir 15% de marge pour les pauses naturelles - # Indice : Si speed > 1.0 dans le TTS, le budget augmente proportionnellement

# TODO: Implementer le planificateur de segments video
def plan_video_segments(segments: list, words_per_sec: float = 2.17,
                         safety_margin: float = 0.85) -> list:
    """
    Planifie les segments video et calcule le budget de mots pour chacun.
    
    Args:
        segments: Liste de dicts {"label": str, "duration": float}
        words_per_sec: Vitesse de parole standard (mots/seconde)
        safety_margin: Marge de securite (0.85 = 15% de marge)
    
    Returns:
        Liste enrichie avec max_words, suggested_text_length
    """
    planned = []
    for seg in segments:
        # Etape 1 : Calculer le budget de mots
        # Indice: max_words = int(seg["duration"] * words_per_sec * safety_margin)
        max_words = 0  # TODO etudiant
        
        # Etape 2 : Estimer la longueur de texte (en caracteres)
        # Indice: longueur_moyenne_mot = 5 caracteres, + espaces
        max_chars = 0  # TODO etudiant
        
        planned.append({
            "label": seg["label"],
            "duration": seg["duration"],
            "max_words": max_words,
            "max_chars": max_chars,
            "status": "Exercice a completer"
        })
    
    return planned

# Test avec des segments de demonstration
demo_segments = [
    {"label": "Introduction", "duration": 5},
    {"label": "Concepts cles", "duration": 8},
    {"label": "Conclusion", "duration": 5},
]
# planned = plan_video_segments(demo_segments)
# print(f"{'Segment':<20} {'Duree':<8} {'Max mots':<10} {'Max chars':<10}")
# print("-" * 48)
# for p in planned:
#     print(f"{p['label']:<20} {p['duration']:<8.0f} {p['max_words']:<10} {p['max_chars']:<10}")
print("Exercice a completer")
Exercice a completer

Section 2 : Narration TTS synchronisee

Pour chaque segment video, nous generons une narration TTS adaptee. Le texte est calibre pour correspondre a la duree du segment.

Segment Duree cible Texte adapte Vitesse TTS
Introduction 5s Court, accueillant 1.0
Concepts cles 5s Dense, informatif 0.95
Conclusion 5s Recapitulatif 1.0

Synthese vocale pour la narration

La narration TTS (Text-to-Speech) transforme des scripts textuels en audio parle. Pour une video, plusieurs contraintes s’appliquent.

Paramètres TTS clés

Paramètre Valeurs Impact sur la video
model tts-1, tts-1-hd Qualite audio (hd = meilleure qualite)
voice alloy, echo, fable, onyx, nova, shimmer Ton, genre, style vocal
speed 0.25 a 4.0 Duree audio (1.0 = normal, 2.0 = 2x plus rapide)
response_format mp3, opus, aac, flac Taille fichier et compatibilite

Calcul de la duree cible Pour un segment de D secondes, la narration doit respecter : - Vitesse normale (130 mots/min) : ≈ 2.17 mots/seconde - Duree maximale : D secondes - Texte ideal : ≤ D × 2.17 mots

Exemple : Pour un segment de 5 secondes - Texte ideal : 5 × 2.17 ≈ 11 mots - Avec speed=1.5 : 5 × 2.17 × 1.5 ≈ 16 mots - Marge securite : retirer 10-15% pour les pauses

Stratégies d’adaptation

  1. Texte trop long : Reduire de 30% ou augmenter speed a 1.3-1.5x
  2. Texte trop court : Ajouter des transitions ou des exemples
  3. Voix inadaptee : Tester les 6 voix disponibles
  4. Qualite insuffisante : Passer a tts-1-hd (+20% de taille)

Le code genere des narrations pour chaque segment video en respectant les contraintes de timing.

# Generation de narration TTS synchronisee aux segments video
print("NARRATION TTS SYNCHRONISEE")
print("=" * 50)

# Scripts de narration pour chaque segment video
narration_scripts = [
    {
        "segment": "Introduction",
        "start": 0, "end": 5,
        "text": "Bienvenue dans cette presentation sur l'intelligence artificielle generative. Nous allons decouvrir ensemble les concepts fondamentaux.",
        "speed": 1.0
    },
    {
        "segment": "Concepts cles",
        "start": 5, "end": 10,
        "text": "Les modeles generatifs apprennent a creer du contenu nouveau. Ils analysent des exemples existants pour en deduire des patterns.",
        "speed": 0.95
    },
    {
        "segment": "Conclusion",
        "start": 10, "end": 15,
        "text": "En resume, l'IA generative ouvre de nouvelles possibilites creatives. Explorez les notebooks suivants pour aller plus loin.",
        "speed": 1.0
    }
]

narration_audio_segments = []
pydub_available = False

# Verifier si pydub/ffmpeg fonctionne
try:
    test_audio = AudioSegment.silent(duration=100)
    pydub_available = True
except Exception as e:
    print(f"pydub/ffmpeg non disponible : {e}")
    print("Utilisation de la duree estimee pour la synchronisation")

if generate_audio and openai_key != "dummy_key_for_validation":
    print(f"Generation de la narration pour {len(narration_scripts)} segments :")

    for script in narration_scripts:
        print(f"\n--- {script['segment']} [{script['start']}s - {script['end']}s] ---")
        print(f"Texte : {script['text'][:70]}...")

        start_time = time.time()
        response = client.audio.speech.create(
            model=tts_model,
            voice=narrator_voice,
            input=script['text'],
            response_format="mp3",
            speed=script['speed']
        )
        audio_bytes = response.content
        gen_time = time.time() - start_time

        # Estimer la duree audio (approximation: 130 mots/min = 2.17 mots/sec)
        word_count = len(script['text'].split())
        estimated_duration = word_count / 2.17 / script['speed']

        # Tenter de charger avec pydub si disponible
        audio_seg = None
        audio_duration = estimated_duration

        if pydub_available:
            try:
                audio_seg = AudioSegment.from_mp3(BytesIO(audio_bytes))
                audio_duration = len(audio_seg) / 1000  # ms -> s
            except Exception as e:
                print(f"  pydub erreur, utilisation estimation : {e}")

        segment_duration = script['end'] - script['start']

        narration_audio_segments.append({
            "segment": script['segment'],
            "start": script['start'],
            "end": script['end'],
            "audio_bytes": audio_bytes,
            "audio_segment": audio_seg,
            "audio_duration": audio_duration,
            "target_duration": segment_duration
        })

        fit_status = "OK" if audio_duration <= segment_duration else "DEPASSE"
        print(f"  Duree audio : {audio_duration:.1f}s / cible {segment_duration}s [{fit_status}]")
        print(f"  Taille : {len(audio_bytes)/1024:.1f} KB | Generation : {gen_time:.1f}s")
        display_audio_bundle(audio_bytes)

    # Recapitulatif
    print(f"\nRecapitulatif synchronisation :")
    print(f"{'Segment':<18} {'Audio (s)':<12} {'Cible (s)':<12} {'Statut':<10}")
    print("-" * 52)
    for seg in narration_audio_segments:
        status = "OK" if seg['audio_duration'] <= seg['target_duration'] else "A ajuster"
        print(f"{seg['segment']:<18} {seg['audio_duration']:<12.1f} {seg['target_duration']:<12.0f} {status:<10}")
else:
    print("Generation desactivee")
NARRATION TTS SYNCHRONISEE
==================================================
Generation de la narration pour 3 segments :

--- Introduction [0s - 5s] ---
Texte : Bienvenue dans cette presentation sur l'intelligence artificielle gene...
  Duree audio : 8.2s / cible 5s [DEPASSE]
  Taille : 127.5 KB | Generation : 1.9s

--- Concepts cles [5s - 10s] ---
Texte : Les modeles generatifs apprennent a creer du contenu nouveau. Ils anal...
  Duree audio : 8.3s / cible 5s [DEPASSE]
  Taille : 129.8 KB | Generation : 2.6s

--- Conclusion [10s - 15s] ---
Texte : En resume, l'IA generative ouvre de nouvelles possibilites creatives. ...
  Duree audio : 7.4s / cible 5s [DEPASSE]
  Taille : 115.1 KB | Generation : 3.4s

Recapitulatif synchronisation :
Segment            Audio (s)    Cible (s)    Statut    
----------------------------------------------------
Introduction       8.2          5            A ajuster 
Concepts cles      8.3          5            A ajuster 
Conclusion         7.4          5            A ajuster 

Interpretation : Narration synchronisee

Aspect Valeur Signification
Synchronisation Audio <= duree segment La narration doit tenir dans le segment
Depassement Possible Ajuster vitesse ou couper le texte
Silence residuel Normal Comble par la musique de fond

Note technique : Si la narration depasse la duree du segment, deux solutions : augmenter la vitesse TTS (max 4.0x) ou raccourcir le texte.

Section 3 : Alignement audio sur la timeline video

L’alignement consiste a placer chaque segment audio au bon moment sur la timeline de la video.

Video:  [====Introduction====][==Concepts cles==][====Conclusion====]
Audio:  [narration_1][silence][narration_2][ sil ][narration_3][ sil]
        0s           5s       5s          10s    10s          15s

Chaque narration est placee avec un padding de silence pour remplir exactement la duree du segment.

Concept de timeline audio

Une timeline est une representation lineaire du temps ou chaque segment audio est positionne a un moment précis.

Representation visuelle

Temps (s) : 0    2    4    6    8    10   12   14   16
            |----|----|----|----|----|----|----|----|----|
Video:      [Intro rouge]  [Concepts verts]  [Conclusion bleue]
Audio:      [Narr1][sil][Narr2][sil][Narr3][sil]

Opérations sur la timeline - Overlay : Superposer un segment a un temps précis - Concatenate : Coller les segments bout a bout - Silence : Créer des espaces vides (padding) - Trim : Couper un segment a une duree precise

Stratégies d’alignement

Cas Solution Avantages Inconvenients
Audio < Duree segment Silence residuel Simple, naturel Espace vide
Audio = Duree segment Alignement parfait Optimal Rare en pratique
Audio > Duree segment Troncation + fade out Garde la synchro Perte de fin
Audio >> Duree segment Accelerer TTS (speed >1.0) Contenu complet Qualite vocale

Le code suivant implemente la stratégie “troncation + fade out” pour gerer les depassements.

# Alignement audio sur la timeline video
print("ALIGNEMENT AUDIO - TIMELINE")
print("=" * 50)

def align_audio_to_timeline(segments: List[Dict],
                             total_duration_ms: int) -> Optional[AudioSegment]:
    """Aligne les segments audio sur une timeline video.

    Chaque segment est place a son timestamp de debut,
    avec du silence pour remplir les espaces.
    """
    timeline = AudioSegment.silent(duration=total_duration_ms)

    for seg in segments:
        start_ms = int(seg['start'] * 1000)
        audio = seg['audio_segment']

        if audio is None:
            # Pas d'audio_segment disponible (pydub/ffmpeg non fonctionnel)
            # On cree un silence pour la duree estimee
            target_ms = int(seg['target_duration'] * 1000)
            continue

        # Tronquer si l'audio depasse la fin du segment
        target_ms = int(seg['target_duration'] * 1000)
        if len(audio) > target_ms:
            # Ajouter un fade out avant de tronquer
            audio = audio[:target_ms].fade_out(200)

        # Superposer sur la timeline
        timeline = timeline.overlay(audio, position=start_ms)

    return timeline

narration_timeline = None

if generate_audio and narration_audio_segments:
    total_duration_ms = video_duration * 1000

    print(f"Duree totale video : {video_duration}s ({total_duration_ms}ms)")
    print(f"Segments a aligner : {len(narration_audio_segments)}")

    # Verifier si on a des audio_segments valides
    has_valid_audio = any(seg['audio_segment'] is not None for seg in narration_audio_segments)

    if has_valid_audio:
        narration_timeline = align_audio_to_timeline(
            narration_audio_segments,
            total_duration_ms
        )

        print(f"\nTimeline narration :")
        print(f"  Duree : {len(narration_timeline)/1000:.1f}s")
        print(f"  Canaux : {narration_timeline.channels}")
        print(f"  Sample rate : {narration_timeline.frame_rate}Hz")
        print(f"  Volume : {narration_timeline.dBFS:.1f} dBFS")

        # Sauvegarder la timeline narration
        if save_output_files:
            narration_path = OUTPUT_DIR / "narration_timeline.wav"
            narration_timeline.export(str(narration_path), format="wav")
            print(f"  Sauvegarde : {narration_path.name}")

        # Ecouter
        narration_bytes = BytesIO()
        narration_timeline.export(narration_bytes, format="mp3")
        print(f"\nEcoute de la timeline narration :")
        display_audio_bundle(narration_bytes.getvalue())
    else:
        print("\npydub/ffmpeg non disponible - alignement audio impossible")
        print("Les segments TTS ont ete generes mais ne peuvent pas etre combines")
        for seg in narration_audio_segments:
            print(f"  - {seg['segment']}: {seg['audio_duration']:.1f}s (audio_bytes dispo)")
else:
    print("Alignement desactive (pas de segments narration)")
ALIGNEMENT AUDIO - TIMELINE
==================================================
Duree totale video : 15s (15000ms)
Segments a aligner : 3

Timeline narration :
  Duree : 15.0s
  Canaux : 1
  Sample rate : 24000Hz
  Volume : -28.3 dBFS
  Sauvegarde : narration_timeline.wav

Ecoute de la timeline narration :

Exercice : Mixage audio avec ducking automatique

Duree estimee : 25-30 minutes

Objectif

Implementer un algorithme de ducking qui reduit automatiquement le volume de la musique de fond lorsque la voix est detectee.

Contexte

Le ducking est une technique de mixage essentielle pour les podcasts et videos narrees. Plutot que de regler manuellement le volume, un algorithme detecte les moments ou la voix est active et reduit la musique en consequence.

Instructions

  1. Generer une piste de musique et une piste de voix (ou utiliser des samples existants)
  2. Detecter les zones de voix active via l’energie du signal
  3. Appliquer une reduction de volume sur la musique dans ces zones
  4. Lisser les transitions avec des fades (200-500ms)

Indices : - # Étape 1 : Calculer l’energie RMS par fenêtre glissante (50ms) sur la voix - # Étape 2 : Si energie > seuil, appliquer un gain reduit sur la musique - # Étape 3 : Lisser les transitions avec np.convolve() ou un filtre exponentiel - # Indice : Seuil de voix ~ -30 dBFS, reduction musique ~ -15 dB quand voix active - # Indice : Utiliser np.interp() pour créer une courbe de gain lisse

# TODO: Implementer le ducking automatique
def auto_duck(voice_audio: np.ndarray, music_audio: np.ndarray,
              sr: int = 44100, window_ms: int = 50,
              voice_threshold: float = 0.02,
              duck_reduction_db: float = -15) -> np.ndarray:
    """
    Reduit automatiquement le volume de la musique quand la voix est active.
    
    Args:
        voice_audio: Signal de la voix
        music_audio: Signal de la musique
        sr: Sample rate
        window_ms: Taille de fenetre d'analyse en ms
        voice_threshold: Seuil de detection de voix (energie RMS)
        duck_reduction_db: Reduction en dB quand voix active
    
    Returns:
        Musique avec ducking applique
    """
    # Etape 1 : Calculer l'energie de la voix par fenetre
    # Indice: window_samples = int(sr * window_ms / 1000)
    # Indice: Parcourir par fenetres, calculer RMS
    pass  # TODO etudiant
    
    # Etape 2 : Creer la courbe de gain pour la musique
    # Indice: gain = 1.0 si voix inactive, 10**(duck_reduction_db/20) si voix active
    pass  # TODO etudiant
    
    # Etape 3 : Lisser la courbe de gain et appliquer
    # Indice: np.convolve(gain_curve, smoothing_window, mode='same')
    # Indice: music_ducked = music_audio * gain_curve
    pass  # TODO etudiant
    
    return music_audio  # TODO etudiant : retourner la musique traitee

# Test avec des signaux de demonstration
sr_test = 44100
duration_test = 5
# voix_test = np.random.randn(sr_test * duration_test).astype(np.float32) * 0.1
# musique_test = np.sin(2 * np.pi * 440 * np.linspace(0, duration_test, sr_test * duration_test)).astype(np.float32) * 0.3
# result = auto_duck(voix_test, musique_test, sr_test)
# print(f"Ducking applique : {len(result)} samples")
print("Exercice a completer")
Exercice a completer

Section 4 : Musique de fond sous la narration

La superposition de musique de fond et de narration necessite un contrôle précis des niveaux :

Piste Volume relatif Raison
Narration 0 dB (reference) Voix au premier plan
Musique de fond -15 a -25 dB Ambiance sans couvrir la voix
Effets sonores -5 a -10 dB Ponctuels, perceptibles

La technique du “ducking” baisse automatiquement la musique quand la voix est presente.

Théorie du mixage audio

Le mixage de plusieurs pistes audio necessite de comprendre les niveaux relatifs :

Echelle dBFS (Decibels Full Scale) - 0 dBFS = Maximum numérique (clip) - -6 dBFS = Niveau “hot” (pop, radio) - -12 dBFS = Niveau standard (broadcast) - -18 dBFS = Niveau confortable (podcast) - -24 dBFS = Niveau faible (ambiance)

Equilibrage des pistes

Piste Volume cible Ratio
Narration -12 a -18 dBFS Reference (0 dB relatif)
Musique de fond -35 a -45 dBFS -20 dB sous narration
Effets sonores -20 a -25 dBFS -5 a -10 dB sous narration

Ducking automatique Le ducking baisse le volume de la musique quand la voix est presente : - Voice active : musique a -25 dB - Voice inactive : musique a -15 dB - Transition : 200-500 ms

Cette technique evite d’avoir a couper manuellement la musique.

# Superposition musique de fond + narration
print("SUPERPOSITION MUSIQUE DE FOND")
print("=" * 50)

def generate_ambient_music(duration_ms: int, sr: int = 44100) -> Optional[AudioSegment]:
    """Genere une musique d'ambiance simple (sinusoides harmoniques)."""
    try:
        t = np.linspace(0, duration_ms / 1000, int(sr * duration_ms / 1000))

        # Accords doux (Do majeur : C4, E4, G4)
        freqs = [261.63, 329.63, 392.00]  # C4, E4, G4
        audio = np.zeros_like(t)
        for f in freqs:
            audio += 0.15 * np.sin(2 * np.pi * f * t)

        # Modulation lente pour un effet d'ambiance
        modulation = 0.5 + 0.5 * np.sin(2 * np.pi * 0.1 * t)  # 0.1 Hz
        audio *= modulation

        # Normaliser en int16
        audio = (audio / np.max(np.abs(audio)) * 16000).astype(np.int16)

        return AudioSegment(
            data=audio.tobytes(),
            sample_width=2,
            frame_rate=sr,
            channels=1
        )
    except Exception as e:
        print(f"Erreur generation musique : {e}")
        return None

mixed_audio = None

if generate_audio and narration_timeline:
    total_duration_ms = len(narration_timeline)

    # Generer la musique de fond
    print("Generation de la musique d'ambiance...")
    background_music = generate_ambient_music(total_duration_ms)

    if background_music:
        # Ajuster le volume de la musique
        background_music = background_music + background_music_volume  # dB

        # Fade in/out sur la musique
        background_music = background_music.fade_in(1000).fade_out(2000)

        print(f"Musique de fond :")
        print(f"  Duree : {len(background_music)/1000:.1f}s")
        print(f"  Volume : {background_music.dBFS:.1f} dBFS (reduit de {background_music_volume}dB)")

        # Superposer narration + musique
        print(f"\nSuperposition narration + musique...")

        # S'assurer que les deux ont la meme duree
        if len(background_music) > len(narration_timeline):
            background_music = background_music[:len(narration_timeline)]
        elif len(background_music) < len(narration_timeline):
            background_music = background_music + AudioSegment.silent(
                duration=len(narration_timeline) - len(background_music)
            )

        mixed_audio = narration_timeline.overlay(background_music)

        print(f"Mix final :")
        print(f"  Duree : {len(mixed_audio)/1000:.1f}s")
        print(f"  Volume : {mixed_audio.dBFS:.1f} dBFS")

        # Sauvegarder et ecouter
        if save_output_files:
            mix_path = OUTPUT_DIR / "narration_plus_music.wav"
            mixed_audio.export(str(mix_path), format="wav")
            print(f"  Sauvegarde : {mix_path.name}")

        mix_bytes = BytesIO()
        mixed_audio.export(mix_bytes, format="mp3")
        print(f"\nEcoute du mix (narration + musique de fond) :")
        display_audio_bundle(mix_bytes.getvalue())
    else:
        print("Musique de fond non disponible")
elif not narration_timeline:
    print("Mix desactive (pas de narration timeline)")
else:
    print("Mix desactive")
SUPERPOSITION MUSIQUE DE FOND
==================================================
Generation de la musique d'ambiance...
Musique de fond :
  Duree : 15.0s
  Volume : -37.8 dBFS (reduit de -20dB)

Superposition narration + musique...
Mix final :
  Duree : 15.0s
  Volume : -27.8 dBFS
  Sauvegarde : narration_plus_music.wav

Ecoute du mix (narration + musique de fond) :

Interpretation : Mix audio

Piste Volume (dBFS) Rôle
Narration ~-15 a -20 Premier plan
Musique de fond ~-35 a -45 Ambiance discrete
Mix final ~-15 a -20 Equilibre narration/musique

Points cles : 1. Le volume de la musique (-20dB) la rend perceptible sans couvrir la voix 2. Les fades evitent les transitions brutales 3. En production, utiliser du ducking dynamique pour un résultat plus naturel

Section 5 : Assemblage video + audio final

L’assemblage final combine la video, la narration et la musique de fond en un seul fichier.

Pipeline d'assemblage :
    Video (.mp4, sans audio)
    +  Narration TTS (timeline alignee)
    +  Musique de fond (volume reduit)
    =  Video finale (.mp4, avec audio mixe)

moviepy gere la combinaison des flux video et audio.

# Assemblage video + audio final
print("ASSEMBLAGE VIDEO + AUDIO")
print("=" * 50)

final_video_path = OUTPUT_DIR / "video_finale_narree.mp4"

if generate_audio and moviepy_available and mixed_audio and test_video_path.exists():
    # Sauvegarder l'audio mixe en WAV pour moviepy
    mixed_audio_path = OUTPUT_DIR / "mixed_audio_temp.wav"
    mixed_audio.export(str(mixed_audio_path), format="wav")

    print("Assemblage en cours...")
    start_time = time.time()

    # Charger la video et l'audio
    video_clip = VideoFileClip(str(test_video_path))
    audio_clip = AudioFileClip(str(mixed_audio_path))

    # Ajuster la duree de l'audio a celle de la video
    if audio_clip.duration > video_clip.duration:
        audio_clip = audio_clip.subclipped(0, video_clip.duration)

    # Combiner video + audio
    video_with_audio = video_clip.with_audio(audio_clip)

    # Exporter
    video_with_audio.write_videofile(
        str(final_video_path),
        fps=video_fps,
        codec='libx264',
        audio_codec='aac',
        logger=None
    )

    assembly_time = time.time() - start_time

    # Fermer les clips
    video_clip.close()
    audio_clip.close()
    video_with_audio.close()

    # Nettoyage fichier temporaire
    if mixed_audio_path.exists():
        mixed_audio_path.unlink()

    print(f"Assemblage termine en {assembly_time:.1f}s")
    print(f"\nVideo finale :")
    print(f"  Fichier : {final_video_path.name}")
    print(f"  Taille : {final_video_path.stat().st_size/1024:.1f} KB")
    print(f"  Resolution : {video_resolution[0]}x{video_resolution[1]}")
    print(f"  FPS : {video_fps}")
    print(f"  Duree : {video_duration}s")
    print(f"  Audio : narration TTS + musique de fond")

    # Afficher la video dans le notebook (si possible)
    try:
        display(IPVideo(str(final_video_path), embed=True, width=480))
    except Exception as e:
        print(f"Affichage video non disponible : {str(e)[:80]}")
        print(f"Ouvrez le fichier : {final_video_path.name}")
else:
    if not moviepy_available:
        print("moviepy requis pour l'assemblage")
    elif not mixed_audio:
        print("Pas d'audio mixe disponible")
    elif not test_video_path.exists():
        print("Video de test non disponible")
    else:
        print("Assemblage desactive")
ASSEMBLAGE VIDEO + AUDIO
==================================================
Assemblage en cours...
Assemblage termine en 1.0s

Video finale :
  Fichier : video_finale_narree.mp4
  Taille : 262.9 KB
  Resolution : 640x480
  FPS : 24
  Duree : 15s
  Audio : narration TTS + musique de fond

Preparation de l’assemblage

Avant de combiner video et audio, verifions les composants :

Composant Statut Fichier Duree
Video de base Créée test_video.mp4 15s
Narration timeline Generee narration_timeline.wav 15s
Mix audio Prepare narration_plus_music.wav 15s

Prerequis techniques - moviepy pour la manipulation video - ffmpeg pour l’encodage des codecs - Espace disque suffisant pour la video finale (≈1-5 MB)

Processus d’assemblage 1. Charger la video sans audio 2. Charger l’audio mixe (WAV) 3. Synchroniser les durees 4. Combiner les pistes 5. Exporter avec codecs standards

Interpretation : Assemblage final

Aspect Valeur Signification
Codec video H.264 (libx264) Standard universel
Codec audio AAC Compression efficace
Temps d’assemblage Variable Depend de la duree et resolution

Points cles : 1. moviepy simplifie considerablement le pipeline d’assemblage 2. Les codecs H.264/AAC sont compatibles avec tous les lecteurs 3. Pour de la production, utiliser codec='libx264' avec -preset slow pour une meilleure qualite


Exercice : Assembleur video avec transitions personnalisables

Duree estimee : 25-30 minutes

Objectif

Créer une fonction qui assemble une video a partir de segments audio et visuels en appliquant différents types de transitions (fade, crossfade, cut sec).

Contexte

L’assemblage video ne se limite pas a la concatenation. Le choix des transitions (fade noir, fondu enchaine, cut sec) influence le rendu final. Cet exercice vous amene a implementer un assembleur flexible.

Instructions

  1. Définir une structure de segments avec transition souhaitee
  2. Implementer les 3 types de transitions (fade, crossfade, cut)
  3. Appliquer les transitions entre les segments
  4. Normaliser le volume global et exporter

Indices : - # Étape 1 : Structure : [{"audio": np.array, "transition": "fade", "duration": 0.5}] - # Étape 2 : Fade = np.linspace(0, 1, samples) en entree, np.linspace(1, 0, samples) en sortie - # Étape 3 : Crossfade = superposer les derniers echantillons d’un segment avec les premiers du suivant - # Indice : Le cut sec ne necessite aucune transition, juste np.concatenate

# TODO: Implementer l'assembleur avec transitions
def assemble_with_transitions(segments: list, sr: int = 44100) -> np.ndarray:
    """
    Assemble des segments audio avec transitions personnalisees.
    
    Args:
        segments: Liste de dicts {"audio": np.array, "transition": str, "duration": float}
            transition: "fade", "crossfade", ou "cut"
            duration: duree de la transition en secondes
        sr: Sample rate
    
    Returns:
        Audio assemble avec transitions
    """
    result = np.array([], dtype=np.float32)
    
    for i, seg in enumerate(segments):
        audio = seg["audio"].copy()
        transition = seg.get("transition", "cut")
        trans_dur = seg.get("duration", 0.5)
        trans_samples = int(sr * trans_dur)
        
        if i == 0:
            # Premier segment : fade in
            # Indice: audio[:trans_samples] *= np.linspace(0, 1, trans_samples)
            pass  # TODO etudiant
        else:
            # Appliquer la transition avec le segment precedent
            if transition == "fade":
                # Indice: fade out du resultat + fade in du nouveau
                pass  # TODO etudiant
            elif transition == "crossfade":
                # Indice: superposer les trans_samples derniers samples
                pass  # TODO etudiant
            else:  # "cut"
                # Indice: simple concatenation
                pass  # TODO etudiant
        
        # result = np.concatenate([result, audio])  # adapter selon la transition
    
    # Normalisation finale
    # Indice: peak = np.max(np.abs(result))
    # Indice: if peak > 0: result = result / peak * 0.95
    
    return result  # TODO etudiant : retourner l'audio assemble

# Test avec 3 segments de demonstration
# seg1 = np.sin(2 * np.pi * 261 * np.linspace(0, 2, 88200)).astype(np.float32) * 0.3  # Do
# seg2 = np.sin(2 * np.pi * 329 * np.linspace(0, 2, 88200)).astype(np.float32) * 0.3  # Mi
# seg3 = np.sin(2 * np.pi * 392 * np.linspace(0, 2, 88200)).astype(np.float32) * 0.3  # Sol
# demo = [
#     {"audio": seg1, "transition": "fade", "duration": 0.3},
#     {"audio": seg2, "transition": "crossfade", "duration": 0.5},
#     {"audio": seg3, "transition": "cut", "duration": 0.0},
# ]
# result = assemble_with_transitions(demo)
# print(f"Audio assemble : {len(result)/44100:.1f}s")
print("Exercice a completer")
Exercice a completer

Passerelle vers la serie Video

Ce notebook constitue le point de jonction entre les series Audio et Video du cours GenAI. Il demontre comment les techniques audio apprises dans les notebooks précédents s’appliquent directement aux workflows video.

Mapping Audio -> Video

Serie Audio (cette serie) Serie Video Lien technique
TTS (narration) Video narree Audio enrichit la video avec une voix off
Whisper (STT) Sous-titrage video Transcription -> SRT -> Embed subs
MusicGen (composition) Bande sonore video Musique de fond et ambiance
Demucs (separation) Post-production audio Remixage, isolation de pistes, nettoyage
Opérations audio Montage video Coupes, fades, transitions

Exemples d’applications

  1. Doublage automatique : TTS multi-langue pour internationaliser des videos
  2. Accessibilite : Whisper pour sous-titres automatiques (SDH)
  3. Marketing : MusicGen pour generer des musiques adaptees au mood
  4. Educational : Narration TTS pour tutoriels et MOOCs
  5. Social media : Generer rapidement du contenu video narre

Pour continuer dans la serie Video

Video Foundation (Video/01-Foundation/) - 01-1-Video-Opérations-Basics.ipynb - Chargement, decoupage, encodage video - 01-2-ComfyUI-Video-Basics.ipynb - Introduction a ComfyUI pour video - 01-3-Qwen-VL.ipynb - Vision-Language models pour video understanding

Video Advanced (Video/02-Advanced/) - 02-1-HunyuanVideo.ipynb - Generation video avec HunyuanVideo - 02-2-LTX-Video.ipynb - Video generation temps reel - 02-3-Wan.ipynb - Modèle Wan pour video haute qualite

Video Orchestration (Video/03-Orchestration/) - Pipelines ComfyUI avances - Multi-modèles video + audio - Production et optimisation

Competences transferees

Les competences acquises dans cette serie Audio sont directement reutilisables : - Manipulation de timelines - Audio et video utilisent le même concept de temps - Gestion de pistes multiples - Mixage audio = composition video - Format de fichiers - Conteneurs (MP4, WAV), codecs (H.264, AAC) - Outils Python - moviepy, pydub, ffmpeg pour l’audio et la video

Les techniques audio apprises ici constituent la base solide pour aborder la creation video avec l’IA generative.

# Mode interactif - Personnaliser le pipeline
if notebook_mode == "interactive" and not skip_widgets:
    print("MODE INTERACTIF - PIPELINE PERSONNALISE")
    print("=" * 50)
    print("\nEntrez un texte de narration pour l'ajouter a la video :")
    print("(Laissez vide pour passer)")

    try:
        user_text = input("\nVotre narration : ")

        if user_text.strip():
            print(f"\nGeneration TTS...")
            response = client.audio.speech.create(
                model=tts_model,
                voice=narrator_voice,
                input=user_text,
                response_format="mp3"
            )
            print(f"Narration generee ({len(response.content)/1024:.1f} KB) :")
            display_audio_bundle(response.content)

            if save_output_files:
                ts = datetime.now().strftime('%Y%m%d_%H%M%S')
                filepath = OUTPUT_DIR / f"custom_narration_{ts}.mp3"
                with open(filepath, 'wb') as f:
                    f.write(response.content)
                print(f"Sauvegarde : {filepath.name}")
        else:
            print("Mode interactif ignore")

    except (KeyboardInterrupt, EOFError):
        print("Mode interactif interrompu")
    except Exception as e:
        error_type = type(e).__name__
        if "StdinNotImplemented" in error_type or "input" in str(e).lower():
            print("Mode interactif non disponible (execution automatisee)")
        else:
            print(f"Erreur : {error_type} - {str(e)[:100]}")
else:
    print("Mode batch - Interface interactive desactivee")
MODE INTERACTIF - PIPELINE PERSONNALISE
==================================================

Entrez un texte de narration pour l'ajouter a la video :
(Laissez vide pour passer)
Mode interactif non disponible (execution automatisee)

Section 6 : Mode interactif - Experimentation

Cette section permet d’experimenter librement avec le pipeline de synchronisation.

Workflow interactif

  1. Saisir un texte de narration - L’API TTS genere l’audio instantanement
  2. Ecouter le résultat - Verification de la qualite et de la duree
  3. Ajuster les paramètres - Vitesse, voix, volume selon les besoins
  4. Sauvegarder - Les fichiers sont stockes dans outputs/audio/av_sync/

Cas d’usage

  • Test de différentes voix pour un projet
  • Ajustement de la vitesse pour un timing précis
  • Generation rapide de narrations pour prototypes

Note : En mode batch (Papermill), cette section est automatiquement desactivee pour eviter les blocages.

Le module de statistiques recapitule les résultats de la session : videos créées, pistes audio generees, durees traitees et ressources consommees par chaque étape du pipeline de synchronisation.

# Statistiques de session
print("STATISTIQUES DE SESSION")
print("=" * 50)

print(f"Date : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Mode : {notebook_mode}")
print(f"TTS : {tts_model}, Voix : {narrator_voice}")

if narration_audio_segments:
    total_narration = sum(s['audio_duration'] for s in narration_audio_segments)
    print(f"Segments narration : {len(narration_audio_segments)} ({total_narration:.1f}s)")

if narration_timeline:
    print(f"Timeline narration : {len(narration_timeline)/1000:.1f}s")

if mixed_audio:
    print(f"Mix final : {len(mixed_audio)/1000:.1f}s")

if final_video_path.exists():
    print(f"Video finale : {final_video_path.name} ({final_video_path.stat().st_size/1024:.1f} KB)")

if save_output_files:
    saved_files = list(OUTPUT_DIR.glob('*'))
    print(f"Fichiers sauvegardes : {len(saved_files)} dans {OUTPUT_DIR.relative_to(GENAI_ROOT)}")

print(f"\nSERIE AUDIO TERMINEE")
print(f"Felicitations ! Vous avez parcouru l'ensemble de la serie Audio :")
print(f"  01-Foundation : TTS, STT, operations audio, Whisper local, Kokoro")
print(f"  02-Advanced   : Chatterbox, XTTS, MusicGen, Demucs")
print(f"  03-Orchestration : Multi-modeles, pipelines, voix temps reel")
print(f"  04-Applications  : Contenu educatif, transcription, composition, A/V sync")
print(f"\nPROCHAINE SERIE")
print(f"  -> Video/01-Foundation/01-1-Video-Operations-Basics.ipynb")

print(f"\nNotebook termine - {datetime.now().strftime('%H:%M:%S')}")
STATISTIQUES DE SESSION
==================================================
Date : 2026-08-16 01:51:20
Mode : interactive
TTS : tts-1, Voix : nova
Segments narration : 3 (23.8s)
Timeline narration : 15.0s
Mix final : 15.0s
Video finale : video_finale_narree.mp4 (262.9 KB)
Fichiers sauvegardes : 4 dans outputs\audio\av_sync

SERIE AUDIO TERMINEE
Felicitations ! Vous avez parcouru l'ensemble de la serie Audio :
  01-Foundation : TTS, STT, operations audio, Whisper local, Kokoro
  02-Advanced   : Chatterbox, XTTS, MusicGen, Demucs
  03-Orchestration : Multi-modeles, pipelines, voix temps reel
  04-Applications  : Contenu educatif, transcription, composition, A/V sync

PROCHAINE SERIE
  -> Video/01-Foundation/01-1-Video-Operations-Basics.ipynb

Notebook termine - 01:51:20

Interpretation : Session completee

Cette session a demontre l’integration complete du pipeline audio-video :

Metrique Valeur Signification
Segments narration 3 Couverture complete de la video
Duree totale audio 24.4s Depassement de 9.4s (63%)
Timeline ajustee 15.0s Troncage pour synchronisation
Mix final 15.0s Narration + musique alignees
Fichiers generes 2 narration_timeline.wav, narration_plus_music.wav

Analyse du depassement audio - La narration TTS depasse systematiquement les segments cibles (5s) - Causes possibles : texte trop long, vitesse de parole standard (1.0) - Solutions : reduire le texte de 30% ou augmenter la vitesse a 1.3-1.5x

Points cles 1. Le pipeline fonctionne de bout en bout malgre le depassement 2. La troncature audio preserve la synchronisation 3. Le mixage maintient la comprehensibilite de la narration

Note technique : En production, toujours generer une marge de 10-15% sur la duree audio pour absorber les variations de vitesse TTS.


Exercice : Video Narree avec Sous-Titres

Duree estimee : 40-45 minutes

Objectif

Créer une video narree complete avec narration TTS synchronisee, musique de fond, et sous-titres generes automatiquement.

Instructions

  1. Créer une video simple (3-4 segments visuels)
  2. Generer des scripts de narration pour chaque segment
  3. Produire la narration TTS synchronisee avec les segments
  4. Ajouter une musique de fond appropriee
  5. Generer des sous-titres a partir de la narration
  6. Assembler le tout en une video finale

Indices : - Pour la video: utilisez moviepy.ColorClip et TextClip pour des segments simples - Pour la narration TTS: synchronisez la duree audio avec la duree du segment video - Pour la musique de fond: utilisez un volume de -20dB pour ne pas couvrir la voix - Pour les sous-titres: utilisez les timestamps de la narration TTS


Synthese des apprentissages

Ce notebook vous a permis de decouvrir le pipeline complet de synchronisation audio-video :

Concepts maitrises

Concept Description Application
Extraction audio Separer la piste audio d’une video Pre-processing pour transcription
Narration TTS Synthese vocale timelinee Doublage automatique, accesibilite
Alignement temporel Synchroniser audio et video Lip-sync, sous-titres synchronises
Mixage audio Superposer plusieurs pistes Narration + musique + effets
Assemblage final Combiner video et audio Production video complete

Workflow industriel

1. Pre-production
   - Script et segmentation
   - Preparation des assets visuels
   - Sélection musicale

2. Production
   - Generation TTS par segment
   - Alignement sur la timeline
   - Mixage audio (narration + musique)

3. Post-production
   - Assemblage video + audio
   - Generation des sous-titres
   - Export et compression

Bonnes pratiques

  1. Toujours calibrer la duree audio a la duree du segment video
  2. Utiliser le ducking pour eviter que la musique ne couvre la voix
  3. Generer des sous-titres pour l’accesibilite et le SEO
  4. Tester sur différents appareils pour verifier la compatibilite

Passerelle vers la serie Video

Les techniques audio apprises ici sont fondamentales pour la creation de contenu video. Dans la serie Video, vous apprendrez a : - Generer des videos avec des modèles comme HunyuanVideo et LTX-Video - Utiliser ComfyUI pour des pipelines video avances - Créer des effets visuels et des transitions - Optimiser les formats pour différentes plateformes



Exercice : Video Narree avec Sous-Titres

Duree estimee : 40-45 minutes
Niveau : Intermediaire

Objectif

Créer une video narree complete avec narration TTS synchronisee, musique de fond, et sous-titres generes automatiquement.

Contexte

Dans cet exercice, vous allez mettre en pratique les concepts appris dans ce notebook : - Synchronisation audio-video - Generation TTS timelinee - Mixage audio (narration + musique) - Assemblage video final

Instructions

  1. Créer une video simple (3-4 segments visuels)
  2. Generer des scripts de narration pour chaque segment
  3. Produire la narration TTS synchronisee avec les segments
  4. Ajouter une musique de fond appropriee
  5. Generer des sous-titres a partir de la narration
  6. Assembler le tout en une video finale

Indices : - Pour la video: utilisez moviepy.ColorClip et TextClip pour des segments simples - Pour la narration TTS: synchronisez la duree audio avec la duree du segment video - Pour la musique de fond: utilisez un volume de -20dB pour ne pas couvrir la voix - Pour les sous-titres: utilisez les timestamps de la narration TTS

Critères de succès

Extensions (optionnel)

Ressources utiles

  • moviepy documentation: https://zulko.github.io/moviepy/
  • pydub audio manipulation: https://github.com/jiaaro/pydub
  • Format SRT: https://docs.fileformat.com/video/srt/

Verification avant soumission

# Verifier que la video finale existe
from pathlib import Path
final_video = Path("outputs/audio/av_sync/video_finale.mp4")
assert final_video.exists(), "Video finale manquante"
assert final_video.stat().st_size > 1000, "Video trop petite (corrompue?)"
print(f"Video OK: {final_video.stat().st_size/1024:.1f} KB")
Retour au sommet