@jjlmoya/utils-audiovisual 1.2.0
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.
- package/package.json +60 -0
- package/src/category/i18n/en.ts +198 -0
- package/src/category/i18n/es.ts +198 -0
- package/src/category/i18n/fr.ts +198 -0
- package/src/category/index.ts +17 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +4 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +32 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/chromaticLens/bibliography.astro +17 -0
- package/src/tool/chromaticLens/component.astro +178 -0
- package/src/tool/chromaticLens/i18n/en.ts +246 -0
- package/src/tool/chromaticLens/i18n/es.ts +244 -0
- package/src/tool/chromaticLens/i18n/fr.ts +244 -0
- package/src/tool/chromaticLens/index.ts +43 -0
- package/src/tool/chromaticLens/logic.ts +87 -0
- package/src/tool/chromaticLens/seo.astro +15 -0
- package/src/tool/chromaticLens/style.css +308 -0
- package/src/tool/chromaticLens/ui.ts +109 -0
- package/src/tool/collageMaker/bibliography.astro +17 -0
- package/src/tool/collageMaker/component.astro +302 -0
- package/src/tool/collageMaker/i18n/en.ts +233 -0
- package/src/tool/collageMaker/i18n/es.ts +231 -0
- package/src/tool/collageMaker/i18n/fr.ts +231 -0
- package/src/tool/collageMaker/index.ts +51 -0
- package/src/tool/collageMaker/logic.ts +134 -0
- package/src/tool/collageMaker/seo.astro +15 -0
- package/src/tool/collageMaker/style.css +386 -0
- package/src/tool/exifCleaner/bibliography.astro +18 -0
- package/src/tool/exifCleaner/component.astro +162 -0
- package/src/tool/exifCleaner/i18n/en.ts +277 -0
- package/src/tool/exifCleaner/i18n/es.ts +277 -0
- package/src/tool/exifCleaner/i18n/fr.ts +277 -0
- package/src/tool/exifCleaner/index.ts +57 -0
- package/src/tool/exifCleaner/logic.ts +135 -0
- package/src/tool/exifCleaner/seo.astro +18 -0
- package/src/tool/exifCleaner/style.css +289 -0
- package/src/tool/exifCleaner/ui.ts +117 -0
- package/src/tool/imageCompressor/bibliography.astro +17 -0
- package/src/tool/imageCompressor/component.astro +262 -0
- package/src/tool/imageCompressor/i18n/en.ts +232 -0
- package/src/tool/imageCompressor/i18n/es.ts +230 -0
- package/src/tool/imageCompressor/i18n/fr.ts +230 -0
- package/src/tool/imageCompressor/index.ts +50 -0
- package/src/tool/imageCompressor/logic.ts +79 -0
- package/src/tool/imageCompressor/seo.astro +15 -0
- package/src/tool/imageCompressor/style.css +503 -0
- package/src/tool/printQualityCalculator/bibliography.astro +18 -0
- package/src/tool/printQualityCalculator/component.astro +318 -0
- package/src/tool/printQualityCalculator/i18n/en.ts +247 -0
- package/src/tool/printQualityCalculator/i18n/es.ts +245 -0
- package/src/tool/printQualityCalculator/i18n/fr.ts +245 -0
- package/src/tool/printQualityCalculator/index.ts +56 -0
- package/src/tool/printQualityCalculator/logic.ts +53 -0
- package/src/tool/printQualityCalculator/seo.astro +18 -0
- package/src/tool/printQualityCalculator/style.css +491 -0
- package/src/tool/printQualityCalculator/ui.ts +122 -0
- package/src/tool/privacyBlur/bibliography.astro +17 -0
- package/src/tool/privacyBlur/component.astro +230 -0
- package/src/tool/privacyBlur/i18n/en.ts +238 -0
- package/src/tool/privacyBlur/i18n/es.ts +236 -0
- package/src/tool/privacyBlur/i18n/fr.ts +236 -0
- package/src/tool/privacyBlur/index.ts +49 -0
- package/src/tool/privacyBlur/logic.ts +249 -0
- package/src/tool/privacyBlur/seo.astro +15 -0
- package/src/tool/privacyBlur/style.css +332 -0
- package/src/tool/privacyBlur/ui.ts +124 -0
- package/src/tool/subtitleSync/bibliography.astro +17 -0
- package/src/tool/subtitleSync/component.astro +187 -0
- package/src/tool/subtitleSync/i18n/en.ts +241 -0
- package/src/tool/subtitleSync/i18n/es.ts +241 -0
- package/src/tool/subtitleSync/i18n/fr.ts +241 -0
- package/src/tool/subtitleSync/index.ts +49 -0
- package/src/tool/subtitleSync/logic.ts +91 -0
- package/src/tool/subtitleSync/seo.astro +15 -0
- package/src/tool/subtitleSync/style.css +325 -0
- package/src/tool/subtitleSync/ui.ts +152 -0
- package/src/tool/timelapseCalculator/bibliography.astro +15 -0
- package/src/tool/timelapseCalculator/component.astro +148 -0
- package/src/tool/timelapseCalculator/i18n/en.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/es.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/fr.ts +169 -0
- package/src/tool/timelapseCalculator/index.ts +52 -0
- package/src/tool/timelapseCalculator/logic.ts +46 -0
- package/src/tool/timelapseCalculator/seo.astro +18 -0
- package/src/tool/timelapseCalculator/style.css +285 -0
- package/src/tool/tvDistance/bibliography.astro +17 -0
- package/src/tool/tvDistance/component.astro +178 -0
- package/src/tool/tvDistance/i18n/en.ts +223 -0
- package/src/tool/tvDistance/i18n/es.ts +223 -0
- package/src/tool/tvDistance/i18n/fr.ts +223 -0
- package/src/tool/tvDistance/index.ts +49 -0
- package/src/tool/tvDistance/logic.ts +47 -0
- package/src/tool/tvDistance/seo.astro +15 -0
- package/src/tool/tvDistance/style.css +435 -0
- package/src/tool/tvDistance/ui.ts +66 -0
- package/src/tool/videoFrameExtractor/bibliography.astro +17 -0
- package/src/tool/videoFrameExtractor/component.astro +285 -0
- package/src/tool/videoFrameExtractor/i18n/en.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/es.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/fr.ts +235 -0
- package/src/tool/videoFrameExtractor/index.ts +53 -0
- package/src/tool/videoFrameExtractor/logic.ts +49 -0
- package/src/tool/videoFrameExtractor/seo.astro +15 -0
- package/src/tool/videoFrameExtractor/style.css +426 -0
- package/src/tool/videoFrameExtractor/ui.ts +179 -0
- package/src/tools.ts +25 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ExifCleanerUI, ExifCleanerLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'nettoyeur-metadonnees-exif-supprimer-gps-confidentialite-photo';
|
|
5
|
+
const title = 'Nettoyeur de Métadonnées EXIF - Supprimez le GPS et les Données Cachées';
|
|
6
|
+
const description = 'Outil en ligne gratuit pour effacer les métadonnées EXIF, les coordonnées GPS et les caractéristiques de l\'appareil de vos images avant de les partager. 100 % confidentiel : fonctionne sans téléversement.';
|
|
7
|
+
|
|
8
|
+
const ui: ExifCleanerUI = {
|
|
9
|
+
dropTitle: "Faites glisser votre image ici",
|
|
10
|
+
dropSubtitle: "Supprimez les métadonnées GPS, le modèle d'appareil photo et les paramètres cachés.",
|
|
11
|
+
dropLocalInfo: "Traitement 100 % local. Rien n'est téléversé sur le cloud.",
|
|
12
|
+
selectButton: "Sélectionner une Image",
|
|
13
|
+
processingText: "Nettoyage des métadonnées...",
|
|
14
|
+
analysisCompleted: "Analyse Terminée",
|
|
15
|
+
downloadButton: "Télécharger l'Image Nettoyée",
|
|
16
|
+
resetButton: "Nettoyer une autre image",
|
|
17
|
+
privacyRiskTitle: "RISQUES DE CONFIDENTIALITÉ DÉTECTÉS :",
|
|
18
|
+
gpsLabel: "GPS :",
|
|
19
|
+
gpsDetected: "DÉTECTÉ",
|
|
20
|
+
gpsNotFound: "NON TROUVÉ",
|
|
21
|
+
cameraLabel: "APPAREIL :",
|
|
22
|
+
softwareLabel: "LOGICIEL :",
|
|
23
|
+
dateLabel: "DATE :",
|
|
24
|
+
otherTechnicalDetails: "AUTRES DÉTAILS TECHNIQUES",
|
|
25
|
+
noMetadataFound: "Aucune métadonnée EXIF lisible n'a été trouvée.",
|
|
26
|
+
alreadyCleanInfo: "L'image est peut-être déjà nettoyée.",
|
|
27
|
+
previewLabel: "Aperçu",
|
|
28
|
+
faqTitle: "Questions fréquemment posées sur la confidentialité des photos",
|
|
29
|
+
bibliographyTitle: "Ressources et documentation technique EXIF",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const faq: ExifCleanerLocaleContent['faq'] = [
|
|
33
|
+
{
|
|
34
|
+
question: "Qu'est-ce que les métadonnées EXIF ?",
|
|
35
|
+
answer: "L'EXIF (Exchangeable Image File Format) est une information cachée que votre appareil photo ou votre mobile enregistre dans chaque photo. Elle comprend la date exacte, le modèle de l'appareil, les réglages de prise de vue (ISO, ouverture) et, plus critique encore, les coordonnées GPS de l'endroit où la photo a été prise.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
question: "Est-il sûr d'utiliser cet outil en ligne ?",
|
|
39
|
+
answer: "Oui, car le traitement est 100 % local dans votre navigateur. Vos photos ne sont jamais téléversées sur un serveur ; le nettoyage s'effectue directement dans la mémoire de votre appareil, garantissant une confidentialité absolue.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
question: "Quelles données spécifiques le nettoyeur supprime-t-il ?",
|
|
43
|
+
answer: "Il efface toutes les balises EXIF (GPS, marque de l'appareil, numéro de série), IPTC (copyright, auteur) et XMP (historique d'édition). Votre image devient 'propre', ne conservant que les pixels visuels.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
question: "L'image perd-elle de sa qualité lors du nettoyage des métadonnées ?",
|
|
47
|
+
answer: "Non. Nous ne supprimons que l'en-tête de données techniques du fichier. Les données de l'image (pixels) restent identiques, de sorte que la qualité visuelle n'est pas affectée du tout.",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const howTo: ExifCleanerLocaleContent['howTo'] = [
|
|
52
|
+
{
|
|
53
|
+
name: "Sélectionner les images",
|
|
54
|
+
text: "Faites glisser vos photos ou sélectionnez-les depuis votre explorateur de fichiers. Vous pouvez en traiter plusieurs à la fois.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "Analyser les données actuelles",
|
|
58
|
+
text: "L'outil vous montrera quelles informations sensibles il a détectées (ex: 'Coordonnées GPS détectées').",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "Traiter et nettoyer",
|
|
62
|
+
text: "Cliquez sur le bouton de nettoyage pour supprimer instantanément toutes les balises de métadonnées.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Télécharger des photos sécurisées",
|
|
66
|
+
text: "Enregistrez les nouvelles versions de vos images, désormais anonymisées et prêtes à être partagées en toute sécurité sur les réseaux sociaux.",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const bibliography: ExifCleanerLocaleContent['bibliography'] = [
|
|
71
|
+
{
|
|
72
|
+
name: "Exchangeable image file format (EXIF) - Wikipédia",
|
|
73
|
+
url: "https://fr.wikipedia.org/wiki/Exchangeable_image_file_format",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "W3C - Metadata in Images",
|
|
77
|
+
url: "https://www.w3.org/community/image-metadata/",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const seo: ExifCleanerLocaleContent['seo'] = [
|
|
82
|
+
{
|
|
83
|
+
type: 'summary',
|
|
84
|
+
title: 'Nettoyeur de Métadonnées EXIF - Protégez votre Vie Privée',
|
|
85
|
+
items: [
|
|
86
|
+
'Supprimez instantanément les coordonnées GPS et la localisation de vos photos',
|
|
87
|
+
'Effacez le modèle de l\'appareil, le numéro de série et les informations techniques',
|
|
88
|
+
'Traitement 100 % par navigateur - vos images ne quittent jamais votre appareil',
|
|
89
|
+
'Préserve la qualité visuelle - seules les données cachées sont supprimées'
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{ type: 'title', text: 'Guide Complet de la Confidentialité et des Métadonnées EXIF en Photographie Numérique', level: 2 },
|
|
93
|
+
{ type: 'paragraph', html: 'Vous êtes-vous déjà demandé combien d\'informations personnelles vous partagez en publiant une photo sur les réseaux sociaux ? Les métadonnées EXIF sont comme une <strong>empreinte digitale invisible</strong> qui peut révéler tout, de l\'emplacement exact de votre domicile au prix de votre matériel photographique. Ce guide explique comment protéger votre vie privée efficacement.' },
|
|
94
|
+
|
|
95
|
+
{ type: 'stats', items: [
|
|
96
|
+
{ value: '100%', label: 'Vie Privée - Traitement Local', icon: 'mdi:shield-check' },
|
|
97
|
+
{ value: '0%', label: 'Données Stockées sur Serveur', icon: 'mdi:database' },
|
|
98
|
+
{ value: 'Instantané', label: 'Suppression des Métadonnées', icon: 'mdi:lightning-bolt' }
|
|
99
|
+
], columns: 3 },
|
|
100
|
+
|
|
101
|
+
{ type: 'title', text: 'Quelles Informations se Cachent dans vos Photos ?', level: 3 },
|
|
102
|
+
{ type: 'paragraph', html: 'Plus de 90 % des photos numériques contiennent des informations sensibles qui peuvent compromettre votre sécurité. Voici toutes les données qui peuvent être révélées :' },
|
|
103
|
+
{ type: 'list', items: [
|
|
104
|
+
'<strong>Coordonnées GPS :</strong> La latitude et la longitude exactes où la capture a été faite (adresse du domicile, travail, lieux fréquentés).',
|
|
105
|
+
'<strong>Identification de l\'Équipement :</strong> Marque, modèle et numéro de série de l\'appareil photo ou du smartphone (informations précieuses).',
|
|
106
|
+
'<strong>Réglages Techniques :</strong> ISO, ouverture (f/), temps d\'exposition et distance focale (permet d\'identifier votre équipement spécifique).',
|
|
107
|
+
'<strong>Date et Heure Exactes :</strong> Un chronologie complète de vos activités quotidiennes.',
|
|
108
|
+
'<strong>Historique d\'Édition :</strong> Logiciel utilisé, logiciel d\'édition et date de dernière modification.',
|
|
109
|
+
'<strong>Données de Copyright :</strong> Photographe, droits d\'auteur et notes personnelles.'
|
|
110
|
+
] },
|
|
111
|
+
|
|
112
|
+
{ type: 'title', text: 'Risques de Sécurité Réels : Cas d\'Utilisation', level: 3 },
|
|
113
|
+
{ type: 'comparative', items: [
|
|
114
|
+
{
|
|
115
|
+
title: 'Photographes Professionnels',
|
|
116
|
+
description: 'Risque de vol de matériel coûteux identifié par numéro de série',
|
|
117
|
+
icon: 'mdi:camera',
|
|
118
|
+
points: [
|
|
119
|
+
'Les voleurs recherchent des photographes avec du matériel de valeur',
|
|
120
|
+
'Le GPS identifie le domicile du propriétaire',
|
|
121
|
+
'Le numéro de série facilite la revente sur le dark web'
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
title: 'Parents et Influenceurs',
|
|
126
|
+
description: 'Danger extrême : localisation des enfants en temps réel',
|
|
127
|
+
icon: 'mdi:alert',
|
|
128
|
+
points: [
|
|
129
|
+
'Les prédateurs suivent les emplacements via l\'OSINT',
|
|
130
|
+
'Les prédateurs peuvent identifier les routines quotidiennes',
|
|
131
|
+
'Risque de harcèlement et de suivi physique'
|
|
132
|
+
],
|
|
133
|
+
highlight: true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
title: 'Utilisateurs de Réseaux Sociaux',
|
|
137
|
+
description: 'Exposition de la vie privée personnelle et professionnelle',
|
|
138
|
+
icon: 'mdi:share-variant',
|
|
139
|
+
points: [
|
|
140
|
+
'Publier depuis le bureau révèle le lieu de travail',
|
|
141
|
+
'La géolocalisation permet d\'inférer le salaire approximatif',
|
|
142
|
+
'Les données publiques permettent un profilage ciblé'
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title: 'Voyageurs et Nomades',
|
|
147
|
+
description: 'Danger de vol dans les domiciles inoccupés',
|
|
148
|
+
icon: 'mdi:map',
|
|
149
|
+
points: [
|
|
150
|
+
'Le GPS publié indique une maison vide pendant le voyage',
|
|
151
|
+
'Les données d\'équipement attirent les délinquents',
|
|
152
|
+
'Les historiques de voyage révèlent des habitudes'
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
], columns: 2 },
|
|
156
|
+
|
|
157
|
+
{ type: 'title', text: 'Comment Fonctionne cet Outil', level: 3 },
|
|
158
|
+
{ type: 'list', items: [
|
|
159
|
+
'<strong>1. Sélectionnez vos images :</strong> Faites glisser des photos ou utilisez le sélecteur. Vous pouvez en traiter plusieurs simultanément.',
|
|
160
|
+
'<strong>2. Analyse automatique :</strong> L\'outil détecte et affiche toutes les métadonnées présentes (GPS, modèle d\'appareil, date, etc.).',
|
|
161
|
+
'<strong>3. Nettoyage instantané :</strong> En un clic, il supprime 100 % des métadonnées nuisibles.',
|
|
162
|
+
'<strong>4. Téléchargement sécurisé :</strong> Recevez des images anonymisées prêtes à être partagées sans risque.',
|
|
163
|
+
'<strong>5. Aucun résidu :</strong> L\'image propre conserve toute sa qualité visuelle d\'origine.'
|
|
164
|
+
], icon: 'mdi:check' },
|
|
165
|
+
|
|
166
|
+
{ type: 'card', title: 'Technologie de Confidentialité', icon: 'mdi:shield-check', html: 'Cet outil utilise l\'<strong>API Canvas du navigateur</strong> pour créer une copie propre de l\'image pixel par pixel, garantissant que :<br><br>- Aucune donnée n\'est envoyée aux serveurs<br>- Votre image ne quitte jamais votre appareil<br>- Traitement 100 % anonyme et sécurisé<br>- Vous pouvez l\'utiliser même sans connexion internet (après le chargement initial)' },
|
|
167
|
+
|
|
168
|
+
{ type: 'title', text: 'Quelles Données sont Spécifiquement Supprimées ?', level: 3 },
|
|
169
|
+
{ type: 'table', headers: ['Type de Métadonnées', 'Exemples', 'Risque'], rows: [
|
|
170
|
+
['EXIF (Échangeable)', 'GPS, ISO, Ouverture, Vitesse d\'Obturation, Modèle d\'Appareil', 'CRITIQUE'],
|
|
171
|
+
['IPTC (Publication)', 'Copyright, Auteur, Mots-clés, Lieu de la Scène', 'ÉLEVÉ'],
|
|
172
|
+
['XMP (XML)', 'Historique d\'Édition, Logiciel Utilisé, Modifications Faites', 'MOYEN'],
|
|
173
|
+
['Données de Fichier de Base', 'Date de Création, Heure Exacte, Appareil', 'ÉLEVÉ']
|
|
174
|
+
] },
|
|
175
|
+
|
|
176
|
+
{ type: 'proscons', items: [
|
|
177
|
+
{
|
|
178
|
+
pro: 'Confidentialité Totale - Traitement 100 % par navigateur',
|
|
179
|
+
con: 'Nécessite un navigateur moderne avec support JavaScript'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
pro: 'Instantané - nettoyer une photo prend moins d\'une seconde',
|
|
183
|
+
con: 'Le traitement de grandes photos peut être lent sur les appareils anciens'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
pro: 'Zéro Perte de Qualité - ne supprime que les données techniques',
|
|
187
|
+
con: 'Ne modifie pas l\'image (recadrage, rotations, etc.)'
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
pro: 'Totalement Gratuit sans Limites - traitez des photos illimitées',
|
|
191
|
+
con: 'Pas de support premium ni de stockage dans le cloud'
|
|
192
|
+
}
|
|
193
|
+
], proTitle: 'Avantages Clés', conTitle: 'Limitations' },
|
|
194
|
+
|
|
195
|
+
{ type: 'tip', title: 'Conseil de Sécurité Numérique', html: '<strong>Nettoyez vos photos AVANT de les publier</strong> sur n\'importe quel réseau social. Même si vous supprimez une publication, les métadonnées ont pu être indexées par des moteurs de recherche ou des archives. Mieux vaut prévenir que guérir. Prenez l\'habitude : <br><br><em>Photo → Nettoyer EXIF → Publier</em>' },
|
|
196
|
+
|
|
197
|
+
{ type: 'diagnostic', variant: 'warning', title: 'Risques Réels du Non-Nettoyage', icon: 'mdi:alert-circle', badge: 'Sécurité Critique', html: '<strong>Cas documentés :</strong><br>- Parents publiant des photos d\'enfants - les prédateurs ont suivi le GPS<br>- Photographes voyageurs - maisons cambriolées pendant leur absence<br>- Influenceurs - lieux de résidence identifiés par des fans obsessifs<br><br>Ce n\'est pas de la paranoïa : c\'est une hygiène numérique de base en 2026.' },
|
|
198
|
+
|
|
199
|
+
{ type: 'glossary', items: [
|
|
200
|
+
{
|
|
201
|
+
term: 'EXIF',
|
|
202
|
+
definition: 'Exchangeable Image File Format. Norme qui stocke des métadonnées techniques dans les fichiers image : GPS, données d\'appareil, réglages d\'exposition. Présent dans environ 90 % des photos numériques sans intention de l\'utilisateur.'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
term: 'Coordonnées GPS',
|
|
206
|
+
definition: 'Latitude et longitude précises de l\'emplacement exact où la photo a été prise. Combinées aux réseaux sociaux, elles permettent un suivi physique des personnes.'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
term: 'IPTC',
|
|
210
|
+
definition: 'International Press Telecommunications Council. Métadonnées de publication lisibles : copyright, auteur, mots-clés, description. Norme en photographie professionnelle.'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
term: 'XMP',
|
|
214
|
+
definition: 'Extensible Metadata Platform. Format XML qui enregistre l\'historique des modifications dans des logiciels comme Lightroom ou Photoshop. Peut révéler toutes les éditions effectuées.'
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
term: 'OSINT',
|
|
218
|
+
definition: 'Open Source Intelligence. Technique de collecte d\'informations publiques (réseaux sociaux, métadonnées, registres) pour profiler des personnes à leur insu.'
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
term: 'Anonymisation Numérique',
|
|
222
|
+
definition: 'Processus de suppression ou d\'offuscation des informations identifiables : localisation, appareil, modèles de comportement. Essentiel pour la vie privée en ligne.'
|
|
223
|
+
}
|
|
224
|
+
] },
|
|
225
|
+
|
|
226
|
+
{ type: 'message', title: 'Votre Vie Privée est votre Responsabilité', ariaLabel: 'Informations sur la confidentialité et les droits des données', html: 'Nous ne stockons, ne traitons ni ne partageons vos images. <strong>Vous avez le contrôle total.</strong> Toutes les opérations se déroulent exclusivement dans votre navigateur. Lorsque vous fermez cet onglet, aucune trace de votre activité ne subsiste. Voici comment protéger votre vie privée sur internet : des outils qui respectent vos données.' },
|
|
227
|
+
|
|
228
|
+
{ type: 'title', text: 'Conclusion : Partagez sans Traces', level: 3 },
|
|
229
|
+
{ type: 'paragraph', html: 'Protéger votre identité numérique commence par de petits détails. Nettoyer vos photos avant de les publier est une <strong>habitude d\'hygiène numérique essentielle</strong> en 2026. Cela ne protège pas seulement votre position : cela protège votre famille, vos biens et votre vie privée professionnelle. Une photo apparemment innocente peut en dire plus sur vous que vous ne l\'auriez jamais imaginé.' }
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
233
|
+
'@context': 'https://schema.org',
|
|
234
|
+
'@type': 'FAQPage',
|
|
235
|
+
mainEntity: faq.map((item) => ({
|
|
236
|
+
'@type': 'Question',
|
|
237
|
+
name: item.question,
|
|
238
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
239
|
+
})),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const howToSchema: WithContext<HowTo> = {
|
|
243
|
+
'@context': 'https://schema.org',
|
|
244
|
+
'@type': 'HowTo',
|
|
245
|
+
name: title,
|
|
246
|
+
description,
|
|
247
|
+
step: howTo.map((step) => ({
|
|
248
|
+
'@type': 'HowToStep',
|
|
249
|
+
name: step.name,
|
|
250
|
+
text: step.text,
|
|
251
|
+
})),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
255
|
+
'@context': 'https://schema.org',
|
|
256
|
+
'@type': 'SoftwareApplication',
|
|
257
|
+
name: title,
|
|
258
|
+
description,
|
|
259
|
+
applicationCategory: 'UtilitiesApplication',
|
|
260
|
+
operatingSystem: 'Web',
|
|
261
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
262
|
+
inLanguage: 'fr',
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const content: ExifCleanerLocaleContent = {
|
|
266
|
+
slug,
|
|
267
|
+
title,
|
|
268
|
+
description,
|
|
269
|
+
ui,
|
|
270
|
+
seo,
|
|
271
|
+
faqTitle: "Frequently Asked Questions",
|
|
272
|
+
faq,
|
|
273
|
+
bibliography,
|
|
274
|
+
bibliographyTitle: "References",
|
|
275
|
+
howTo,
|
|
276
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
277
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AudiovisualToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import ExifCleaner from './component.astro';
|
|
3
|
+
import ExifCleanerSEO from './seo.astro';
|
|
4
|
+
import ExifCleanerBibliography from './bibliography.astro';
|
|
5
|
+
|
|
6
|
+
export interface ExifCleanerUI {
|
|
7
|
+
dropTitle: string;
|
|
8
|
+
dropSubtitle: string;
|
|
9
|
+
dropLocalInfo: string;
|
|
10
|
+
selectButton: string;
|
|
11
|
+
processingText: string;
|
|
12
|
+
analysisCompleted: string;
|
|
13
|
+
downloadButton: string;
|
|
14
|
+
resetButton: string;
|
|
15
|
+
privacyRiskTitle: string;
|
|
16
|
+
gpsLabel: string;
|
|
17
|
+
gpsDetected: string;
|
|
18
|
+
gpsNotFound: string;
|
|
19
|
+
cameraLabel: string;
|
|
20
|
+
softwareLabel: string;
|
|
21
|
+
dateLabel: string;
|
|
22
|
+
otherTechnicalDetails: string;
|
|
23
|
+
noMetadataFound: string;
|
|
24
|
+
alreadyCleanInfo: string;
|
|
25
|
+
previewLabel: string;
|
|
26
|
+
faqTitle: string;
|
|
27
|
+
bibliographyTitle: string;
|
|
28
|
+
[key: string]: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ExifCleanerLocaleContent = ToolLocaleContent<ExifCleanerUI>;
|
|
32
|
+
|
|
33
|
+
import { content as es } from './i18n/es';
|
|
34
|
+
import { content as en } from './i18n/en';
|
|
35
|
+
import { content as fr } from './i18n/fr';
|
|
36
|
+
|
|
37
|
+
export const exifCleaner: AudiovisualToolEntry<ExifCleanerUI> = {
|
|
38
|
+
id: 'exif-cleaner',
|
|
39
|
+
icons: {
|
|
40
|
+
bg: 'mdi:camera-off',
|
|
41
|
+
fg: 'mdi:shield-check',
|
|
42
|
+
},
|
|
43
|
+
i18n: {
|
|
44
|
+
es: async () => es as unknown as ExifCleanerLocaleContent,
|
|
45
|
+
en: async () => en as unknown as ExifCleanerLocaleContent,
|
|
46
|
+
fr: async () => fr as unknown as ExifCleanerLocaleContent,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { ExifCleaner, ExifCleanerSEO, ExifCleanerBibliography };
|
|
51
|
+
|
|
52
|
+
export const EXIF_CLEANER_TOOL: ToolDefinition = {
|
|
53
|
+
entry: exifCleaner as unknown as AudiovisualToolEntry,
|
|
54
|
+
Component: ExifCleaner,
|
|
55
|
+
SEOComponent: ExifCleanerSEO,
|
|
56
|
+
BibliographyComponent: ExifCleanerBibliography,
|
|
57
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export interface ExifTags extends Record<string, string | number | boolean | undefined> {
|
|
2
|
+
GPSDetected?: boolean;
|
|
3
|
+
GPSLocation?: string;
|
|
4
|
+
Model?: string;
|
|
5
|
+
Software?: string;
|
|
6
|
+
DateTimeOriginal?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface MetaParams {
|
|
10
|
+
view: DataView;
|
|
11
|
+
tiffStart: number;
|
|
12
|
+
offset: number;
|
|
13
|
+
littleEndian: boolean;
|
|
14
|
+
tags: ExifTags;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getBytesPerComponent = (type: number): number => {
|
|
18
|
+
const map: Record<number, number> = { 1: 1, 2: 1, 7: 1, 3: 2, 4: 4, 9: 4, 5: 8, 10: 8 };
|
|
19
|
+
return map[type] || 0;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const readString = (view: DataView, offset: number, length: number): string => {
|
|
23
|
+
let str = "";
|
|
24
|
+
for (let n = 0; n < length && n < 100; n++) {
|
|
25
|
+
const charCode = view.getUint8(offset + n);
|
|
26
|
+
if (charCode === 0) break;
|
|
27
|
+
str += String.fromCharCode(charCode);
|
|
28
|
+
}
|
|
29
|
+
return str.trim();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const readRational = (view: DataView, offset: number, numValues: number, littleEndian: boolean): number[] => {
|
|
33
|
+
const values = [];
|
|
34
|
+
for (let i = 0; i < numValues; i++) {
|
|
35
|
+
const num = view.getUint32(offset + i * 8, littleEndian);
|
|
36
|
+
const den = view.getUint32(offset + i * 8 + 4, littleEndian);
|
|
37
|
+
values.push(den === 0 ? 0 : Math.round((num / den) * 1000) / 1000);
|
|
38
|
+
}
|
|
39
|
+
return values;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getGPSCoord = (view: DataView, tiffStart: number, entryOffset: number, littleEndian: boolean): { tag: number, val: number | number[] | string | null } => {
|
|
43
|
+
const tag = view.getUint16(entryOffset, littleEndian);
|
|
44
|
+
const type = view.getUint16(entryOffset + 2, littleEndian);
|
|
45
|
+
const numValues = view.getUint32(entryOffset + 4, littleEndian);
|
|
46
|
+
const dataSize = numValues * getBytesPerComponent(type);
|
|
47
|
+
const valOffset = dataSize > 4 ? tiffStart + view.getUint32(entryOffset + 8, littleEndian) : entryOffset + 8;
|
|
48
|
+
|
|
49
|
+
if (tag === 1 || tag === 3) return { tag, val: String.fromCharCode(view.getUint8(valOffset)) };
|
|
50
|
+
if (tag === 2 || tag === 4) return { tag, val: readRational(view, valOffset, 3, littleEndian) };
|
|
51
|
+
return { tag, val: null };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const parseGPS = (view: DataView, tiffStart: number, offset: number, littleEndian: boolean): string | null => {
|
|
55
|
+
const numEntries = view.getUint16(offset, littleEndian);
|
|
56
|
+
let lat: number[] | null = null, latRef = "N", lon: number[] | null = null, lonRef = "E";
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < numEntries; i++) {
|
|
59
|
+
const { tag, val } = getGPSCoord(view, tiffStart, offset + 2 + i * 12, littleEndian);
|
|
60
|
+
if (tag === 1) latRef = val as string;
|
|
61
|
+
else if (tag === 2) lat = val as number[];
|
|
62
|
+
else if (tag === 3) lonRef = val as string;
|
|
63
|
+
else if (tag === 4) lon = val as number[];
|
|
64
|
+
}
|
|
65
|
+
return lat && lon ? `${lat[0]}° ${lat[1]}' ${lat[2]}" ${latRef}, ${lon[0]}° ${lon[1]}' ${lon[2]}" ${lonRef}` : null;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const processIFDEntry = (params: MetaParams) => {
|
|
69
|
+
const { view, tiffStart, offset, littleEndian, tags } = params;
|
|
70
|
+
const tag = view.getUint16(offset, littleEndian);
|
|
71
|
+
const type = view.getUint16(offset + 2, littleEndian);
|
|
72
|
+
const numValues = view.getUint32(offset + 4, littleEndian);
|
|
73
|
+
const dataSize = numValues * getBytesPerComponent(type);
|
|
74
|
+
const valOffset = dataSize > 4 ? tiffStart + view.getUint32(offset + 8, littleEndian) : offset + 8;
|
|
75
|
+
|
|
76
|
+
if (tag === 0x8825) {
|
|
77
|
+
tags.GPSDetected = true;
|
|
78
|
+
const gps = parseGPS(view, tiffStart, tiffStart + view.getUint32(offset + 8, littleEndian), littleEndian);
|
|
79
|
+
if (gps) tags.GPSLocation = gps;
|
|
80
|
+
} else if (tag === 0x0110) tags.Model = readString(view, valOffset, numValues);
|
|
81
|
+
else if (tag === 0x9003) tags.DateTimeOriginal = readString(view, valOffset, numValues);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const parseIFD = (params: MetaParams) => {
|
|
85
|
+
const { view, offset, littleEndian } = params;
|
|
86
|
+
const numEntries = view.getUint16(offset, littleEndian);
|
|
87
|
+
for (let i = 0; i < numEntries; i++) {
|
|
88
|
+
processIFDEntry({ ...params, offset: offset + 2 + i * 12 });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const findExifHeader = (view: DataView): ExifTags | null => {
|
|
93
|
+
let off = 2;
|
|
94
|
+
while (off + 4 < view.byteLength) {
|
|
95
|
+
if (view.getUint8(off) !== 0xff) break;
|
|
96
|
+
if (view.getUint8(off + 1) === 0xe1) {
|
|
97
|
+
const tStart = off + 10;
|
|
98
|
+
const le = view.getUint16(tStart, false) === 0x4949;
|
|
99
|
+
const tags: ExifTags = {};
|
|
100
|
+
parseIFD({ view, tiffStart: tStart, offset: tStart + view.getUint32(tStart + 4, le), littleEndian: le, tags });
|
|
101
|
+
return tags;
|
|
102
|
+
}
|
|
103
|
+
const sLen = view.getUint16(off + 2, false);
|
|
104
|
+
if (sLen < 2) break;
|
|
105
|
+
off += 2 + sLen;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const extractExif = async (file: File): Promise<ExifTags> => {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const reader = new FileReader();
|
|
113
|
+
reader.onload = (e) => {
|
|
114
|
+
try {
|
|
115
|
+
if (!e.target?.result) return resolve({});
|
|
116
|
+
const view = new DataView(e.target.result as ArrayBuffer);
|
|
117
|
+
if (view.getUint16(0, false) !== 0xffd8) return resolve({});
|
|
118
|
+
resolve(findExifHeader(view) || {});
|
|
119
|
+
} catch {
|
|
120
|
+
resolve({});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
reader.readAsArrayBuffer(file.slice(0, 65536));
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const createCleanImage = async (img: HTMLImageElement): Promise<Blob | null> => {
|
|
128
|
+
const canvas = document.createElement("canvas");
|
|
129
|
+
canvas.width = img.width;
|
|
130
|
+
canvas.height = img.height;
|
|
131
|
+
const ctx = canvas.getContext("2d");
|
|
132
|
+
if (!ctx) return null;
|
|
133
|
+
ctx.drawImage(img, 0, 0);
|
|
134
|
+
return new Promise((resolve) => canvas.toBlob(resolve, "image/webp"));
|
|
135
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { exifCleaner } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const localeContentLoader = (exifCleaner.i18n as any)[locale];
|
|
12
|
+
const content = localeContentLoader ? await localeContentLoader() : null;
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
|
|
15
|
+
const seoSections = content.seo;
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<SEORenderer content={{ locale, sections: seoSections }} />
|