Merge pull request #953 from th-ch/visualizer-plugin

[New plugin] Music visualizers
This commit is contained in:
th-ch
2023-01-14 14:53:29 +01:00
committed by GitHub
12 changed files with 347 additions and 1 deletions

View File

@ -94,6 +94,72 @@ const defaultConfig = {
"skip-silences": {
onlySkipBeginning: false,
},
visualizer: {
enabled: false,
type: "butterchurn",
// Config per visualizer
butterchurn: {
preset: "martin [shadow harlequins shape code] - fata morgana",
renderingFrequencyInMs: 500,
blendTimeInSeconds: 2.7,
},
vudio: {
effect: "lighting",
accuracy: 128,
lighting: {
maxHeight: 160,
maxSize: 12,
lineWidth: 1,
color: "#49f3f7",
shadowBlur: 2,
shadowColor: "rgba(244,244,244,.5)",
fadeSide: true,
prettify: false,
horizontalAlign: "center",
verticalAlign: "middle",
dottify: true,
},
},
wave: {
animations: [
{
type: "Cubes",
config: {
bottom: true,
count: 30,
cubeHeight: 5,
fillColor: { gradient: ["#FAD961", "#F76B1C"] },
lineColor: "rgba(0,0,0,0)",
radius: 20,
},
},
{
type: "Cubes",
config: {
top: true,
count: 12,
cubeHeight: 5,
fillColor: { gradient: ["#FAD961", "#F76B1C"] },
lineColor: "rgba(0,0,0,0)",
radius: 10,
},
},
{
type: "Circles",
config: {
lineColor: {
gradient: ["#FAD961", "#FAD961", "#F76B1C"],
rotate: 90,
},
lineWidth: 4,
diameter: 20,
count: 10,
frequencyBand: "base",
},
},
],
},
},
},
};

View File

@ -9,6 +9,9 @@ const setDefaultPluginOptions = (store, plugin) => {
}
const migrations = {
">=1.20.0": (store) => {
setDefaultPluginOptions(store, "visualizer");
},
">=1.17.0": (store) => {
setDefaultPluginOptions(store, "picture-in-picture");

View File

@ -96,9 +96,12 @@
"@cliqz/adblocker-electron": "^1.25.1",
"@ffmpeg/core": "^0.11.0",
"@ffmpeg/ffmpeg": "^0.11.6",
"@foobar404/wave": "^2.0.4",
"Simple-YouTube-Age-Restriction-Bypass": "https://gitpkg.now.sh/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/dist?v2.5.4",
"async-mutex": "^0.4.0",
"browser-id3-writer": "^4.4.0",
"butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7",
"chokidar": "^3.5.3",
"custom-electron-prompt": "^1.5.0",
"custom-electron-titlebar": "^4.1.2",
@ -116,6 +119,7 @@
"mpris-service": "^2.1.2",
"node-fetch": "^2.6.7",
"node-notifier": "^10.0.1",
"vudio": "^2.1.1",
"ytdl-core": "^4.11.1",
"ytpl": "^2.3.0"
},

View File

@ -0,0 +1,6 @@
const { injectCSS } = require("../utils");
const path = require("path");
module.exports = (win, options) => {
injectCSS(win.webContents, path.join(__dirname, "empty-player.css"));
};

View File

@ -0,0 +1,9 @@
#player {
margin: 0 !important;
background: black;
}
#song-image,
#song-video {
display: none !important;
}

View File

@ -0,0 +1,61 @@
const defaultConfig = require("../../config/defaults");
module.exports = (options) => {
const optionsWithDefaults = {
...defaultConfig.plugins.visualizer,
...options,
};
const VisualizerType = require(`./visualizers/${optionsWithDefaults.type}`);
document.addEventListener(
"audioCanPlay",
(e) => {
const video = document.querySelector("video");
const visualizerContainer = document.querySelector("#player");
let canvas = document.getElementById("visualizer");
if (!canvas) {
canvas = document.createElement("canvas");
canvas.id = "visualizer";
canvas.style.position = "absolute";
canvas.style.background = "black";
visualizerContainer.append(canvas);
}
const resizeCanvas = () => {
canvas.width = visualizerContainer.clientWidth;
canvas.height = visualizerContainer.clientHeight;
};
resizeCanvas();
const gainNode = e.detail.audioContext.createGain();
gainNode.gain.value = 1.25;
e.detail.audioSource.connect(gainNode);
const visualizer = new VisualizerType(
e.detail.audioContext,
e.detail.audioSource,
visualizerContainer,
canvas,
gainNode,
video.captureStream(),
optionsWithDefaults[optionsWithDefaults.type]
);
const resizeVisualizer = (width, height) => {
resizeCanvas();
visualizer.resize(width, height);
};
resizeVisualizer(canvas.width, canvas.height);
const visualizerContainerObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
resizeVisualizer(entry.contentRect.width, entry.contentRect.height);
});
});
visualizerContainerObserver.observe(visualizerContainer);
visualizer.render();
},
{ passive: true }
);
};

View File

@ -0,0 +1,23 @@
const { readdirSync } = require("fs");
const path = require("path");
const { setMenuOptions } = require("../../config/plugins");
const visualizerTypes = readdirSync(path.join(__dirname, "visualizers")).map(
(filename) => path.parse(filename).name
);
module.exports = (win, options) => [
{
label: "Type",
submenu: visualizerTypes.map((visualizerType) => ({
label: visualizerType,
type: "radio",
checked: options.type === visualizerType,
click: () => {
options.type = visualizerType;
setMenuOptions("visualizer", options);
},
})),
},
];

