hachure 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. hachure-0.3.0/LICENSE +21 -0
  2. hachure-0.3.0/PKG-INFO +572 -0
  3. hachure-0.3.0/README.md +545 -0
  4. hachure-0.3.0/hachure/__init__.py +5 -0
  5. hachure-0.3.0/hachure/__main__.py +5 -0
  6. hachure-0.3.0/hachure/charsets.py +194 -0
  7. hachure-0.3.0/hachure/cli.py +697 -0
  8. hachure-0.3.0/hachure/color.py +161 -0
  9. hachure-0.3.0/hachure/edges.py +117 -0
  10. hachure-0.3.0/hachure/export.py +304 -0
  11. hachure-0.3.0/hachure/media/__init__.py +1 -0
  12. hachure-0.3.0/hachure/media/image.py +91 -0
  13. hachure-0.3.0/hachure/media/video.py +609 -0
  14. hachure-0.3.0/hachure/menu.py +952 -0
  15. hachure-0.3.0/hachure/render.py +311 -0
  16. hachure-0.3.0/hachure/renderers/__init__.py +38 -0
  17. hachure-0.3.0/hachure/renderers/blackhole.py +68 -0
  18. hachure-0.3.0/hachure/renderers/common.py +12 -0
  19. hachure-0.3.0/hachure/renderers/cube.py +129 -0
  20. hachure-0.3.0/hachure/renderers/donut.py +65 -0
  21. hachure-0.3.0/hachure/renderers/planet.py +48 -0
  22. hachure-0.3.0/hachure/renderers/sphere.py +34 -0
  23. hachure-0.3.0/hachure/terminal.py +286 -0
  24. hachure-0.3.0/hachure/tone.py +93 -0
  25. hachure-0.3.0/hachure.egg-info/PKG-INFO +572 -0
  26. hachure-0.3.0/hachure.egg-info/SOURCES.txt +41 -0
  27. hachure-0.3.0/hachure.egg-info/dependency_links.txt +1 -0
  28. hachure-0.3.0/hachure.egg-info/entry_points.txt +2 -0
  29. hachure-0.3.0/hachure.egg-info/requires.txt +5 -0
  30. hachure-0.3.0/hachure.egg-info/top_level.txt +1 -0
  31. hachure-0.3.0/pyproject.toml +52 -0
  32. hachure-0.3.0/setup.cfg +4 -0
  33. hachure-0.3.0/tests/test_charsets.py +95 -0
  34. hachure-0.3.0/tests/test_cli.py +71 -0
  35. hachure-0.3.0/tests/test_edges.py +101 -0
  36. hachure-0.3.0/tests/test_export.py +59 -0
  37. hachure-0.3.0/tests/test_image.py +41 -0
  38. hachure-0.3.0/tests/test_menu.py +624 -0
  39. hachure-0.3.0/tests/test_render.py +214 -0
  40. hachure-0.3.0/tests/test_renderers.py +23 -0
  41. hachure-0.3.0/tests/test_terminal.py +147 -0
  42. hachure-0.3.0/tests/test_tone.py +81 -0
  43. hachure-0.3.0/tests/test_video.py +185 -0
