Implement song tagging

This commit is contained in:
MeowcaTheoRange 2024-02-21 10:25:06 -06:00
parent e70ce4a9ca
commit eb9263972e
2 changed files with 78 additions and 30 deletions

107
index.js
View file

@ -1,5 +1,5 @@
require('dotenv').config(); require('dotenv').config();
const { Readable } = require("stream"); const { Readable, PassThrough } = require("stream");
const { finished } = require("stream/promises"); const { finished } = require("stream/promises");
const { searchMusics } = require('fix-esm').require("node-youtube-music"); const { searchMusics } = require('fix-esm').require("node-youtube-music");
const { GetListByKeyword } = require("youtube-search-api"); const { GetListByKeyword } = require("youtube-search-api");
@ -9,9 +9,10 @@ const path = require("path");
const { readFileSync, writeFileSync } = require("fs"); const { readFileSync, writeFileSync } = require("fs");
const io = require('socket.io-client'); const io = require('socket.io-client');
const { program } = require('commander'); const { program } = require('commander');
const NodeID3 = require('node-id3');
function pathGenerator({ url, name, channelName }) { 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 }) { function checkLastSong({ name, channelName }) {
@ -28,6 +29,7 @@ function writeLastSong({ name, channelName }) {
async function getVideo({ msName, msArtist }) { async function getVideo({ msName, msArtist }) {
let trackData; let trackData;
let tags;
if (msName != null && msArtist != null) { if (msName != null && msArtist != null) {
trackData = { trackData = {
@ -40,6 +42,17 @@ async function getVideo({ msName, msArtist }) {
trackData = await fetch( 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` `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]); ).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({ if (checkLastSong({
@ -68,8 +81,6 @@ async function getVideo({ msName, msArtist }) {
return skewered(song.title).includes(skewered(trackData.name)); return skewered(song.title).includes(skewered(trackData.name));
}); });
console.log(youtubeMusicVideo);
if (youtubeMusicVideo == null) { if (youtubeMusicVideo == null) {
const videoList = await GetListByKeyword(`${trackData.artist.name} - ${trackData.name}`, false, 1, [ const videoList = await GetListByKeyword(`${trackData.artist.name} - ${trackData.name}`, false, 1, [
{type: "video"} {type: "video"}
@ -91,7 +102,11 @@ async function getVideo({ msName, msArtist }) {
selectedVideo.channelName = selectedVideo.channelName.replace(/\s-\sTopic/ig, ""); 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) { async function checkNextcloud({ url, name, channelName }, nextcloudClient) {
@ -100,13 +115,12 @@ async function checkNextcloud({ url, name, channelName }, nextcloudClient) {
return await nextcloudClient.exists(fileName); 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`, { const processor = await fetch(`https://${process.env.COBALT_INSTANCE}/api/json`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
url: `https://youtu.be/${url}`, url: `https://youtu.be/${url}`,
vCodec: "h264", vCodec: "h264",
vQuality: "144",
aFormat: "mp3", aFormat: "mp3",
filenamePattern: "basic", filenamePattern: "basic",
isAudioOnly: true, isAudioOnly: true,
@ -142,23 +156,37 @@ async function downloadFromCobalt({ url, name, channelName }) {
break; break;
} }
const { body } = await fetch(processorURL); const body = Buffer.from(await fetch(processorURL).then(x => x.arrayBuffer()));
return { return body;
fileStream: Readable.fromWeb(body),
url,
name,
channelName
};
} }
async function uploadToNextcloud({ fileStream, url, name, channelName }, nextcloudClient) { async function downloadAlbumCover({ albumCover }) {
await nextcloudClient.createDirectory(process.env.NEXTCLOUD_FOLDER, { 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, recursive: true,
}); });
const fileBufferStream = new PassThrough().end(fileStream);
const albumBufferStream = new PassThrough().end(albumCover);
const fileName = pathGenerator({ url, name, channelName }); 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; return fileName;
} }
@ -182,7 +210,6 @@ async function main() {
msName: options.song, msName: options.song,
msArtist: options.artist msArtist: options.artist
}); });
console.log(video);
if (video == null) return dismantle(socket); if (video == null) return dismantle(socket);
@ -201,23 +228,43 @@ async function main() {
password: process.env.NOTIFICATION_SERVER_PASSWORD password: process.env.NOTIFICATION_SERVER_PASSWORD
}) })
const cobalt = await downloadFromCobalt(video); const albumCover = await downloadAlbumCover(video);
console.log(cobalt);
socket.emit('nodeMessage', { socket.emit('nodeMessage', {
message: `Nextcloud upload running - ${cobalt.name} from ${cobalt.channelName}`, message: `Cobalt download starting - ${video.name} from ${video.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}`,
password: process.env.NOTIFICATION_SERVER_PASSWORD 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); return dismantle(socket);
} }

View file

@ -14,6 +14,7 @@
"dotenv": "^16.4.4", "dotenv": "^16.4.4",
"express": "^4.18.2", "express": "^4.18.2",
"fix-esm": "^1.0.1", "fix-esm": "^1.0.1",
"node-id3": "^0.2.6",
"node-notifier": "^10.0.1", "node-notifier": "^10.0.1",
"node-youtube-music": "^0.10.3", "node-youtube-music": "^0.10.3",
"skewered": "^1.0.0", "skewered": "^1.0.0",