Citations : sources → transcriptions → citations → incrustations et extraits
Outil : agent-citations/citekit.py (Python standard + mlx_whisper et ffmpeg). Il n'écrit jamais par-dessus les entrées et refuse de remplacer ses propres sorties sans --force.
Structure des dossiers
Un dossier par sujet sous active_edits/<sujet>/ :
sources/video_sources.txt la liste d'URLs de l'utilisateur
sources/audio/<id>.m4a .info.json depuis yt-dlp (étape 1)
sources/transcripts/<id>.json .md étape 2
podcast/<ep>.json .md sous-titres de notre propre épisode, importés (étape 2b)
sources/quotes.json étape 3, écrit par vous
sources/citations/ sorties de l'étape 4
1. Téléchargement (l'utilisateur exécute)
YouTube bloque yt-dlp anonyme ici. Donnez cette commande à l'utilisateur plutôt que de l'exécuter. Elle nécessite les cookies Firefox et les clients tv/safari, et garde l'audio petit (Whisper n'a besoin que de 16 kHz mono) :
cd active_edits/<sujet>/sources && for id in ID1 ID2; do yt-dlp --cookies-from-browser firefox --extractor-args "youtube:player_client=tv,web_safari,mweb" -f "wa/w" -x --audio-format m4a --postprocessor-args "ExtractAudio:-ac 1 -ar 16000" --write-info-json --write-auto-subs --sub-langs en --sub-format json3 --no-overwrites -o "audio/%(id)s.%(ext)s" "https://youtu.be/$id"; done
Pour l'historique d'une chaîne, listez-le d'abord, puis choisissez : yt-dlp --flat-playlist --print "%(id)s %(duration_string)s %(title)s" https://www.youtube.com/@CHAINE/videos.
Classez les titres avec Jev (vibe-classification) si la liste est longue.
2. Transcrire
citekit.py transcribe sources/audio/<id>.m4a: mlx-whisper large-v3-turbo, timestamps par mot. L'invite initiale est le titre et la description de la vidéo, ce qui corrige la plupart des orthographes de noms. Il tourne environ 85× temps réel sur ce Mac (un extrait de 15 minutes prend ~35 s ; le modèle se télécharge une fois).citekit.py captions sources/audio/<id>.en.json3: sous-titres automatiques de YouTube. Instantané, mais sans ponctuation. Le client tv ne les sauvegarde souvent pas.- 2b. Nos propres épisodes :
citekit.py itt EP.itt --id podcastN --media EP.mp4 --out active_edits/<sujet>/podcast --fix 'REGEX=>Correct'. Les sous-titres de FCP déforment les noms. Pour l'épisode 1 :--fix '\bver ?m+[iu]l+e?\b=>Vermeule' --fix '\bvermil\b=>Vermeule' --fix '\bAsian Vermeule=>Adrian Vermeule'. L'épisode 1 possède aussi des corrections au niveau mot enregistrées danspodcast/podcast1.fixes.json(Schmidt→Schmitt etc.), rejouées lors de la réimportation.
Corrigez les erreurs de transcription au fur et à mesure (ne demandez pas)
Lorsque vous remarquez une erreur certaine (un nom, une citation déformée, un bégaiement que Whisper a doublé), corrigez-la :
citekit.py fix TRANSCRIPT 'anciens mots=>nouveaux mots' [...] [--near SECONDES]. Cela édite <id>.json/.md sur place (les nouveaux mots partagent le timing de l'ancien segment, les correspondances peuvent traverser les segments, la ponctuation finale est conservée), conserve la sortie non modifiée du moteur sous <id>.raw.json, et enregistre chaque correction dans <id>.fixes.json. transcribe et itt rejouent ce journal, donc les corrections survivent à une réexécution. Incertain ? Réécoutez l'endroit plutôt que de deviner :
ffmpeg -ss T -to T2 -i audio.m4a x.wav && mlx_whisper x.wav --output-format txt. Corrigez d'abord, puis citez les mots corrigés : locate cherche dans la transcription.
3. Extraire des citations (votre jugement)
- Lisez la transcription de notre épisode, si elle existe, et listez les affirmations que les animateurs font sur le sujet, chacune avec son horaire dans l'épisode. Ce sont les moments qu'un clip peut étayer ou réfuter.
- Pour chaque affirmation,
citekit.py search 'regex' sources/transcripts/pour trouver des candidats, puis lisez le.mdautour du résultat. Lisez aussi les conférences complètes : les meilleures lignes ne sont pas toujours celles que les animateurs ont nommées. - Une bonne citation est courte (5–25 s parlées), autonome, indubitablement la position de l'orateur (pas un résumé d'un adversaire), et citable dans un bandeau inférieur. Gardez les mots exacts de la transcription. Ne les nettoyez pas, sinon
locatene pourra pas les trouver. - Écrivez
sources/quotes.json:
{"project": "Ep1 Vermeule citations", "fps": 29.97, "episode": "../podcast/podcast1.json",
"quotes": [{"id": "publius-weak-exec", "source": "Bw9M01S8MFI", "approx": 1234,
"text": "mots exacts de la transcription",
"episode_at": 2575, "episode_context": "animateurs sur Hamilton et le dictateur romain",
"why": "son propre énoncé de la thèse", "tags": ["executif", "Fédéraliste"],
"citation": {"author": "~Adrian Vermeule", "title": "The Publius Paradox", "date": "5 juin 2018",
"publication": "Chorley Lecture, LSE"}}]}
episode (relatif à quotes.json) est la transcription de notre épisode. Avec elle, l'episode_at de chaque citation (approximativement quand les animateurs soulèvent le point) devient un point d'insertion : la fin de la phrase de l'animateur en cours à ce moment. Citez les pensées entières. Si la phrase suivante complète l'image ou l'argument, incluez-la (un clip de 5–25 s est l'objectif, pas une limite stricte).
citation utilise par défaut le titre de la vidéo, la date de publication et la chaîne. Mettez le vrai lieu et la date (depuis la description) une fois par conférence dans une section "sources": {"<id>": {"citation": {...}}} de premier niveau. La citation propre à une citation remplace cela. Les auteurs reçoivent le préfixe ~ de l'émission, et \n coupe un titre long sur deux lignes.
Vérifiez tout ce qu'il cite (Hamilton, Kafka, le Digeste) par rapport au texte original. Whisper entend des mots plausibles mais faux : dans la conférence Publius, il a donné les "sacrificial pitchers" de Kafka comme "pictures".
4. Incrustations et extraits
citekit.py cues sources/quotes.json aligne chaque citation sur les timestamps des mots (flou ; il avertit si le score de correspondance est < 0,8) et écrit dans sources/citations/ :
| Fichier | Pour |
|---|---|
citations.md | feuille de révision dans l'ordre de l'épisode : heure d'insertion (enregistrée et après insertions précédentes), mots des animateurs juste avant la coupure, la citation, heures source/extrait, lien YouTube ?t= |
clips.json | montage agent : entrée/sortie source, entrée/sortie avec padding (5 s de chaque côté par défaut, --pad-before/--pad-after, pad_before/pad_after par citation), insert_at, insert_at_after_earlier_inserts, insert_after |
citation_cues.json | agent-motion/titles_fcpxml.py : incrustations Citation v2 (titres inférieurs) synchronisées pour l'épisode avec clips insérés, apparaissant au moment où il commence à parler. Les citations non placées sont mises bout à bout |
download_clips.sh | l'utilisateur l'exécute : vidéo pleine qualité pour seulement ces plages avec padding (--download-sections) |
Ensuite, citekit.py audio-clips sources/citations/clips.json découpe des aperçus audio avec padding depuis l'audio local, pour vérifier à l'oreille. Pour placer les incrustations inférieures dans FCP, suivez la compétence motion-graphics :
python3 agent-motion/titles_fcpxml.py sources/citations/citation_cues.json -o citations.fcpxml.
Remarques
-
transcribedécode lui-même en WAV 16 kHz et refuse un audio dont la durée décodée est bien plus courte que la vidéo. Un téléchargement yt-dlp repris avec un-fdifférent concatène deux codecs dans un seul .m4a (vu le 18/10/2026 : AAC-LC puis HE-AAC), et ffmpeg supprime silencieusement tout ce qui suit le changement. Re-téléchargez avec--no-continue. Ne reprenez jamais après un changement de format. -
Vérifiez un clip à l'oreille, ou à moindre coût :
mlx_whisper citations/audio_clips/<id>.m4a --output-format txtdevrait afficher la citation avec quelques secondes d'introduction et de fin. -
Les timestamps Whisper sont précis à environ ±0,3 s au niveau du mot. C'est pourquoi les pads des clips existent. Les sous-titres iTT de FCP ne synchronisent que des lignes entières, donc
ittrépartit les mots uniformément sur chaque ligne. -
Le connecteur Wispr Flow lit uniquement les transcriptions du Meeting Recorder. Il ne peut pas transcrire un fichier.