Skip to content

App Web

Théo178 edited this page Aug 19, 2026 · 7 revisions

App Web

Branche : web-converter Code : dossier public/ En ligne : bloopstudio.github.io/Anyform

Fonctionnement

App statique 100% client-side : aucun serveur, aucun backend, aucune build step. Toutes les librairies tierces (ImageTracer.js, SheetJS, heic2any, UTIF.js, ffmpeg.wasm) sont vendorisées localement dans public/vendor/ — aucun appel réseau externe, même pour la vidéo/l'audio ou le décodage HEIC. Le thème (clair/sombre) suit automatiquement les préférences de l'appareil (prefers-color-scheme, pas de bouton manuel).

Interface

  1. Choisir le mode en haut : Convertisseur, Compresseur (voir Compresseur), Inspecteur (voir Inspecteur) ou Comparateur (voir Comparateur)
  2. Convertisseur/Compresseur : choisir le type de fichier via les onglets (Image / Données / Audio / Vidéo / Sous-titres — seuls Image, Audio et Vidéo restent visibles en mode Compresseur). Inspecteur/Comparateur n'ont pas cet onglet : la catégorie est déduite du fichier déposé.
  3. En mode Convertisseur, choisir le format d'entrée et de sortie
  4. Déposer un fichier (glisser-déposer ou clic) — deux dropzones côte à côte en mode Comparateur (fichier A / fichier B)
  5. Cliquer sur le bouton d'action ("Convertir", "Compresser", "Inspecter" ou "Comparer")

Pendant le traitement : le bouton affiche un spinner, une barre de progression suit l'avancement réel pour l'audio/vidéo (événement progress de ffmpeg.wasm). Le résultat s'affiche dans une carte dédiée (nom, taille, bouton télécharger, bouton nouveau fichier), en plus du téléchargement automatique. En mode Compresseur, la taille affiche avant → après et le pourcentage gagné. Un encart sous le résultat garde les 5 derniers fichiers traités (voir Historique local), et si l'onglet est en arrière-plan à la fin d'une conversion audio/vidéo, une notification native prévient.

