Compare commits
No commits in common. "eff3f8956cdb6b54dcd16085d4c9f8fdbc64f197" and "81d128694efa0b4972e76b73f440266232f01f12" have entirely different histories.
eff3f8956c
...
81d128694e
11
README.md
11
README.md
|
@ -10,12 +10,12 @@ Inspired by [DatMusic](https://github.com/alashow/datmusic-api), [MyFreeMp3](htt
|
||||||
|
|
||||||
*For now*
|
*For now*
|
||||||
|
|
||||||
* Scrape Spotify playlist page
|
* Parse a Spotify Playlist page
|
||||||
* Download all the titles from MyFreeMp3 aka VK Music
|
* Download a title from the playlist
|
||||||
* Generate a .m3u8 playlist file
|
|
||||||
|
|
||||||
*TODO*
|
*TODO*
|
||||||
|
|
||||||
|
* Download the html spotify page
|
||||||
* Dowload all songs in a folder
|
* Dowload all songs in a folder
|
||||||
* Add proper command line options
|
* Add proper command line options
|
||||||
* Document how to get a VK access token (need php and other things this
|
* Document how to get a VK access token (need php and other things this
|
||||||
|
@ -38,5 +38,6 @@ BEWARE WIP
|
||||||
|
|
||||||
1. `git clone THIS`
|
1. `git clone THIS`
|
||||||
1. `npm install`
|
1. `npm install`
|
||||||
1. (Optional) Only for direct VK search, fill the `.env` with a `ACCESS_TOKEN=` for VK [HowTo](https://github.com/vodka2/vk-audio-token)
|
1. Fill the `.env` with a `ACCESS_TOKEN=` for VK [HowTo](https://github.com/vodka2/vk-audio-token)
|
||||||
1. `node . https://open.spotify.com/playlist/6LgeEhc97Azxq6sinJQt6w`
|
1. `curl https://open.spotify.com/playlist/6LgeEhc97Azxq6sinJQt6w > test.html`
|
||||||
|
1. `node . 0` => Download the first title of the playlist
|
||||||
|
|
50
index.js
50
index.js
|
@ -11,10 +11,8 @@ const m3u = require('m3u');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
|
|
||||||
async function getSpotifyPlaylist(playListUrl){
|
async function getSpotifyPlaylist(){
|
||||||
const spotifyPlaylistPageContent = await fetch(playListUrl)
|
const spotifyPlaylistPageContent = await fs.readFile('./test.html');
|
||||||
.then(res => res.text())
|
|
||||||
|
|
||||||
const $ = Cheerio.load(spotifyPlaylistPageContent);
|
const $ = Cheerio.load(spotifyPlaylistPageContent);
|
||||||
|
|
||||||
let playlist;
|
let playlist;
|
||||||
|
@ -46,33 +44,19 @@ async function searchOnVkMusic(query){
|
||||||
if(responseBody.error){
|
if(responseBody.error){
|
||||||
throw _.assign(responseBody.error, new Error(responseBody.error.error_msg));
|
throw _.assign(responseBody.error, new Error(responseBody.error.error_msg));
|
||||||
} else {
|
} else {
|
||||||
return responseBody.response.items;
|
return responseBody;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchOnMyFreeMp3(query){
|
|
||||||
const url = new URL('https://myfreemp3cc.com/api/search.php?callback=callback');
|
|
||||||
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: new URLSearchParams({
|
|
||||||
q: query,
|
|
||||||
page: 0,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(res => res.text())
|
|
||||||
.then(jsonp => vm.runInNewContext(jsonp, { callback: (payload) => payload.response }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchScore(spotifyMetas, vkmusicMetas){
|
function matchScore(spotifyMetas, vkmusicMetas){
|
||||||
const originalArtistNames = _.map(spotifyMetas.artists, 'name').join(', ').toLowerCase();
|
const originalArtistNames = _.map(spotifyMetas.artists, 'name').join(', ').toLowerCase();
|
||||||
const originalTitle = spotifyMetas.name.toLowerCase();
|
const originalTitle = spotifyMetas.name.toLowerCase();
|
||||||
const originalDuration = Math.round(spotifyMetas.duration_ms/1000);
|
const originalDuration = Math.round(spotifyMetas.duration_ms/1000);
|
||||||
|
|
||||||
const matchArtistScore = 1 - (leven(originalArtistNames, _.get(vkmusicMetas, 'artist', '').toLowerCase()) / Math.max(originalArtistNames.length, _.get(vkmusicMetas, 'artist', '').length));
|
const matchArtistScore = 1 - (leven(originalArtistNames, vkmusicMetas.artist.toLowerCase()) / Math.max(originalArtistNames.length, vkmusicMetas.artist.length));
|
||||||
const matchTitleScore = 1 - (leven(originalTitle, _.get(vkmusicMetas, 'title', '').toLowerCase()) / Math.max(originalTitle.length, _.get(vkmusicMetas, 'title', '').length));
|
const matchTitleScore = 1 - (leven(originalTitle, vkmusicMetas.title.toLowerCase()) / Math.max(originalTitle.length, vkmusicMetas.title.length));
|
||||||
const matchDurationScore = 1 - (Math.abs(originalDuration - _.get(vkmusicMetas, 'duration', 0)) / originalDuration); // TODO this can return more than 1 or less than 0
|
const matchDurationScore = 1 - (Math.abs(originalDuration - vkmusicMetas.duration) / originalDuration); // TODO this can return more than 1 or less than 0
|
||||||
debug('matchArtistScore=%f matchTitleScore=%f matchDurationScore=%f', matchArtistScore, matchTitleScore, matchDurationScore);
|
debug('matchArtistScore=%f matchTitleScore=%f matchDurationScore=%f', matchArtistScore, matchTitleScore, matchDurationScore);
|
||||||
return matchArtistScore + matchTitleScore + matchDurationScore;
|
return matchArtistScore + matchTitleScore + matchDurationScore;
|
||||||
}
|
}
|
||||||
|
@ -83,24 +67,24 @@ async function generateM3U8Playlist(filesWithMetas){
|
||||||
await fs.writeFile(`playlist.m3u8`, m3uWriter.toString());
|
await fs.writeFile(`playlist.m3u8`, m3uWriter.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(playlistUrl){
|
async function main(){
|
||||||
const playlist = await getSpotifyPlaylist(playlistUrl);
|
const playlist = await getSpotifyPlaylist();
|
||||||
const vkPlaylist = await Promise.map(playlist.tracks.items, async ({track}) => {
|
const vkPlaylist = await Promise.map(playlist.tracks.items, async ({track}) => {
|
||||||
const artistNames = _.map(track.artists, 'name').join(', ');
|
const artistNames = _.map(track.artists, 'name').join(', ');
|
||||||
debug('%s - %s', track.name, artistNames);
|
debug('%s - %s', track.name, artistNames);
|
||||||
const items = await searchOnMyFreeMp3(`${artistNames} ${track.name}`);
|
const { response } = await searchOnVkMusic(`${artistNames} ${track.name}`);
|
||||||
debug('items=%O', items);
|
debug('response=%O', response);
|
||||||
const bestMatch = _.chain(items).map((item) => {
|
const bestMatch = _.chain(response.items).map((item) => {
|
||||||
item.score = matchScore(track, item);
|
item.score = matchScore(track, item);
|
||||||
return item;
|
return item;
|
||||||
}).filter(item => item.score && item.url).sortBy('score').last().value();
|
}).sortBy('score').last().value();
|
||||||
|
debug('bestMatch=%O', bestMatch);
|
||||||
if(!bestMatch){
|
if(!bestMatch){
|
||||||
console.log(`You are on your own for ${track.name} - ${artistNames}`);
|
console.log(`You are on your own for ${track.name} - ${artistNames}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bestMatch.path = `${bestMatch.artist} - ${bestMatch.title}.mp3`;
|
bestMatch.path = `${bestMatch.artist} - ${bestMatch.title}.mp3`;
|
||||||
debug('bestMatch=%O', bestMatch);
|
await fs.access(bestMatch.path).catch(async () => {
|
||||||
await fs.access(bestMatch.path).catch(async () => { // TODO find a proper way to not re-download, lower/uppercase problems
|
|
||||||
await fetch(bestMatch.url).then(res => {
|
await fetch(bestMatch.url).then(res => {
|
||||||
res.body.pipe(createWriteStream(bestMatch.path));
|
res.body.pipe(createWriteStream(bestMatch.path));
|
||||||
});
|
});
|
||||||
|
@ -112,11 +96,11 @@ async function main(playlistUrl){
|
||||||
await generateM3U8Playlist(_.compact(vkPlaylist));
|
await generateM3U8Playlist(_.compact(vkPlaylist));
|
||||||
}
|
}
|
||||||
|
|
||||||
main(process.argv[2]).catch(err => console.error(err));
|
//main().catch(err => console.error(err));
|
||||||
|
|
||||||
async function test(query){
|
async function test(query){
|
||||||
const response = await searchOnMyFreeMp3(query);
|
const { response } = await searchOnVkMusic(query);
|
||||||
console.log(response);
|
console.log(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
//test(process.argv[2]);
|
test(process.argv[2]);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user