WIP blurash & sync card ?
All checks were successful
Deploy App / build (push) Successful in 26s
Deploy App / deploy (push) Successful in 18s

This commit is contained in:
valere
2026-02-21 09:07:45 +01:00
parent 543b513e08
commit aef705834b
8 changed files with 425 additions and 20 deletions

View File

@@ -115,6 +115,7 @@ const isTrackLoaded = ref(false)
.face-up { .face-up {
border-radius: 1rem; border-radius: 1rem;
border: none;
transform: rotateY(0deg); transform: rotateY(0deg);
transition: box-shadow 0.6s; transition: box-shadow 0.6s;

Binary file not shown.

View File

@@ -13,7 +13,7 @@ export default defineNuxtConfig({
tasks: true tasks: true
}, },
scheduledTasks: { scheduledTasks: {
'*/5 * * * *': ['syncTracks'] '*/1 * * * *': ['sync-tracks']
} }
}, },
compatibilityDate: '2025-07-15', compatibilityDate: '2025-07-15',

View File

@@ -23,9 +23,11 @@
"@nuxtjs/tailwindcss": "6.14.0", "@nuxtjs/tailwindcss": "6.14.0",
"@pinia/nuxt": "0.11.2", "@pinia/nuxt": "0.11.2",
"atropos": "^2.0.2", "atropos": "^2.0.2",
"blurhash": "^2.0.5",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"nuxt": "^4.3.0", "nuxt": "^4.3.0",
"pinia": "^3.0.3", "pinia": "^3.0.3",
"sharp": "^0.34.5",
"vue": "^3.5.18", "vue": "^3.5.18",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"

279
pnpm-lock.yaml generated
View File

@@ -23,6 +23,9 @@ importers:
atropos: atropos:
specifier: ^2.0.2 specifier: ^2.0.2
version: 2.0.2 version: 2.0.2
blurhash:
specifier: ^2.0.5
version: 2.0.5
drizzle-orm: drizzle-orm:
specifier: ^0.45.1 specifier: ^0.45.1
version: 0.45.1(@libsql/client@0.17.0) version: 0.45.1(@libsql/client@0.17.0)
@@ -32,6 +35,9 @@ importers:
pinia: pinia:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
sharp:
specifier: ^0.34.5
version: 0.34.5
vue: vue:
specifier: ^3.5.18 specifier: ^3.5.18
version: 3.5.27(typescript@5.9.3) version: 3.5.27(typescript@5.9.3)
@@ -845,6 +851,143 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'} engines: {node: '>=18.18'}
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
cpu: [ppc64]
os: [linux]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
cpu: [riscv64]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ppc64]
os: [linux]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [riscv64]
os: [linux]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@ioredis/commands@1.5.0': '@ioredis/commands@1.5.0':
resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==} resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==}
@@ -2339,6 +2482,9 @@ packages:
birpc@2.9.0: birpc@2.9.0:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
blurhash@2.0.5:
resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==}
boolbase@1.0.0: boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -5270,6 +5416,10 @@ packages:
setprototypeof@1.2.0: setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0: shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -6706,6 +6856,102 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {} '@humanwhocodes/retry@0.4.3': {}
'@img/colour@1.0.0': {}
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
optional: true
'@img/sharp-darwin-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
dependencies:
'@emnapi/runtime': 1.8.1
optional: true
'@img/sharp-win32-arm64@0.34.5':
optional: true
'@img/sharp-win32-ia32@0.34.5':
optional: true
'@img/sharp-win32-x64@0.34.5':
optional: true
'@ioredis/commands@1.5.0': {} '@ioredis/commands@1.5.0': {}
'@isaacs/balanced-match@4.0.1': {} '@isaacs/balanced-match@4.0.1': {}
@@ -8431,6 +8677,8 @@ snapshots:
birpc@2.9.0: {} birpc@2.9.0: {}
blurhash@2.0.5: {}
boolbase@1.0.0: {} boolbase@1.0.0: {}
brace-expansion@1.1.12: brace-expansion@1.1.12:
@@ -11798,6 +12046,37 @@ snapshots:
setprototypeof@1.2.0: {} setprototypeof@1.2.0: {}
sharp@0.34.5:
dependencies:
'@img/colour': 1.0.0
detect-libc: 2.1.2
semver: 7.7.3
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
shebang-command@2.0.0: shebang-command@2.0.0:
dependencies: dependencies:
shebang-regex: 3.0.0 shebang-regex: 3.0.0

View File