Formats (mode Convertisseur)

  • Images : SVG, PNG, JPG, WebP, GIF, BMP, HEIC en entrée ⇄ PNG/JPG/WebP/AVIF/ICO/TIFF/SVG en sortie. Vectorisation raster → SVG via ImageTracer.js. Décodage HEIC via heic2any/libheif WASM.
  • Audio : WAV, MP3, OGG, M4A, FLAC, AAC, WMA, Opus — moteur ffmpeg.wasm.
  • Vidéo : MP4, WebM, MOV, MKV, AVI, FLV, OGV en entrée ⇄ les mêmes + GIF animé en sortie — moteur ffmpeg.wasm (partagé avec l'audio, chargé une seule fois par session).
  • Données : CSV ⇄ JSON ⇄ XLSX via SheetJS (build patché, sans vulnérabilité connue).
  • Sous-titres : SRT ⇄ VTT ⇄ ASS, en pur JavaScript (aucune dépendance, pas de moteur à charger). Le style ASS (police, couleur, position) n'est pas préservé — non représentable en SRT/VTT, perdu dans les deux sens. Code : public/subtitles.js.

Compresseur

Mode séparé (onglet en haut de l'interface) : réduit la taille d'un fichier sans changer son format, contrairement au Convertisseur. Limité aux images, à l'audio et aux vidéos — pas de notion de "compression sans changer de format" pour les données ou les sous-titres (fichiers texte). Trois niveaux : Léger, Moyen, Fort.

  • Images (PNG, JPG, WebP, HEIC) — réencodage via Canvas. GIF et BMP ne sont pas compressibles : canvas.toBlob() ne sait pas les encoder dans Chromium (il retombe silencieusement sur du PNG), donc ils sont exclus du Compresseur (ils restent convertibles en entrée dans le mode Convertisseur) :
    • JPG/WebP ont un curseur de qualité natif → qualité réduite (0.82 / 0.6 / 0.4 selon le niveau).
    • PNG n'a pas de curseur de qualité → redimensionnement à la place (100% / 85% / 65% selon le niveau).
  • Audio (WAV, MP3, OGG, M4A, FLAC, AAC, WMA, Opus) — ffmpeg.wasm :
    • Formats à bitrate réglable (MP3, OGG, M4A, AAC, Opus, WMA) : bitrate réduit à 192 / 128 / 96 kbps selon le niveau.
    • FLAC (sans perte) : pas de curseur de qualité qui aurait du sens — -compression_level 5 / 8 / 12 à la place (taille réduite, qualité inchangée, encodage plus lent).
    • WAV (PCM brut, aucune compression possible) : fréquence d'échantillonnage réduite à 44100 / 32000 / 22050 Hz, comme le redimensionnement pour le PNG.
  • Vidéo (MP4, WebM, MOV, MKV, AVI, FLV, OGV) — ffmpeg.wasm, CRF réduit sur le même codec/conteneur d'origine (pas de réencodage vers un autre codec) :
    • H.264 (mp4/mov/mkv/avi/flv) : CRF 26 / 30 / 35
    • VP9 (webm) : CRF 26 / 30 / 35 avec -b:v 0 (mode qualité constante)
    • Theora (ogv) : -qscale:v 6 / 10 / 18
    • L'audio n'est pas retouché (-c:a copy) — la vidéo domine presque toujours la taille du fichier.

Code : public/compress.js.

Inspecteur

Mode séparé : lit les propriétés d'un fichier sans le modifier ni produire de fichier de sortie. Pas d'onglet Type de fichier — la catégorie est déduite de l'extension du fichier déposé (categoryOfExt). Volontairement léger : la durée/résolution audio/vidéo vient des métadonnées natives du navigateur (éléments <audio>/<video>, événement loadedmetadata), pas d'un décodage complet via ffmpeg.wasm — pas de moteur à charger, résultat quasi instantané.

  • Images : dimensions, ratio. Pour un SVG, les attributs width/height/viewBox déclarés dans le fichier plutôt qu'un rendu (pas de rasterisation juste pour inspecter).
  • Audio/Vidéo : durée, résolution (vidéo), débit moyen estimé (taille du fichier / durée).
  • Données : nombre de feuilles, lignes et colonnes de la première feuille, en-têtes.
  • Sous-titres : nombre de sous-titres, premier/dernier timecode, plage couverte.

Code : public/inspect.js.

Comparateur

Seul mode à deux entrées (fichier A / fichier B) au lieu d'une seule — deux dropzones côte à côte remplacent la dropzone unique. Les deux fichiers doivent être de la même catégorie (déduite de leur extension), sinon une erreur explicite s'affiche.

  • Images : diff pixel par pixel sur la zone commune (les deux images redimensionnées aux plus petites dimensions communes si elles diffèrent). Pixels identiques affichés en gris atténué, pixels qui changent (écart R+G+B > 30, pour ignorer le bruit de compression JPEG) en rouge. Résultat : pourcentage de pixels identiques + image de diff téléchargeable.
  • Données/Sous-titres : diff ligne à ligne par plus longue sous-séquence commune (LCS), la méthode standard derrière git diff — coût O(n×m) en temps et en mémoire, donc repli automatique sur une comparaison d'empreinte SHA-256 au-delà de 3000 lignes plutôt que de figer l'onglet.
  • Reste (audio, vidéo, XLSX...) : comparaison par empreinte SHA-256 uniquement via crypto.subtle — identique ou différent, sans détail sur ce qui change.

Code : public/compare.js.

Historique local

Les 5 derniers fichiers convertis/compressés (nom, taille, bouton "Télécharger") restent accessibles sous le résultat, stockés dans IndexedDB (jamais envoyé nulle part, cohérent avec le reste d'Anyform) — un bouton "Vider" efface tout. Un ajout au-delà de 5 fait sortir le plus ancien. Code : public/history.js.

Notifications natives

Les conversions audio/vidéo peuvent prendre du temps ; si l'onglet n'est plus visible à la fin, l'API Notification du navigateur prévient plutôt que de compter sur l'utilisateur pour revenir vérifier. La permission est demandée au premier clic sur "Convertir" pour une catégorie audio/vidéo (geste utilisateur requis par les navigateurs), pas au chargement de la page. Silencieux si la permission est refusée ou déjà utilisée (l'onglet est visible).

Déploiement

Le workflow .github/workflows/deploy-pages.yml déploie automatiquement public/ à chaque push sur web-converter (pas besoin de merger vers main, qui n'est qu'une page d'accueil). Il applique aussi un cache-busting (?v=<sha>) sur les scripts/styles pour que les navigateurs ne servent jamais une version périmée.

Prérequis côté dépôt (déjà fait, à titre de référence) :

  • Settings → Pages → source "GitHub Actions"
  • Settings → Environments → github-pages → Deployment branches → autoriser web-converter (sinon le déploiement échoue silencieusement avec une erreur de protection d'environnement)

Un second workflow (bump-version.yml) incrémente automatiquement VERSION à chaque push.

Développement local

Pas de build nécessaire :

npx serve public

Structure du code

  • public/app.js — interface (mode, onglets, formats, glisser-déposer, progression, résultat, notifications, historique)
  • public/convert.js — conversion d'images (Canvas API + ImageTracer.js + heic2any + UTIF.js)
  • public/compress.js — compression d'images/audio/vidéos (même format en sortie)
  • public/data.js — conversion de données (CSV/JSON/XLSX)
  • public/subtitles.js — conversion de sous-titres SRT/VTT/ASS (texte pur)
  • public/history.js — historique local des 5 derniers fichiers (IndexedDB)
  • public/inspect.js — lecture des propriétés d'un fichier, sans le modifier
  • public/compare.js — diff entre deux fichiers (image, texte ligne à ligne, ou empreinte)
  • public/ffmpeg-engine.js — chargement partagé du moteur ffmpeg.wasm
  • public/audio.js / public/video.js — conversion audio/vidéo
  • public/privacy.html — politique de confidentialité
  • public/vendor/ — librairies vendorisées

Pièges connus (déjà corrigés, pour référence)

  • [hidden] vs display: flex : une règle CSS .preview-row { display: flex } a priorité sur l'attribut hidden natif — nécessite .preview-row[hidden] { display: none } explicite.
  • <select> désynchronisé au premier chargement mobile : peupler les options via JS avant le premier paint pouvait laisser le picker natif incohérent. Fix : options statiques dans le HTML, JS seulement pour les changements déclenchés par l'utilisateur.
  • canvas.toBlob() fallback silencieux : pour un format non supporté par le navigateur (ex. AVIF), certains navigateurs renvoient un PNG avec le mime-type demandé au lieu d'échouer. Détecté en comparant blob.type au mime attendu.
  • Encore [hidden] vs display: grid : même piège que .preview-row, cette fois sur #categoryTabs (.tabs { display: grid } neutralisait son attribut hidden en mode Inspecteur/Comparateur). Fix : masquer le <div class="field"> englobant (#categoryField, qui a déjà la règle .field[hidden] { display: none }) plutôt que #categoryTabs lui-même.

Clone this wiki locally