Last active
January 1, 2022 05:57
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// # 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