Incruster des sous-titres dans les vidéos avec Lua et FFmpeg
Utilisez Lua pour exécuter FFmpeg et incruster un fichier SRT existant dans une vidéo. Le script ci-dessous produit un MP4 avec des sous-titres incrustés et une copie de l’audio. Une vidéo de test est générée pour vous permettre de vérifier le texte et le minutage avant d’utiliser vos propres fichiers.
Les sous-titres incrustés deviennent des pixels de la vidéo. Les spectateurs ne peuvent ni les masquer ni sélectionner une autre langue, et les lecteurs d’écran ne peuvent pas les lire comme une piste de texte. Conservez le SRT séparément si votre lecteur nécessite des sous-titres sélectionnables. Cet exemple affiche le texte fourni ; il ne transcrit ni ne traduit l’audio.
Vérifier les prérequis sous Linux
Utilisez un shell POSIX, /dev/fd, Lua 5.4 ou Lua 5.5, ainsi que FFmpeg avec le filtre
subtitles de libass et l’encodeur libx264. Ce guide cible Linux.
Les commandes ci-dessous installent les prérequis sur Ubuntu 24.04 ; d’autres variantes compilées des paquets
peuvent proposer des fonctionnalités FFmpeg différentes. La vérification du décodage utilise aussi
mktemp, cat et rm du paquet
coreutils habituel sous Linux.
L’exemple a été testé avec Lua 5.4.6 et FFmpeg 6.1.1 fournis par Ubuntu, ainsi qu’avec Lua 5.5.1 et FFmpeg 9.0.1. Utilisez des paquets maintenus plutôt que d’installer une ancienne version uniquement pour retrouver ces versions testées.
Installez Lua, FFmpeg et une police contenant les caractères utilisés dans cet exemple :
sudo apt-get update && sudo apt-get install lua5.4 ffmpeg fonts-dejavu-core
Vérifiez l’exécutable nommé lua avant de créer des fichiers. Si votre
distribution fournit uniquement lua5.4, utilisez ce nom pour chaque
invocation de Lua ci-dessous. La documentation du filtre subtitles
de FFmpeg explique pourquoi libass est nécessaire.
lua -v &&
ffmpeg -hide_banner -h filter=subtitles &&
ffmpeg -hide_banner -h encoder=libx264 &&
ffprobe -version &&
command -v mktemp && command -v cat && command -v rm
Arrêtez-vous si le filtre ou l’encodeur est inconnu. Lua exécute la commande shell ; FFmpeg assure le décodage, le rendu et l’encodage. Aucune bibliothèque multimédia Lua n’est nécessaire.
Créer une vidéo et des sous-titres minutés
Travaillez dans un nouveau répertoire privé sans écritures concurrentes. Générez une vidéo noire de 8,25 secondes avec une tonalité de test de 660 Hz et de l’audio AAC. La petite taille des images et le nombre limité de threads de l’encodeur maintiennent le coût de cette vérification faible :
(
mkdir lua-subtitles-demo && cd lua-subtitles-demo &&
ffmpeg -nostdin -n -f lavfi -i 'color=c=black:s=640x360:r=24:d=8.25' \
-f lavfi -i 'sine=frequency=660:sample_rate=48000:duration=8.25' \
-c:v libx264 -threads 2 -pix_fmt yuv420p -c:a aac -shortest input.mp4
)
Lorsque cette commande réussit, placez-vous dans le nouveau répertoire pour les étapes restantes :
cd lua-subtitles-demo
Enregistrez le contenu suivant sous subtitles.srt en UTF-8. Les horodatages SRT
utilisent une virgule avant les millisecondes ; des lignes vides séparent les entrées de sous-titres.
Le mot accentué permet de vérifier que votre police et votre encodage sont compatibles.
1
00:00:01,000 --> 00:00:02,000
Hello from Lua: café!
2
00:00:04,000 --> 00:00:05,000
The second subtitle.
Si la préparation s’arrête en cours de route, conservez les fichiers qu’elle a créés. L’option
-n de FFmpeg refuse un fichier input.mp4 existant.
Inspectez-le avant de réessayer, ou recommencez dans un autre nouveau répertoire ; ne répétez pas
la commande mkdir dans le premier.
Enregistrer et exécuter le programme Lua
Enregistrez ce programme sous add_subtitles.lua à côté de la vidéo et du SRT. Il protège
les arguments du shell par des guillemets et transmet le fichier de sous-titres via le descripteur
3, de sorte que son nom ne fasse jamais partie de la syntaxe des filtres de FFmpeg. Cette méthode
gère les espaces, les apostrophes, les deux-points, les crochets et les traits d’union initiaux
dans les noms de fichiers locaux.
#!/usr/bin/env lua
if #arg ~= 0 and #arg ~= 3 then
io.stderr:write("Usage: lua add_subtitles.lua <video.mp4> <subtitles.srt> <new-output.mp4>\n")
os.exit(1)
end
local video_file = arg[1] or "input.mp4"
local subtitles_file = arg[2] or "subtitles.srt"
local output_file = arg[3] or "output.mp4"
local existing = io.open(output_file, "rb")
if existing then
existing:close()
io.stderr:write("Output already exists; choose a new filename.\n")
os.exit(1)
end
local function shell_quote(value)
return "'" .. value:gsub("'", "'\\''") .. "'"
end
local function local_path(value)
if value:sub(1, 1) == "/" then return value end
return "./" .. value
end
local filter = "subtitles=/dev/fd/3"
local command = string.format(
"ffmpeg -nostdin -n -filter_threads 1 -i %s -vf %s -c:v libx264 -threads 2 -crf 23 -c:a copy %s 3<%s",
shell_quote(local_path(video_file)),
shell_quote(filter),
shell_quote(local_path(output_file)),
shell_quote(local_path(subtitles_file))
)
local success = os.execute(command)
if not success then
io.stderr:write("Subtitle conversion failed. Inspect any partial output before retrying.\n")
os.exit(1)
end
print("Subtitles added successfully.")
Exécutez-le avec la vidéo, le fichier de sous-titres et un nouveau nom de fichier de sortie, dans cet ordre :
lua add_subtitles.lua input.mp4 subtitles.srt output.mp4
Une fois FFmpeg terminé, le programme affiche :
Subtitles added successfully.
Le programme signale une sortie existante comme une erreur, et -n
indique à FFmpeg de ne pas la remplacer. Il utilise le statut de la commande shell
de Lua pour ne pas afficher le message de réussite lorsque FFmpeg échoue. La publication du
résultat n’est pas transactionnelle : une conversion échouée peut laisser un nouveau fichier
partiel, et la vérification d’existence ne coordonne pas les écritures concurrentes. Inspectez
toute sortie partielle et choisissez un nouveau chemin de sortie avant de réessayer.
Vérifier le résultat visible et l’audio
Ouvrez output.mp4 dans votre lecteur vidéo. Vous devriez entendre la tonalité
pendant toute la vidéo, voir « Hello from Lua: café! » de 1 à 2 secondes, puis
« The second subtitle. » de 4 à 5 secondes. Le fond devrait être noir et sans texte en dehors de
ces intervalles. Vérifiez l’accent ainsi que le minutage ; la réussite de l’encodage ne prouve pas
à elle seule que les sous-titres sont lisibles.
Inspectez les flux et décodez entièrement la sortie pour effectuer une vérification supplémentaire :
ffprobe -v error -show_entries stream=codec_type,codec_name -of compact output.mp4 &&
(
decode_log=$(mktemp) || exit 1
trap 'rm -f "$decode_log"' EXIT
if ffmpeg -nostdin -v error -xerror -i output.mp4 -map 0:v:0 -map 0:a:0 -f null - \
2>"$decode_log" && [ ! -s "$decode_log" ]; then
printf 'Full decode finished without errors.\n'
else
cat "$decode_log" >&2
false
fi
)
Pour le fichier de test généré, le résultat attendu est une vidéo H.264, de l’audio AAC et le
message « Full decode finished without errors. ». Le sous-shell vérifie la sortie d’erreur de
FFmpeg ainsi que son statut : certaines variantes compilées signalent une erreur de décodage tout en renvoyant
un statut zéro. Il supprime son journal temporaire et laisse votre shell appelant actif dans les
deux cas. Cette vérification n’évalue ni le texte ni le minutage des sous-titres ; conservez la
vérification visuelle. Avec vos propres médias, utilisez une vidéo locale valide et un codec audio
pris en charge par MP4, car -c:a copy copie le flux audio sélectionné sans le
convertir.
Cette copie signifie aussi que la commande Lua peut se terminer alors qu’un paquet audio endommagé est encore présent dans la sortie. Le décodage complet ci-dessus peut révéler ces dommages même lorsque la liste des flux semble correcte. Le message de réussite seul ne constitue pas une vérification d’intégrité.
Retarder les sous-titres ou choisir une police
Pour retarder chaque entrée de sous-titres de 2,5 secondes, créez un SRT décalé distinct et
transmettez ce fichier au même programme. L’option -itsoffset
de FFmpeg ajoute le décalage aux horodatages d’entrée :
ffmpeg -nostdin -n -itsoffset 2.5 -i subtitles.srt -c:s srt shifted.srt &&
lua add_subtitles.lua input.mp4 shifted.srt delayed.mp4
Dans delayed.mp4, la première entrée devrait apparaître de 3,5 à 4,5 secondes et
la seconde de 6,5 à 7,5 secondes. Aucun texte ne devrait apparaître à 1,5 seconde. Ouvrez
shifted.srt pour vérifier les horodatages modifiés si le résultat suit encore
le minutage d’origine.
Pour fixer la police et sa taille, remplacez uniquement la variable filter
dans le programme enregistré par cette ligne. DejaVu Sans doit être installée :
local filter = "subtitles=/dev/fd/3:force_style='FontName=DejaVu Sans,FontSize=24'"
Exécutez le programme modifié avec un nouveau nom de fichier de sortie :
lua add_subtitles.lua input.mp4 subtitles.srt styled.mp4
Conservez cette chaîne de style fixe dans le script. Les expressions de filtre arbitraires
fournies par les utilisateurs nécessitent leur propre validation ; la protection des arguments
par des guillemets dans le shell ne valide pas la syntaxe des filtres de FFmpeg. FFmpeg documente
les réglages force_style dans la même référence du filtre subtitles liée plus haut.
Utiliser vos propres fichiers et diagnostiquer les échecs
Transmettez vos propres chemins comme trois arguments. Les originaux restent des entrées ;
choisissez une sortie distincte avec une extension .mp4. Des fichiers
manquants, un SRT invalide et de l’audio qui ne peut pas être copié dans un MP4 devraient produire
un statut de sortie non nul. Lisez le diagnostic de FFmpeg avant de réessayer. Ce petit programme
n’effectue pas une validation de chaque paquet ni de chaque entrée de sous-titres ; les décodeurs
peuvent récupérer des médias endommagés. Comparez donc le résultat à l’original jusqu’à la fin de
l’enregistrement.
Si les caractères accentués s’affichent incorrectement, confirmez que le SRT est en UTF-8 et que la police choisie les contient. Convertissez un encodage ancien connu vers un fichier distinct plutôt que d’écraser la source :
iconv -f ISO-8859-1 -t UTF-8 legacy.srt > converted.srt
Utilisez une destination encore inutilisée pour cette redirection, qui pourrait sinon écraser un fichier même si la conversion échoue. Ne transmettez pas directement des fichiers téléversés non fiables à cette commande locale : exécutez FFmpeg dans un processus de traitement isolé avec des limites d’accès au système de fichiers et au réseau, de mémoire et de durée d’exécution. La protection des arguments par des guillemets dans le shell ne constitue pas un environnement isolé pour le traitement des médias.
