diff --git a/index.js b/index.js index 4883201..cef1feb 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ require('dotenv').config(); -const { Readable } = require("stream"); +const { Readable, PassThrough } = require("stream"); const { finished } = require("stream/promises"); const { searchMusics } = require('fix-esm').require("node-youtube-music"); const { GetListByKeyword } = require("youtube-search-api"); @@ -9,9 +9,10 @@ const path = require("path"); const { readFileSync, writeFileSync } = require("fs"); const io = require('socket.io-client'); const { program } = require('commander'); +const NodeID3 = require('node-id3'); function pathGenerator({ url, name, channelName }) { - return path.join(process.env.NEXTCLOUD_FOLDER, `${url}-${skewered(`${name} - ${channelName}`)}.mp3`) + return `${url}-${skewered(`${name} - ${channelName}`)}.mp3`; } function checkLastSong({ name, channelName }) { @@ -28,6 +29,7 @@ function writeLastSong({ name, channelName }) { async function getVideo({ msName, msArtist }) { let trackData; + let tags; if (msName != null && msArtist != null) { trackData = { @@ -40,6 +42,17 @@ async function getVideo({ msName, msArtist }) { trackData = await fetch( `https://${process.env.LASTFM_INSTANCE}/2.0/?method=user.getrecenttracks&user=${process.env.LASTFM_USERNAME}&api_key=${process.env.LASTFM_API_KEY}&format=json&limit=1&extended=1` ).then(x => x.json()).then(data => data.recenttracks.track[0]); + + tags = { + title: trackData.name, + artist: trackData.artist.name, + album: trackData.album["#text"], + APIC: "./cover.png", + userDefinedUrl: [{ + description: "Last.FM page", + url: trackData.url + }] + } } if (checkLastSong({ @@ -68,8 +81,6 @@ async function getVideo({ msName, msArtist }) { return skewered(song.title).includes(skewered(trackData.name)); }); - console.log(youtubeMusicVideo); - if (youtubeMusicVideo == null) { const videoList = await GetListByKeyword(`${trackData.artist.name} - ${trackData.name}`, false, 1, [ {type: "video"} @@ -91,7 +102,11 @@ async function getVideo({ msName, msArtist }) { selectedVideo.channelName = selectedVideo.channelName.replace(/\s-\sTopic/ig, ""); - return selectedVideo; + return { + tags, + ...selectedVideo, + albumCover: trackData.image.at(-1)["#text"] + }; } async function checkNextcloud({ url, name, channelName }, nextcloudClient) { @@ -100,13 +115,12 @@ async function checkNextcloud({ url, name, channelName }, nextcloudClient) { return await nextcloudClient.exists(fileName); } -async function downloadFromCobalt({ url, name, channelName }) { +async function downloadFromCobalt({ url }) { const processor = await fetch(`https://${process.env.COBALT_INSTANCE}/api/json`, { method: "POST", body: JSON.stringify({ url: `https://youtu.be/${url}`, vCodec: "h264", - vQuality: "144", aFormat: "mp3", filenamePattern: "basic", isAudioOnly: true, @@ -142,23 +156,37 @@ async function downloadFromCobalt({ url, name, channelName }) { break; } - const { body } = await fetch(processorURL); + const body = Buffer.from(await fetch(processorURL).then(x => x.arrayBuffer())); - return { - fileStream: Readable.fromWeb(body), - url, - name, - channelName - }; + return body; } -async function uploadToNextcloud({ fileStream, url, name, channelName }, nextcloudClient) { - await nextcloudClient.createDirectory(process.env.NEXTCLOUD_FOLDER, { +async function downloadAlbumCover({ albumCover }) { + const body = Buffer.from(await fetch(albumCover).then(x => x.arrayBuffer())); + + return body; +} + +function applyTags({ fileStream, tags }) { + const body = NodeID3.update(tags, fileStream); + + return body; +} + +async function uploadToNextcloud({ fileStream, url, name, channelName, tags, albumCover }, nextcloudClient) { + await nextcloudClient.createDirectory(path.join(process.env.NEXTCLOUD_FOLDER, tags.album), { recursive: true, }); + const fileBufferStream = new PassThrough().end(fileStream); + + const albumBufferStream = new PassThrough().end(albumCover); + const fileName = pathGenerator({ url, name, channelName }); - await finished(fileStream.pipe(nextcloudClient.createWriteStream(fileName))); + + await finished(fileBufferStream.pipe(nextcloudClient.createWriteStream(path.join(process.env.NEXTCLOUD_FOLDER, fileName)))); + + await finished(albumBufferStream.pipe(nextcloudClient.createWriteStream(path.join(process.env.NEXTCLOUD_FOLDER, "cover.png")))); return fileName; } @@ -182,7 +210,6 @@ async function main() { msName: options.song, msArtist: options.artist }); - console.log(video); if (video == null) return dismantle(socket); @@ -201,23 +228,43 @@ async function main() { password: process.env.NOTIFICATION_SERVER_PASSWORD }) - const cobalt = await downloadFromCobalt(video); - console.log(cobalt); + const albumCover = await downloadAlbumCover(video); socket.emit('nodeMessage', { - message: `Nextcloud upload running - ${cobalt.name} from ${cobalt.channelName}`, - password: process.env.NOTIFICATION_SERVER_PASSWORD - }); - - const nextcloud = await uploadToNextcloud(cobalt, nextcloudClient); - console.log(nextcloud); - - socket.emit('nodeMessage', { - message: `Nextcloud upload finished - ${cobalt.name} from ${cobalt.channelName}`, + message: `Cobalt download starting - ${video.name} from ${video.channelName}`, password: process.env.NOTIFICATION_SERVER_PASSWORD }) - + const cobalt = await downloadFromCobalt(video); + + socket.emit('nodeMessage', { + message: `Cobalt download finished - ${video.name} from ${video.channelName}`, + password: process.env.NOTIFICATION_SERVER_PASSWORD + }); + + const taggedMusic = applyTags({ + fileStream: cobalt, + tags: video.tags + }); + + socket.emit('nodeMessage', { + message: `Nextcloud upload running - ${video.name} from ${video.channelName}`, + password: process.env.NOTIFICATION_SERVER_PASSWORD + }); + + const nextcloud = await uploadToNextcloud( + { + ...video, + fileStream: taggedMusic, + albumCover: albumCover + }, + nextcloudClient + ); + + socket.emit('nodeMessage', { + message: `Nextcloud upload finished - ${tags.name} from ${tags.channelName}`, + password: process.env.NOTIFICATION_SERVER_PASSWORD + }) return dismantle(socket); } diff --git a/package.json b/package.json index abdd575..0acdecf 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dotenv": "^16.4.4", "express": "^4.18.2", "fix-esm": "^1.0.1", + "node-id3": "^0.2.6", "node-notifier": "^10.0.1", "node-youtube-music": "^0.10.3", "skewered": "^1.0.0",