Files
brainfm-extractor/main.ts

327 lines
6.7 KiB
TypeScript

import * as fs from 'fs';
import * as https from 'https';
import chalk from 'chalk';
import * as dotenv from 'dotenv';
dotenv.config();
let songCount = 1;
const AUTHTOKEN = process.env.AUTHTOKEN;
if (!AUTHTOKEN) {
console.error('Please enter all the required information');
process.exit(1);
}
type MentalState = "focus" | "relax" | "sleep" | "meditate";
type MentalStateArray = MentalState[];
let mentalStates: MentalStateArray = ["focus", "relax", "sleep", "meditate"];
type GenreStructure = {
base: string[];
nature: string[];
};
type GenresMap = Record<MentalState, GenreStructure>;
let genres: GenresMap = {
"focus": {
base: [
"Acoustic",
"Atmospheric",
"Cinematic",
"Classical",
"Drone",
"Electronic",
"Grooves",
"Lofi",
"Piano",
"Post Rock"
],
nature: [
"Beach",
"Chimes & Bowls",
"Forest",
"Nightsounds",
"Rain",
"Rainforest",
"River",
"Thunder",
"Underwater",
"Wind"
]
},
"relax": {
base: [
"Atmospheric",
"Electronic"
],
nature: [
"Beach",
"Chimes & Bowls",
"Forest",
"Nightsounds",
"Rain",
"Rainforest",
"River",
"Thunder",
"Underwater",
"Wind"
]
},
"sleep": {
base: [
"Atmospheric"
],
nature: [
"Beach",
"Forest",
"Nightsounds",
"Rain",
"Rainforest",
"River",
"Thunder",
"Underwater",
"Wind"
]
},
"meditate": {
base: [
"Atmospheric",
"Electronic"
],
nature: [
"Beach",
"Chimes & Bowls",
"Forest",
"Nightsounds",
"Rain",
"Rainforest",
"River",
"Thunder",
"Underwater",
"Wind"
]
}
};
// Commenting out moods since it's not used in the code
/*
type MoodsMap = Record<MentalState, string[]>;
let moods: MoodsMap = {
"focus": [
"Brooding",
"Calm",
"Chill",
"Dark",
"Downtempo",
"Dreamlike",
"Driving",
"Energizing",
"Epic",
"Floating",
"Heavy",
"Hopeful",
"Inspiring",
"Meditative",
"Mysterious",
"Ominous",
"Optimistic",
"Playful",
"Ponderous",
"Serene",
"Strong",
"Upbeat",
"Uplifting"
],
"relax": [
"Brooding",
"Calm",
"Chill",
"Dark",
"Downtempo",
"Dreamlike",
"Driving",
"Energizing",
"Epic",
"Floating",
"Hopeful",
"Inspiring",
"Meditative",
"Mysterious",
"Optimistic",
"Playful",
"Ponderous",
"Serene",
"Strong",
"Upbeat",
"Uplifting"
],
"sleep": [
"Brooding",
"Calm",
"Chill",
"Dark",
"Dreamlike",
"Epic",
"Floating",
"Heavy",
"Meditative",
"Mysterious",
"Optimistic",
"Ponderous",
"Serene",
"Strong"
],
"meditate": [
"Brooding",
"Calm",
"Chill",
"Dark",
"Downtempo",
"Dreamlike",
"Driving",
"Energizing",
"Epic",
"Floating",
"Heavy",
"Hopeful",
"Inspiring",
"Meditative",
"Mysterious",
"Optimistic",
"Playful",
"Ponderous",
"Serene",
"Strong",
"Upbeat",
"Uplifting"
]
}
*/
type QueueEntry = {
url: string;
folder: string;
filename: string;
};
class DownloadQueue {
private queue: QueueEntry[] = [];
private activeDownloads = 0;
private maxConcurrency: number
constructor(maxConcurrency: number) {
this.maxConcurrency = maxConcurrency
}
public enqueue(url: string, folder: string, filename: string) {
this.queue.push({ url, folder, filename });
this.processQueue();
}
private async processQueue(): Promise<void> {
while (this.activeDownloads < this.maxConcurrency && this.queue.length > 0) {
const { url, folder, filename } = this.queue.shift()!;
this.activeDownloads++;
this.processDownload(url, folder, filename).then(() => {
this.activeDownloads--;
this.processQueue();
}).catch((error) => {
console.error('Error downloading song:', error);
this.activeDownloads--;
this.processQueue();
});
}
}
private async processDownload(url: string, folder: string, filename: string): Promise<void> {
await downloadSong(url, folder, filename);
songCount++;
console.log(`${songCount} songs downloaded successfully \n${this.queue.length} remaining`);
}
}
const downloadQueue = new DownloadQueue(3)
for (const mentalState of mentalStates) {
console.log(chalk.red(`Starting mental state ${mentalState}`))
for (const genre of [...genres[mentalState].base, ...genres[mentalState].nature]) {
console.log(chalk.yellow(`Starting genre ${genre}`))
//
// Phase 1 : Fetch all song data
//
const response = await fetch(`https://api.brain.fm/v3/servings/search?genre=${genre}&dynamicMentalStateId=${mentalState}`, {
headers: {
authorization: `Bearer ${AUTHTOKEN}`
}
});
const responseData = await response.json();
const data = formatAudioData(responseData.result);
if (checkIfJsonExists(`./json-data/${mentalState}/${genre}.json`)) continue;
ensureDirectory(`./json-data/${mentalState}`);
let file = fs.createWriteStream(`./json-data/${mentalState}/${genre}.json`);
file.write(JSON.stringify(data));
file.close();
//
// Phase 2 : Download songs to device
//
console.log(chalk.green(`Started queuing ${genre} ${mentalState}`))
for (const song of data) {
let activity = song.track.tags.filter((x: any) => x.type == 'activity').map((x: any) => x.value).join('/');
let NEL = song.trackVariation.neuralEffectLevel;
let level = (NEL > 0.66 ? "high" : NEL > 0.33 ? "medium" : "low");
let folder = `./songs/${genre}/${mentalState}/${activity}/${level}`
let filename = song.trackVariation.baseUrl;
let downloadLink = song.trackVariation.tokenedUrl;
downloadQueue.enqueue(downloadLink, folder, filename);
}
}
};
function downloadSong(downloadLink: string, folder: string, filename: string): Promise<void> {
return new Promise((resolve, reject) => {
ensureDirectory(folder)
https.get(downloadLink, (response) => {
response.pipe(fs.createWriteStream(`${folder}/${filename}`))
.on('finish', () => {
resolve();
})
.on('error', (error: Error) => {
console.error('Error downloading song:', error);
reject(error);
});
});
});
}
function formatAudioData(arr: any[]) {
return arr.map(item => {
delete item.track.similarTracks;
return item;
});
}
function ensureDirectory(directory: string) {
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
}
function checkIfJsonExists(filePath: string) {
return fs.existsSync(filePath);
}