Ver código fonte

feat: added way to import only music to a playlist

Kristian Vos 5 anos atrás
pai
commit
8669818b4c

+ 32 - 9
backend/logic/actions/playlists.js

@@ -295,23 +295,36 @@ let lib = {
 	 * @param {Object} session - the session object automatically added by socket.io
 	 * @param {String} url - the url of the the YouTube playlist
 	 * @param {String} playlistId - the id of the playlist we are adding the set of songs to
+	 * @param {Boolean} musicOnly - whether to only add music to the playlist
 	 * @param {Function} cb - gets called with the result
 	 */
-	addSetToPlaylist: hooks.loginRequired((session, url, playlistId, cb) => {
+	addSetToPlaylist: hooks.loginRequired((session, url, playlistId, musicOnly, cb) => {
+		let videosInPlaylistTotal = 0;
+		let songsInPlaylistTotal = 0;
+		let songsSuccess = 0;
+		let songsFail = 0;
 		async.waterfall([
 			(next) => {
-				utils.getPlaylistFromYouTube(url, songs => {
-					next(null, songs);
+				utils.getPlaylistFromYouTube(url, musicOnly, (songIds, otherSongIds) => {
+					if (otherSongIds) {
+						videosInPlaylistTotal = songIds.length;
+						songsInPlaylistTotal = otherSongIds.length;
+					} else {
+						songsInPlaylistTotal = videosInPlaylistTotal = songIds.length;
+					}
+					next(null, songIds);
 				});
 			},
-			(songs, next) => {
+			(songIds, next) => {
 				let processed = 0;
 				function checkDone() {
-					if (processed === songs.length) next();
+					if (processed === songIds.length) next();
 				}
-				for (let s = 0; s < songs.length; s++) {
-					lib.addSongToPlaylist(session, songs[s].contentDetails.videoId, playlistId, () => {
+				for (let s = 0; s < songIds.length; s++) {
+					lib.addSongToPlaylist(session, songIds[s], playlistId, (res) => {
 						processed++;
+						if (res.status === "success") songsSuccess++;
+						else songsFail++;
 						checkDone();
 					});
 				}
@@ -330,8 +343,18 @@ let lib = {
 				logger.error("PLAYLIST_IMPORT", `Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
 				return cb({ status: 'failure', message: err});
 			} else {
-				logger.success("PLAYLIST_IMPORT", `Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}".`);
-				cb({ status: 'success', message: 'Playlist has been successfully imported.', data: playlist.songs });
+				logger.success("PLAYLIST_IMPORT", `Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${songsSuccess}, songs failed: ${songsFail}.`);
+				cb({
+					status: 'success',
+					message: 'Playlist has been successfully imported.',
+					data: playlist.songs,
+					stats: {
+						videosInPlaylistTotal,
+						songsInPlaylistTotal,
+						songsAddedSuccessfully: songsSuccess,
+						songsFailedToAdd: songsFail
+					}
+				});
 			}
 		});
 	}),

+ 6 - 6
backend/logic/actions/queueSongs.js

@@ -217,17 +217,17 @@ let lib = {
 	addSetToQueue: hooks.loginRequired((session, url, cb) => {
 		async.waterfall([
 			(next) => {
-				utils.getPlaylistFromYouTube(url, songs => {
-					next(null, songs);
+				utils.getPlaylistFromYouTube(url, false, songIds => {
+					next(null, songIds);
 				});
 			},
-			(songs, next) => {
+			(songIds, next) => {
 				let processed = 0;
 				function checkDone() {
-					if (processed === songs.length) next();
+					if (processed === songIds.length) next();
 				}
-				for (let s = 0; s < songs.length; s++) {
-					lib.add(session, songs[s].contentDetails.videoId, () => {
+				for (let s = 0; s < songIds.length; s++) {
+					lib.add(session, songIds[s], () => {
 						processed++;
 						checkDone();
 					});

+ 56 - 5
backend/logic/utils.js

@@ -393,9 +393,55 @@ module.exports = class extends coreClass {
 		}
 	}
 
-	async getPlaylistFromYouTube(url, cb) {
+	async filterMusicVideosYouTube(videoIds, cb) {
 		try { await this._validateHook(); } catch { return; }
 
+		function getNextPage(cb2) {
+			let localVideoIds = videoIds.splice(0, 50);
+
+			const youtubeParams = [
+				'part=topicDetails',
+				`id=${encodeURIComponent(localVideoIds.join(","))}`,
+				`maxResults=50`,
+				`key=${config.get('apis.youtube.key')}`
+			].join('&');
+
+			request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, async (err, res, body) => {
+				if (err) {
+					console.error(err);
+					return next('Failed to find playlist from YouTube');
+				}
+
+				body = JSON.parse(body);
+
+				let songIds = [];
+				body.items.forEach(item => {
+					const songId = item.id;
+					if (!item.topicDetails) return;
+					else if (item.topicDetails.topicIds.indexOf("/m/04rlf") !== -1) {
+						songIds.push(songId);
+					}
+				});
+
+				if (videoIds.length > 0) {
+					getNextPage(newSongIds => {
+						cb2(songIds.concat(newSongIds));
+					});
+				} else cb2(songIds);
+			});
+		}
+
+		if (videoIds.length === 0) cb([]);
+		else getNextPage(songIds => {
+			cb(songIds);
+		});
+	}
+
+	async getPlaylistFromYouTube(url, musicOnly, cb) {
+		try { await this._validateHook(); } catch { return; }
+
+		let local = this;
+
 		let name = 'list'.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
 		var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
 		let playlistId = regex.exec(url)[1];
@@ -405,12 +451,12 @@ module.exports = class extends coreClass {
 			const youtubeParams = [
 				'part=contentDetails',
 				`playlistId=${encodeURIComponent(playlistId)}`,
-				`maxResults=5`,
+				`maxResults=50`,
 				`key=${config.get('apis.youtube.key')}`,
 				nextPageToken
 			].join('&');
 
-			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, (err, res, body) => {
+			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, async (err, res, body) => {
 				if (err) {
 					console.error(err);
 					return next('Failed to find playlist from YouTube');
@@ -420,8 +466,13 @@ module.exports = class extends coreClass {
 				songs = songs.concat(body.items);
 				if (body.nextPageToken) getPage(body.nextPageToken, songs);
 				else {
-					console.log(songs);
-					cb(songs);
+					songs = songs.map(song => song.contentDetails.videoId);
+					if (!musicOnly) cb(songs);
+					else {
+						local.filterMusicVideosYouTube(songs.slice(), (filteredSongs) => {
+							cb(filteredSongs, songs);
+						});
+					}
 				}
 			});
 		}

+ 27 - 5
frontend/components/Modals/Playlists/Edit.vue

@@ -103,8 +103,19 @@
 					/>
 				</p>
 				<p class="control">
-					<a class="button is-info" @click="importPlaylist()" href="#"
-						>Import</a
+					<a
+						class="button is-info"
+						@click="importPlaylist(true)"
+						href="#"
+						>Import music</a
+					>
+				</p>
+				<p class="control">
+					<a
+						class="button is-info"
+						@click="importPlaylist(false)"
+						href="#"
+						>Import all</a
 					>
 				</p>
 			</div>
@@ -289,7 +300,7 @@ export default {
 				}
 			);
 		},
-		importPlaylist() {
+		importPlaylist(musicOnly) {
 			new Toast({
 				content:
 					"Starting to import your playlist. This can take some time to do.",
@@ -299,10 +310,21 @@ export default {
 				"playlists.addSetToPlaylist",
 				this.importQuery,
 				this.playlist._id,
+				musicOnly,
 				res => {
-					if (res.status === "success")
-						this.playlist.songs = res.data;
 					new Toast({ content: res.message, timeout: 4000 });
+					if (res.status === "success") {
+						new Toast({
+							content: `Successfully added ${res.stats.songsAddedSuccessfully} songs. Failed to add ${res.stats.songsFailedToAdd} songs.`,
+							timeout: 4000
+						});
+						if (musicOnly) {
+							new Toast({
+								content: `${res.stats.songsInPlaylistTotal} of the ${res.stats.videosInPlaylistTotal} videos in the playlist were songs.`,
+								timeout: 4000
+							});
+						}
+					}
 				}
 			);
 		},