Comment transcrire automatiquement un flux RSS de podcast
Un flux RSS de podcast contient déjà les informations dont votre worker de transcription a besoin : titres, dates de publication, GUID et URLs audio. Le travail consiste à transformer chaque nouvel enclosure en transcription, une seule fois, puis à l’indexer.
Pour un épisode isolé, une transcription manuelle suffit. Pour une émission, un réseau ou un catalogue historique, il faut un petit pipeline d’ingestion.
La forme utile reste simple :
Flux RSS
↓
Détection des nouveaux épisodes
↓
Envoi de l’URL audio à l’API de transcription
↓
Polling des jobs
↓
Stockage du transcript et des horodatagesTechtuel expose une API de transcription de podcast et une API de transcription RSS pour ce workflow.
1. Lire le flux RSS
La plupart des flux podcast exposent chaque épisode dans un <item>. L’URL audio se trouve généralement dans la balise <enclosure>.
Stockez au minimum :
- GUID RSS ;
- titre de l’épisode ;
- date de publication ;
- URL de la page épisode ;
- URL audio de l’enclosure ;
- identifiant de job Techtuel ;
- statut du job.
Le GUID est souvent le meilleur identifiant externe. S’il reste stable chez l’éditeur, il évite les jobs de transcription en double.
2. Détecter les épisodes non indexés
import Parser from 'rss-parser'
const parser = new Parser()
type StoredEpisode = {
guid: string
}
export async function findNewEpisodes(
feedUrl: string,
storedEpisodes: StoredEpisode[],
) {
const feed = await parser.parseURL(feedUrl)
const knownGuids = new Set(storedEpisodes.map((episode) => episode.guid))
return feed.items
.filter((item) => item.guid && !knownGuids.has(item.guid))
.map((item) => ({
guid: item.guid!,
title: item.title ?? 'Épisode sans titre',
publishedAt: item.isoDate ?? null,
episodeUrl: item.link ?? null,
audioUrl: item.enclosure?.url ?? null,
}))
.filter((episode) => episode.audioUrl)
}En production, dédupliquez aussi sur l’URL audio normalisée. Certains flux changent leurs GUID lors de migrations.
3. Soumettre chaque URL audio
Pour une ingestion de flux, utilisez l’endpoint asynchrone. Les épisodes sont longs, et un rattrapage de catalogue ne doit pas dépendre d’une requête HTTP maintenue ouverte.
curl -X POST https://api.techtuel.com/v1/transcriptions \
-H "Authorization: Bearer $TECHTUEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://cdn.example.com/podcast/episode-42.mp3"
}'Réponse :
{
"id": "job_9f2c",
"status": "processing"
}Persistez immédiatement cet identifiant à côté de la ligne épisode.
4. Interroger le job jusqu’à son statut final
Il n’y a pas de webhook. Le polling garde l’intégration prévisible et évite d’exposer un endpoint de callback public.
const TERMINAL = new Set(['completed', 'failed'])
export async function waitForTranscript(jobId: string) {
while (true) {
const response = await fetch(
`https://api.techtuel.com/v1/transcriptions/${jobId}`,
{ headers: { Authorization: `Bearer ${process.env.TECHTUEL_API_KEY}` } },
)
const job = await response.json()
if (TERMINAL.has(job.status)) return job
await new Promise((resolve) => setTimeout(resolve, 15000))
}
}Traitez claimed comme processing : un worker a pris le job, mais la transcription n’est pas prête.
5. Stocker la transcription
Le job terminé renvoie le transcript complet et les segments horodatés :
{
"id": "job_9f2c",
"status": "completed",
"title": "La recherche hybride en production",
"language": "fr",
"minutes": 54.7,
"transcript": "Bienvenue dans cet épisode...",
"segments": [
{
"start_seconds": 0,
"text": "Bienvenue dans cet épisode."
},
{
"start_seconds": 5.8,
"text": "Aujourd’hui nous parlons de systèmes de recherche."
}
]
}Stockez le transcript complet et les segments. Le transcript sert aux exports et aux résumés. Les segments servent à la recherche, aux citations et au RAG.
6. Rendre le worker idempotent
L’ingestion de podcast échoue de façons banales :
- flux temporairement indisponible ;
- URL d’enclosure expirée ;
- changement de CDN ;
- métadonnées modifiées ;
- épisode présent deux fois ;
- job de transcription en échec.
Gardez un workflow reprenable :
- persistez l’identifiant du job avant de poller ;
- ne soumettez pas un nouveau job si un job est déjà en cours ;
- ne relancez que les jobs échoués ;
- stockez le GUID RSS et l’URL audio ;
- séparez votre statut d’ingestion du statut du job Techtuel.
Si un job Techtuel échoue après un problème transitoire de source, relancez-le avec POST /v1/transcriptions/{id}/retry.
7. Indexer pour la recherche ou le RAG
Une fois stockée, découpez la transcription en chunks avec les métadonnées de l’épisode :
- titre du podcast ;
- titre de l’épisode ;
- URL de l’épisode ;
- date de publication ;
- instant de départ ;
- texte ;
- identifiant de job Techtuel.
Votre résultat de recherche peut alors citer le passage exact et renvoyer l’auditeur au bon moment.
Pour un guide plus complet sur l’indexation, lisez Transformer vos épisodes de podcast en documents pour le RAG.
Commencer petit
Démarrez avec un flux, un worker et une table. Ajoutez de la concurrence après avoir mesuré le nombre de nouveaux épisodes par jour et la durée réelle de transcription de votre catalogue.