-
Notifications
You must be signed in to change notification settings - Fork 2
/
spotydrive-sync-playlist.js
154 lines (122 loc) · 3.91 KB
/
spotydrive-sync-playlist.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
const program = require('commander')
const request = require('request-promise')
const YoutubeMp3Downloader = require('youtube-mp3-downloader')
const path = require('path')
const fs = require('fs-extra')
const Multiprogress = require('multi-progress')
const state = require('./state')
const createSyncState = require('./sync-state')
const homeDir = require('os').homedir()
const syncStatePath = path.join(homeDir, 'spotydrive')
const syncState = createSyncState(syncStatePath)
program.parse(process.argv)
const [ playlistUri ] = program.args
const fetchPlaylist = uri => {
const accessToken = state.get('spotify.token.access_token').value()
// spotify:user:spotify:playlist:37i9dQZF1DWTlgzqHpWg4m
const [ , , username, , playlistId ] = playlistUri.split(':')
const options = {
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
return request(`https://api.spotify.com/v1/users/${username}/playlists/${playlistId}`, options)
.then(response => JSON.parse(response))
.then(playlist => ({
uri: playlist.uri,
name: playlist.name,
tracks: playlist.tracks.items.map(item => ({
uri: item.track.uri,
name: item.track.name,
artists: item.track.artists.map(artist => artist.name)
}))
}))
}
const matchYouTubeVideo = track => {
const accessToken = state.get('youtube.token.access_token').value()
const { name, artists } = track
const options = {
headers: {
'Authorization': `Bearer ${accessToken}`
},
qs: {
q: `${name} - ${artists.join(', ')}`,
part: 'snippet',
maxResults: 1,
order: 'relevance',
type: 'video'
}
}
return request('https://www.googleapis.com/youtube/v3/search', options)
.then(response => JSON.parse(response))
.then(response => response.items[0])
.then(video => ({
...track,
video: {
id: video.id.videoId,
title: video.snippet.title
}
}))
}
const sync = playlist => {
const { uri, name } = playlist
const getSyncInfo = () => syncState.get(`sync.${uri}`).value()
const create = () => {
syncState.set(`sync.${uri}`, { uri, name, tracks: [] }).write()
fs.ensureDirSync(path.join(syncStatePath, name))
return true
}
const playlistSync = getSyncInfo() || (create() && getSyncInfo())
const tasks = playlist.tracks.filter(track => !playlistSync.tracks.some(t => t.uri === track.uri))
console.log(`Syncing playlist: ${name}`)
return Promise.all(tasks.map(matchYouTubeVideo))
.then(tracks => ({ ...playlist, tracks }))
}
const download = matched => {
const { uri, name, tracks } = matched
const YD = new YoutubeMp3Downloader({
outputPath: path.join(syncStatePath, name),
youtubeVideoQuality: 'highest',
queueParallelism: 3,
progressTimeout: 2000
})
const multi = Multiprogress(process.stderr)
const onFinished = {}
const onProgress = {}
tracks.map(function (track) {
const trackDisplay = `${track.name} - ${track.artists.join(', ')}`
YD.download(track.video.id, `${track.name} - ${track.artists.join(', ')}.mp3`)
let bar
onProgress[track.video.id] = progress => {
if (!bar)
bar = multi.newBar(` downloading ${trackDisplay} [:bar] :percent :etas `, {
total: progress.progress.length,
curr: progress.progress.percentage,
complete: '=',
incomplete: ' ',
width: 20
})
else
bar.tick(progress.progress.delta)
}
onFinished[track.video.id] = (err, data) => {
if (err) throw console.error()
syncState.get(`sync.${uri}.tracks`)
.push(track)
.write()
bar.tick(bar.total - bar.curr)
}
})
YD.on('finished', (err, data) => {
onFinished[data.videoId](err, data)
})
YD.on('progress', progress => {
onProgress[progress.videoId](progress)
})
YD.on('error', (err) => {
console.debug(err)
})
}
fetchPlaylist(playlistUri)
.then(sync)
.then(download)