mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-10 18:21:47 +00:00
130 lines
3.5 KiB
JavaScript
130 lines
3.5 KiB
JavaScript
const { ipcMain, nativeImage } = require("electron");
|
|
|
|
const fetch = require("node-fetch");
|
|
|
|
const config = require("../config");
|
|
|
|
// Fill songInfo with empty values
|
|
/**
|
|
* @typedef {songInfo} SongInfo
|
|
*/
|
|
const songInfo = {
|
|
title: "",
|
|
artist: "",
|
|
views: 0,
|
|
uploadDate: "",
|
|
imageSrc: "",
|
|
image: null,
|
|
isPaused: undefined,
|
|
songDuration: 0,
|
|
elapsedSeconds: 0,
|
|
url: "",
|
|
album: undefined,
|
|
videoId: "",
|
|
playlistId: "",
|
|
};
|
|
|
|
// Grab the native image using the src
|
|
const getImage = async (src) => {
|
|
const result = await fetch(src);
|
|
const buffer = await result.buffer();
|
|
const output = nativeImage.createFromBuffer(buffer);
|
|
if (output.isEmpty() && !src.endsWith(".jpg") && src.includes(".jpg")) { // fix hidden webp files (https://github.com/th-ch/youtube-music/issues/315)
|
|
return getImage(src.slice(0, src.lastIndexOf(".jpg") + 4));
|
|
} else {
|
|
return output;
|
|
}
|
|
};
|
|
|
|
const handleData = async (responseText, win) => {
|
|
const data = JSON.parse(responseText);
|
|
if (!data) return;
|
|
|
|
const microformat = data.microformat?.microformatDataRenderer;
|
|
if (microformat) {
|
|
songInfo.uploadDate = microformat.uploadDate;
|
|
songInfo.url = microformat.urlCanonical?.split("&")[0];
|
|
songInfo.playlistId = new URL(microformat.urlCanonical).searchParams.get("list");
|
|
// used for options.resumeOnStart
|
|
config.set("url", microformat.urlCanonical);
|
|
}
|
|
|
|
const videoDetails = data.videoDetails;
|
|
if (videoDetails) {
|
|
songInfo.title = cleanupName(videoDetails.title);
|
|
songInfo.artist = cleanupName(videoDetails.author);
|
|
songInfo.views = videoDetails.viewCount;
|
|
songInfo.songDuration = videoDetails.lengthSeconds;
|
|
songInfo.elapsedSeconds = videoDetails.elapsedSeconds;
|
|
songInfo.isPaused = videoDetails.isPaused;
|
|
songInfo.videoId = videoDetails.videoId;
|
|
songInfo.album = data?.videoDetails?.album; // Will be undefined if video exist
|
|
|
|
const oldUrl = songInfo.imageSrc;
|
|
songInfo.imageSrc = videoDetails.thumbnail?.thumbnails?.pop()?.url.split("?")[0];
|
|
if (oldUrl !== songInfo.imageSrc) {
|
|
songInfo.image = await getImage(songInfo.imageSrc);
|
|
}
|
|
|
|
win.webContents.send("update-song-info", JSON.stringify(songInfo));
|
|
}
|
|
};
|
|
|
|
// This variable will be filled with the callbacks once they register
|
|
const callbacks = [];
|
|
|
|
// This function will allow plugins to register callback that will be triggered when data changes
|
|
/**
|
|
* @callback songInfoCallback
|
|
* @param {songInfo} songInfo
|
|
* @returns {void}
|
|
*/
|
|
/**
|
|
* @param {songInfoCallback} callback
|
|
*/
|
|
const registerCallback = (callback) => {
|
|
callbacks.push(callback);
|
|
};
|
|
|
|
const registerProvider = (win) => {
|
|
// This will be called when the song-info-front finds a new request with song data
|
|
ipcMain.on("video-src-changed", async (_, responseText) => {
|
|
await handleData(responseText, win);
|
|
callbacks.forEach((c) => {
|
|
c(songInfo);
|
|
});
|
|
});
|
|
ipcMain.on("playPaused", (_, { isPaused, elapsedSeconds }) => {
|
|
songInfo.isPaused = isPaused;
|
|
songInfo.elapsedSeconds = elapsedSeconds;
|
|
callbacks.forEach((c) => {
|
|
c(songInfo);
|
|
});
|
|
})
|
|
};
|
|
|
|
const suffixesToRemove = [
|
|
" - topic",
|
|
"vevo",
|
|
" (performance video)",
|
|
" (official music video)",
|
|
" (official video)",
|
|
" (clip officiel)",
|
|
];
|
|
|
|
function cleanupName(name) {
|
|
if (!name) return name;
|
|
const lowCaseName = name.toLowerCase();
|
|
for (const suffix of suffixesToRemove) {
|
|
if (lowCaseName.endsWith(suffix)) {
|
|
return name.slice(0, -suffix.length);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
module.exports = registerCallback;
|
|
module.exports.setupSongInfo = registerProvider;
|
|
module.exports.getImage = getImage;
|
|
module.exports.cleanupName = cleanupName;
|