--- name: radio-juridique description: "Génère une émission radio à 3 voix résumant la veille juridique hebdomadaire de Force Ouvrière (PDF) — actualités législatives + brèves de jurisprudence + focus droit du travail." version: 1.3.0 author: Hermes Agent license: MIT metadata: hermes: tags: [radio, juridique, pdf, force-ouvriere, tts, multi-voix, xtts, edge-tts, droit-du-travail] --- # Radio Juridique FO — Émission à 3 voix Skill pour produire une émission radio en MP3 à partir de la **veille juridique hebdomadaire de Force Ouvrière** (PDF). - **3 voix distinctes** : Présentateur (Henri) + Expert brèves (Marc) + Expert focus (Sophie) - **3 parties** : Actualités législatives/réglementaires + Brèves de jurisprudence + Focus juridique - **Format dialogué** : questions/réponses variées, pas de monologues - **Moteurs TTS** : XTTS v2 (primaire) + edge-tts (fallback) - **PDF multi-pages** : gère 2 à 4+ pages, brèves et focus peuvent traverser les pages ## Quand utiliser ce skill - L'utilisateur demande une "radio juridique", "briefing juridique audio", "veille FO audio", "résumé radio du PDF juridique". - Mots-clés : radio juridique, FO, veille juridique, PDF juridique, droit du travail audio. ## Structure du PDF Force Ouvrière Le PDF contient généralement **2 pages** mais peut en comporter **plus** (jusqu'à 4+ pour les semaines chargées). | Page(s) | Contenu | |---------|---------| | **Page 1** | Actualités législatives et réglementaires (optionnel) + Brèves de jurisprudence (arrêts Cassation soc.) | | **Pages 1→2+** | Continuation des brèves de jurisprudence (peut traverser les pages) | | **Pages 2→N** | Focus sur un point de droit du travail d'actualité (développement long, peut traverser plusieurs pages) | **Important** : ne pas se fier au nombre de pages. Extraire TOUT le contenu, toutes pages confondues. Les semaines peuvent contenir 25+ arrêts et un focus de 2 pages. Le parser `extract_pdf.py` gère automatiquement le contenu multi-pages. ## Workflow A→Z ### 1. Extraire le contenu du PDF ```bash # Texte brut python3 ~/.hermes/skills/media/radio-juridique/scripts/extract_pdf.py /chemin/vers/veille.pdf # Markdown (meilleure mise en forme) python3 ~/.hermes/skills/media/radio-juridique/scripts/extract_pdf.py /chemin/vers/veille.pdf --markdown ``` ### 2. Construire le script dialogué Utiliser `create_radio_script_dialogue()` avec les 3 voix : - `presentateur` → Henri (fr-FR) - `expert_breves` → Marc (fr-FR, masculin) - `expert_focus` → Sophie (fr-FR, féminin) ### 3. Valider le script ```python from validate_juridique import validate_juridique_script, format_report errors, warnings = validate_juridique_script(script) print(format_report(errors, warnings)) if errors: raise RuntimeError("Correction requise") ``` > **Attention** : `validate_juridique_script` se trouve dans `validate_juridique.py`, PAS dans `generate_juridique.py`. ### 4. Générer l'audio ```bash # XTTS v2 (par défaut) COQUI_TOS_AGREED=1 OMP_NUM_THREADS=24 MKL_NUM_THREADS=24 \ OPENBLAS_NUM_THREADS=24 PYTORCH_CPU_THREAD_POOL_SIZE=24 \ TORCH_NUM_THREADS=24 \ ~/.hermes/xtts-venv/bin/python /tmp/run_radio_juridique_YYYY_MM_DD.py # edge-tts (test rapide) ~/.hermes/tts-venv/bin/python /tmp/run_radio_juridique_YYYY_MM_DD.py ``` ### 5. Vérifier et livrer ```bash ffprobe -v quiet -show_entries format=duration,size,bit_rate -of csv=p=0 ``` ## Voix, rôles et personnalités | Rôle | Voix XTTS | Voix edge-tts | Personnalité | |------|-----------|---------------|-------------| | **Présentateur** | Henri (fr-FR) | fr-FR-HenriNeural | Posé, chaleureux, questionne, relance | | **Expert brèves** | Marc (fr-FR) | fr-FR-RemyMultilingualNeural | Juriste rigoureux, synthétique, pédagogique | | **Expert focus** | Sophie (fr-FR) | fr-FR-VivienneMultilingualNeural | Juriste spécialisée, nuancée, développe les nuances | ### Fichiers de référence XTTS Placer dans `~/.hermes/voices_tts/` : | Fichier | Rôle | Comment générer | |---------|------|-----------------| | `presentateur_radio.wav` | Présentateur | Déjà existant | | `expert_breves_marc.wav` | Expert brèves | edge-tts → Hugo → ffmpeg PCM | | `expert_focus_sophie.wav` | Expert focus | edge-tts → Vivienne → ffmpeg PCM | **Générer un WAV de référence** : ```bash # edge-tts pour Marc (Remy — voix masculine FR) ~/.hermes/tts-venv/bin/python -c " import asyncio, json, sys import edge_tts async def main(): comm = edge_tts.Communicate('Bonjour, je suis Marc, juriste spécialisé en droit du travail', 'fr-FR-RemyMultilingualNeural') await comm.save('/tmp/marc_ref.wav') asyncio.run(main()) " ffmpeg -y -i /tmp/marc_ref.wav -acodec pcm_s16le -ac 1 -ar 22050 \ ~/.hermes/voices_tts/expert_breves_marc.wav # edge-tts pour Sophie (Vivienne — voix féminine FR mature) ~/.hermes/tts-venv/bin/python -c " import asyncio, json, sys import edge_tts async def main(): comm = edge_tts.Communicate('Bonjour, je suis Sophie, juriste en droit du travail', 'fr-FR-VivienneMultilingualNeural') await comm.save('/tmp/sophie_ref.wav') asyncio.run(main()) " ffmpeg -y -i /tmp/sophie_ref.wav -acodec pcm_s16le -ac 1 -ar 22050 \ ~/.hermes/voices_tts/expert_focus_sophie.wav ``` ## Structure de l'émission ### 1. [JINGLE + INTRO] - Jingle musical généré via ffmpeg (4 tonalités synthétiques) - Intro du présentateur : date, sommaire (2 experts), annonce du PDF de la semaine ### 2. [PARTIE 1 — ACTUALITÉS LÉGISLATIVES ET RÉGLEMENTAIRES] - Le présentateur annonce la rubrique - L'expert brèves (Marc) détaille les textes publiés cette semaine - Si aucune actualité législative : le présentateur l'annonce brièvement et passe à la suite - **Règle** : transcrire l'intégralité du contenu, sans limite de durée ### 3. [PARTIE 2 — LES BRÈVES DE JURISPRUDENCE] - Le présentateur présente les arrêts récents de la Cour de cassation - L'expert brèves (Marc) explique chaque arrêt en détail, en citant les références - Le présentateur pose des questions de clarification - **Règle** : chaque arrêt est couvert en entier, sans limite de durée ### 4. [PARTIE 3 — LE FOCUS JURIDIQUE] - Le présentateur introduit le sujet du focus (page 2) - L'expert focus (Sophie) détaille le point de droit dans son intégralité - Le présentateur pose des questions pratiques (conséquences, conseils) - **Règle** : format conversationnel, pas de cours magistral, mais contenu complet ### 5. [CONCLUSION] - Le présentateur résume en quelques phrases - Annonce la prochaine édition ## Règles d'oralisation - **Texte lisible à voix haute** : pas de markdown, tableaux, listes. - **Chiffres épelés** : « six mai deux mille vingt-six ». - **N° d'arrêt** : « numéro vingt-quatre tiret treize mille cinq cent quatre-vingt-dix-neuf ». Les chiffres sont convertis automatiquement par `prononcer_numero()`. - **Dates d'arrêts** : « le six mai deux mille vingt-six » (format `Cass. soc., 6-5-26`). Converties par `prononcer_date_cass()` — gère les espaces inconsistants (`18-3- 26`). - **Dates en français** : `_pronounce_french_dates()` convertit les dates comme `28 mars 2026`, `du 15 novembre 2024`, `1er avril` en prononciation. Le préfixe `du ` est conservé. - **Marqueurs oraux** : « alors », « effectivement », « écoutez », « c'est intéressant ». - **Points "."** : `clean_tts_text()` supprime automatiquement tous les points avant synthèse. - **Symboles "•"** : `clean_tts_text()` remplace par des virgules. - **Flèches "►"** : `clean_tts_text()` supprime les flèches du PDF FO. - **JORF** : `clean_tts_text()` convertit en "Journal Officiel". - **FO → F.O.** : toujours écrire « F.O. » (avec points) pour prononciation correcte. - **Cass. soc.** : prononcer « Cour de cassation, chambre sociale ». - **Références juridiques complètes** : `prononcer_reference_juridique()` remplace automatiquement `Cass. soc., 6-5-26, n°24-13599` par `Cour de cassation, chambre sociale, le six mai deux mille vingt-six, numéro vingt-quatre tiret treize mille cinq cent quatre-vingt-dix-neuf`. - **Articles de loi** : prononcer « article L deux mille cent trente-trois tiret trois » (converti par `prononcer_article_loi()`). Gère `article L`, `articles L`, `article L.`. ## Règles de dialogue - JAMAIS un expert > 2 min sans interruption du présentateur. - Questions du présentateur : COURTES (1-2 phrases). - Réponses : format oral, langage clair. Pas de limite de nombre de phrases — transcrire le contenu complet. - Le présentateur reformule si nécessaire. - Chaque fait juridique = source citée (n° d'arrêt, date, article de loi). - **Important** : aucune coupe dans le contenu. Si la semaine est chargée, l'émission sera longue. C'est acceptable. ### Règles de concision (anti-répétition) - **Le présentateur NE doit JAMAIS répéter le titre/le contenu que l'expert va dire.** Exemple interdit : Henri dit *"Marc, dis-nous tout sur CDD : rupture anticipée..."* puis Marc répond *"C'est un arrêt important concernant CDD : rupture anticipée..."*. - **L'expert ne répète PAS le titre annoncé par le présentateur.** `_format_expert_response()` doit retourner directement le corps du contenu, sans préambule du type *"Voici ce qu'il faut retenir concernant [titre]"*. - **L'expert fait une transition naturelle mais brève.** Exemple : *"Alors Henri, cette semaine plusieurs arrêts intéressants. Je vous les détaille."* — puis passe directement au contenu. - **Le présentateur utilise des relances neutres.** Exemple : *"Marc, dis-nous tout sur ce point."* — jamais de paraphrase du titre. - **Le focus : même règle.** Sophie ne répète pas le titre du focus que Henri vient d'annoncer. ## Workflow détaillé ### Étape 1 : Charger et analyser le PDF ```python from extract_pdf import extract_structured result = extract_structured(pdf_path) # result["page_1"]["actualites_legislatives"] → section législative (peut être vide) # result["page_1"]["breves"] → liste des brèves de jurisprudence # result["page_2"]["focus"] → contenu du focus ``` ### Étape 2 : Transcrire les brèves Extraire les **titres** et **contenu complet** de chaque brève. Pour chaque brève : - Titre de la brève (ex: "Visite de reprise - Dispositions conventionnelles - Délai") - Contenu complet, reformulé en langage oral - Numéro d'arrêt et date ### Étape 3 : Transcrire le focus Extraire le **sujet** du focus et **l'intégralité du contenu** : - Titre du focus (ex: "Attention à l'indemnité de préavis !") - Contexte juridique complet - Tous les arrêts clés avec leurs enseignements - Conséquences pratiques détaillées ### Étape 4 : Construire le dialogue ```python script = create_radio_script_dialogue( date_range="04 au 07 mai 2026", breves_summary=breves_text, focus_summary=focus_text, actualites_legislatives=legislative_text, # optionnel, peut être "" breves_arrêts=breves_arrêts_list, focus_arrêts=focus_arrêts_list, ) ``` ### Étape 5 : Valider et générer ```python # Validation (depuis validate_juridique.py) from validate_juridique import validate_juridique_script, format_report errors, warnings = validate_juridique_script(script) if errors: raise RuntimeError("Correction requise") # Génération output = "/home/mlapprand/.hermes/audio_cache/radio-juridique-DATE.mp3" ``` ## Checklist agent - [ ] PDF extrait (page 1 brèves + page 2 focus) - [ ] Section 1 (actualités législatives) traitée (même si vide) - [ ] Toutes les brèves de jurisprudence couvertes (Partie 2) - [ ] Focus juridique traité dans son intégralité (Partie 3) - [ ] Validation pré-génération passée (validate_juridique.py — 0 erreur) - [ ] Aucun nom halluciné dans les textes - [ ] Chaque rubrique commence par une question du présentateur - [ ] Alternance présentateur/expert respectée - [ ] N° d'arrêts prononcés correctement (tiret entre partie, chiffres en toutes lettres) - [ ] Dates d'arrêts prononcées correctement (format « le six mai deux mille vingt-six ») - [ ] Articles de loi prononcés correctement - [ ] Dates en français - [ ] 3 noms distincts : Henri, Marc, Sophie - [ ] Script de génération dans `/tmp/run_radio_juridique_YYYY_MM_DD.py` - [ ] **MP3 vérifié post-génération** (durée adaptée au contenu, > 3 Mo) ## Pitfalls - **XTTS venv = Python 3.11 obligatoire** : voir skill `radio-briefing`. - **XTTS : COQUI_TOS_AGREED=1** : obligatoire pour chargement non-interactif. - **XTTS : PyTorch 2.11 weights_only + add_safe_globals** : voir skill `radio-briefing`. - **XTTS : transformers==4.30.0 exact** : voir skill `radio-briefing`. - **XTTS : stream_generator.py — 7 imports à corriger** : voir skill `radio-briefing`. - **XTTS : chargement modèle une seule fois** : singleton `_get_xtts_model()`. - **XTTS : 24 cœurs CPU** : variables OMP/MKL/OPENBLAS/TORCH forcées. - **Fichiers de référence vides** : régénérer avec edge-tts + ffmpeg PCM. - **Rôle TTS invalide** : `ValueError` levée avec la liste des rôles valides. - **Segment échoué** : l'émission continue sans le segment raté. - **Silences WAV** : en WAV PCM (pas OGG) pour compatibilité concat ffmpeg. - **Jingle musical via ffmpeg** : utiliser `s=24000` pour le sample rate. - **Cass. soc.** : toujours développer en "Cour de cassation, chambre sociale" à l'oral. - **Cass. crim.** : toujours développer en "Cour de cassation, chambre criminelle" à l'oral. - **N° d'arrêts** : écrire les chiffres en toutes lettres pour la synthèse vocale. - **Art. L. xxx-xx** : écrire "Article L xxx virgule xx" pour la synthèse. - **edge-tts fallback** : vérifier `~/.hermes/tts-venv/` en premier. Le fallback dans le code (`~/.tts-venv/`) est un safety net qui n'existe probablement pas — s'assurer que le venv principal `~/.hermes/tts-venv/` est bien installé. - **Voix Hugo non disponible** : `fr-FR-HugoNeural` peut renvoyer `NoAudioReceived`. Utiliser `fr-FR-RemyMultilingualNeural` comme voix masculine FR alternative. - **edge-tts timeout dynamique** : le timeout ne doit JAMAIS être fixe à 30s. Le corps du focus peut faire 5000+ caractères et dépasser 30s de synthèse. Utiliser `timeout = max(60, len(text) // 20)` pour un timeout adaptatif (minimum 60s, ~1 char/20ms ≈ débit edge-tts). - **edge-tts venv path** : `EDGE_VENV_PYTHON` doit pointer vers `~/.hermes/tts-venv/bin/python`, PAS `~/.tts-venv/bin/python`. L'erreur `OSError: [Errno 2] No such file or directory` sur ce chemin signifie que le path est incorrect. Le fallback doit être vérifié SECONDAIREMENT, jamais en premier : utiliser `EDGE_VENV_PYTHON or EDGE_VENV_PYTHON_FALLBACK` (PRIMARY then FALLBACK), jamais l'inverse. - **Fichiers de référence WAV identiques** : Si le présentateur (Henri) et l'expert brèves (Marc) ont la même voix dans l'émission, c'est que les fichiers `expert_breves_marc.wav` et/ou `presentateur_radio.wav` dans `~/.hermes/voices_tts/` ont été générés avec la même voix edge-tts. Régénérer avec les bonnes voix : Marc → `fr-FR-RemyMultilingualNeural`, Sophie → `fr-FR-VivienneMultilingualNeural`, Henri → `fr-FR-HenriNeural`. NE PAS utiliser ffmpeg in-place (même fichier entrée/sortie) — utiliser un fichier temporaire. - **Références juridiques** : `prononcer_reference_juridique()` doit être appliquée à TOUS les textes contenant des références juridiques. Les numéros d'arrêts utilisent le mot « tiret » entre les parties (ex: « vingt-quatre tiret treize mille cinq cent quatre-vingt-dix-neuf »). - **Section législative vide** : même si aucune actualité législative cette semaine, la section doit être annoncée brièvement. - **Durée illimitée** : aucune limite de durée. Si la semaine est chargée, l'émission sera longue. Transcrire le contenu complet, sans coupe. - **Pas de limite de phrases** : `_format_expert_response()` ne coupe plus à 3 phrases. Toutes les phrases du contenu sont incluses. - **Pas de limite de caractères** : le validateur ne rejette plus les segments > 4000 caractères. - **Import validate_juridique_script** : toujours `from validate_juridique import validate_juridique_script`, jamais depuis `generate_juridique`. Ce dernier module n'exporte pas cette fonction. - **Anti-répétition présentateur/expert** : l'expert ne JAMAIS répéter le titre que le présentateur vient d'annoncer. **`_format_expert_response()` doit retourner UNIQUEMENT le corps du texte (body), SANS préambule**. Piège classique : ajouter `"C'est un arrêt important. Voici ce qu'il faut en retenir concernant {title}."` avant le body — cela répète le titre déjà annoncé par Henri. Correction : `"".join(sentences)` directement. Le présentateur utilise des relances neutres (*"Marc, dis-nous tout sur ce point."*). - **PDF multi-pages** : le parser `extract_pdf.py` gère automatiquement les PDF de 2 à 4+ pages. Les brèves et le focus peuvent traverser les pages. Ne jamais hardcoder `page_num == 2` pour le focus — utiliser le parser structuré qui concatène toutes les pages. - **JO → Journal Officiel** : `clean_tts_text()` convertit automatiquement "JO" et "JORF" en "Journal Officiel" pour la prononciation. - **FO → F.O.** : toujours écrire "F.O." (avec points) pour prononciation correcte. `clean_tts_text()` fait cette conversion automatiquement. - **URLs** : `clean_tts_text()` supprime automatiquement les URLs (`https://...`, `.fr/...`) qui ne se lisent pas à voix haute. - **Césures PDF** : `clean_tts_text()` nettoie les tirets de césure (`-\n`, `- `) issus de l'extraction PDF. - **Flèches ►** : `clean_tts_text()` supprime les flèches ► utilisées dans le PDF FO. - **Dates Cass. soc. avec espaces** : `prononcer_date_cass()` gère les espaces inconsistants dans les dates (ex: `18-3- 26`, `25- 3-26`). Les dates sont toujours prononcées correctement. - **Dates françaises législatives** : `_pronounce_french_dates()` convertit automatiquement les dates comme `28 mars 2026`, `du 15 novembre 2024`, `1er avril` en prononciation. Le préfixe `du ` est conservé (pas de `du le`). - **Articles de loi** : `prononcer_reference_juridique()` gère `article L`, `articles L`, `article L.` (avec point). Le préfixe `article` n'est PAS dupliqué. - **Ordonnance n°** : `prononcer_reference_juridique()` prononce les numéros d'ordonnance (ex: `n°2017-1386` → `n°deux mille dix-sept tiret mille trois cent quatre-vingt-six`), avec ou sans espace après `n°`. - **JO → Journal Officiel** : `clean_tts_text()` convertit automatiquement "JO" et "JORF" en "Journal Officiel" pour la prononciation. ## Références croisées - **radio-briefing** : même infrastructure TTS (XTTS + edge-tts), structure similaire - **ocr-and-documents** : extraction PDF (pymupdf) - **scripts/validate_juridique.py** : validation pré-génération - **scripts/extract_pdf.py** : extraction du PDF Force Ouvrière - **references/pdf-structure.md** : détail de la structure PDF et pattern de parsing - **references/session-fixes.md** : corrections et patterns spécifiques par session