@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,230 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ImageCompressorUI, ImageCompressorLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'compresseur-images-en-ligne-reduire-taille-sans-perte-qualite';
|
|
5
|
+
const title = 'Compresseur d\'Images en Ligne - Réduisez le poids sans perte de qualité';
|
|
6
|
+
const description = 'Optimisez et compressez vos photos JPG, PNG et WebP gratuitement. Réduisez la taille des fichiers pour améliorer la vitesse de chargement de votre site web localement.';
|
|
7
|
+
|
|
8
|
+
const ui: ImageCompressorUI = {
|
|
9
|
+
dropTitle: "Optimiser les Images",
|
|
10
|
+
dropSubtitle: "Faites glisser vos photos pour réduire leur poids instantanément.",
|
|
11
|
+
settingsTitle: "Paramètres de Compression",
|
|
12
|
+
qualityLabel: "Qualité Visuelle",
|
|
13
|
+
widthLabel: "Largeur Maximale (Pixels)",
|
|
14
|
+
convertToWebpLabel: "Convertir en WebP (Recommandé)",
|
|
15
|
+
compressBtn: "Compresser l'Image",
|
|
16
|
+
processingLabel: "Traitement...",
|
|
17
|
+
resultsTitle: "Images Optimisées",
|
|
18
|
+
originalSizeLabel: "Taille Originale",
|
|
19
|
+
newSizeLabel: "Nouvelle Taille",
|
|
20
|
+
reductionLabel: "Économie",
|
|
21
|
+
downloadBtn: "Télécharger",
|
|
22
|
+
addMoreBtn: "Ajouter"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const faq: ImageCompressorLocaleContent['faq'] = [
|
|
26
|
+
{
|
|
27
|
+
question: "Comment fonctionne la compression sans perte ?",
|
|
28
|
+
answer: "Nous utilisons des algorithmes qui suppriment les métadonnées inutiles et optimisent l'encodage des pixels. En choisissant le WebP, une technologie de compression plus avancée que les formats traditionnels est utilisée.",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
question: "Mes images sont-elles téléchargées sur un serveur ?",
|
|
32
|
+
answer: "Non. Tout le traitement se déroule à 100 % dans votre navigateur (côté client). Vos photos ne quittent jamais votre ordinateur, garantissant une confidentialité absolue.",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
question: "Pourquoi devrais-je convertir en WebP ?",
|
|
36
|
+
answer: "Le WebP est le format standard moderne pour le web. Il offre une qualité supérieure au JPG et au PNG avec un poids jusqu'à 30 % inférieur, ce qui accélère le temps de chargement des pages.",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const howTo: ImageCompressorLocaleContent['howTo'] = [
|
|
41
|
+
{
|
|
42
|
+
name: "Charger l'image",
|
|
43
|
+
text: "Téléchargez le fichier que vous souhaitez compresser (JPG, PNG ou WebP).",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: "Ajuster la qualité",
|
|
47
|
+
text: "Utilisez le curseur pour trouver l'équilibre parfait entre la taille du fichier et la qualité visuelle (recommandé : 75-85 %).",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "Redimensionner (optionnel)",
|
|
51
|
+
text: "Si l'image est très grande, vous pouvez indiquer une largeur maximale pour réduire sa résolution.",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "Télécharger le résultat",
|
|
55
|
+
text: "Appuyez sur le bouton de téléchargement pour obtenir votre image optimisée prête à l'emploi.",
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const bibliography: ImageCompressorLocaleContent['bibliography'] = [
|
|
60
|
+
{
|
|
61
|
+
name: "WebP: An Image Format for the Web",
|
|
62
|
+
url: "https://developers.google.com/speed/webp",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "L'importance de l'optimisation des images pour le SEO",
|
|
66
|
+
url: "https://developers.google.com/search/docs/appearance/google-images",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const seo: ImageCompressorLocaleContent['seo'] = [
|
|
71
|
+
{
|
|
72
|
+
type: 'summary',
|
|
73
|
+
title: 'Optimisation Professionnelle d\'Images Web',
|
|
74
|
+
items: [
|
|
75
|
+
'Compression intelligente JPG, PNG et WebP',
|
|
76
|
+
'Réduisez le poids de 50 à 80 % tout en conservant la qualité visuelle',
|
|
77
|
+
'Traitement 100 % local - confidentialité garantie',
|
|
78
|
+
'Améliorez les Core Web Vitals et le positionnement SEO'
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{ type: 'title', text: 'Optimisation des Images : Vitesse Web et Core Web Vitals', level: 2 },
|
|
82
|
+
{ type: 'paragraph', html: 'À l\'ère de l\'immédiateté numérique, un site lent perd des utilisateurs. Les images non optimisées représentent 60 à 70 % du poids total d\'une page web. Les optimiser est la première étape pour améliorer vos métriques Google (LCP, CLS) et votre positionnement dans les résultats de recherche.' },
|
|
83
|
+
|
|
84
|
+
{ type: 'stats', items: [
|
|
85
|
+
{ value: '50-80%', label: 'Réduction Typique du Poids', icon: 'mdi:trending-down' },
|
|
86
|
+
{ value: '100%', label: 'Confidentialité Locale', icon: 'mdi:lock' },
|
|
87
|
+
{ value: '+30%', label: 'Plus Rapide que le JPG', icon: 'mdi:speedometer' }
|
|
88
|
+
], columns: 3 },
|
|
89
|
+
|
|
90
|
+
{ type: 'title', text: 'Formats de Compression Expliqués', level: 3 },
|
|
91
|
+
{ type: 'table', headers: ['Format', 'Compression', 'Cas d\'Utilisation', 'Compatibilité'], rows: [
|
|
92
|
+
['JPEG', 'Lossy 50-90%', 'Photos d\'appareil, contenu éditorial', 'Universel (100 %)'],
|
|
93
|
+
['PNG', 'Lossless 30-50%', 'Graphiques, logos, transparences', 'Universel (100 %)'],
|
|
94
|
+
['WebP', 'Lossy/Lossless 25-35% plus', 'Web moderne, réseaux sociaux', '95 % des navigateurs modernes'],
|
|
95
|
+
['AVIF', 'Lossy/Lossless améliore 20%', 'Nouvelle génération web', 'Navigateurs récents uniquement']
|
|
96
|
+
] },
|
|
97
|
+
|
|
98
|
+
{ type: 'card', title: 'Pourquoi le WebP est l\'Avenir', html: 'Google a développé le WebP spécifiquement pour le web. Il offre une compression supérieure au JPEG et au PNG, tout en conservant ou en améliorant la qualité visuelle. Il est 25 à 35 % plus petit que ses équivalents en JPEG. Les navigateurs modernes le supportent à 95 %.' },
|
|
99
|
+
|
|
100
|
+
{ type: 'title', text: 'Compression Avec Perte vs Sans Perte', level: 3 },
|
|
101
|
+
{ type: 'comparative', items: [
|
|
102
|
+
{
|
|
103
|
+
title: 'Compression Avec Perte (Lossy)',
|
|
104
|
+
description: 'JPG, WebP - Supprime des informations visuelles imperceptibles',
|
|
105
|
+
icon: 'mdi:compress',
|
|
106
|
+
points: [
|
|
107
|
+
'Réduit de 70 à 90 % le poids d\'origine',
|
|
108
|
+
'Imperceptible à l\'œil humain si la qualité est maintenue > 75 %',
|
|
109
|
+
'Idéal pour la photographie réaliste',
|
|
110
|
+
'Non recommandé pour les logos ou les textes nets'
|
|
111
|
+
],
|
|
112
|
+
highlight: true
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: 'Compression Sans Perte (Lossless)',
|
|
116
|
+
description: 'PNG, WebP sans perte - Conserve 100 % des données visuelles',
|
|
117
|
+
icon: 'mdi:shield',
|
|
118
|
+
points: [
|
|
119
|
+
'Réduit de 20 à 50 % le poids',
|
|
120
|
+
'Qualité parfaite, sans dégradation',
|
|
121
|
+
'Idéal pour les graphiques, les logos, les transparences',
|
|
122
|
+
'Fichiers plus lourds que le Lossy'
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
], columns: 2 },
|
|
126
|
+
|
|
127
|
+
{ type: 'title', text: 'Impact sur le SEO et la Conversion', level: 3 },
|
|
128
|
+
{ type: 'list', items: [
|
|
129
|
+
'<strong>Core Web Vitals :</strong> Google pénalise les sites lents. Les images optimisées améliorent directement le LCP (Largest Contentful Paint).',
|
|
130
|
+
'<strong>Taux de Rebond :</strong> Chaque seconde de retard = 7 % de rebond en plus. Des images plus rapides = moins d\'utilisateurs qui partent.',
|
|
131
|
+
'<strong>Classement de Recherche :</strong> La vitesse est un facteur de classement. Optimiser les images booste le positionnement.',
|
|
132
|
+
'<strong>Conversion :</strong> Des temps de chargement plus rapides = plus de conversions. Les études montrent +10 % de conversion avec l\'optimisation.'
|
|
133
|
+
], icon: 'mdi:check' },
|
|
134
|
+
|
|
135
|
+
{ type: 'proscons', items: [
|
|
136
|
+
{
|
|
137
|
+
pro: 'Confidentialité totale : traitement 100 % local, sans serveurs cloud',
|
|
138
|
+
con: 'Nécessite un navigateur supportant l\'API Canvas (c\'est universel)'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
pro: 'Le WebP est 25 à 35 % plus petit que le JPEG pour une qualité égale',
|
|
142
|
+
con: 'Les anciens Safari et IE ne supportent pas le WebP (fallback disponible)'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
pro: 'Traitement instantané de plusieurs images',
|
|
146
|
+
con: 'Les images très volumineuses (>50 Mo) peuvent nécessiter un délai'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
pro: 'Redimensionnement optionnel : réduit la résolution en plus de la compression',
|
|
150
|
+
con: 'Le redimensionnement entraîne une perte d\'information - mieux vaut optimiser à la source'
|
|
151
|
+
}
|
|
152
|
+
], proTitle: 'Avantages', conTitle: 'Limitations' },
|
|
153
|
+
|
|
154
|
+
{ type: 'diagnostic', variant: 'warning', title: 'Attention : Une Compression Excessive Dégrade', icon: 'mdi:alert', badge: 'Qualité', html: 'Maintenez une qualité > 75 % pour la photographie et > 85 % pour le contenu éditorial. En dessous, les artefacts de compression (taches, bandes) deviennent visibles. Une image de belle apparence en vignette peut être hideuse en grand.' },
|
|
155
|
+
|
|
156
|
+
{ type: 'glossary', items: [
|
|
157
|
+
{
|
|
158
|
+
term: 'Compression Lossy',
|
|
159
|
+
definition: 'Supprime les données visuelles que l\'œil humain perçoit comme du "bruit". Idéal pour la photographie.'
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
term: 'Compression Lossless',
|
|
163
|
+
definition: 'Réduit la taille sans perdre d\'informations visuelles. PNG et WebP sans perte.'
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
term: 'Core Web Vitals (Google)',
|
|
167
|
+
definition: 'Indicateurs d\'expérience utilisateur : LCP (vitesse de chargement), FID (latence d\'interaction), CLS (stabilité visuelle). Affectent le SEO.'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
term: 'WebP',
|
|
171
|
+
definition: 'Format développé par Google. 25 à 35 % plus petit que le JPEG. Supporté par 95 % des navigateurs modernes.'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
term: 'Artefacts de Compression',
|
|
175
|
+
definition: 'Défauts visuels causés par une compression excessive : taches, bandes de couleur, bords flous.'
|
|
176
|
+
}
|
|
177
|
+
] },
|
|
178
|
+
|
|
179
|
+
{ type: 'message', title: 'Optimisation Web Professionnelle', ariaLabel: 'Informations sur l\'optimisation d\'images pour le SEO', html: 'Ce n\'est pas de la vanité technique : compresser les images est un investissement direct dans l\'expérience utilisateur et le classement Google. Chaque kilo-octet compte sur mobile. Notre outil transforme ce qui nécessitait auparavant un logiciel professionnel ($$$) en un processus gratuit en 3 clics.' },
|
|
180
|
+
|
|
181
|
+
{ type: 'title', text: 'Prêt pour le Web Moderne', level: 3 },
|
|
182
|
+
{ type: 'paragraph', html: 'Utilisez la compression intelligente et le format WebP pour que votre présence en ligne soit rapide, responsive et compétitive dans les recherches.' }
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
186
|
+
'@context': 'https://schema.org',
|
|
187
|
+
'@type': 'FAQPage',
|
|
188
|
+
mainEntity: faq.map((item) => ({
|
|
189
|
+
'@type': 'Question',
|
|
190
|
+
name: item.question,
|
|
191
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
192
|
+
})),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const howToSchema: WithContext<HowTo> = {
|
|
196
|
+
'@context': 'https://schema.org',
|
|
197
|
+
'@type': 'HowTo',
|
|
198
|
+
name: title,
|
|
199
|
+
description,
|
|
200
|
+
step: howTo.map((step) => ({
|
|
201
|
+
'@type': 'HowToStep',
|
|
202
|
+
name: step.name,
|
|
203
|
+
text: step.text,
|
|
204
|
+
})),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
208
|
+
'@context': 'https://schema.org',
|
|
209
|
+
'@type': 'SoftwareApplication',
|
|
210
|
+
name: title,
|
|
211
|
+
description,
|
|
212
|
+
applicationCategory: 'UtilitiesApplication',
|
|
213
|
+
operatingSystem: 'Web',
|
|
214
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
215
|
+
inLanguage: 'fr',
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const content: ImageCompressorLocaleContent = {
|
|
219
|
+
slug,
|
|
220
|
+
title,
|
|
221
|
+
description,
|
|
222
|
+
ui,
|
|
223
|
+
seo,
|
|
224
|
+
faqTitle: "Frequently Asked Questions",
|
|
225
|
+
faq,
|
|
226
|
+
bibliography,
|
|
227
|
+
bibliographyTitle: "References",
|
|
228
|
+
howTo,
|
|
229
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
230
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AudiovisualToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import ImageCompressor from './component.astro';
|
|
3
|
+
import ImageCompressorSEO from './seo.astro';
|
|
4
|
+
import ImageCompressorBibliography from './bibliography.astro';
|
|
5
|
+
|
|
6
|
+
export interface ImageCompressorUI {
|
|
7
|
+
dropTitle: string;
|
|
8
|
+
dropSubtitle: string;
|
|
9
|
+
settingsTitle: string;
|
|
10
|
+
qualityLabel: string;
|
|
11
|
+
widthLabel: string;
|
|
12
|
+
convertToWebpLabel: string;
|
|
13
|
+
compressBtn: string;
|
|
14
|
+
processingLabel: string;
|
|
15
|
+
resultsTitle: string;
|
|
16
|
+
originalSizeLabel: string;
|
|
17
|
+
newSizeLabel: string;
|
|
18
|
+
reductionLabel: string;
|
|
19
|
+
downloadBtn: string;
|
|
20
|
+
addMoreBtn: string;
|
|
21
|
+
[key: string]: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ImageCompressorLocaleContent = ToolLocaleContent<ImageCompressorUI>;
|
|
25
|
+
|
|
26
|
+
import { content as es } from './i18n/es';
|
|
27
|
+
import { content as en } from './i18n/en';
|
|
28
|
+
import { content as fr } from './i18n/fr';
|
|
29
|
+
|
|
30
|
+
export const imageCompressor: AudiovisualToolEntry<ImageCompressorUI> = {
|
|
31
|
+
id: 'compresor-imagenes',
|
|
32
|
+
icons: {
|
|
33
|
+
bg: 'mdi:image-size-select-small',
|
|
34
|
+
fg: 'mdi:file-image-outline',
|
|
35
|
+
},
|
|
36
|
+
i18n: {
|
|
37
|
+
es: async () => es as unknown as ImageCompressorLocaleContent,
|
|
38
|
+
en: async () => en as unknown as ImageCompressorLocaleContent,
|
|
39
|
+
fr: async () => fr as unknown as ImageCompressorLocaleContent,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { ImageCompressor, ImageCompressorSEO, ImageCompressorBibliography };
|
|
44
|
+
|
|
45
|
+
export const IMAGE_COMPRESSOR_TOOL: ToolDefinition = {
|
|
46
|
+
entry: imageCompressor as unknown as AudiovisualToolEntry,
|
|
47
|
+
Component: ImageCompressor,
|
|
48
|
+
SEOComponent: ImageCompressorSEO,
|
|
49
|
+
BibliographyComponent: ImageCompressorBibliography,
|
|
50
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface CompressorSettings {
|
|
2
|
+
quality: number;
|
|
3
|
+
width: number | null;
|
|
4
|
+
convertToWebp: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CompressedImage {
|
|
8
|
+
id: string;
|
|
9
|
+
originalName: string;
|
|
10
|
+
originalSize: number;
|
|
11
|
+
newSize: number;
|
|
12
|
+
originalWidth: number;
|
|
13
|
+
originalHeight: number;
|
|
14
|
+
newWidth: number;
|
|
15
|
+
newHeight: number;
|
|
16
|
+
dataUrl: string;
|
|
17
|
+
format: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function formatBytes(bytes: number): string {
|
|
21
|
+
if (bytes === 0) return "0 B";
|
|
22
|
+
const k = 1024;
|
|
23
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
24
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
25
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + (sizes[i] || "B");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function generateId(): string {
|
|
29
|
+
return Math.random().toString(36).substring(2, 9);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function processCompressedImage(file: File, img: HTMLImageElement, settings: CompressorSettings, canvas: HTMLCanvasElement): CompressedImage {
|
|
33
|
+
const ctx = canvas.getContext("2d")!;
|
|
34
|
+
let targetW = img.naturalWidth, targetH = img.naturalHeight;
|
|
35
|
+
if (settings.width && settings.width < img.naturalWidth) {
|
|
36
|
+
targetW = settings.width;
|
|
37
|
+
targetH = Math.round((img.naturalHeight / img.naturalWidth) * targetW);
|
|
38
|
+
}
|
|
39
|
+
canvas.width = targetW;
|
|
40
|
+
canvas.height = targetH;
|
|
41
|
+
ctx.drawImage(img, 0, 0, targetW, targetH);
|
|
42
|
+
const format = settings.convertToWebp ? "image/webp" : (file.type || "image/jpeg");
|
|
43
|
+
const dataUrl = canvas.toDataURL(format, settings.quality / 100);
|
|
44
|
+
const newSize = Math.round((dataUrl.split(',')[1]?.length || 0) * 0.75);
|
|
45
|
+
return {
|
|
46
|
+
id: generateId(),
|
|
47
|
+
originalName: file.name,
|
|
48
|
+
originalSize: file.size,
|
|
49
|
+
newSize,
|
|
50
|
+
originalWidth: img.naturalWidth,
|
|
51
|
+
originalHeight: img.naturalHeight,
|
|
52
|
+
newWidth: targetW,
|
|
53
|
+
newHeight: targetH,
|
|
54
|
+
dataUrl,
|
|
55
|
+
format
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function compressImage(file: File, settings: CompressorSettings): Promise<CompressedImage> {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const reader = new FileReader();
|
|
62
|
+
reader.onload = (e) => {
|
|
63
|
+
const img = new Image();
|
|
64
|
+
img.onload = () => {
|
|
65
|
+
const canvas = document.createElement("canvas");
|
|
66
|
+
if (!canvas.getContext("2d")) return reject("Canvas context not available");
|
|
67
|
+
try {
|
|
68
|
+
resolve(processCompressedImage(file, img, settings, canvas));
|
|
69
|
+
} catch (err) {
|
|
70
|
+
reject(err);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
img.onerror = () => reject("Failed to load image");
|
|
74
|
+
img.src = e.target?.result as string;
|
|
75
|
+
};
|
|
76
|
+
reader.onerror = () => reject("Failed to read file");
|
|
77
|
+
reader.readAsDataURL(file);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { imageCompressor } 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 content = await imageCompressor.i18n[locale]?.();
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<SEORenderer content={{ locale, sections: content.seo || [] }} />
|