hachure-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Niladri Pal and Talal Alqahs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hachure-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,572 @@
1
+ Metadata-Version: 2.4
2
+ Name: hachure
3
+ Version: 0.3.0
4
+ Summary: Rend images, vidéos, caméras et art 3D procédural en ASCII dans un terminal
5
+ Author: Josué AGBETA
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Josueagbeta0/hachure-ascii
8
+ Project-URL: Repository, https://github.com/Josueagbeta0/hachure-ascii
9
+ Project-URL: Issues, https://github.com/Josueagbeta0/hachure-ascii/issues
10
+ Keywords: ascii-art,terminal,video,camera,graphics,ansi,truecolor,tui
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Multimedia :: Graphics
17
+ Classifier: Topic :: Multimedia :: Video
18
+ Classifier: Natural Language :: French
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy>=1.24
23
+ Requires-Dist: Pillow>=10.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # hachure
29
+
30
+ Rendre des images, des vidéos, une caméra en direct et des scènes 3D procédurales **en caractères, dans un terminal**.
31
+
32
+ La *hachure* est la technique qui rend le ton et la forme par des traits directionnels. C'est
33
+ littéralement ce que fait ce moteur : une rampe de luminosité pour le ton, et un tenseur de
34
+ structure qui choisit `-`, `\`, `|` ou `/` par cellule pour la forme.
35
+
36
+ Tape `hachure`, sans rien d'autre, et navigue au clavier. Aucun argument à retenir, aucun chemin à
37
+ taper : un menu, un navigateur de fichiers, des réglages qui se parcourent.
38
+
39
+ ```text
40
+ pixels source ou géométrie 3D
41
+
42
+ luminosité / éclairage
43
+
44
+ niveaux automatiques et gamma
45
+
46
+ orientation du contour par cellule
47
+
48
+ correspondance caractère ou bloc
49
+
50
+ couleur ANSI facultative
51
+
52
+ terminal
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Sommaire
58
+
59
+ [Le menu](#le-menu) · [Installation](#installation) · [En ligne de commande](#en-ligne-de-commande) ·
60
+ [Le mode caractère](#tirer-le-meilleur-du-mode-caractère) · [Image](#rendu-dimage) ·
61
+ [Vidéo](#rendu-vidéo) · [Caméra](#capture-caméra) · [Enregistrement](#enregistrement) ·
62
+ [Démos](#démos-procédurales) · [Couleurs](#couleurs) · [Performance](#notes-de-performance) ·
63
+ [Développement](#développement) · [Crédits](#crédits)
64
+
65
+ ---
66
+
67
+ ## Le menu
68
+
69
+ Une seule commande, sans argument :
70
+
71
+ ```powershell
72
+ hachure
73
+ ```
74
+
75
+ ```text
76
+ hachure · rendu d'images, de vidéos et de 3D en caractères
77
+
78
+ Image fixe convertir une photo en caractères
79
+ › Vidéo lire un fichier vidéo
80
+ Caméra diffuser une caméra en direct
81
+ Démo procédurale cube, sphère, tore, planète, trou noir
82
+ Diagnostic dépendances et capacités du terminal
83
+ Moteurs disponibles ce que le projet sait rendre
84
+ Calibrer une rampe mesurer une police à chasse fixe
85
+ Quitter
86
+
87
+ ↑↓ déplacer · Entrée valider · Échap revenir
88
+ ```
89
+
90
+ **Rien ne se tape.** Choisis une source et un navigateur de fichiers s'ouvre, filtré sur les formats
91
+ que la commande sait lire, avec la taille de chaque fichier :
92
+
93
+ ```text
94
+ Quelle vidéo ?
95
+ C:\Users\moi\Videos
96
+
97
+ › .. dossier parent
98
+ archives/ dossier
99
+ vacances/ dossier
100
+ concert.mp4 84.2 Mo
101
+ timelapse.mkv 12.7 Mo
102
+ [ changer de disque ]
103
+ [ annuler ]
104
+ ```
105
+
106
+ Puis un écran de réglages, où chaque ligne se change sans jamais saisir de valeur — `Espace` ou
107
+ ←→ pour passer à la suivante, `Entrée` pour ouvrir la liste complète :
108
+
109
+ ```text
110
+ Réglages · video
111
+
112
+ Largeur maximale 160
113
+ Hauteur maximale défaut
114
+ Ajustement cover
115
+ Couleur oui
116
+ › Contours (--edges) oui
117
+ Niveaux automatiques oui
118
+ Jeu de caractères detailed
119
+ Géométrie de cellule char
120
+ Inverser la rampe non
121
+ Images par seconde 24
122
+ Couper le son non
123
+ Lire en boucle non
124
+ Départ en secondes défaut
125
+ Durée en secondes 30
126
+ Enregistrer le rendu concert.mp4
127
+
128
+ Lancer le rendu
129
+ Annuler
130
+
131
+ hachure … --width 160 --fit cover --color --edges --auto-levels --charset detailed --fps 24 …
132
+ ↑↓ déplacer · Espace/←→ changer · Entrée ouvrir · Échap revenir
133
+ ```
134
+
135
+ La ligne du bas est le point important : **le menu ne fait que composer une ligne de commande**, il
136
+ te la montre, puis il la joue. Tu repars donc en sachant quoi retaper directement :
137
+
138
+ ```text
139
+ $ hachure video concert.mp4 --width 160 --fit cover --color --edges --auto-levels --charset detailed
140
+ ```
141
+
142
+ Un réglage laissé sur `défaut` n'est pas transmis : c'est la valeur par défaut de la CLI qui
143
+ s'applique, jamais une copie figée dans le menu.
144
+
145
+ `hachure menu` ouvre le même écran explicitement. Quand l'entrée ou la sortie est redirigée — un
146
+ pipe, un script, Git Bash, où Python ne reconnaît pas le terminal — la navigation aux flèches
147
+ devient impossible : le menu retombe alors sur des listes numérotées, et toutes les sous-commandes
148
+ ci-dessous restent utilisables directement.
149
+
150
+ ---
151
+
152
+ ## Installation
153
+
154
+ ### Prérequis
155
+
156
+ | Composant | Rôle |
157
+ | --- | --- |
158
+ | **Python ≥ 3.10** | La CI valide 3.10 et 3.14. NumPy et Pillow s'installent automatiquement. |
159
+ | **ffmpeg** | Décode la vidéo et la caméra en images brutes. Requis par `video` et `camera` seulement. |
160
+ | **ffprobe** | Lit dimensions et rotation de la source, pour redresser une vidéo filmée verticalement. |
161
+ | **ffplay** | Lit la piste audio en parallèle. Facultatif : `--no-audio` s'en passe. |
162
+ | **Police à chasse fixe** | Doit contenir `▀` et `▄` pour le mode demi-bloc. Cascadia Mono, Consolas, DejaVu Sans Mono, Menlo. |
163
+ | **Terminal truecolor** | Windows Terminal, VS Code ou tout émulateur moderne, pour `--color` en 24 bits. |
164
+
165
+ FFmpeg livre les trois binaires ensemble. **Rouvre le terminal après l'installation**, pour que le
166
+ `PATH` soit rechargé :
167
+
168
+ ```powershell
169
+ winget install Gyan.FFmpeg # Windows
170
+ brew install ffmpeg # macOS
171
+ sudo apt install ffmpeg # Debian / Ubuntu
172
+ ```
173
+
174
+ ### Depuis les sources
175
+
176
+ ```powershell
177
+ git clone https://github.com/Josueagbeta0/hachure-ascii.git
178
+ cd hachure-ascii
179
+ py -m venv .venv
180
+ .venv\Scripts\Activate.ps1
181
+ python -m pip install -e ".[dev]"
182
+ ```
183
+
184
+ L'extra `dev` ajoute pytest. `-e` installe en mode éditable : le paquet pointe vers les sources, une
185
+ modification est prise en compte sans réinstaller.
186
+
187
+ ### Pour que `hachure` réponde partout
188
+
189
+ Installé dans un environnement virtuel, `hachure` n'existe que quand cet environnement est activé.
190
+ Pour l'avoir dans n'importe quel terminal, installe-le dans ton Python principal, dont le dossier
191
+ `Scripts` est déjà dans le `PATH` :
192
+
193
+ ```powershell
194
+ py -m pip install -e "C:\chemin\vers\hachure-ascii"
195
+ ```
196
+
197
+ Si la commande reste introuvable, la forme module fonctionne toujours :
198
+
199
+ ```powershell
200
+ python -m hachure
201
+ ```
202
+
203
+ ### Vérifier
204
+
205
+ ```powershell
206
+ hachure doctor
207
+ ```
208
+
209
+ ```text
210
+ hachure 0.3.0
211
+ python 3.11.9 (C:\...\Python311\python.exe)
212
+ numpy 1.26.4
213
+ PIL 12.1.1
214
+ ffmpeg ffmpeg version 9.0.1-full_build
215
+ ffplay ffplay version 9.0.1-full_build
216
+ ffprobe ffprobe version 9.0.1-full_build
217
+ terminal 120 x 40 cellules
218
+ profondeur truecolor
219
+ rapport cell. 0.5
220
+ tty stdout True
221
+ codec stdout utf-8
222
+ ```
223
+
224
+ Les cinq dernières lignes décrivent le terminal, pas le projet, et expliquent la plupart des
225
+ surprises de rendu :
226
+
227
+ | Ligne | Ce qu'elle contrôle |
228
+ | --- | --- |
229
+ | `terminal` | La grille disponible. `--width` est un plafond, pas une cible : il y est ramené. |
230
+ | `profondeur` | `truecolor` = 24 bits. Si c'est `none`, `--color` ne produira rien de visible. |
231
+ | `rapport cell.` | Largeur d'une cellule divisée par sa hauteur. Monte vers `0.6` si l'image paraît étirée. |
232
+ | `codec stdout` | Doit être `utf-8` pour les glyphes demi-bloc. |
233
+
234
+ `tty stdout` et `profondeur` changent selon que la sortie va vers un vrai terminal ou vers un
235
+ fichier. Redirigée, la détection renvoie `False` et `none` : c'est attendu, pas une panne.
236
+
237
+ Un guide pas à pas — prérequis, vérification, premiers rendus, dépannage — est dans
238
+ [`docs/INSTALLATION.md`](docs/INSTALLATION.md), avec une version mise en page dans
239
+ [`docs/installation.html`](docs/installation.html).
240
+
241
+ ---
242
+
243
+ ## En ligne de commande
244
+
245
+ ```powershell
246
+ hachure # le menu interactif
247
+ hachure list # tous les moteurs disponibles
248
+ hachure doctor # diagnostic
249
+
250
+ hachure image photo.jpg --width 100 --color --edges --auto-levels
251
+ hachure video clip.mp4 --color --charset detailed --edges --auto-levels --fit cover --width 1000
252
+ hachure camera --color --edges
253
+ hachure demo blackhole
254
+ ```
255
+
256
+ `Ctrl+C` arrête proprement toute animation, vidéo ou caméra : le curseur et les couleurs du terminal
257
+ sont restaurés dans tous les cas, y compris après une erreur.
258
+
259
+ ---
260
+
261
+ ## Tirer le meilleur du mode caractère
262
+
263
+ Le mode caractère est celui par défaut, et c'est tout l'intérêt du projet : l'image est faite de
264
+ caractères, et c'est ce qui produit l'effet. Trois réglages l'affinent sans jamais sortir de cette
265
+ contrainte.
266
+
267
+ ### `--edges` — la plus grosse amélioration à elle seule
268
+
269
+ La luminosité seule jette la forme. Avec `--edges`, la source est échantillonnée au-dessus de la
270
+ résolution des cellules, l'orientation de contour dominante dans chaque cellule est mesurée par un
271
+ tenseur de structure, et un glyphe de ligne adapté remplace le caractère de luminosité :
272
+
273
+ | Direction du contour | Glyphe |
274
+ | --- | --- |
275
+ | horizontale | `-` |
276
+ | diagonale descendante | `\` |
277
+ | verticale | `\|` |
278
+ | diagonale montante | `/` |
279
+
280
+ Silhouettes, visages et contours sortent du bruit. Seules les cellules portant un contour fort *et*
281
+ cohérent sont remplacées : les zones plates gardent leur ton. `--edge-strength` (de 0 à 1, défaut
282
+ `0.5`) fixe la force requise — baisse-la pour plus de traits, monte-la pour moins.
283
+
284
+ Le tenseur de structure plutôt qu'un gradient moyenné, parce que des gradients opposés le long d'un
285
+ même contour s'annulent à la moyenne : exactement ce qui arrive dans une cellule à cheval sur une
286
+ ligne fine.
287
+
288
+ ### `--auto-levels` et `--gamma`
289
+
290
+ La luminosité est projetée linéairement sur la rampe : une scène sombre n'atteint donc jamais les
291
+ caractères denses, ni une scène claire les caractères clairsemés. `--auto-levels` étire chaque image
292
+ sur toute la rampe, mesurée aux 2ᵉ et 98ᵉ percentiles pour que quelques pixels isolés ne définissent
293
+ pas la plage. Les niveaux glissent d'une image à l'autre, si bien que la lecture ne pulse pas quand
294
+ un élément lumineux traverse le plan.
295
+
296
+ `--gamma` remodèle les tons moyens par-dessus : au-dessus de `1` ça éclaircit, en dessous ça
297
+ assombrit. Sur une source en couleur, les deux s'appliquent comme un gain sur les trois canaux, ce
298
+ qui préserve la teinte.
299
+
300
+ ### `--charset smooth`
301
+
302
+ Les rampes intégrées sont ordonnées à l'œil. `smooth` est **mesurée** : chaque glyphe ASCII
303
+ imprimable a été rendu puis noté sur sa couverture d'encre et sur la régularité de répartition de
304
+ cette encre, avant que la rampe ne soit choisie de façon à ce que ses pas soient régulièrement
305
+ espacés en couverture réelle. Les dégradés progressent proprement au lieu de vaciller entre des
306
+ caractères sosies. Les quatre glyphes de ligne sont volontairement exclus, pour rester sans
307
+ ambiguïté quand `--edges` est actif.
308
+
309
+ En calibrer une pour ta propre police :
310
+
311
+ ```powershell
312
+ hachure calibrate --font "C:\Windows\Fonts\CascadiaMono.ttf" --length 12
313
+ ```
314
+
315
+ ### Tout mettre ensemble
316
+
317
+ ```powershell
318
+ hachure video clip.mp4 --color --charset detailed --edges --auto-levels --width 1000
319
+ ```
320
+
321
+ ### Cellules demi-bloc
322
+
323
+ `--cells half`, ou son raccourci `--half`, tasse deux pixels empilés dans chaque cellule grâce au
324
+ demi-bloc supérieur : le premier plan peint le pixel du haut, l'arrière-plan celui du bas. Le
325
+ résultat approche la photographie, ce qui veut aussi dire qu'il cesse de ressembler à des
326
+ caractères — à utiliser quand la fidélité compte plus que l'effet. `--edges` ne s'applique pas,
327
+ puisque le glyphe est toujours le même.
328
+
329
+ ### Remplir l'écran
330
+
331
+ Le rendu préserve le rapport d'image de la source : un plan en 16:9 réclame environ 3,5 colonnes par
332
+ ligne. Si le terminal a moins de lignes que ce rapport ne l'exige, la largeur est réduite et des
333
+ colonnes restent vides. Trois éléments pilotent cela :
334
+
335
+ - **`--width`** est un maximum, pas une cible. Il vaut `160` par défaut : sur un terminal large il
336
+ faut le monter, et `--width 1000` est ramené sans risque à la largeur réelle.
337
+ - **`--fit cover`** recadre la source à la forme du terminal au lieu de l'encadrer de bandes. C'est
338
+ ce qui rend exploitables les vidéos verticales de téléphone.
339
+ - **`--char-aspect`** indique la largeur d'une cellule par rapport à sa hauteur. `0.5` convient à la
340
+ plupart des polices ; monte vers `0.6` si l'image paraît étirée verticalement. La variable
341
+ d'environnement `HACHURE_CHAR_ASPECT` le règle globalement.
342
+
343
+ Redimensionner la fenêtre en cours de lecture est pris en charge : la grille est recalculée et le
344
+ flux reprend à la position courante.
345
+
346
+ ---
347
+
348
+ ## Rendu d'image
349
+
350
+ ```powershell
351
+ hachure image IMAGE [options]
352
+
353
+ hachure image photo.png --width 120
354
+ hachure image photo.png --width 120 --output rendu.txt
355
+ hachure image photo.png --width 1000 --fit cover --half --color
356
+ ```
357
+
358
+ | Option | Rôle |
359
+ | --- | --- |
360
+ | `--width N` | Largeur de sortie maximale. Défaut : `100`. |
361
+ | `--height N` | Hauteur de sortie maximale, facultative. |
362
+ | `--fit MODE` | `contain` (bandes) ou `cover` (recadre pour remplir). Défaut : `contain`. |
363
+ | `--char-aspect N` | Largeur d'une cellule divisée par sa hauteur. Défaut : `0.5`. |
364
+ | `--cells MODE` | `char` ou `half`. Défaut : `char`. |
365
+ | `--half` | Raccourci de `--cells half`. |
366
+ | `--color` / `--no-color` | Active ou désactive la couleur. Les images sont monochromes par défaut. |
367
+ | `--color-depth NOM` | `auto`, `truecolor`, `ansi256` ou `none`. |
368
+ | `--quant N` | Pas de quantification des couleurs. Défaut : `4`. |
369
+ | `--edges` | Remplace le caractère de rampe par un glyphe de ligne sur les vrais contours. |
370
+ | `--edge-strength N` | Force requise d'un contour, de `0` à `1`. Défaut : `0.5`. |
371
+ | `--auto-levels` | Étire l'image sur toute la rampe. |
372
+ | `--gamma N` | Tons moyens ; au-dessus de `1` ça éclaircit. Défaut : `1.0`. |
373
+ | `--charset NOM` | `classic`, `detailed`, `letters` ou `smooth`. |
374
+ | `--invert` | Retourne la rampe sombre-vers-clair. |
375
+ | `-o`, `--output CHEMIN` | Écrit le texte rendu dans un fichier UTF-8. |
376
+
377
+ ---
378
+
379
+ ## Rendu vidéo
380
+
381
+ ```powershell
382
+ hachure video VIDEO [options]
383
+
384
+ hachure video clip.mp4 --no-audio
385
+ hachure video clip.mp4 --color --charset detailed --edges --auto-levels --width 1000
386
+ hachure video clip.mp4 --start 30 --duration 10 --loop
387
+ hachure video clip.mp4 --color --smoothing 0.35
388
+ hachure video clip.mp4 --color --half --record renders\clip.mp4
389
+ ```
390
+
391
+ | Option | Rôle |
392
+ | --- | --- |
393
+ | `--fps N` | Cadence de lecture visée. Défaut : `20`. |
394
+ | `--width N` / `--height N` | Taille de rendu maximale en caractères. Largeur : `160` par défaut. |
395
+ | `--fit MODE` | `contain` ou `cover`. Défaut : `contain`. |
396
+ | `--cells MODE` / `--half` | Géométrie de cellule, comme ci-dessus. |
397
+ | `--color` / `--no-color` | Sortie en couleur. La vidéo est monochrome par défaut. |
398
+ | `--color-depth NOM` | Court-circuite la détection de couleur du terminal. |
399
+ | `--quant N` | Pas de quantification des couleurs. Défaut : `4`. |
400
+ | `--edges`, `--edge-strength N` | Glyphes de contour, comme ci-dessus. |
401
+ | `--auto-levels`, `--gamma N` | Mise en forme tonale, comme ci-dessus. |
402
+ | `--smoothing N` | Fondu temporel de `0` à `1` ; `1` est net, plus bas laisse des traînées. |
403
+ | `--max-frame-skip N` | Images consécutives abandonnables pour rattraper le retard. Défaut : `5`. |
404
+ | `--start N` | Position de départ en secondes. |
405
+ | `--duration N` | Arrête après ce nombre de secondes. |
406
+ | `--loop` | Redémarre quand la vidéo se termine. |
407
+ | `--no-audio` | Ne lance pas FFplay. |
408
+ | `--audio-delay N` | Décale l'audio de -30 à +30 s ; une valeur positive le retarde. |
409
+ | `--record CHEMIN` | Écrit la sortie rendue en `.mp4` ou `.gif`. |
410
+ | `--charset NOM`, `--invert` | Rampe de caractères, comme ci-dessus. |
411
+
412
+ Les métadonnées de rotation sont respectées : un plan filmé en portrait au téléphone est mesuré et
413
+ rendu droit, plutôt qu'écrasé.
414
+
415
+ ```text
416
+ ┌─ FFmpeg → images brutes mises à l'échelle → NumPy → cellules → terminal
417
+ vidéo source ──────────┤
418
+ └─ FFplay → audio
419
+ ```
420
+
421
+ Le moteur suit un calendrier à l'horloge murale. Quand le rendu prend du retard, il abandonne un
422
+ nombre borné d'images décodées au lieu de laisser la dérive s'installer.
423
+
424
+ ---
425
+
426
+ ## Capture caméra
427
+
428
+ ```powershell
429
+ hachure camera --list
430
+ hachure camera --color --half
431
+ hachure camera --device "Integrated Webcam" --size 1280x720 --duration 10 --record me.gif
432
+ ```
433
+
434
+ L'entrée caméra passe par DirectShow sous Windows, AVFoundation sous macOS et Video4Linux2 sous
435
+ Linux. L'audio n'est jamais capturé. Si une caméra listée refuse de s'ouvrir sous Windows, autorise
436
+ les applications de bureau dans *Paramètres → Confidentialité et sécurité → Caméra*, et ferme tout
437
+ ce qui l'utilise déjà.
438
+
439
+ ---
440
+
441
+ ## Enregistrement
442
+
443
+ `--record CHEMIN` peint chaque image rendue avec une police à chasse fixe et redirige le résultat
444
+ vers FFmpeg. Les séquences d'échappement sont relues au cours de cette passe : les enregistrements
445
+ gardent donc leurs couleurs.
446
+
447
+ | Option | Rôle |
448
+ | --- | --- |
449
+ | `--record CHEMIN` | Destination ; `.gif` produit un GIF animé, tout le reste une vidéo H.264. |
450
+ | `--font CHEMIN` | `.ttf` à chasse fixe pour peindre. Une police système est trouvée automatiquement. |
451
+ | `--font-size N` | Corps de la police, qui fixe la résolution de sortie. Défaut : `16`. |
452
+
453
+ Disponible pour `video`, `camera` et `demo`.
454
+
455
+ ---
456
+
457
+ ## Démos procédurales
458
+
459
+ Cinq scènes calculées en temps réel, sans source externe : projection, éclairage et tampon de
460
+ profondeur écrits à la main.
461
+
462
+ ```powershell
463
+ hachure demo NOM [options]
464
+
465
+ hachure demo donut --fps 30
466
+ hachure demo planet --width 120 --charset detailed
467
+ hachure demo blackhole --width 140 --record blackhole.gif
468
+ ```
469
+
470
+ | Démo | Technique |
471
+ | --- | --- |
472
+ | `cube` | Rotation des sommets, projection en perspective, normales de face, élimination des faces arrière, remplissage de triangles, tampon de profondeur interpolé. |
473
+ | `sphere` | Reconstruction de la sphère cellule par cellule et éclairage directionnel. |
474
+ | `donut` | Échantillonnage d'un tore paramétrique, éclairage par les normales, perspective, tampon de profondeur. |
475
+ | `planet` | Sphère en rotation, relief procédural, face nocturne, halo atmosphérique. |
476
+ | `blackhole` | Disque d'accrétion en coordonnées polaires, étoiles déterministes, lueur asymétrique, effet d'anneau de photons. |
477
+
478
+ Toutes acceptent `--width`, `--height`, `--fps`, `--charset`, `--invert` et les options
479
+ d'enregistrement. Les dimensions sont réduites au besoin pour tenir dans le terminal, et la scène se
480
+ recompose quand la fenêtre est redimensionnée.
481
+
482
+ ---
483
+
484
+ ## Couleurs
485
+
486
+ La profondeur de couleur est déduite de `COLORTERM`, de `TERM` et du terminal hôte, et `NO_COLOR`
487
+ est respecté. `--color-depth` court-circuite le résultat :
488
+
489
+ - `truecolor` — premier plan et arrière-plan sur 24 bits. Windows Terminal, VS Code, la plupart des
490
+ émulateurs modernes.
491
+ - `ansi256` — la palette xterm-256, pour les terminaux plus anciens.
492
+ - `none` — monochrome.
493
+
494
+ `--quant` aligne les valeurs de canal sur un pas avant leur émission. Des valeurs plus grandes
495
+ produisent de plus longues plages de couleur identique, donc moins de séquences d'échappement — ce
496
+ qui compte sur un terminal lent.
497
+
498
+ ---
499
+
500
+ ## Notes de performance
501
+
502
+ - Les séquences d'échappement sont construites depuis des préfixes en cache et émises **par plage**
503
+ plutôt que par cellule, et seules les lignes réellement modifiées sont repeintes.
504
+ - Monter `--quant`, baisser `--fps` ou baisser `--width` sont les leviers efficaces quand la lecture
505
+ saccade, dans cet ordre.
506
+ - Les demi-cellules doublent le nombre de pixels : elles coûtent environ deux fois plus par image
507
+ que les cellules `char`, à grille égale.
508
+ - `--edges` échantillonne trois pixels par côté de cellule, donc FFmpeg décode neuf fois plus de
509
+ pixels. Sur une grille de terminal normale cela reste faible, mais c'est la seule option qui
510
+ augmente le coût de **décodage** et pas seulement celui du rendu.
511
+
512
+ ---
513
+
514
+ ## Développement
515
+
516
+ ```powershell
517
+ python -m unittest discover -s tests -v # suite complète, comme la CI
518
+ python -m pytest # équivalent
519
+ python -m pytest tests/test_render.py -k half
520
+ python -m unittest tests.test_render.RenduMonochromeTests
521
+ ```
522
+
523
+ 197 tests, moins d'une seconde. Aucun n'ouvre de vrai terminal ni ne lit de vrai fichier média : les
524
+ appels FFmpeg sont simulés, les touches du menu et les saisies clavier le sont aussi, et les rendus
525
+ sont comparés sur de petits tableaux NumPy construits à la main.
526
+
527
+ Ni linter ni formateur configuré. La CI lance la suite sur Python 3.10 et 3.14, puis vérifie que
528
+ `hachure --version` et `hachure list` répondent.
529
+
530
+ **Le code, les commentaires, la documentation et les noms de tests sont en français.** Deux
531
+ exceptions, marquées par un commentaire à leur emplacement : les sous-chaînes comparées à la sortie
532
+ de FFmpeg, et les messages internes d'argparse et d'unittest, qui passent par gettext sans catalogue
533
+ français dans CPython.
534
+
535
+ ### Architecture
536
+
537
+ Tout converge vers un point de conversion unique. Deux familles de sources — pixels décodés,
538
+ géométrie procédurale — et trois sorties — terminal, fichier texte, vidéo enregistrée — se
539
+ rejoignent dans `render.py`.
540
+
541
+ ```text
542
+ media/image.py (Pillow) ─┐
543
+ media/video.py (FFmpeg) ─┼→ tableau NumPy → tone.py → edges.py → render.py → texte ANSI ─┬→ terminal.py
544
+ renderers/*.py ──────────┘ (les démos émettent du texte directement) color.py └→ export.py
545
+ ```
546
+
547
+ | Module | Rôle |
548
+ | --- | --- |
549
+ | `render.py` | Le pivot. `RenderStyle` porte rampe, mode de cellule, profondeur de couleur, quantification et contours. Ses `pixel_cols()`/`pixel_rows()` disent au décodeur *en amont* combien de pixels réclame une grille de caractères. La sortie est bâtie en plages compressées par ligne, jamais cellule par cellule — et une plage ne franchit jamais une frontière de ligne, pour que chaque ligne reste redessinable seule. |
550
+ | `edges.py` | Suréchantillonne 3× par côté de cellule et choisit `-`, `\`, `\|` ou `/` via un tenseur de structure. Les glyphes de contour sont rangés après la fin de la rampe, si bien que l'aval indexe un alphabet unique sans cas particulier. |
551
+ | `tone.py` | Niveaux automatiques et gamma. À état entre les images : `reset()` sur un saut ou un redémarrage de boucle. |
552
+ | `color.py` | Mémoïse les préfixes ANSI, indexés par RVB compacté. Détecte la profondeur via `NO_COLOR`, `COLORTERM`, `TERM`. |
553
+ | `terminal.py` | Dimensionnement et boucle d'animation. `Screen.draw()` ne repeint que les lignes modifiées. `terminal_session()` garantit la restauration du curseur et des couleurs. |
554
+ | `media/video.py` | Construit la commande FFmpeg, lit des images brutes de taille fixe, suit un calendrier à l'horloge murale. L'audio est un processus FFplay distinct. |
555
+ | `renderers/` | Démos autonomes enregistrées dans un dictionnaire `DEMOS`. Ajouter une démo = un module plus une entrée. |
556
+ | `export.py` | Réanalyse les codes ANSI reçus et les repeint avec une police à chasse fixe, vers FFmpeg. |
557
+ | `menu.py` | Le menu. Ne duplique aucune option : il compose une liste d'arguments et la passe à `main()`. |
558
+ | `cli.py` | Argparse seulement. Les groupes d'options sont partagés entre sous-commandes. |
559
+
560
+ NumPy et Pillow sont importés paresseusement, et les fonctions manipulant des tableaux reçoivent
561
+ `np` en paramètre explicite plutôt que de l'importer au niveau du module.
562
+
563
+ ---
564
+
565
+ ## Crédits
566
+
567
+ `hachure` dérive de [ASCII-Art](https://github.com/RipperdocNiladri/ASCII-Art), publié sous licence
568
+ MIT par **Niladri Pal** et **Talal Alqahs**. Le rendu, la CLI et le menu ont été réécrits depuis,
569
+ mais la notice de copyright d'origine est conservée dans [`LICENSE`](LICENSE), comme la licence MIT
570
+ l'exige.
571
+
572
+ Distribué sous licence MIT.