CLI tool used to import custom music into Crypt of the Necrodancer without being in-game.
- Downloads the provided media URL with
youtube-dl - Converts the media to
MP3format withffmpegor any other valid converter - Beat-maps the audio file with
beattrackerbased onessentia(shipped with the game on Windows); alternatively, uses a static beats-per-minute value - Edits the game's save file to add dummy symlinks - all at once (only happens when run for the first time)
- Creates a symlink for the music file in
./symlinks<==>./music - Creates a symlink for the beatmap file in
./beatmaps<==>(game-dir)/data/custom_music/
Remarks:
- Audio files are stored in
./music/ - Beatmap files are stored in
./beatmaps - Since the script is using symlinks, the music can be swapped:
- when the game is not running
- ingame, in lobby
- ingame, in a different level
- On Windows, in order to create symlinks, admin rights are required.
Alternatively, the
SeCreateSymbolicLinkPrivilegeprivilege needs to be allowed for the current user. More info: StackExchange discussion
Exported members:
const {
beatmap,
detectSaveFile,
downloadMedia,
fullProcess,
fetchPath,
getMediaInfo,
prepareZoneSymlinks,
removeZoneSymlinks,
resetZones,
SaveFileEditor
} = require("necrodancer-custom-music");Fully processes the provided link - downloads + beatmaps + saves it into the game save file into a given zone.
Arguments:
{string} options.gameDirGame directory path.{string} options.linkMedia link path - needs to be compatible with youtube-dl.{string} options.zoneGame zone identifier.boolean} [options.backupSaveFile]If true explicitly, will create backups of save file.{boolean} [options.prepareAllSymlinks]If false explicitly, will not attempt to create symlinks for music in all zones.{string} [options.saveFile]Optional full path to game's save file. If not provided, one is detected automatically.{boolean} [options.forceDownload]If true, the audio file will be re-downloaded even if it is present already{boolean} [options.forceBeatmap]If true, the audio file will be beatmapped even if the beatmap file is present already{number} [options.bpm]When not using auto bpm detection, this is the song's BPM measure{number} [options.offset]When not using auto bpm detection, this is the song's BPM shift forward in seconds{string} [options.beatmapExecutable]When not using auto bpm detection, this is the path to the auto-detect executable
Returns:
- nothing
Creates a beatmap file for a provided audio file and path.
Arguments:
{Object} options{string} [options.beatmapExecutable]Path to the beatmapping executable{string} options.fileNameAudio file name{string} options.filePathPath to the audio file to beatmap{number} [options.bpm]Static BPM - used when no executable is provided{number} [options.offset]Static BPM - offset - shift forward, in seconds
Returns:
{Object} result{string} result.filePathResulting beatmap file name{"auto"|"manual"} result.beatsConfirmation of what BPM mapping method was used
Detects a save file within the game's install directory.
Arguments:
{string} gameDirGame install directory
Returns:
{string|null} pathFull savefile path, ornull, if none were found
Downloads the audio file based on a URL, using youtube-dl.
Arguments:
{string} linkMedia file URL{Object} options{string} [options.filePath]If provided, this is the path the resulting audio file will be saved to. Determined automatically if not present.{boolean} [options.force]If true, the audio will be re-downloaded even if it exists already.
Returns:
{*} resultyoutube-dl download result{*} infoyoutube-dl get-video-info result{string|null} filePathResult audio file path{boolean} skippedDetermines if the download was skipped
Small utils method for non-throwing check if a file/directory exists.
Arguments:
{string} path
Returns:
{*|null}result offs.statif file exists; if it doesn't,nullinstead
Simple async wrapper for youtube-dl.getInfo.
Arguments:
{string} linkmedia URL
Returns:
{Object} resultreturn value ofyoutube-dl.getInfo
Edits the game save file, so that all zones' music will point to symlinks. This is so no more editing of the save files is necessary later.
Arguments
{string} saveFilePathfull save file path
Returns
- nothing
Removes symlinks for each provided zone.
Arguments
{...string} ...zonesZones to as a list of arguments
Returns
- nothing
Resets one or more zone music to "none" - the default null value in game * @param * @param
Arguments
{string} saveFilePathfull save file path{string[]} zoneslist of zones to reset. if the first value is "all", resets all zones
Returns
- nothing
Works with a single save file, and edits some of its attributes. Acts as a wrapper around the save file's XML structure.
- clone/fork the repo
- run
npmoryarn - run
npm run initoryarn run init - edit
config.jsonand fill in thedirectoryproperty with your Crypt of the Necrodancer install directory - make sure to back up your game save file before proceeding - in case it needs to be rolled back
- run script with
node ./crypt.jsor./crypt.js(with shebang)
directory- path to Crypt of the NecroDancer install directorybeatmapExecutable- path to the beat-mapping executable (note that relevant argument has higher priority than config value)
./crypt.js (video-url) (zone-id) [-h|--help] [--bpm] [--offset] [--force-reload] [--beat-tracker]
-h,--helpPrints a simple usage help(video-url)First argument - media video link to be downloaded, beat-mapped and added to the game(zone-id)Second argument - game zone identifier--bpm=#If provided, the beat will be static, and generated from the provided BPM number--offset=#If provided along with--bpm, the beats will be shifted forward by this offset - provided in seconds--force-reloadRe-downloads the audio file, and re-runs beat mapping even if the files are already present--beat-trackerWhen using automatic beat mapping, this is the path to the beat-mapping executable. If not provided, uses the default one found on Windows in(game-dir)/data/essentia/beattracker.exe--debugEnables debug logging
Each song can be bound to exactly one game zone track per execution.
The full list of identifiers can be found in ./zone-map.json, and edited - if this is desired
These follow the identifier format (zone)-(level), ranging from 1-1 through 5-3.
Boss areas can use either of multiple identifiers:
| Boss | Full | Simple | Code |
|---|---|---|---|
| King Conga Kappa | king-conga |
conga |
boss-1 |
| Deep Blues | deep-blues |
chess |
boss-2 |
| Death Metal | death-metal |
metal |
boss-3 |
| Coral Riff | coral-riff |
coral |
boss-4 |
| Fortissimole | fortissimole |
mole |
boss-5 |