@@ -14,6 +14,7 @@ export const cards = sqliteTable('cards', {
slug: text('slug').notNull(), slug: text('slug').notNull(),
suit: text('suit').notNull(), suit: text('suit').notNull(),
rank: text('rank').notNull(), rank: text('rank').notNull(),
blurhash: text('blurhash').notNull(), // blurhash of the image
createdAt: int('created_at', { mode: 'timestamp' }) createdAt: int('created_at', { mode: 'timestamp' })
.notNull() .notNull()
.$defaultFn(() => new Date()), .$defaultFn(() => new Date()),

View File

@@ -1,6 +1,7 @@
import { eq, notInArray } from 'drizzle-orm' import { eq, notInArray } from 'drizzle-orm'
import { useDB, schema } from '../db' import { useDB, schema } from '../db'
import { scanMusicFolder } from '../utils/fileScanner' import { scanMusicFolder } from '../utils/fileScanner'
import { generateBlurhash } from '../utils/blurHash'
const { cards } = schema const { cards } = schema
@@ -21,11 +22,13 @@ export async function syncCardsWithDatabase(folderPath: string) {
const scannedEsids = new Set(scannedCards.map((t) => t.esid)) const scannedEsids = new Set(scannedCards.map((t) => t.esid))
const cardsToDelete = existingCards.filter((t) => !scannedEsids.has(t.esid)) const cardsToDelete = existingCards.filter((t) => !scannedEsids.has(t.esid))
// 4. Insérer les nouvelles cards // 4. Insérer les nouvelles cartes
if (cardsToInsert.length > 0) { if (cardsToInsert.length > 0) {
// Dans la fonction syncCardsWithDatabase // Générer tous les blurhash en parallèle
await db.insert(cards).values( const cardsWithBlurhash = await Promise.all(
cardsToInsert.map((card) => ({ cardsToInsert.map(async (card) => {
const blurhash = await generateBlurhash(card.url_image)
return {
url_audio: card.url_audio, url_audio: card.url_audio,
url_image: card.url_image, url_image: card.url_image,
year: card.year, year: card.year,
@@ -38,10 +41,15 @@ export async function syncCardsWithDatabase(folderPath: string) {
slug: card.slug, slug: card.slug,
createdAt: card.createdAt, createdAt: card.createdAt,
suit: card.suit, suit: card.suit,
rank: card.rank rank: card.rank,
})) blurhash: blurhash
}
})
) )
console.log(`${cardsToInsert.length} cards ajoutées`)
// Insérer les cartes avec les blurhash déjà résolus
await db.insert(cards).values(cardsWithBlurhash)
console.log(`${cardsToInsert.length} cartes ajoutées`)
} }
// 5. Supprimer les cards obsolètes avec une requête distincte pour chaque esid // 5. Supprimer les cards obsolètes avec une requête distincte pour chaque esid

114
server/utils/blurHash.ts Normal file
View File

@@ -0,0 +1,114 @@
// server/utils/blurHash.ts
import sharp from 'sharp'
import { encode } from 'blurhash'
import { $fetch } from 'ofetch'
import { mkdir, writeFile, unlink } from 'node:fs/promises'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import { randomUUID } from 'node:crypto'
// Dossier temporaire pour les images téléchargées
const TMP_DIR = join(tmpdir(), 'evilspins-images')
async function ensureTmpDir() {
try {
await mkdir(TMP_DIR, { recursive: true })
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
}
}
async function downloadImage(url: string): Promise<string> {
await ensureTmpDir()
const tmpPath = join(TMP_DIR, `${randomUUID()}.jpg`)
try {
const response = await $fetch(url, {
responseType: 'arrayBuffer',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
Accept: 'image/*,*/*;q=0.8'
},
// Timeout plus long
timeout: 60000, // 60 secondes
// Désactiver la compression pour les images
headers: {
'Accept-Encoding': 'identity'
}
})
await writeFile(tmpPath, Buffer.from(response))
return tmpPath
} catch (error) {
// Nettoyer en cas d'erreur
try {
await unlink(tmpPath).catch(() => {})
} catch {
/* Ignorer les erreurs de suppression */
}
console.error(`Failed to download image from ${url}:`, error.message)
throw error
}
}
function createDefaultBlurhash() {
// Un blurhash plus discret
return 'L5H2EC=H00~q^-=wD6xuxvV@%KxZ'
}
export async function generateBlurhash(
input: Buffer | string,
componentX: number = 4,
componentY: number = 3
): Promise<string> {
let tmpPath: string | null = null
try {
if (typeof input === 'string') {
if (input.startsWith('http')) {
try {
tmpPath = await downloadImage(input)
input = tmpPath
} catch (error) {
console.warn(`Using default blurhash for ${input} due to download error`)
return createDefaultBlurhash()
}
}
// Vérifier si le fichier existe
try {
const image = sharp(input).resize(32, 32, { fit: 'inside' }).ensureAlpha()
const { data, info } = await image.raw().toBuffer({ resolveWithObject: true })
return encode(new Uint8ClampedArray(data), info.width, info.height, componentX, componentY)
} catch (error) {
console.warn(`Error processing image ${input}:`, error.message)
return createDefaultBlurhash()
}
} else {
// Si c'est déjà un Buffer
try {
const image = sharp(input).resize(32, 32, { fit: 'inside' }).ensureAlpha()
const { data, info } = await image.raw().toBuffer({ resolveWithObject: true })
return encode(new Uint8ClampedArray(data), info.width, info.height, componentX, componentY)
} catch (error) {
console.warn('Error processing image buffer:', error.message)
return createDefaultBlurhash()
}
}
} catch (error) {
console.error('Unexpected error in generateBlurhash:', error.message)
return createDefaultBlurhash()
} finally {
// Nettoyer le fichier temporaire s'il existe
if (tmpPath) {
try {
await unlink(tmpPath).catch(() => {})
} catch {
/* Ignorer les erreurs de suppression */
}
}
}
}