View File

@ -0,0 +1,46 @@
const butterchurn = require("butterchurn");
const butterchurnPresets = require("butterchurn-presets");
const presets = butterchurnPresets.getPresets();
class ButterchurnVisualizer {
constructor(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options
) {
this.visualizer = butterchurn.default.createVisualizer(
audioContext,
canvas,
{
width: canvas.width,
height: canvas.height,
}
);
const preset = presets[options.preset];
this.visualizer.loadPreset(preset, options.blendTimeInSeconds);
this.visualizer.connectAudio(audioNode);
this.renderingFrequencyInMs = options.renderingFrequencyInMs;
}
resize(width, height) {
this.visualizer.setRendererSize(width, height);
}
render() {
const renderVisualizer = () => {
requestAnimationFrame(() => renderVisualizer());
this.visualizer.render();
};
setTimeout(renderVisualizer(), this.renderingFrequencyInMs);
}
}
module.exports = ButterchurnVisualizer;

View File

@ -0,0 +1,33 @@
const Vudio = require("vudio/umd/vudio");
class VudioVisualizer {
constructor(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options
) {
this.visualizer = new Vudio(stream, canvas, {
width: canvas.width,
height: canvas.height,
// Visualizer config
...options,
});
}
resize(width, height) {
this.visualizer.setOption({
width: width,
height: height,
});
}
render() {
this.visualizer.dance();
}
}
module.exports = VudioVisualizer;

View File

@ -0,0 +1,31 @@
const { Wave } = require("@foobar404/wave");
class WaveVisualizer {
constructor(
audioContext,
audioSource,
visualizerContainer,
canvas,
audioNode,
stream,
options
) {
this.visualizer = new Wave(
{ context: audioContext, source: audioSource },
canvas
);
options.animations.forEach((animation) => {
this.visualizer.addAnimation(
eval(`new this.visualizer.animations.${animation.type}(
${JSON.stringify(animation.config)}
)`)
);
});
}
resize(width, height) {}
render() {}
}
module.exports = WaveVisualizer;

View File

@ -108,6 +108,8 @@ winget install th-ch.YouTubeMusic
- **Video Toggle**: Adds a [button](https://user-images.githubusercontent.com/28893833/173663950-63e6610e-a532-49b7-9afa-54cb57ddfc15.png) to switch between Video/Song mode. can also optionally remove the whole video tab
- **Visualizer**: Different music visualizers
---
- **Auto confirm when paused** (Always Enabled): disable the ["Continue Watching?"](https://user-images.githubusercontent.com/61631665/129977894-01c60740-7ec6-4bf0-9a2c-25da24491b0e.png) popup that pause music after a certain time

View File

@ -202,6 +202,13 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8"
integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==
"@babel/runtime@^7.0.0":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.7.2":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
@ -374,6 +381,11 @@
regenerator-runtime "^0.13.7"
resolve-url "^0.2.1"
"@foobar404/wave@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@foobar404/wave/-/wave-2.0.4.tgz#c9bc54c41b18642c6a4587851e28b8f858af98b0"
integrity sha512-FEyg37hDvQtrQVlFxbit7ov5e487BjsR32bZfJ4oAb5i+NnlbGaNyy6iYBZ8ocVHo8fgug+SL+mFdDTzqjvPww==
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
@ -1364,6 +1376,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -1557,6 +1577,23 @@ builtin-modules@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
butterchurn-presets@^2.4.7:
version "2.4.7"
resolved "https://registry.yarnpkg.com/butterchurn-presets/-/butterchurn-presets-2.4.7.tgz#41e4e37cd3af2aec124fa8062352816100956c29"
integrity sha512-4MdM8ripz/VfH1BCldrIKdAc/1ryJFBDvqlyow6Ivo1frwj0H3duzvSMFC7/wIjAjxb1QpwVHVqGqS9uAFKhpg==
dependencies:
babel-runtime "^6.26.0"
ecma-proposal-math-extensions "0.0.2"
lodash "^4.17.4"
butterchurn@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/butterchurn/-/butterchurn-2.6.7.tgz#1ff0c1365731a4fb7ada7bb16ae1c6f09a110c12"
integrity sha512-BJiRA8L0L2+84uoG2SSfkp0kclBuN+vQKf217pK7pMlwEO2ZEg3MtO2/o+l8Qpr8Nbejg8tmL1ZHD1jmhiaaqg==
dependencies:
"@babel/runtime" "^7.0.0"
ecma-proposal-math-extensions "0.0.2"
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@ -1892,6 +1929,11 @@ convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
core-js@^2.4.0:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -2266,6 +2308,11 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecma-proposal-math-extensions@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/ecma-proposal-math-extensions/-/ecma-proposal-math-extensions-0.0.2.tgz#a6a3d64819db70cee0d2e1976b6315d00e4714a0"
integrity sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==
ejs@^3.1.6:
version "3.1.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006"
@ -4525,7 +4572,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.3.0:
lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -5605,6 +5652,16 @@ redent@^4.0.0:
indent-string "^5.0.0"
strip-indent "^4.0.0"
regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@ -6582,6 +6639,11 @@ verror@1.10.0, verror@^1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vudio@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/vudio/-/vudio-2.1.1.tgz#af256c4e1c8ae8bdbbba0e718f8106a30a44e96e"
integrity sha512-VkFQcFt/b/kpF5Eg5Sq+oXUo1Zp5aRFF4BSmIrOzau5o+5WMWwX9ae/EGJZstCyZFiCTU5iw1Y+u2BCGW6Y6Jw==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"