veza/fixtures/core/generators/audio.ts
2025-12-03 22:56:50 +01:00

500 lines
No EOL
17 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid'
import { vezaFaker } from '../utils/faker-config'
import { globalConfig } from '../config'
import type {
Audio,
AudioGenre,
AudioMetadata,
Playlist,
PlaylistVisibility
} from '../schemas/database'
/**
* Audio generation options
*/
export interface AudioGenerationOptions {
genre?: AudioGenre
uploadedById?: string
albumId?: string
albumTitle?: string
isPublic?: boolean
isExplicit?: boolean
downloadEnabled?: boolean
withWaveform?: boolean
withCover?: boolean
withLyrics?: boolean
isPopular?: boolean
releaseDate?: Date
}
/**
* Album data structure
*/
export interface Album {
id: string
title: string
artist: string
genre: AudioGenre
releaseDate: Date
coverImageUrl?: string
tracks: Audio[]
totalDuration: number
isExplicit: boolean
}
/**
* Playlist generation options
*/
export interface PlaylistGenerationOptions {
createdById?: string
trackCount?: number
visibility?: PlaylistVisibility
isCollaborative?: boolean
withCover?: boolean
genre?: AudioGenre
theme?: 'workout' | 'chill' | 'party' | 'focus' | 'random'
}
/**
* Advanced audio content generator for Veza Platform
*/
export class AudioGenerator {
private static generatedTracks: Map<string, Audio> = new Map()
private static generatedAlbums: Map<string, Album> = new Map()
private static generatedPlaylists: Map<string, Playlist> = new Map()
private static usedSlugs: Set<string> = new Set()
/**
* Generate a single audio track
*/
static generate(options: AudioGenerationOptions = {}): Audio {
const genre = options.genre || vezaFaker.music.genre()
const title = vezaFaker.music.songTitle()
const artist = vezaFaker.music.artistName()
const slug = this.generateUniqueSlug(title, artist)
const metadata = vezaFaker.music.audioMetadata(genre)
const createdAt = vezaFaker.date.contentCreation(new Date('2024-01-01'))
const releaseDate = options.releaseDate || vezaFaker.date.releaseDate()
const track: Audio = {
id: uuidv4(),
title,
slug,
artist,
albumId: options.albumId,
albumTitle: options.albumTitle,
genre,
subgenre: this.generateSubgenre(genre),
tags: vezaFaker.music.tags(),
description: this.generateTrackDescription(title, artist, genre),
lyrics: options.withLyrics !== false ? this.generateLyrics() : undefined,
metadata,
waveformData: options.withWaveform !== false ? vezaFaker.music.waveformData(metadata.duration) : undefined,
fileUrl: vezaFaker.utils.fileUrl('audio', `${slug}.${metadata.format}`),
coverImageUrl: options.withCover !== false ? vezaFaker.utils.fileUrl('image', `${slug}-cover.jpg`) : undefined,
uploadedById: options.uploadedById || uuidv4(), // Will be replaced with real user ID
isPublic: options.isPublic ?? vezaFaker.datatype.boolean({ probability: 0.8 }),
isExplicit: options.isExplicit ?? vezaFaker.datatype.boolean({ probability: 0.1 }),
downloadEnabled: options.downloadEnabled ?? vezaFaker.datatype.boolean({ probability: 0.3 }),
stats: vezaFaker.stats.audioStats(createdAt, options.isPopular),
releaseDate,
createdAt,
updatedAt: vezaFaker.date.between({ from: createdAt, to: new Date() }),
}
this.generatedTracks.set(track.id, track)
return track
}
/**
* Generate multiple tracks
*/
static generateBatch(count: number, options: AudioGenerationOptions = {}): Audio[] {
const tracks: Audio[] = []
for (let i = 0; i < count; i++) {
tracks.push(this.generate(options))
}
return tracks
}
/**
* Generate a complete album with tracks
*/
static generateAlbum(trackCount: number = 10, options: Partial<AudioGenerationOptions> = {}): Album {
const albumId = uuidv4()
const albumTitle = vezaFaker.music.albumTitle()
const artist = vezaFaker.music.artistName()
const genre = options.genre || vezaFaker.music.genre()
const releaseDate = options.releaseDate || vezaFaker.date.releaseDate()
const isExplicit = options.isExplicit ?? vezaFaker.datatype.boolean({ probability: 0.15 })
const tracks: Audio[] = []
let totalDuration = 0
for (let i = 0; i < trackCount; i++) {
const track = this.generate({
...options,
albumId,
albumTitle,
genre: i === 0 ? genre : (vezaFaker.datatype.boolean({ probability: 0.7 }) ? genre : vezaFaker.music.genre()),
uploadedById: options.uploadedById || uuidv4(),
isExplicit: isExplicit || vezaFaker.datatype.boolean({ probability: 0.05 }),
releaseDate,
})
// Override artist for consistency (80% same artist, 20% featuring)
if (vezaFaker.datatype.boolean({ probability: 0.8 })) {
track.artist = artist
} else {
track.artist = `${artist} feat. ${vezaFaker.music.artistName()}`
}
tracks.push(track)
totalDuration += track.metadata.duration
}
const album: Album = {
id: albumId,
title: albumTitle,
artist,
genre,
releaseDate,
coverImageUrl: vezaFaker.utils.fileUrl('image', `${albumId}-album-cover.jpg`),
tracks,
totalDuration,
isExplicit
}
this.generatedAlbums.set(albumId, album)
return album
}
/**
* Generate multiple albums
*/
static generateAlbums(count: number, options: Partial<AudioGenerationOptions> = {}): Album[] {
const albums: Album[] = []
for (let i = 0; i < count; i++) {
const trackCount = vezaFaker.number.int({ min: 3, max: 15 })
albums.push(this.generateAlbum(trackCount, options))
}
return albums
}
/**
* Generate a playlist
*/
static generatePlaylist(options: PlaylistGenerationOptions = {}): Playlist {
const trackCount = options.trackCount || vezaFaker.number.int({ min: 5, max: 50 })
const theme = options.theme || vezaFaker.helpers.arrayElement(['workout', 'chill', 'party', 'focus', 'random'])
const title = this.generatePlaylistTitle(theme)
const slug = this.generateUniqueSlug(title)
const createdAt = vezaFaker.date.contentCreation(new Date('2024-01-01'))
// Generate track IDs (will be replaced with real track IDs)
const trackIds = Array.from({ length: trackCount }, () => uuidv4())
const playlist: Playlist = {
id: uuidv4(),
title,
slug,
description: this.generatePlaylistDescription(title, theme),
coverImageUrl: options.withCover !== false ? vezaFaker.utils.fileUrl('image', `${slug}-playlist.jpg`) : undefined,
createdById: options.createdById || uuidv4(),
visibility: options.visibility || vezaFaker.helpers.arrayElement(['public', 'unlisted', 'private']),
isCollaborative: options.isCollaborative ?? vezaFaker.datatype.boolean({ probability: 0.1 }),
tags: this.generatePlaylistTags(theme),
trackIds,
stats: {
followers: vezaFaker.number.int({ min: 0, max: 500 }),
plays: vezaFaker.number.int({ min: 0, max: 10000 }),
shares: vezaFaker.number.int({ min: 0, max: 100 }),
},
createdAt,
updatedAt: vezaFaker.date.between({ from: createdAt, to: new Date() }),
}
this.generatedPlaylists.set(playlist.id, playlist)
return playlist
}
/**
* Generate multiple playlists
*/
static generatePlaylists(count: number, options: PlaylistGenerationOptions = {}): Playlist[] {
const playlists: Playlist[] = []
for (let i = 0; i < count; i++) {
playlists.push(this.generatePlaylist(options))
}
return playlists
}
/**
* Generate genre-specific content
*/
static generateGenreCollection(genre: AudioGenre, trackCount: number = 20): {
tracks: Audio[]
albums: Album[]
playlists: Playlist[]
} {
// Generate individual tracks
const tracks = this.generateBatch(Math.floor(trackCount * 0.6), { genre })
// Generate albums
const albumCount = Math.floor(trackCount * 0.3 / 8) // Average 8 tracks per album
const albums = this.generateAlbums(albumCount, { genre })
// Add album tracks to tracks collection
albums.forEach(album => {
tracks.push(...album.tracks)
})
// Generate playlists
const playlistCount = Math.max(1, Math.floor(trackCount / 10))
const playlists = this.generatePlaylists(playlistCount, { genre })
return { tracks, albums, playlists }
}
/**
* Generate content for specific user roles
*/
static generateUserContent(userId: string, role: 'artist' | 'producer' | 'user'): {
tracks: Audio[]
albums: Album[]
playlists: Playlist[]
} {
const content = {
artist: () => {
const trackCount = vezaFaker.number.int({ min: 5, max: 30 })
const albumCount = vezaFaker.number.int({ min: 1, max: 4 })
const playlistCount = vezaFaker.number.int({ min: 2, max: 8 })
return {
tracks: this.generateBatch(trackCount, {
uploadedById: userId,
isPopular: vezaFaker.datatype.boolean({ probability: 0.3 })
}),
albums: this.generateAlbums(albumCount, { uploadedById: userId }),
playlists: this.generatePlaylists(playlistCount, { createdById: userId })
}
},
producer: () => {
const trackCount = vezaFaker.number.int({ min: 10, max: 50 })
const albumCount = vezaFaker.number.int({ min: 2, max: 6 })
const playlistCount = vezaFaker.number.int({ min: 5, max: 15 })
return {
tracks: this.generateBatch(trackCount, {
uploadedById: userId,
genre: vezaFaker.helpers.arrayElement(['electronic', 'hip-hop', 'pop']),
downloadEnabled: true
}),
albums: this.generateAlbums(albumCount, { uploadedById: userId }),
playlists: this.generatePlaylists(playlistCount, {
createdById: userId,
isCollaborative: vezaFaker.datatype.boolean({ probability: 0.3 })
})
}
},
user: () => ({
tracks: [],
albums: [],
playlists: this.generatePlaylists(vezaFaker.number.int({ min: 1, max: 5 }), {
createdById: userId,
visibility: vezaFaker.helpers.arrayElement(['private', 'unlisted', 'public'])
})
})
}
return content[role]()
}
/**
* Private helper methods
*/
private static generateUniqueSlug(title: string, artist?: string): string {
let baseSlug = vezaFaker.utils.slug(artist ? `${artist} ${title}` : title)
let slug = baseSlug
let counter = 1
while (this.usedSlugs.has(slug)) {
slug = `${baseSlug}-${counter}`
counter++
}
this.usedSlugs.add(slug)
return slug
}
private static generateSubgenre(genre: AudioGenre): string | undefined {
const subgenres: Record<AudioGenre, string[]> = {
rock: ['alternative', 'classic', 'indie', 'progressive', 'punk'],
pop: ['synth-pop', 'indie-pop', 'electro-pop', 'dance-pop'],
jazz: ['smooth', 'fusion', 'bebop', 'swing', 'contemporary'],
electronic: ['house', 'techno', 'ambient', 'dubstep', 'trance'],
'hip-hop': ['trap', 'boom-bap', 'conscious', 'mumble', 'old-school'],
classical: ['baroque', 'romantic', 'contemporary', 'chamber'],
country: ['modern', 'traditional', 'bluegrass', 'folk'],
blues: ['electric', 'acoustic', 'chicago', 'delta'],
reggae: ['roots', 'dancehall', 'dub', 'ska'],
folk: ['indie', 'traditional', 'contemporary', 'acoustic'],
metal: ['heavy', 'death', 'black', 'progressive', 'thrash'],
punk: ['hardcore', 'pop-punk', 'post-punk', 'ska-punk'],
indie: ['indie-rock', 'indie-folk', 'indie-electronic'],
ambient: ['dark', 'drone', 'new-age', 'cinematic'],
techno: ['minimal', 'acid', 'detroit', 'hard']
}
const options = subgenres[genre]
return options ? vezaFaker.helpers.arrayElement(options) : undefined
}
private static generateTrackDescription(title: string, artist: string, genre: AudioGenre): string {
const templates = [
`Nouveau single de ${artist} - ${title}. Un morceau ${genre} qui ne vous laissera pas indifférent.`,
`${title} par ${artist}. Une composition ${genre} originale et captivante.`,
`Découvrez ${title}, le dernier titre de ${artist} dans un style ${genre} moderne.`,
`${artist} nous présente ${title}, une pièce ${genre} pleine d'émotion.`,
`${title} - Une création ${genre} signée ${artist}. À écouter absolument !`
]
return vezaFaker.helpers.arrayElement(templates)
}
private static generateLyrics(): string {
const verses = [
"Dans la nuit étoilée, je marche seul\nMes pensées s'envolent vers l'horizon\nLa musique résonne dans mon cœur\nComme un écho de ma passion",
"Les mélodies dansent dans l'air\nPortées par le vent du soir\nChaque note raconte une histoire\nD'amour, de joie et d'espoir",
"Sur cette scène illuminée\nJe chante ma vérité\nLes mots s'échappent de mon âme\nPour toucher l'humanité"
]
return vezaFaker.helpers.arrayElements(verses, vezaFaker.number.int({ min: 1, max: 3 })).join('\n\n')
}
private static generatePlaylistTitle(theme: string): string {
const titles: Record<string, string[]> = {
workout: ['Motivation Max', 'Energie Workout', 'Cardio Beats', 'Pump It Up', 'Training Zone'],
chill: ['Détente Totale', 'Ambiance Chill', 'Relax Time', 'Zen Vibes', 'Sunday Morning'],
party: ['Party Mix', 'Dancefloor Hits', 'Soirée Endiablée', 'Club Anthems', 'Weekend Vibes'],
focus: ['Focus Mode', 'Concentration', 'Study Beats', 'Deep Work', 'Productivité'],
random: ['Mix du Jour', 'Découvertes', 'Playlist Perso', 'Mes Favoris', 'Random Play']
}
const themeOptions = titles[theme] || titles.random
return vezaFaker.helpers.arrayElement(themeOptions)
}
private static generatePlaylistDescription(title: string, theme: string): string {
const descriptions: Record<string, string[]> = {
workout: [
'Playlist parfaite pour vos séances de sport et d\'entraînement.',
'Des beats énergiques pour vous motiver pendant l\'effort.',
'La bande sonore idéale pour vos sessions fitness.'
],
chill: [
'Musiques relaxantes pour se détendre et décompresser.',
'Ambiance apaisante pour vos moments de repos.',
'Sons doux pour accompagner vos instants de tranquillité.'
],
party: [
'Hits incontournables pour faire la fête toute la nuit.',
'Morceaux dansants pour mettre l\'ambiance.',
'La playlist ultime pour vos soirées entre amis.'
],
focus: [
'Musiques instrumentales pour améliorer votre concentration.',
'Sons apaisants pour favoriser la productivité.',
'Ambiance sonore idéale pour le travail et les études.'
],
random: [
'Sélection éclectique de mes morceaux préférés.',
'Mix varié pour toutes les humeurs.',
'Découvertes musicales et coups de cœur.'
]
}
const themeDescriptions = descriptions[theme] || descriptions.random
return vezaFaker.helpers.arrayElement(themeDescriptions)
}
private static generatePlaylistTags(theme: string): string[] {
const tags: Record<string, string[]> = {
workout: ['sport', 'motivation', 'énergie', 'cardio', 'fitness'],
chill: ['détente', 'relaxation', 'calme', 'zen', 'ambient'],
party: ['fête', 'danse', 'club', 'soirée', 'hits'],
focus: ['concentration', 'étude', 'travail', 'instrumental', 'productivité'],
random: ['mix', 'varié', 'découverte', 'éclectique', 'personnel']
}
const themeTags = tags[theme] || tags.random
return vezaFaker.helpers.arrayElements(themeTags, vezaFaker.number.int({ min: 2, max: 4 }))
}
/**
* Utility methods
*/
static getGeneratedTrack(id: string): Audio | undefined {
return this.generatedTracks.get(id)
}
static getGeneratedAlbum(id: string): Album | undefined {
return this.generatedAlbums.get(id)
}
static getGeneratedPlaylist(id: string): Playlist | undefined {
return this.generatedPlaylists.get(id)
}
static getAllGeneratedTracks(): Audio[] {
return Array.from(this.generatedTracks.values())
}
static getAllGeneratedAlbums(): Album[] {
return Array.from(this.generatedAlbums.values())
}
static getAllGeneratedPlaylists(): Playlist[] {
return Array.from(this.generatedPlaylists.values())
}
static clearCache(): void {
this.generatedTracks.clear()
this.generatedAlbums.clear()
this.generatedPlaylists.clear()
this.usedSlugs.clear()
}
static getStats(): {
totalTracks: number
totalAlbums: number
totalPlaylists: number
byGenre: Record<AudioGenre, number>
} {
const tracks = this.getAllGeneratedTracks()
const byGenre: Record<AudioGenre, number> = {
rock: 0, pop: 0, jazz: 0, classical: 0, electronic: 0, 'hip-hop': 0,
country: 0, blues: 0, reggae: 0, folk: 0, metal: 0, punk: 0,
indie: 0, ambient: 0, techno: 0
}
tracks.forEach(track => {
byGenre[track.genre]++
})
return {
totalTracks: tracks.length,
totalAlbums: this.getAllGeneratedAlbums().length,
totalPlaylists: this.getAllGeneratedPlaylists().length,
byGenre
}
}
}