Move playback setting extras to ExtrasMenu; New mod: Filterworks / Anime4K

This commit is contained in:
Jade Macho 2024-10-19 02:40:36 +02:00
parent e67a71ab3e
commit d42172174a
Signed by: 0x0ade
GPG Key ID: E1960710FE4FBEEF
14 changed files with 535 additions and 139 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/priv/
/.vscode/
update.sh
mods_dev.json

View File

@ -185,7 +185,7 @@ export default JadefinIntegrity("Jadefin", import.meta.url, () => window["Jadefi
];
await this._loadingMods;
await this.initMods();
await this._initLoadingMods();
await Promise.all(initing);
@ -389,7 +389,7 @@ export default JadefinIntegrity("Jadefin", import.meta.url, () => window["Jadefi
}
async initMods(list) {
async _initLoadingMods() {
await Promise.all(this._loadingMods_all.map(async (mod) => {
this.log.i(`Initializing mod ${mod.name}`);
@ -400,7 +400,6 @@ export default JadefinIntegrity("Jadefin", import.meta.url, () => window["Jadefi
this.log.dir(e);
}
}));
}
})());

View File

@ -20,7 +20,7 @@ class JadefinMod {
this.modUrl = url;
}
initStyle() {
async initStyle() {
document.head.appendChild(rd$()`<link rel="stylesheet" type="text/css" href=${this.getUrl(".css")}>`);
}

View File

@ -42,7 +42,7 @@ jadefinInitExtremelyEarly();
console.log("[jadefin-init] loadJadefin");
const loader = document.createElement("script");
this.document.head.appendChild(loader);
document.head.appendChild(loader);
window["_JadefinLoaded"] = () => resolve(true);
window["_JadefinErrored"] = (e) => reject(e);
@ -95,7 +95,7 @@ jadefinInitExtremelyEarly();
}
const loader = document.createElement("script");
this.document.head.appendChild(loader);
document.head.appendChild(loader);
loader.addEventListener("load", () => resolve(true));
loader.addEventListener("error", (e) => reject(e));

View File

@ -15,6 +15,8 @@ export default JadefinIntegrity("ExtrasMenu", import.meta.url, () => new (class
IN_DRAWER = 2;
IN_LIBRARY = 4;
IN_MOVIE = 8;
IN_PLAYBACKSETTINGS = 16;
IN_PLAYBACKSETTINGS_ADVANCED = 32;
_popupId = 0;
_items = [];
@ -83,23 +85,25 @@ export default JadefinIntegrity("ExtrasMenu", import.meta.url, () => new (class
async init(name, url) {
await super.init(name, url);
await JadefinUtils.waitUntil(() => document.querySelector(".headerRight"));
this.initStyle();
this.initHeaderExtras();
this.initDrawerExtras();
await Promise.all([
this.initStyle(),
this.initHeaderExtras(),
this.initDrawerExtras(),
this.initHookActionSheetShow()
]);
this.log.i("Ready");
}
initHeaderExtras() {
async initHeaderExtras() {
await JadefinUtils.waitUntil(() => document.querySelector(".headerRight"));
document.querySelector(".headerRight")?.appendChild(this.headerExtrasEl);
}
/**
* @param {boolean} [silentWarn]
*/
initDrawerExtras(silentWarn) {
async initDrawerExtras(silentWarn) {
const drawer = document.querySelector(".mainDrawer > .mainDrawer-scrollContainer");
const userMenuOptions = drawer?.querySelector("& > .userMenuOptions");
@ -129,51 +133,100 @@ export default JadefinIntegrity("ExtrasMenu", import.meta.url, () => new (class
this.update();
}
openExtrasPopup() {
const currentVisibility = JadefinUtils.isInMovie ? this.IN_MOVIE : this.IN_LIBRARY;
async initHookActionSheetShow() {
await JadefinUtils.waitUntil(() => JadefinModules.actionSheet);
const dialogClass = `extrasMenuPopup-${this._popupId++}`;
const orig = this._actionSheetShowOrig = JadefinModules.actionSheet.show.bind(JadefinModules.actionSheet);
JadefinModules.actionSheet.show = (options) => {
const optionsOrig = Object.assign({}, options, {
items: options.items.slice()
});
const items = this._items.map((item, i) => Object.assign({
// Options menu during playback
if (!options.dialogClass &&
options.items.length >= 6 &&
options.items[0].id == "aspectratio" &&
options.items[1].id == "playbackrate" &&
options.items[2].id == "quality" &&
options.items[3].id == "repeatmode" &&
options.items[4].id == "suboffset" &&
options.items[5].id == "stats" &&
true
) {
return new Promise((resolve, reject) => {
const currentVisibility = this.IN_PLAYBACKSETTINGS;
const items = [
{id: "extrasMenuPopup-all", name: "Advanced..."},
{divider: true},
...this._items.map((item, i) => Object.assign({
id: i
}, item)).filter(item => this.checkVisibility(currentVisibility, item));
}, item)).filter(item => this.checkVisibility(currentVisibility, item)),
...optionsOrig.items.slice(4)
];
const p = JadefinModules.actionSheet.show({
dialogClass,
title: "Extras",
positionTo: this.headerExtrasEl,
const p = this._actionSheetShow({
positionTo: optionsOrig.positionTo,
currentVisibility,
items
});
p.then(id => {
if (!id) {
if (id == "extrasMenuPopup-all") {
const currentVisibility = this.IN_PLAYBACKSETTINGS_ADVANCED;
const items = [
...optionsOrig.items.slice(0, 4),
...this._items.map((item, i) => Object.assign({
id: i
}, item)).filter(item => this.checkVisibility(currentVisibility, item)),
{divider: true},
{id: "extrasMenuPopup-back", name: "Back..."}
];
const p = this._actionSheetShow(Object.assign({}, optionsOrig, {
currentVisibility,
items
}));
p.then(id => {
if (id == "extrasMenuPopup-back") {
JadefinModules.actionSheet.show(optionsOrig).then(resolve).catch(reject);
return;
}
this._items[id]?.cb?.();
if (id && this._items[id]) {
reject();
return;
}
resolve(id);
}).catch(reject);
return;
}
if (id && this._items[id]) {
reject();
return;
}
resolve(id);
}).catch(reject);
});
const dialogEl = document.querySelector(`.${dialogClass}`);
if (!dialogEl) {
this.log.e(`Couldn't find .${dialogClass}`);
return;
}
this.log.i(`Opened .${dialogClass}`);
this.log.dir(dialogEl);
dialogEl.classList.add("extrasMenuPopup");
const dialogButtons = dialogEl.querySelectorAll("button");
for (let i in items) {
items[i].cbEl?.(dialogButtons[i], currentVisibility, true);
return orig(options);
};
}
p.finally(() => {
for (let i in items) {
items[i].cbEl?.(dialogButtons[i], currentVisibility, false);
}
openExtrasPopup() {
const currentVisibility = JadefinUtils.isInMovie ? this.IN_MOVIE : this.IN_LIBRARY;
this._actionSheetShow({
title: "Extras",
positionTo: this.headerExtrasEl,
currentVisibility,
items: this._items.map((item, i) => Object.assign({
id: i
}, item)).filter(item => this.checkVisibility(currentVisibility, item))
});
}
@ -219,4 +272,45 @@ export default JadefinIntegrity("ExtrasMenu", import.meta.url, () => new (class
return (item.in & current) == current;
}
/**
* @param {{ positionTo: any; currentVisibility: any; items: any; title?: string; dialogClass?: any; }} options
*/
_actionSheetShow(options) {
options.dialogClass = `extrasMenuPopup-${this._popupId++}`;
const p = JadefinModules.actionSheet.show(options);
p.then(id => {
if (!id) {
return;
}
this._items[id]?.cb?.(options.positionTo);
});
const dialogEl = document.querySelector(`.${options.dialogClass}`);
if (!dialogEl) {
this.log.e(`Couldn't find .${options.dialogClass}`);
return p;
}
this.log.i(`Opened .${options.dialogClass}`);
this.log.dir(dialogEl);
dialogEl.classList.add("extrasMenuPopup");
const dialogButtons = dialogEl.querySelectorAll("button");
for (let i in options.items) {
options.items[i].cbEl?.(dialogButtons[i], options.currentVisibility, true);
}
p.finally(() => {
for (let i in options.items) {
options.items[i].cbEl?.(dialogButtons[i], options.currentVisibility, false);
}
});
return p;
}
})());

View File

@ -72,7 +72,7 @@ export default JadefinIntegrity("InputEater", import.meta.url, () => new (class
await JadefinUtils.waitUntil(() => JadefinModules.syncPlay);
this.initStyle();
await this.initStyle();
this.initHookSyncPlayEnabled();
this.initHookSyncPlayDisabled();
this.initHookMediaSessionHandlers();

View File

@ -121,8 +121,8 @@ export default JadefinIntegrity("Transcript", import.meta.url, () => new (class
async init(name, url) {
await super.init(name, url);
this.initStyle();
const initing = [
this.initStyle(),
this.initHookSetTrackForDisplay(),
this.initHookFetchSubtitles(),
this.initHookSetOffset(),

View File

@ -2,6 +2,7 @@
import JadefinIntegrity from '../JadefinIntegrity.js';
import Jadefin from "../Jadefin.js";
import JadefinMod from "../JadefinMod.js";
import JadefinModules from "../JadefinModules.js";
import JadefinUtils from "../JadefinUtils.js";
@ -62,9 +63,34 @@ export default JadefinIntegrity("VolumeBoost", import.meta.url, () => new (class
async init(name, url) {
await super.init(name, url);
await JadefinUtils.waitUntil(() => JadefinModules.actionSheet);
const ExtrasMenu = /** @type {import("./ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu"));
this.initHookActionSheetShow();
ExtrasMenu.items.push({
name: "Volume Boost",
in: ExtrasMenu.IN_CUSTOM,
inCustom: (current, item) => {
item.asideText = `${this.currentGain}x`;
return (current & (ExtrasMenu.IN_PLAYBACKSETTINGS)) == current;
},
cb: async (positionTo) => {
JadefinModules.actionSheet.show({
positionTo,
items: [
{id: "1", name: "1x", selected: this.currentGain == 1},
{id: "2", name: "2x", selected: this.currentGain == 2},
{id: "3", name: "3x", selected: this.currentGain == 3},
{id: "4", name: "4x", selected: this.currentGain == 4},
]
}).then(id => {
if (!id) {
return;
}
this.currentGain = parseInt(id);
});
}
});
document.addEventListener("viewshow", () => {
if (JadefinUtils.routePathIsVideo) {
@ -83,86 +109,6 @@ export default JadefinIntegrity("VolumeBoost", import.meta.url, () => new (class
this.log.i("Ready");
}
initHookActionSheetShow() {
const orig = this._actionSheetShowOrig = JadefinModules.actionSheet.show.bind(JadefinModules.actionSheet);
JadefinModules.actionSheet.show = (options) => {
const optionsOrig = Object.assign({}, options, {
items: options.items.slice()
});
this.log.v("actionSheet.show(...)");
this.log.dir(options);
// Options menu during playback
if (options.items.length >= 6 &&
options.items[0].id == "aspectratio" &&
options.items[1].id == "playbackrate" &&
options.items[2].id == "quality" &&
options.items[3].id == "repeatmode" &&
options.items[4].id == "suboffset" &&
options.items[5].id == "stats" &&
true
) {
options.items.splice(4, 0, {id: "custom-volumeboost", name: "Volume Boost", asideText: `${this.currentGain}x`});
return new Promise((resolve, reject) => {
JadefinModules.actionSheet.show({
positionTo: options.positionTo,
items: [
{id: "all", name: "Advanced..."},
{divider: true},
...options.items.slice(4),
]
}).then(id => {
if (id == "all") {
orig(Object.assign({}, options, {
items: [
...options.items.slice(0, 4),
{divider: true},
{id: "back", name: "Back..."},
]
})).then(id => {
if (id == "back") {
JadefinModules.actionSheet.show(optionsOrig).then(resolve).catch(reject);
} else {
resolve(id);
}
}).catch(reject);
return;
}
if (id == "custom-volumeboost") {
reject();
JadefinModules.actionSheet.show({
positionTo: options.positionTo,
items: [
{id: "1", name: "1x", selected: this.currentGain == 1},
{id: "2", name: "2x", selected: this.currentGain == 2},
{id: "3", name: "3x", selected: this.currentGain == 3},
{id: "4", name: "4x", selected: this.currentGain == 4},
]
}).then(id => {
if (!id) {
return;
}
this.currentGain = parseInt(id);
});
return;
}
resolve(id);
}).catch(reject);
});
}
return orig(options);
};
}
connect() {
const video = JadefinUtils.video;
if (!video) {

29
mods/jade/Filterworks.css Normal file
View File

@ -0,0 +1,29 @@
.htmlvideoplayer[data-filterworks-canvas="1"] {
/* Still needs to be layouted for subs and our own canvas. */
opacity: 0;
}
.filterworks-canvasParent {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
order: -2;
z-index: -1;
}
.filterworks-canvas {
position: absolute;
display: block;
padding: 0;
margin: auto;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

228
mods/jade/Filterworks.js Normal file
View File

@ -0,0 +1,228 @@
//@ts-check
import JadefinIntegrity from '../../JadefinIntegrity.js';
import Jadefin from "../../Jadefin.js";
import JadefinMod from "../../JadefinMod.js";
import JadefinModules from "../../JadefinModules.js";
import JadefinUtils from "../../JadefinUtils.js";
import { rd, rdom, rd$, RDOMListHelper } from "../../utils/rdom.js";
export class FilterworksFilter {
name = "Hello, World!";
/**
* @param {HTMLVideoElement} video
*/
start(video) {
}
stop(video) {
}
}
export class FilterworksSrc {
modName = "ExampleSource";
/** @type {{
[id: string]: FilterworksFilter;
}} */
filters = {
test: new FilterworksFilter()
};
}
export default JadefinIntegrity("Filterworks", import.meta.url, () => new (class Filterworks extends JadefinMod {
PATH_OFF = "Filterworks/Off";
_sources = [];
_pathsToFilters = {};
_filtersToMeta = {};
/** @type {any} */
appliedFilter = null;
canvasParentEl = rd$()`
<div class="filterworks-canvasParent">
</div>
`;
/** @type {typeof this._sources} */
sources = new Proxy(this._sources, {
deleteProperty: (target, property) => {
delete target[property];
this.updateSources();
return true;
},
set: (target, property, value, receiver) => {
target[property] = value;
this.updateSources();
return true;
}
});
constructor() {
super();
this.sources.push({
modName: "Filterworks",
filters: {
Off: {
name: "Off",
start: (video) => {},
stop: () => {}
}
}
});
}
get currentFilterPathPath() {
let id = "default";
const item = JadefinUtils.currentPlayer?.streamInfo?.item;
if (item) {
id = item.SeriesId || item.ParentId || item.Id || id;
}
return `currentFilterPath-${id}`;
}
get currentFilterPath() {
return this.storage.get(this.currentFilterPathPath, this.PATH_OFF);
}
set currentFilterPath(value) {
this.storage.set(this.currentFilterPathPath, value);
this.update();
}
get currentFilter() {
return this._pathsToFilters[this.currentFilterPath] || this._pathsToFilters[this.PATH_OFF];
}
async init(name, url) {
await super.init(name, url);
const ExtrasMenu = /** @type {import("../ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu"));
await this.initStyle();
ExtrasMenu.items.push({
name: "Filter",
in: ExtrasMenu.IN_CUSTOM,
inCustom: (current, item) => {
item.asideText = this.currentFilter.name;
return (current & (ExtrasMenu.IN_PLAYBACKSETTINGS)) == current;
},
cb: async (positionTo) => {
const items = [];
items.push({id: this.PATH_OFF, name: this._pathsToFilters[this.PATH_OFF].name, selected: this.currentFilter == this._pathsToFilters[this.PATH_OFF]});
for (const path in this._pathsToFilters) {
if (path == this.PATH_OFF) {
continue;
}
items.push({id: path, name: this._pathsToFilters[path].name, selected: this.currentFilterPath == path});
}
JadefinModules.actionSheet.show({
positionTo,
items
}).then(id => {
if (!id) {
return;
}
this.currentFilterPath = id;
});
}
});
document.addEventListener("viewshow", () => {
if (JadefinUtils.routePathIsVideo) {
if (this._tryUpdateRepeat) {
clearInterval(this._tryUpdateRepeat);
}
clearInterval(this._tryUpdateRepeat);
this._tryUpdateRepeat = setInterval(() => this.tryUpdate(), 100);
}
this.update();
});
this.log.i("Ready");
}
updateSources() {
this._pathsToFilters = {};
for (const src of this._sources) {
for (const id in src.filters) {
this._pathsToFilters[`${src.modName}/${id}`] = src.filters[id];
this._filtersToMeta[src.filters[id]] = { src, id };
}
}
}
tryUpdate() {
const video = JadefinUtils.video;
if (!video) {
return;
}
this.update();
if (this._tryUpdateRepeat) {
clearInterval(this._tryUpdateRepeat);
}
}
update() {
const video = JadefinUtils.video;
if (!video) {
if (this.appliedFilter) {
this.appliedFilter.stop();
this.appliedFilter = null;
}
this.video = null;
this.canvas = null;
return;
}
if (this.video == video && this.appliedFilter == this.currentFilter) {
return;
}
this.appliedFilter?.stop();
if (this.canvas) {
video.removeAttribute("data-filterworks-canvas");
this.canvas.remove();
}
this.video = video;
this.appliedFilter = this.currentFilter;
this.canvas = this.appliedFilter.start(video);
if (this.canvas) {
this.canvas.classList.add("filterworks-canvas");
this.canvasParentEl.append(this.canvas);
if (this.canvasParentEl.parentElement != video.parentElement) {
video.parentElement?.prepend(this.canvasParentEl);
}
video.setAttribute("data-filterworks-canvas", "1");
}
}
})());

View File

@ -16,7 +16,7 @@ export default JadefinIntegrity("Shortcuts", import.meta.url, () => new (class S
async init(name, url) {
await super.init(name, url);
this.initStyle();
await this.initStyle();
const ExtrasMenu = /** @type {import("../ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu"));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,95 @@
//@ts-check
import JadefinIntegrity from '../../../JadefinIntegrity.js';
import Jadefin from "../../../Jadefin.js";
import JadefinMod from "../../../JadefinMod.js";
import JadefinModules from "../../../JadefinModules.js";
import JadefinUtils from "../../../JadefinUtils.js";
class FilterworksAnime4KFilter {
constructor(name, key) {
this.name = name;
this.key = key;
this.a4k = window["Anime4KJS"];
if (!this.a4k) {
throw new Error("Anime4KJS not loaded");
}
this.profile = this.a4k[key];
}
/**
* @param {HTMLVideoElement} video
*/
start(video) {
if (this.upscaler) {
throw new Error("Filter already in use");
}
const stream = JadefinUtils.currentPlayer.streamInfo.mediaSource.MediaStreams.find(x => x.Type == "Video");
const fps = stream?.RealFrameRate || stream?.AverageFrameRate || 60;
this.upscaler = new this.a4k.VideoUpscaler(fps, this.profile);
this.video = video;
this.canvas = document.createElement("canvas");
this.upscaler.attachVideo(this.video, this.canvas);
this.upscaler.start();
return this.canvas;
}
stop() {
this.upscaler?.stop();
this.upscaler?.detachVideo();
this.upscaler = null;
this.canvas?.remove();
this.video?.removeAttribute("data-filterworks-canvas");
this.video = null;
}
}
export default JadefinIntegrity("FilterworksAnime4KSrc", import.meta.url, () => new (class FilterworksAnime4KSrc extends JadefinMod {
filters = {
}
constructor() {
super();
}
async init(name, url) {
await super.init(name, url);
await new Promise((resolve, reject) => {
const loader = document.createElement("script");
document.head.appendChild(loader);
loader.addEventListener("load", () => resolve(true));
loader.addEventListener("error", (e) => reject(e));
loader.defer = true;
loader.src = this.getUrl(".dist.js");
});
this.filters = {
lqSIMPLE: new FilterworksAnime4KFilter("Anime4K LQ Simple", "ANIME4KJS_SIMPLE_M_2X"),
lqA: new FilterworksAnime4KFilter("Anime4K LQ Mode A", "ANIME4K_LOWEREND_MODE_A_FAST"),
lqB: new FilterworksAnime4KFilter("Anime4K LQ Mode B", "ANIME4K_LOWEREND_MODE_B_FAST"),
lqC: new FilterworksAnime4KFilter("Anime4K LQ Mode C", "ANIME4K_LOWEREND_MODE_C_FAST"),
hqSIMPLE: new FilterworksAnime4KFilter("Anime4K HQ Simple", "ANIME4KJS_SIMPLE_L_2X"),
hqA: new FilterworksAnime4KFilter("Anime4K HQ Mode A", "ANIME4K_HIGHEREND_MODE_A_FAST"),
hqB: new FilterworksAnime4KFilter("Anime4K HQ Mode B", "ANIME4K_HIGHEREND_MODE_B_FAST"),
hqC: new FilterworksAnime4KFilter("Anime4K HQ Mode C", "ANIME4K_HIGHEREND_MODE_C_FAST")
};
const Filterworks = /** @type {import("../Filterworks.js").default} */ (Jadefin.getMod("Filterworks"));
Filterworks.sources.push(this);
this.log.i("Ready");
}
})());

View File

@ -8,5 +8,8 @@
"Transcript.js",
"PatchForceHLSJS.js",
"jade/Shortcuts.js"
"jade/Shortcuts.js",
"jade/Filterworks.js",
"jade/filters/Anime4K.js"
]