[Slim Video #2] SSD Staging et VideoToolbox à 300 fps : Quand le hardware impose ses règles
Dans l'Épisode 1, nous avions fixé le scope : cibler uniquement le transcodage H.264 vers HEVC et préserver intactes toutes les pistes audio et sous-titres.
Mais dès que j'ai branché le script sur le disque dur externe Western Digital de mon serveur Plex, je me suis heurté à un mur de réalité physique : le goulot d'étranglement I/O des disques mécaniques.
🐢 Pourquoi FFmpeg agonise sur un disque dur USB mécanique
Sur le papier, les encodeurs matériels VideoToolbox d'Apple Silicon sont des monstres de puissance capables de traiter du flux vidéo 1080p à plus de 300 images par seconde.
Mais dans la pratique, quand vous exécutez une commande FFmpeg classique directement sur un disque dur USB mécanique externe :
ffmpeglit le flux source volumineux (ex: un fichier de 8 Go).- Il transcode les images en mémoire vive.
- Il écrit simultanément le flux réencodé sur le même disque dur physique.
Résultat ? La tête de lecture magnétique du disque passe son temps à sauter frénétiquement entre la zone de lecture et la zone d'écriture (head thrashing).
Le débit s'effondre à 12-15 Mo/s. La puce Apple Silicon passe 85% de son temps à attendre que le bus USB et la tête mécanique répondent. Un film qui devrait prendre 3 minutes à encoder en prend 40 !
⚡ Le Hack Architectural : Le mode SSD Staging
Pour débrider la puce Apple Silicon, il fallait séparer physiquement la lecture/écriture lente de l'encodage intensif. C'est là qu'intervient le SSD Staging Engine intégré dans slim_video/transcoder.py.
sequenceDiagram
autonumber
participant USB as 💽 Disque Dur Externe (Lent)
participant SSD as ⚡ SSD NVMe Mac Mini (Rapide)
participant VT as 🍎 Apple VideoToolbox Core
USB->>SSD: 1. Copie séquentielle brute du fichier source
SSD->>VT: 2. Lecture ultra-rapide @ 3 Go/s
VT->>SSD: 3. Écriture HEVC locale @ 300+ fps
SSD->>USB: 4. Transfert séquentiel du .mkv compressé
USB->>USB: 5. Déplacement de la source vers _originals_to_delete
Le cycle de vie d'un transcodage en mode Staging :
- Contrôle d'espace 2x : Vérification que le SSD interne dispose d'au moins 2 fois la taille du fichier source pour accueillir le temporaire et le fichier transcodé sans saturer macOS.
- Copie séquentielle : Une simple copie de fichier de A vers B est une opération 100% séquentielle pour le disque dur mécanique, maximisant sa vitesse de pointe (~120 Mo/s).
- Transcode local pur : FFmpeg lit et écrit sur le SSD NVMe interne à plus de 3 Go/s. Zéro contention d'accès.
- Rapatriement et Quarantaine : Le fichier final compressé est renvoyé sur le disque externe, et l'original est déplacé dans le dossier
_originals_to_delete/.
Grâce à ce pipeline, le débit de transcodage est passé instantanément de 18 fps à plus de 280-350 fps sur un Mac Mini M-series !
🛠️ Sous le capot : Les bons flags VideoToolbox
Apple ne documente pas toujours clairement les options de hevc_videotoolbox dans FFmpeg. Après de nombreux tests de qualité visuelle et de compatibilité avec QuickTime, l'Apple TV et les clients Plex, voici la commande exacte générée par build_transcode_command :
# slim_video/transcoder.py
video_args = [
"-c:v",
"hevc_videotoolbox", # Moteur matériel Apple Silicon
"-q:v",
str(quality), # Facteur de qualité (50 = sweetspot idéal)
"-tag:v",
"hvc1", # Indispensable pour la compatibilité Apple / Safari
"-pix_fmt",
"p010le", # Encodage 10-bit : élimine les bandes de dégradés
"-spatial_aq",
"1", # Adaptive Quantization spatiale pour préserver les détails
]
Pourquoi le 10-bit (p010le) même sur une source 8-bit ?
C'est un secret bien connu des ingénieurs vidéo : encoder en 10-bit permet à l'algorithme HEVC de calculer ses tables de compression avec une précision 4 fois supérieure. Non seulement cela élimine l'effet de banding (les marches d'escalier visibles dans les ciels ou les scènes sombres), mais cela permet paradoxalement d'obtenir un fichier plus compact à qualité perçue égale.
🧟 La gestion des sous-processus FFmpeg orphelins
Quand on vibe code avec l'IA, celle-ci adore écrire des subprocess.run(cmd) basiques.
Problème : quand l'utilisateur fait un Ctrl+C dans son terminal pour annuler un batch, le script Python s'arrête, mais le processus enfant ffmpeg continue de tourner en tâche de fond en arrière-plan comme un zombie ! Au bout de trois essais, vous avez trois instances FFmpeg qui saturent la mémoire vive et font chauffer le processeur pour rien.
Nous avons blindé la méthode _run_with_progress dans slim_video/estimator.py :
# slim_video/estimator.py
try:
proc.wait()
except BaseException:
try:
proc.terminate()
proc.wait(timeout=2.0)
except Exception:
try:
proc.kill()
proc.wait()
except Exception:
pass
raise
En interceptant proprement BaseException (qui capture KeyboardInterrupt et SIGTERM), Slim Video s'assure d'envoyer un signal d'arrêt propre (SIGTERM) suivi si nécessaire d'un SIGKILL pour ne jamais laisser de process zombie derrière lui.
⏭️ Prochain épisode : L'estimation en 30 secondes et le bilan chiffré
Notre moteur tourne désormais à 300 fps sans faire souffrir le disque USB. Mais comment savoir à l'avance quels fichiers valent vraiment le coup d'être transcodés ?
👉 Rendez-vous dans l'Épisode 3 pour découvrir l'algorithme d'estimation par échantillonnage en 30 secondes, l'interface TUI Curses et le screenshot réel de notre session Disney Integrale HD (14.45 Go libérés) !
🔗 Liens & Ressources de la Série
- ⬅️ Épisode 1 : Disque Plex saturé et le crash-test de Caméra Café
- 📖 Épisode 2 : SSD Staging et VideoToolbox : Dompter Apple Silicon à 300 fps (cet article)
- ➡️ Épisode 3 : Estimation en 30s, TUI Curses et 14 Go libérés : Le grand bilan
- 💻 Code source GitHub : github.com/Boblebol/slim-video
- 📦 Package PyPI :
pip install slim-video/ pypi.org/project/slim-video/ - 🌐 Fiche Lab Portfolio : alexandre-enouf.fr/#lab