2019-10-27 16:50:10 +01:00
const readline = require ( "readline" ) ;
const ytdl = require ( "ytdl-core" ) ;
const ffmpeg = require ( "fluent-ffmpeg" ) ;
const id3 = require ( 'node-id3' ) ;
const axios = require ( 'axios' ) ;
2019-11-19 16:46:16 +01:00
const request = require ( "request" ) . defaults ( { encoding : null } ) ;
2019-10-27 16:50:10 +01:00
const chalk = require ( 'chalk' ) ;
2019-11-19 16:46:16 +01:00
const cp = require ( 'child_process' ) ;
const fs = require ( 'fs' ) ;
const path = require ( 'path' ) ;
const terminalLink = require ( 'terminal-link' ) ;
2019-11-19 17:56:34 +01:00
const hasflag = require ( 'has-flag' ) ;
const gradient = require ( 'gradient-string' ) ;
const figlet = require ( 'figlet' ) ;
2019-10-27 16:50:10 +01:00
/ * *
* log function for logging in one shell line .
2019-11-19 16:46:16 +01:00
* @ param { any } something the thing that should be logged .
2019-10-27 16:50:10 +01:00
* /
const log = ( something ) => {
readline . clearLine ( process . stdout , 0 ) ;
readline . cursorTo ( process . stdout , 0 ) ;
process . stdout . write ( something ) ;
}
2019-11-19 16:46:16 +01:00
/ * *
* convertTime converts a string to a number of seconds .
* @ param { string } timeString like '00:00:46.27'
* /
const convertTime = ( timeString ) => {
2019-10-27 16:50:10 +01:00
const timeParts = timeString . split ( ':' ) ;
let seconds = Number ( timeParts [ 2 ] ) ;
seconds = seconds + ( Number ( timeParts [ 1 ] ) * 60 ) + ( Number ( timeParts [ 0 ] ) * 60 * 60 ) ;
return seconds ;
} ;
2019-11-19 16:46:16 +01:00
if ( process . argv . find ( v => v === "help" || v === "-h" || v === "--help" ) ) {
2019-11-19 17:56:34 +01:00
console . log ( gradient . rainbow . multiline ( figlet . textSync ( "ytdownloader" , { horizontalLayout : 'fitted' } ) ) ) ;
console . log ( chalk . underline ( "\nUsage:" ) ) ;
2019-11-19 16:46:16 +01:00
console . log ( ` node main.js ${ chalk . italic ( "<YouTube video url> <?Artist name> <?Song name>" ) } \n ` ) ;
console . log ( "The arguments starting with <? are optional. If not specified the song won't have id3 tags written to it." ) ;
process . exit ( ) ;
}
2019-10-27 16:50:10 +01:00
if ( process . argv . length !== 5 ) {
2019-11-19 17:56:34 +01:00
console . log ( chalk . yellowBright ( "⚠️ Not getting metadata for this song; expected arguments are not given!" ) ) ;
2019-10-27 16:50:10 +01:00
}
2019-11-19 16:46:16 +01:00
const ffmpegPath = path . join ( _ _dirname + "/ffmpeg" ) ;
if ( fs . existsSync ( ffmpegPath ) ) {
// On linux, you require the command-line version of ffmpeg.
if ( process . platform !== "linux" ) ffmpeg . setFfmpegPath ( ffmpegPath ) ;
} else {
2019-11-19 17:56:34 +01:00
console . error ( chalk . redBright ( "❌ The FFmpeg executable wasn't found. See " + terminalLink ( "ffmpeg's website" , "https://www.ffmpeg.org" ) + " for downloads." ) )
2019-11-19 16:46:16 +01:00
process . exit ( 1 ) ;
}
2019-10-27 16:50:10 +01:00
log ( chalk . yellow ( "ℹ ️ Getting information from YouTube..." ) ) ;
ytdl . getInfo ( process . argv [ 2 ] , ( err , info ) => {
let stream = ytdl ( process . argv [ 2 ] , {
quality : "highestaudio"
//filter: 'audioonly',
} ) ;
2019-11-26 07:30:26 +01:00
const filePath = path . join ( _ _dirname , ` /music/ ${ info . title } .mp3 ` ) ;
2019-10-27 16:50:10 +01:00
log ( chalk . yellow ( "🏁 Starting download..." ) ) ;
ffmpeg ( stream )
. audioBitrate ( 128 )
2019-11-26 07:30:26 +01:00
. save ( filePath )
2019-10-27 16:50:10 +01:00
. on ( "progress" , p => {
const progress = convertTime ( p . timemark ) ;
log (
chalk . blue (
` ⬇️ ${ Math . floor (
( progress / Number ( info . length _seconds ) ) * 100
2019-11-19 16:46:16 +01:00
) } % downloaded . ( $ { p . targetSize } kB ) `
2019-10-27 16:50:10 +01:00
)
) ;
} )
. on ( 'error' , err => {
2019-11-19 17:56:34 +01:00
console . error ( chalk . redBright ( '❌ Found an error: ' + err . message ) ) ;
2019-11-19 16:46:16 +01:00
process . exit ( 1 ) ;
2019-10-27 16:50:10 +01:00
} )
. on ( "end" , ( ) => {
if ( process . argv . length === 5 ) {
// Retreiving info from itunes api and writing tags to downloaded file.
const artist = process . argv [ 3 ] ;
const song = process . argv [ 4 ] ;
log ( chalk . yellow ( "🎵 Calling iTunes api..." ) ) ;
axios . get ( ` https://itunes.apple.com/search?term= ${ artist } ${ song } &entity=song ` ) . then ( res => {
2019-11-19 16:46:16 +01:00
request . get ( res . data . results [ 0 ] . artworkUrl100 . replace ( '100x100' , '3000x3000' ) , function ( err , _res , body ) {
axios . get ( ` https://itunes.apple.com/search?term= ${ artist } ${ res . data . results [ 0 ] . collectionName } &entity=album ` ) . then ( album => {
log ( chalk . green ( "✅ Retreived information!" ) ) ;
var tags = {
title : res . data . results [ 0 ] . trackName ,
artist : res . data . results [ 0 ] . artistName ,
performerInfo : album . data . results [ 0 ] . artistName ,
album : res . data . results [ 0 ] . collectionName ,
copyright : album . data . results [ 0 ] . copyright ,
year : new Date ( res . data . results [ 0 ] . releaseDate ) . getFullYear ( ) ,
image : {
mime : "jpeg" ,
type : {
id : 3 ,
name : "front cover"
} ,
imageBuffer : body
2019-10-27 16:50:10 +01:00
} ,
2019-11-19 16:46:16 +01:00
fileType : "mp3" ,
genre : res . data . results [ 0 ] . primaryGenreName
} ;
log ( chalk . yellow ( "✍🏻 Writing tags to mp3 file..." ) ) ;
2019-11-26 07:30:26 +01:00
id3 . update ( tags , filePath , ( ) => {
if ( fs . existsSync ( filePath ) && process . platform === "darwin" && ! hasflag ( "no-itunes" ) ) {
2019-11-19 16:46:16 +01:00
log ( chalk . yellow ( "🎵 Adding song to Apple Music library..." ) ) ;
2019-11-26 07:30:26 +01:00
cp . execSync ( ` cp " ${ filePath } " "/Users/job/Music/Music/Media/Automatically \ Add \ to \ Music.localized" ` ) ;
2019-11-19 16:46:16 +01:00
}
log ( chalk . green ( ` ✅ Successfully downloaded ${ info . title } .mp3! \n ` ) ) ;
process . exit ( ) ;
} ) ;
} ) ;
2019-10-27 16:50:10 +01:00
} ) ;
} ) ;
} else {
log ( chalk . green ( ` ✅ Successfully downloaded ${ info . title } .mp3! ` ) ) ;
process . exit ( ) ;
}
} ) ;
} ) ;