Pourquoi votre aperçu vidéo ment sur l'exportation
Votre éditeur vidéo semble terminé. L'aperçu est net, la légende est placée exactement là où l'utilisateur l'a glissée, et la timeline défile sans accroc. Puis l'utilisateur appuie sur Export, et la légende réapparaît au mauvais endroit, à la mauvaise taille, parfois entièrement coupée. Rien n'a planté, il n'y a pas d'erreur dans le journal — l'aperçu et le fichier sont simplement en désaccord.
C'est le bug le plus courant dans tous les éditeurs vidéo, et il s'agit d'un problème de modélisation des données plutôt que de rendu. Ci-dessous, la seule habitude qui rend ce bug impossible, comment dimensionner correctement les images à partir d'un rapport d'aspect, et comment maintenir la fluidité du glisser-déposer sur les téléphones bon marché. Ces idées s'appliquent dans n'importe quel langage ou framework UI.
Le rapport d'aspect est une forme, la résolution est une taille
La plupart des bugs d'image commencent ici. Un rapport d'aspect décrit la forme de l'image et rien d'autre : 9:16 est vertical, 16:9 est horizontal. Une résolution décrit la taille en pixels, comme 1080x1920. Une même forme supporte de nombreuses tailles, donc les deux valeurs ne sont jamais interchangeables.
| Rapport d'aspect | Valeur numérique | Usage typique |
| 16:9 | 1.78 | YouTube, web paysage |
| 9:16 | 0.56 | Reels, TikTok, Shorts |
| 1:1 | 1.00 | Publications carrées |
| 4:5 | 0.80 | Publications portrait |
Stockez le rapport sous forme de texte brut et ne le convertissez en nombre que lorsque vous effectuez des calculs. Stockez une hauteur cible pour l'exportation, puis dérivez la largeur à partir du rapport, et forcez les deux nombres à être pairs avant qu'ils n'atteignent un encodeur. Les dimensions impaires sont la cause silencieuse d'un nombre surprenant d'exportations échouées.
height = width / ratioToNumber(ratio) // dimensionnement de la boîte d'aperçu
function widthFromHeight(ratio, height): raw = height * ratioToNumber(ratio) return makeEven(round(raw)) // les encodeurs exigent des côtés pairs
// 9:16 Ă 1080 de haut -> 608 x 1080// 16:9 Ă 1080 de haut -> 1920 x 1080
Stockez les positions sous forme de fractions, jamais de pixels
Un éditeur vit dans trois mondes, à trois tailles différentes : le document sauvegardé, l'aperçu à l'écran et le fichier exporté. Le document est la seule source de vérité — les deux autres ne sont que des rendus à une échelle différente.
Project -> Clip (source, trim, filters) -> Overlay (texte / autocollant)
Document Rendu d'aperçu Rendu d'exportation x = 0.5, y = 0.9 --> x * 360 px --> x * 1080 px --> MP4 | ^ ^ +------------------------+------------------------+ une fraction stockée, une formule, deux tailles
Si vous stockez 540 pixels, ce nombre n'est correct que sur l'appareil où il a été mesuré. Si vous stockez 0.5, cela signifie "le centre horizontal" à chaque taille, pour toujours. Chaque position, décalage et échelle dans le modèle devrait être une fraction entre 0 et 1.
type Overlay { x: number // 0.0-1.0 horizontal (0.5 = centre) y: number // 0.0-1.0 vertical (0.9 = près du bas) scale: number // 1.0 = taille normale rotation: number // degrés startTimeMs: number // quand il apparaît endTimeMs: number // quand il disparaît}
L'exportation devient alors presque ennuyeuse, et c'est le but. Le moteur de rendu utilise la même formule que l'aperçu avec un multiplicateur plus grand : px = overlay.x * exportWidth. FFmpeg gère l'image elle-même, et son expression de centrage (ow-iw)/2 est la même arithmétique que l'aperçu utilise pour le letterbox. Parce que la valeur stockée n'a jamais changé, la légende atterrit exactement au bon endroit.
ffmpeg -i input.mp4 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,\ pad=1080:1920:(ow-iw)/2:(oh-ih)/2:0xD3D3D3" \ -c:v libx264 -crf 23 -pix_fmt yuv420p -c:a aac output.mp4
Dessiner l'aperçu en un seul passage
Mesurez la boîte d'aperçu à l'exécution au lieu de la coder en dur, car un téléphone, une tablette et une fenêtre de bureau redimensionnée vous donneront tous une taille différente. Dessinez chaque superposition en un seul passage sur le canvas plutôt que de monter chacune comme un élément d'UI séparé — un seul passage reste fluide pendant qu'un doigt se déplace.
Deux règles garantissent l'intégrité de la boucle. Ignorez toute superposition en dehors de sa plage horaire, et associez toujours save() à restore() afin qu'un élément ne puisse pas propager sa transformation au suivant.
function drawPreview(canvas, overlays, currentTime, box): for each overlay in overlays: if currentTime < overlay.startTimeMs: passer if currentTime > overlay.endTimeMs: passer px = overlay.x * box.width // la formule clé py = overlay.y * box.height canvas.save() canvas.move(px, py) canvas.rotate(overlay.rotation) canvas.resize(overlay.scale) canvas.drawText(overlay.content) canvas.restore() // jamais optionnel
Maintenir la fluidité du glisser-déposer avec un état à deux niveaux
Un doigt qui glisse rapporte environ soixante positions par seconde. Si chacune écrit dans votre magasin de projet, vous payez pour la validation, la persistance et une reconstruction complète de l'état soixante fois par seconde, et le glisser-déposer saccade visiblement. Divisez plutôt le travail en deux niveaux :
- Niveau 1, éphémère : une petite carte
liveDragcontenant la position actuelle du doigt. Mettez-la à jour à chaque mouvement et redessinez le canvas. Rien d'autre ne s'exécute. - Niveau 2, durable : à la fin du glisser-déposer, validez la fraction finale dans la superposition du modèle de projet, effacez l'entrée
liveDraget enregistrez une étape d'annulation.
Les avantages vont au-delà du taux de rafraîchissement. L'historique d'annulation reste utile car un glisser-déposer ne produit qu'une seule entrée au lieu de centaines, et la sauvegarde automatique cesse de solliciter excessivement le disque. C'est ainsi que Figma et Canva maintiennent la réactivité de la manipulation directe.
Contexte réel
Chez MicrocosmWorks, nous avons repris un éditeur de vidéos courtes dont les légendes se décalaient à l'exportation. L'équipe avait persisté les positions des superpositions en tant que pixels de l'appareil lus directement depuis le canvas de prévisualisation, de sorte qu'une légende créée sur un petit téléphone atterrissait haute et petite dans le fichier 1080p, et changer le rapport d'aspect poussait certaines superpositions entièrement hors du cadre.
Nous avons migré le modèle vers des coordonnées normalisées de 0 à 1, fait en sorte que les deux moteurs de rendu partagent une seule fonction d'aide "fraction-fois-taille", et imposé des dimensions de sortie paires dérivées du rapport stocké. L'aperçu et l'exportation correspondaient sur chaque appareil de test. Le déplacement des validations de glisser-déposer vers la fin du glisser-déposer a éliminé le décalage que les utilisateurs avaient signalé sur les appareils Android bas de gamme. Vous pouvez voir le type de vidéos courtes et de travaux d'édition qui en sont issus dans notre portefeuille de projets.
Conclusion
Un aperçu et une exportation sont deux rendus d'un seul document, alors donnez-leur une seule source de vérité. Stockez les positions sous forme de fractions, utilisez la même formule "fraction-fois-taille" dans les deux moteurs de rendu, dérivez la largeur du rapport d'aspect, maintenez les dimensions paires et validez les modifications de glisser-déposer uniquement lorsque le doigt est levé. Ces cinq habitudes éliminent toute une catégorie de bugs avant même qu'ils ne soient écrits.
La création de moteurs multimédia où la vue interactive et le fichier final concordent est un objectif central de notre travail d'ingénierie vidéo et streaming chez MicrocosmWorks.
Vous livrez un éditeur vidéo ou de contenu et souhaitez que l'aperçu et l'exportation concordent réellement ? Nous avons résolu cette classe exacte de bugs dans les outils d'édition de vidéos courtes. Parlez à notre équipe d'ingénierie →
En savoir plus sur notre équipe
1. Optimisation du logo de chaîne pour différentes résolutions vidéo
2. Prenez une photo, enregistrez un repas : un pipeline de nutrition par vision par ordinateur
3. Optimisation du logo de chaîne pour différentes résolutions vidéo

