120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
import { readdir, readFile } from 'node:fs/promises'
|
|
import { join, extname, basename } from 'node:path'
|
|
import { createHash } from 'node:crypto'
|
|
import { slugify } from './slugify'
|
|
import { getCardFromDate } from './getCardFromDate'
|
|
import type { Card } from '@/types/types'
|
|
|
|
const listAudioExts = ['.mp3', '.opus', 'flac']
|
|
const listImageExts = ['.jpg', '.jpeg', '.webp']
|
|
|
|
export async function scanMusicFolder(folderPath: string): Promise<Card[]> {
|
|
try {
|
|
const files = await readdir(folderPath)
|
|
const cardMap = new Map<string, Card>()
|
|
|
|
// D'abord, on traite tous les fichiers audio
|
|
for (const file of files) {
|
|
const ext = extname(file).toLowerCase()
|
|
|
|
// On ne traite que les fichiers audio
|
|
if (!listAudioExts.includes(ext)) continue
|
|
|
|
const parsed = await parseFilename(join(folderPath, file))
|
|
if (parsed) {
|
|
// On vérifie s'il existe une image avec le même nom de base
|
|
const baseName = basename(file, ext)
|
|
let imageUrl = ''
|
|
|
|
// On cherche une image correspondante
|
|
for (const imgExt of listImageExts) {
|
|
const potentialImage = baseName + imgExt
|
|
if (files.includes(potentialImage)) {
|
|
imageUrl = process.env.URL_PREFIX + baseName + imgExt
|
|
break
|
|
}
|
|
}
|
|
|
|
cardMap.set(parsed.esid, {
|
|
...parsed,
|
|
url_audio: process.env.URL_PREFIX + baseName + ext,
|
|
url_image: imageUrl,
|
|
suit: parsed.suit,
|
|
rank: parsed.rank
|
|
})
|
|
}
|
|
}
|
|
|
|
return Array.from(cardMap.values())
|
|
} catch (error) {
|
|
console.error('Erreur lors du scan du dossier:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async function parseFilename(
|
|
filename: string
|
|
): Promise<Omit<Card, 'url_audio' | 'url_image'> | null> {
|
|
// Format: yyyymmddhh__artist__title.ext
|
|
const nameWithoutExt = basename(filename, extname(filename))
|
|
const parts = nameWithoutExt.split('__')
|
|
|
|
if (parts.length !== 3) {
|
|
console.warn(`Nom de fichier invalide: ${filename}`)
|
|
return null
|
|
}
|
|
|
|
const [datetime, artist, title] = parts
|
|
|
|
if (!datetime || !artist || !title) {
|
|
console.warn(`Format de fichier invalide: ${filename} - manque des parties`)
|
|
return null
|
|
}
|
|
|
|
if (datetime.length !== 10) {
|
|
console.warn(`Format de date invalide: ${filename}`)
|
|
return null
|
|
}
|
|
|
|
// Utilisation d'un hash basé sur le contenu du fichier pour un ESID stable
|
|
let fileHash = ''
|
|
try {
|
|
const fileContent = await readFile(filename)
|
|
fileHash = createHash('md5').update(fileContent).digest('hex').substring(0, 8)
|
|
} catch (error) {
|
|
console.warn(`Impossible de lire le fichier pour générer le hash: ${filename}`)
|
|
fileHash = createHash('md5').update(filename).digest('hex').substring(0, 8)
|
|
}
|
|
|
|
const year = datetime.substring(0, 4)
|
|
const month = datetime.substring(4, 6)
|
|
const day = datetime.substring(6, 8)
|
|
const hour = datetime.substring(8, 10)
|
|
// Créer l'ID unique pour la card
|
|
const esid = createHash('md5')
|
|
.update(`${year}${month}${day}${hour}${artist}${title}`)
|
|
.digest('hex')
|
|
|
|
const date = new Date(
|
|
parseInt(year, 10),
|
|
parseInt(month, 10) - 1,
|
|
parseInt(day, 10),
|
|
parseInt(hour, 10)
|
|
)
|
|
const card = getCardFromDate(date)
|
|
|
|
return {
|
|
year,
|
|
month,
|
|
day,
|
|
hour,
|
|
artist: artist.replace(/_/g, ' '), // Remplacer les _ par des espaces
|
|
title: title.replace(/_/g, ' '),
|
|
esid,
|
|
slug: slugify(`${artist} ${title}`),
|
|
createdAt: date,
|
|
suit: card.suit,
|
|
rank: card.rank
|
|
}
|
|
}
|