Skip to content

Instantly share code, notes, and snippets.

@TheSavior
Created October 1, 2023 22:53
Show Gist options
  • Save TheSavior/2130aa5fc797c19256293d775233b1a3 to your computer and use it in GitHub Desktop.
Save TheSavior/2130aa5fc797c19256293d775233b1a3 to your computer and use it in GitHub Desktop.
Spotify : Chorus crosschecking
import fs from 'fs';
import { parse } from 'csv-parse';
import path from 'path';
import os from 'node:os';
const CLONE_HERO_SONGS_FOLDER = path.join(os.homedir(), 'Clone Hero', 'Songs');
const CHORUS_DUMP = new URL('Chorus_Dump-export_feb_15_2023.csv', import.meta.url).pathname;
const SPOTIFY_DATA_DUMP_FOLDER = new URL('SpotifyData-Aug-26', import.meta.url).pathname;
async function run() {
const artistTrackPlays = createPlaysMapOfSpotifyData();
const isInstalled = await createIsInstalledFilter();
const notInstalledSongs = await filterAndGroupCharts(artistTrackPlays, isInstalled);
const results = sortAndFilterCharts(notInstalledSongs);
results.forEach(result => {
console.log(`${result.spotifyPlayCount}, ${result.artist}, ${result.song}, ${result.recommendedChart.link}`)
});
}
run();
function sortAndFilterCharts(artistTrackChartData) {
/* {
spotifyPlayCount: 10,
artist: "There For Tomorrow"
song: "A LIttle Faster"
recommendedChart: Chart
}
*/
const results = [];
for (const [artist, songs] of artistTrackChartData) {
for (const [song, {plays, charts}] of songs) {
let recommendedChart = charts[0];
for (let chartIndex = 1; chartIndex < charts.length; chartIndex++) {
const chart = charts[chartIndex];
// Prefer newer charts from the same charter
if (chart.charter == recommendedChart.charter && new Date(chart.uploadedAt) < new Date(recommendedChart.uploadedAt)) {
continue;
}
// Prefer Harmonix
if (recommendedChart.charter == 'Harmonix' && chart.charter != 'Harmonix') {
continue;
}
// Prefer official tracks
if (['Harmonix', 'Neversoft'].includes(recommendedChart.charter)) {
continue;
}
// Prefer charts with drums
if (recommendedChart.diff_drums != '' && chart.diff_drums == '') {
continue;
}
recommendedChart = chart;
}
if (charts.length == 1) {
results.push({
spotifyPlayCount: plays,
artist,
song,
recommendedChart,
});
continue;
}
}
}
results.sort((a, b) => {
return a.spotifyPlayCount - b.spotifyPlayCount
})
return results;
}
async function filterAndGroupCharts(spotifyData, isInstalledFilter) {
const notInstalledSongs = new Map();
await scanChorusDump(async (track) => {
const artistTracks = spotifyData.get(track.artist);
if (artistTracks == null) {
return;
}
const trackPlays = artistTracks.get(track.name);
if (trackPlays == null) {
return;
}
// Check if it matches song difficulty / track requirements
if (!(track.diff_guitar >= 8 || track.diff_bass >= 8 || track.diff_drums >= 8)) {
return;
}
if (!isInstalledFilter(track.artist, track.name)) {
if (notInstalledSongs.get(track.artist) == null) {
notInstalledSongs.set(track.artist, new Map());
}
if (notInstalledSongs.get(track.artist).get(track.name) == null) {
notInstalledSongs.get(track.artist).set(track.name, {plays: trackPlays, charts: []});
}
notInstalledSongs.get(track.artist).get(track.name).charts.push(track);
}
});
return notInstalledSongs;
}
async function createIsInstalledFilter() {
const installedSongs = fs.readdirSync(CLONE_HERO_SONGS_FOLDER, {withFileTypes: true})
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
const installedArtistsSongs = new Map();
for (const folderName of installedSongs) {
const [artist, song] = folderName.split(' - ').map(s => s.trim());
if (installedArtistsSongs.get(artist) == null) {
installedArtistsSongs.set(artist, []);
}
installedArtistsSongs.get(artist).push(song);
}
return function isInstalled(artist, song) {
const artistSongs = installedArtistsSongs.get(artist);
if (artistSongs == null) {
return false;
}
if (artistSongs.includes(song)) {
return true;
}
return false;
}
}
async function scanChorusDump(processTrack) {
const stream = fs
.createReadStream(CHORUS_DUMP)
.pipe(parse({
columns: true
}));
for await (const track of stream) {
await processTrack(track);
}
}
function createPlaysMapOfSpotifyData() {
const artistsTracks = new Map();
const spotifyHistoryFiles = fs.readdirSync(SPOTIFY_DATA_DUMP_FOLDER).filter(f => f.endsWith('.json'));
for (const spotifyHistoryFile of spotifyHistoryFiles) {
const history = JSON.parse(fs.readFileSync(path.join(SPOTIFY_DATA_DUMP_FOLDER, spotifyHistoryFile), 'utf8'));
for (const song of history) {
if (song.reason_end == 'fwdbtn') {
continue;
}
const artist = song.master_metadata_album_artist_name;
const track = song.master_metadata_track_name;
let tracksPlays = artistsTracks.get(artist);
if (tracksPlays == null) {
tracksPlays = new Map();
artistsTracks.set(artist, tracksPlays);
}
tracksPlays.set(track, (tracksPlays.get(track) ?? 0) + 1);
}
}
return artistsTracks;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment