Skip to content

Instantly share code, notes, and snippets.

@KL13NT
Last active January 1, 2022 05:57
Show Gist options
  • Save KL13NT/91f18d9ca1609a2b29d472c9f1c84391 to your computer and use it in GitHub Desktop.
Save KL13NT/91f18d9ca1609a2b29d472c9f1c84391 to your computer and use it in GitHub Desktop.
Spotify-dl: A NodeJS script to download spotify tracks from youtube. Depends on node-fetch and youtube-dl.
@echo off
SET CLIENT_ID=
SET CLIENT_SECRET=
SET YOUTUBE_KEY=
node PATH\TO\SCRIPT\spotify-dl.js %1
rem %1 track link
rem this file may be added to your env to quickly execute the script anywhere on your pc.
// # About
// This script downloads spotify tracks as mp3 files.
// Please state the source if your share this.
//
// MAKE SURE TO ADD CLIENT_ID, CLIENT_SECRET, AND YOUTUBE_KEY TO YOUR ENV. MORE
// INFO BELOW!
//
// # Getting Started
// This script depends on youtube-dl and node-fetch.
// > npm i -g node-fetch
// > http://ytdl-org.github.io/youtube-dl/download.html
//
// The script first authorises with Spotify then tries to fetch the track you
// passed based on the parsed track id. If it's successful it'll authenticate
// with YouTube to search youtube for the title of the track, and using the
// first result passed the found video URL to youtube-dl. Youtube-dl downloads
// and transforms the audio to the popular mp3.
// https://github.com/kl13nt
/**
* @param {string} CLIENT_ID Your spotify app client id
* @param {string} CLIENT_SECRET Your spotify app client secret
* @link https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow
*
* @param {string} YOUTUBE_KEY Your youtube api key
* @link https://developers.google.com/youtube/v3/guides/auth/server-side-web-apps#prerequisites
*/
const { CLIENT_ID, CLIENT_SECRET, YOUTUBE_KEY } = process.env
const child = require('child_process')
const path = require('path')
const fetch = require('node-fetch')
const terminateWithError = (error = '[fatal] error') => {
console.log(error)
process.exit(1)
}
const params = process.argv.slice(2)
if (params.length < 1) terminateWithError('[fatal] need spotify track url')
const id = params[0].match(
/https:\/\/open?\.?spotify\.com\/track\/(\S+)\?\S+/i
)[1]
const DOWNLOADS = 'C:\\Users\\<USER_NAME>\\Downloads'
const SPOTIFY_AUTH = 'https://accounts.spotify.com/api/token'
const SPOTIFY_API = 'https://api.spotify.com'
const WATCH = 'https://www.youtube.com/watch?v='
let name = ''
let output = ''
fetch(`${SPOTIFY_AUTH}?grant_type=client_credentials`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization:
'Basic ' + Buffer.from(CLIENT_ID + ':' + CLIENT_SECRET).toString('base64')
}
})
.then(async res => {
if (!res.ok) {
terminateWithError(`[fatal] spotify auth status ${res.status}`)
}
console.log('[info] spotify auth successful')
return res.json()
})
.then(auth => {
// https://github.com/spotify/web-api-auth-examples/blob/master/client_credentials/app.js#L31
const token = auth.access_token
const options = {
headers: {
Authorization: 'Bearer ' + token
}
}
return fetch(`${SPOTIFY_API}/v1/tracks/${id}`, options)
})
.then(res => {
if (!res.ok) terminateWithError(`[fatal] spotify status ${res.status}`)
return res.json()
})
.then(track => {
if (!track) terminateWithError('[fatal] track not found')
name = encodeURIComponent(track.name)
const YOUTUBE_API = `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=${name}&key=${YOUTUBE_KEY}`
return fetch(YOUTUBE_API)
})
.then(res => {
if (!res.ok) terminateWithError(`[fatal] youtube status ${res.status}`)
return res.json()
})
.then(json => {
const { videoId } = json.items[0].id
console.log(`[info] Fetching track ${WATCH}${videoId}`)
output = path.resolve(DOWNLOADS, name).replace(/%20/gi, '_')
const cmd = `youtube-dl`
const args = [
'-f',
'bestaudio',
'--extract-audio',
'--audio-format',
'mp3',
'--audio-quality',
'0',
'-o',
`"${output}.%(ext)s"`,
`${WATCH}${videoId}`
]
console.log(`[info] ${cmd} ${args.join(' ')}`)
const ydl = child.spawn(cmd, args)
ydl.stdout.on('data', data => {
console.log(`[info] ${data}`)
})
ydl.stderr.on('data', data => {
console.log(`[fatal] ${data}`)
})
ydl.on('close', code => {
console.log(`[info] youtube_dl exited with code ${code}`)
})
})
.catch(error => terminateWithError(`[fatal] ${error} ${error.stack}`))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment