Merge branch 'master' into feature/player-ui

This commit is contained in:
Tamius Han 2021-10-19 20:27:41 +02:00
commit 2a747e11af
14 changed files with 292 additions and 256 deletions

2
.gitignore vendored
View File

@ -13,3 +13,5 @@ build/
src/res/img/git-ignore/
test/debug-configs/
debugging-resources/

View File

@ -1,23 +0,0 @@
image: node:current
cache:
paths:
- node_modules/
- .yarn
stages:
- prep
- build
- pack
install deps:
stage: prep
script: yarn install
build:
stage: build
script: npm run build
create zip:
stage: pack
script: npm run build-zip

View File

@ -1,7 +1,5 @@
{
"cSpell.words": [
"PILLARBOX",
"PILLARBOXED",
"aard",
"ardetector",
"autodetect",
@ -16,6 +14,7 @@
"csui",
"decycle",
"disneyplus",
"endregion",
"equalish",
"fith",
"fitw",
@ -24,6 +23,7 @@
"gmail",
"guardline",
"han",
"haram",
"iframe",
"imgur",
"insta",
@ -34,6 +34,8 @@
"nogrow",
"noshrink",
"outro",
"PILLARBOX",
"PILLARBOXED",
"polyfill",
"recursing",
"reddit",

View File

@ -18,12 +18,17 @@
## v5.x (current major)
### v5.1.1
* Fixed autodetection
### v5.1.0
* Re-enable logger
* Move aspect ratio autodetection to requestAnimationFrame
* Fix netflix
### v5.0.7
### v5.0.7
* Videos of square-ish aspect ratios on 1440p (and lower) resolutions now no longer get misaligned ([#162](https://github.com/tamius-han/ultrawidify/issues/162))
* Alignment of featured videos on youtube channel page should now also be fixed

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "5.1.0",
"version": "5.1.1",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "5.1.0",
"version": "5.1.1",
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
"author": "Tamius Han <tamius.han@gmail.com>",
"scripts": {

View File

@ -33,6 +33,9 @@
<h2>Logger configuration</h2>
</div>
<div class="flex flex-row flex-end w100">
<div v-if="!showTextMode" class="button" @click="loadDefaultConfig()">
Default config
</div>
<div v-if="!showTextMode" class="button" @click="showTextMode = true">
<Icon icon="clipboard-plus" style="font-size: 2em"></Icon>&nbsp; Paste config ...
</div>
@ -56,7 +59,7 @@
<template v-else>
<JsonObject label="logger-settings"
:value="currentSettings"
:ignoreKeys="{'allowLogging': true}"
:ignoreKeys="{'allowLogging': false}"
@change="updateSettingsUi"
></JsonObject>
</template>
@ -107,11 +110,16 @@
</template>
<template v-else>
<div class="panel-middle scrollable flex-grow">
<div v-if="!parsedSettings" class="text-center w100">
Please paste logger config into the text box to the left.
<div>
<p>Here's express usage tutorial on how to use the logger.</p>
<p>Quick rundown of all the options you can put into logger configuration can be found <a href="https://github.com/tamius-han/ultrawidify/wiki/Development&Debugging:-Logger-options" target="_blank">here</a>.</p>
<p>If you want logging results to appear here, in this window, you need to put appropriate configuration in the fileOptions section of the settings. You can edit the settings by clicking 'paste config' button.</p>
<p>To start logging to console, it's enough to click that tiny 'save' button down there, under logger options (<code>logger-settings.allowLogging</code> must be set to true. Then depending on where you want logging to happen you also need to enable either <code>logger-settings.fileOptions.enabled</code> or <code>logger-settings.consoleOptions.enabled</code>)</p>
<p>You can quickly toggle values for various components by clicking on "true" or "false".</p>
<p>Click the small 'save' down at the bottom of this window to immediately apply the logger configuration changes.</p>
<p>Yes, I know this is not a pinnacle of user-friendliness, nor a pinnacle of web design. It was put together very quickly, mostly for my convenience. I have plans to move extension UI from the popup into the player, tho, and these plans will make this window obsolete. Because of that, I plan to do absolutely nothing about the state of this window. Wait for the extension redesign pls.</p>
</div>
<div v-else-if="confHasError" class="warn">
<div v-if="confHasError" class="warn">
Logger configuration contains an error. Cannot start logging.
</div>
<div v-else-if="lastSettings && lastSettings.allowLogging && lastSettings.consoleOptions && lastSettings.consoleOptions.enabled"
@ -160,7 +168,7 @@
<script>
import { mapState } from 'vuex';
import Logger from '../ext/lib/Logger';
import Logger, { baseLoggingOptions } from '../ext/lib/Logger';
import Comms from '../ext/lib/comms/Comms';
import IO from '../common/js/IO';
import JsonObject from '../common/components/JsonEditor/JsonObject';
@ -196,6 +204,12 @@ export default {
}, {
header: "Tracer",
subheader: "I'm already printing stack traces"
}, {
header: "Grûmsh",
subheader: "He who watches"
}, {
header: "Situation Room/The Council",
subheader: "We will always be watching"
}];
this.header = headerRotation[Math.floor(+Date.now() / (3600000*24)) % headerRotation.length] || this.header;
@ -233,8 +247,13 @@ export default {
}
},
methods: {
loadDefaultConfig() {
this.lastSettings = baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},
async getLoggerSettings() {
this.lastSettings = await Logger.getConfig() || {};
this.lastSettings = await Logger.getConfig() || baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},

View File

@ -234,6 +234,13 @@ const ExtensionConfPatch = [
playerNodeCss: "",
}
}
}, {
forVersion: '5.0.8',
updateFn: (userOptions, defaultOptions) => {
userOptions.sites['www.netflix.com'].DOM.player = {
manual: false
}
}
}
];

View File

@ -1048,13 +1048,9 @@ const ExtensionConf: SettingsInterface = {
videoAlignment: VideoAlignmentType.Default,
keyboardShortcutsEnabled: ExtensionMode.Default,
arPersistence: true, // persist aspect ratio between different videos
"DOM": {
"player": {
"manual": true,
"querySelectors": ".VideoContainer",
"additionalCss": "",
"useRelativeAncestor": false,
"playerNodeCss": ""
DOM: {
player: {
manual: false, // as of 2021-09, netflix no longer requires manual player class. This may change in the future tho.
}
}
},

View File

@ -28,10 +28,10 @@ export const baseLoggingOptions: LoggerConfig = {
"resizer": true,
"scaler": true,
"stretcher": true,
// "videoRescan": true,
// "playerRescan": true,
"videoRescan": false,
"playerRescan": false,
"arDetect": true,
"arDetect_verbose": true
"arDetect_verbose": false
},
allowBlacklistedOrigins: {
'periodicPlayerCheck': false,
@ -166,7 +166,7 @@ class Logger {
if (this.conf.fileOptions === undefined) {
this.conf.fileOptions = {};
}
this.startTime = performance.now();
this.temp_disable = false;
this.stopTime = this.conf.timeout ? performance.now() + (this.conf.timeout * 1000) : undefined;
@ -205,7 +205,7 @@ class Logger {
} catch (e) {
console.warn('[uwLogger] Error while trying to parse new conf for logger:', e, '\nWe received the following changes:', changes, 'for area:', area);
}
// This code can only execute if user tried to enable or disable logging
// through the popup. In cases like this, we do not gate the console.log
// behind a check, since we _always_ want to have this feedback in response
@ -225,7 +225,7 @@ class Logger {
setVuexStore(store) {
this.vuexStore = store;
}
clear() {
this.history = [];
this.startTime = performance.now();
@ -239,12 +239,12 @@ class Logger {
}
// async getSaved() {
// return Logger.getSaved();
// return Logger.getSaved();
// }
// allow syncing of start times between bg and page scripts.
// may result in negative times in the log file, but that doesn't
// may result in negative times in the log file, but that doesn't
// really matter
getStartTime() {
return this.startTime;
@ -286,7 +286,7 @@ class Logger {
}
// this should be used mostly in background page instance of logger, btw
//
//
addToGlobalHistory(key, log) {
this.globalHistory[key] = log;
this.log('info', 'debug', 'Added log for', key, 'to global history. Current history:', this.globalHistory);
@ -358,8 +358,8 @@ class Logger {
stackInfo.stack.trace.splice(i);
break;
}
}
}
return stackInfo;
}
@ -411,7 +411,7 @@ class Logger {
if (this.isBlacklistedOrigin(stackInfo)) {
return false;
}
}
// if either of these two is true, we allow logging to happen (forbidden origins were checked above)
return (this.canLogFile(component) || this.canLogConsole(component) || stackInfo.exitLogs);
@ -448,7 +448,7 @@ class Logger {
return this.conf.logAll;
}
logToFile(message, stackInfo) {
let ts = performance.now();
if (ts <= this.history[this.history.length - 1]) {
@ -465,16 +465,16 @@ class Logger {
logToConsole(level, message, stackInfo) {
try {
switch (level) {
case 'error':
case 'error':
console.error(...message, {stack: stackInfo});
break;
case 'warn':
console.warn(...message, {stack: stackInfo});
break;
case 'info':
case 'info':
console.info(...message, {stack: stackInfo});
break;
default:
default:
console.log(...message, {stack: stackInfo});
}
} catch (e) {
@ -512,7 +512,7 @@ class Logger {
if (this.isBlacklistedOrigin(stackInfo)) {
return;
}
if (this.conf.fileOptions?.enabled) {
if (this.canLogFile(component) || stackInfo.exitLogs) {
this.logToFile(message, stackInfo);
@ -545,7 +545,7 @@ class Logger {
message: "-------------------------------------- CAHEN --------------------------------------"
});
// find the spot for the half second mark. In this case, we don't really particularly care whether timestamps
// find the spot for the half second mark. In this case, we don't really particularly care whether timestamps
// are duped due to cahen warnings
while (this.history[i--].ts > halfSecondMark) {}
this.history.push({
@ -593,7 +593,7 @@ class Logger {
Comms.sendMessage({cmd: 'show-logger', forwardToSameFramePort: true, port: 'content-ui-port'});
let exportObject;
let exportObject;
try {
exportObject = {
pageLogs: decycle(this.history),
@ -631,7 +631,7 @@ class Logger {
console.info('[info] vuex store present. Parsing logs.');
let exportObject;
let exportObject;
try {
exportObject = {
pageLogs: decycle(this.history),

View File

@ -114,9 +114,9 @@ class ArDetector {
* We get one animation frame per this many ms. This means that our autodetection
* stuff must run in less than this many ms. This valuz is averaged out over multiple
* samples for better accuracy.
*
*
* Returns value in ms.
*
*
* A very important caveat: if autodetection takes up too much time, it WILL artificially
* increase time budget. Therefore, you should use (and firstly even implement) getTimeBudget()
* that turns off autodetection for a second or so to gather accurate timing info.
@ -146,7 +146,7 @@ class ArDetector {
this.conf = videoData;
this.video = videoData.video;
this.settings = videoData.settings;
this.sampleCols = [];
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
@ -193,7 +193,7 @@ class ArDetector {
this.edgeDetector = new EdgeDetect(this);
// this.debugCanvas = new DebugCanvas(this);
//
// [1] initiate canvases
//
@ -210,7 +210,7 @@ class ArDetector {
this.blackframeCanvas.remove();
}
// things to note: we'll be keeping canvas in memory only.
// things to note: we'll be keeping canvas in memory only.
this.canvas = document.createElement("canvas");
this.canvas.width = cwidth;
this.canvas.height = cheight;
@ -233,10 +233,10 @@ class ArDetector {
let ncol = this.settings.active.arDetect.sampling.staticCols;
let nrow = this.settings.active.arDetect.sampling.staticRows;
let colSpacing = this.canvas.width / ncol;
let rowSpacing = (this.canvas.height << 2) / nrow;
this.sampleLines = [];
this.sampleCols = [];
@ -276,7 +276,7 @@ class ArDetector {
} catch (e) {
this.canDoFallbackMode = false;
}
//
// [5] do other things setup needs to do
//
@ -289,12 +289,12 @@ class ArDetector {
this.canvasImageDataRowLength = cwidth << 2;
this.noLetterboxCanvasReset = false;
if (this.settings.canStartAutoAr() ) {
// this.main();
this.start();
}
if(Debug.debugCanvas.enabled){
// this.debugCanvas.init({width: cwidth, height: cheight});
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
@ -324,9 +324,9 @@ class ArDetector {
this.conf.resizer.setLastAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
}
this._paused = false;
this._halted = false;
this._paused = false;
this._halted = false;
this._exited = false;
// start autodetection
this.startLoop();
@ -342,13 +342,16 @@ class ArDetector {
}
this.animationFrameHandle = window.requestAnimationFrame( (ts) => this.animationFrameBootstrap(ts));
this.logger.log('info', 'debug', `"%c[ArDetect::startLoop] <@${this.arid}> AARD loop started.`, _ard_console_start);
}
stop() {
if (this.animationFrameHandle) {
this.logger.log('info', 'debug', `"%c[ArDetect::stop] <@${this.arid}> Stopping AnimationFrame loop.`, _ard_console_stop);
window.cancelAnimationFrame(this.animationFrameHandle);
} else {
this.logger.log('info', 'debug', `"%c[ArDetect::stop] <@${this.arid}> AnimationFrame loop is already paused (due to an earlier call of this function).`);
}
this.logger.log('info', 'debug', `"%c[ArDetect::stop] <@${this.arid}> Stopping AnimationFrame loop.`, _ard_console_stop);
}
unpause() {
@ -405,16 +408,17 @@ class ArDetector {
/**
* Checks whether conditions for granting a frame check are fulfilled
* @returns
* @returns
*/
private canTriggerFrameCheck() {
if (this._paused) {
if (this._paused || this._halted || this._exited) {
return false;
}
// if video was paused & we know that we already checked that frame,
// we will not check it again.
const videoState = this.getVideoPlaybackState();
if (videoState !== VideoPlaybackState.Playing) {
if (this.status.lastVideoStatus === videoState) {
return false;
@ -435,10 +439,10 @@ class ArDetector {
timeout = 100;
}
// don't allow more than 1 instance
if(this.setupTimer){
if(this.setupTimer){
clearTimeout(this.setupTimer);
}
let ths = this;
this.setupTimer = setTimeout(function(){
ths.setupTimer = null;
@ -475,8 +479,8 @@ class ArDetector {
/**
* Adds execution time sample for performance metrics
* @param performanceObject
* @param executionTime
* @param performanceObject
* @param executionTime
*/
private addPerformanceTimeMeasure(performanceObject, executionTime) {
performanceObject.sampleTime[performanceObject.currentIndex] = executionTime;
@ -489,19 +493,19 @@ class ArDetector {
/**
* Returns time ultrawidify spends on certain aspects of autodetection.
*
*
* The returned object contains the following:
*
*
* eyeballedTimeBudget a very inaccurate time budget
* fps framerate at which we run
* aardTime time spent on average frameCheck loop.
* It's a nearly useless metric, because
* aardTime time spent on average frameCheck loop.
* It's a nearly useless metric, because
* frameCheck can exit very early.
* drawWindowTime how much time browser spends on executing
* drawWindow() calls.
* getImageData how much time browser spends on executing
* getImageData() calls.
*
*
* Most of these are on "per frame" basis and averaged.
*/
getTimings() {
@ -527,31 +531,35 @@ class ArDetector {
/**
* This is the "main loop" for aspect ratio autodetection
*/
private animationFrameBootstrap(timestamp: number) {
private async animationFrameBootstrap(timestamp: number) {
// this.logger.log('info', 'arDetect_verbose', `[ArDetect::animationFrameBootstrap] <@${this.arid}> New animation frame.\nmanualTickEnabled: ${!this.manualTickEnabled}\ncan trigger frame check? ${this.canTriggerFrameCheck()}\nnext tick? ${this._nextTick}\n => (a&b | c) => Can we do tick? ${ (!this.manualTickEnabled && this.canTriggerFrameCheck()) || this._nextTick}\n\ncan we continue running? ${this && !this._halted && !this._paused}`);
// do timekeeping first
this.addPerformanceTimeMeasure(this.performance.animationFrame, timestamp - this.performance.animationFrame.lastTime)
this.addPerformanceTimeMeasure(this.performance.animationFrame, timestamp - this.performance.animationFrame.lastTime);
this.performance.animationFrame.lastTime = timestamp;
// trigger frame check, if we're allowed to
if ( (!this.manualTickEnabled && this.canTriggerFrameCheck()) || this._nextTick) {
this.logger.log('info', 'arDetect_verbose', `[ArDetect::animationFrameBootstrap] <@${this.arid}> Processing next tick.`);
this._nextTick = false;
try {
const startTime = performance.now();
this.frameCheck();
await this.frameCheck();
this.addPerformanceTimeMeasure(this.performance.aard, performance.now() - startTime);
} catch (e) {
this.logger.log('error', 'debug', `%c[ArDetect::main] <@${this.arid}> Frame check failed:`, "color: #000, background: #f00", e);
this.logger.log('error', 'debug', `%c[ArDetect::animationFrameBootstrap] <@${this.arid}> Frame check failed:`, "color: #000, background: #f00", e);
}
}
if (this && !this._halted && !this._paused) {
this.animationFrameHandle = window.requestAnimationFrame( (ts) => this.animationFrameBootstrap(ts));
} else if (this._halted) {
this.logger.log('info', 'debug', `%c[ArDetect::main] <@${this.arid}> Main autodetection loop exited. Halted? ${this._halted}`, _ard_console_stop);
this.logger.log('info', 'debug', `%c[ArDetect::animationFrameBootstrap] <@${this.arid}> Main autodetection loop exited. Halted? ${this._halted}`, _ard_console_stop);
this._exited = true;
} else {
this.logger.log('info', 'debug', `[ArDetect::main] <@${this.arid}> Not renewing animation frame for some reason. Paused? ${this._paused}; Halted?: ${this._halted}, Exited?: ${this._exited}`);
this.logger.log('info', 'debug', `[ArDetect::animationFrameBootstrap] <@${this.arid}> Not renewing animation frame for some reason. Paused? ${this._paused}; Halted?: ${this._halted}, Exited?: ${this._exited}`);
}
}
@ -565,11 +573,11 @@ class ArDetector {
}
let letterbox = edges.top + edges.bottom;
if (! this.fallbackMode) {
// Since video is stretched to fit the canvas, we need to take that into account when calculating target
// aspect ratio and correct our calculations to account for that
// aspect ratio and correct our calculations to account for that
const fileAr = this.video.videoWidth / this.video.videoHeight;
const canvasAr = this.canvas.width / this.canvas.height;
@ -590,7 +598,7 @@ class ArDetector {
// fallback mode behaves a wee bit differently
let zoomFactor = 1;
// there's stuff missing from the canvas. We need to assume canvas' actual height is bigger by a factor x, where
// x = [video.zoomedHeight] / [video.unzoomedHeight]
//
@ -598,7 +606,7 @@ class ArDetector {
// letterbox += [video.zoomedHeight] - [video.unzoomedHeight]
let vbr = this.video.getBoundingClientRect();
zoomFactor = vbr.height / this.video.clientHeight;
letterbox += vbr.height - this.video.clientHeight;
@ -608,7 +616,7 @@ class ArDetector {
this.logger.log('info', 'arDetect', `%c[ArDetect::calculateArFromEdges] <@${this.arid}> Edge is in the no-trigger zone. Aspect ratio change is not triggered.`)
return;
}
// varnostno območje, ki naj ostane črno (da lahko v fallback načinu odkrijemo ožanje razmerja stranic).
// x2, ker je safetyBorderPx definiran za eno stran.
// safety border so we can detect aspect ratio narrowing (21:9 -> 16:9).
@ -620,8 +628,12 @@ class ArDetector {
}
processAr(trueAr){
if (!this.isRunning()) {
this.logger.log('warn', 'debug', `[ArDetect::processAr] <@${this.arid}> Trying to change aspect ratio while AARD is paused.`);
return;
}
this.detectedAr = trueAr;
// poglejmo, če se je razmerje stranic spremenilo
// check if aspect ratio is changed:
let lastAr = this.conf.resizer.getLastAr();
@ -630,14 +642,14 @@ class ArDetector {
// že nastavili.
//
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
let arDiff = trueAr - lastAr.ratio;
if (arDiff < 0)
arDiff = -arDiff;
const arDiff_percent = arDiff / trueAr;
// ali je sprememba v mejah dovoljenega? Če da -> fertik
// is ar variance within acceptable levels? If yes -> we done
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> New aspect ratio varies from the old one by this much:\n`,"color: #aaf","old Ar", lastAr.ratio, "current ar", trueAr, "arDiff (absolute):",arDiff,"ar diff (relative to new ar)", arDiff_percent);
@ -649,7 +661,7 @@ class ArDetector {
this.logger.log('info', 'arDetect', `%c[ArDetect::processAr] <@${this.arid}> aspect ratio change accepted — diff %: ${arDiff_percent}`, "background: #153; color: #4f9");
}
this.logger.log('info', 'debug', `%c[ArDetect::processAr] <@${this.arid}> Triggering aspect ratio change. New aspect ratio: ${trueAr}`, _ard_console_change);
this.conf.resizer.updateAr({type: AspectRatioType.Automatic, ratio: trueAr});
}
@ -660,7 +672,9 @@ class ArDetector {
id = undefined;
}
frameCheck(){
async frameCheck(){
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::processAr] <@${this.arid}> Starting frame check.`);
if(! this.video){
this.logger.log('error', 'debug', `%c[ArDetect::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`);
this.conf.destroy();
@ -670,7 +684,7 @@ class ArDetector {
if (!this.blackframeContext) {
this.init();
}
let startTime;
let partialDrawImageTime = 0;
let sampleCols = this.sampleCols.slice(0);
@ -680,7 +694,15 @@ class ArDetector {
//
try {
startTime = performance.now();
this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
// do it in ghetto async. This way, other javascript function should be able to
// get a chance to do something _before_ we process our data
await new Promise<void>(
resolve => {
this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
resolve();
}
);
partialDrawImageTime += performance.now() - startTime;
this.fallbackMode = false;
@ -698,10 +720,10 @@ class ArDetector {
if (! this.canvasReadyForDrawWindow()) {
// this means canvas needs to be resized, so we'll just re-run setup with all those new parameters
this.halt();
let newCanvasWidth = window.innerHeight * (this.video.videoWidth / this.video.videoHeight);
let newCanvasHeight = window.innerHeight;
if (this.conf.resizer.videoAlignment === VideoAlignmentType.Center) {
this.canvasDrawWindowHOffset = Math.round((window.innerWidth - newCanvasWidth) * 0.5);
} else if (this.conf.resizer.videoAlignment === VideoAlignmentType.Left) {
@ -711,9 +733,9 @@ class ArDetector {
}
this.setup(newCanvasWidth, newCanvasHeight);
return;
}
}
// if this is the case, we'll first draw on canvas, as we'll need intermediate canvas if we want to get a
// smaller sample for blackframe check
this.fallbackMode = true;
@ -726,7 +748,12 @@ class ArDetector {
return; // it's prolly just a fluke, so we do nothing special here
}
// draw blackframe sample from our main sample:
this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height);
await new Promise<void>(
resolve => {
this.blackframeContext.drawImage(this.canvas, this.blackframeCanvas.width, this.blackframeCanvas.height);
resolve();
}
);
partialDrawImageTime += performance.now() - startTime;
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] canvas.drawImage seems to have worked`, "color:#000; backgroud:#2f5;");
@ -743,7 +770,12 @@ class ArDetector {
// if we are in normal mode though, the frame has yet to be drawn
if (!this.fallbackMode) {
startTime = performance.now();
this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
await new Promise<void>(
resolve => {
this.context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
resolve();
}
)
partialDrawImageTime += performance.now() - startTime;
}
@ -773,20 +805,20 @@ class ArDetector {
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
this.noLetterboxCanvasReset = false;
// poglejmo, če obrežemo preveč.
// let's check if we're cropping too much
const guardLineOut = this.guardLine.check(imageData, this.fallbackMode);
// če ni padla nobena izmed funkcij, potem se razmerje stranic ni spremenilo
// if both succeed, then aspect ratio hasn't changed.
// if both succeed, then aspect ratio hasn't changed.
if (!guardLineOut.imageFail && !guardLineOut.blackbarFail) {
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut);
this.clearImageData(imageData);
return;
}
// drugače nadaljujemo, našemu vzorcu stolpcev pa dodamo tiste stolpce, ki so
// drugače nadaljujemo, našemu vzorcu stolpcev pa dodamo tiste stolpce, ki so
// kršili blackbar (če obstajajo) ter jih razvrstimo
// otherwise we continue. We add blackbar violations to the list of the cols
// we'll sample and sort them
@ -795,11 +827,13 @@ class ArDetector {
(a: number, b: number) => a - b
);
}
// if we're in fallback mode and blackbar test failed, we restore CSS and quit
// (since the new letterbox edge isn't present in our sample due to technical
// limitations)
if (this.fallbackMode && guardLineOut.blackbarFail) {
this.logger.log('warn', 'arDetect_verbose', `%c[ArDetect::frameCheck] <@${this.arid}> We are in fallback mode and blackbar failed. Reverting to initial aspect ratio.`);
this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
this.guardLine.reset();
this.noLetterboxCanvasReset = true;
@ -807,46 +841,39 @@ class ArDetector {
this.clearImageData(imageData);
return;
}
// će se razmerje stranic spreminja iz ožjega na širšega, potem najprej poglejmo za prisotnostjo navpičnih črnih obrob.
// če so prisotne navpične obrobe tudi na levi in desni strani, potlej obstaja možnost, da gre za logo na črnem ozadju.
// v tem primeru obstaja nevarnost, da porežemo preveč. Ker obstaja dovolj velika možnost, da bi porezali preveč, rajši
// ne naredimo ničesar.
//
// če je pillarbox zaznan v primeru spremembe iz ožjega na širše razmerje stranice, razmerje povrnemo na privzeto vrednost.
//
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
try{
if(guardLineOut.blackbarFail || guardLineOut.imageFail){
if(this.edgeDetector.findBars(imageData, null, EdgeDetectPrimaryDirection.Horizontal).status === 'ar_known'){
if(guardLineOut.blackbarFail){
this.logger.log('info', 'arDetect', `[ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio.`);
this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
this.guardLine.reset();
} else {
this.logger.log('info', 'arDetect_verbose', `[ArDetect::frameCheck] Guardline failed, blackbar didn't, and we got pillarbox. Doing nothing.`);
}
this.clearImageData(imageData);
return;
}
}
} catch(e) {
} catch(e) {
this.logger.log('info', 'arDetect', `[ArDetect::frameCheck] something went wrong while checking for pillarbox. Error:\n`, e);
}
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
// let's see where black bars end.
this.sampleCols_current = sampleCols.length;
// blackSamples -> {res_top, res_bottom}
let edgePost = this.edgeDetector.findBars(imageData, sampleCols, EdgeDetectPrimaryDirection.Vertical, EdgeDetectQuality.Improved, guardLineOut, bfanalysis);
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] edgeDetector returned this\n`, "color: #aaf", edgePost);
if (edgePost.status !== EdgeStatus.ARKnown){
// rob ni bil zaznan, zato ne naredimo ničesar.
// no edge was detected. Let's leave things as they were
@ -857,12 +884,12 @@ class ArDetector {
}
let newAr = this.calculateArFromEdges(edgePost);
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${newAr}`, "color: #aaf");
// we also know edges for guardline, so set them.
// we need to be mindful of fallbackMode though
// if edges are okay and not invalid, we also
// if edges are okay and not invalid, we also
// allow automatic aspect ratio correction. If edges
// are bogus, we remain aspect ratio unchanged.
try {
@ -883,16 +910,16 @@ class ArDetector {
this.processAr(newAr);
} catch (e) {
// edges weren't gucci, so we'll just reset
// edges weren't gucci, so we'll just reset
// the aspect ratio to defaults
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] There was a problem setting blackbar. Doing nothing. Error:`, e);
try {
this.guardLine.reset();
} catch (e) {
// no guardline, no bigge
}
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
// (eg. here: https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410)
//
// this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
@ -902,7 +929,7 @@ class ArDetector {
}
resetBlackLevel(){
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
this.blackLevel = this.settings.active.arDetect.blackbar.blackLevel;
}
blackLevelTest_full() {
@ -928,7 +955,7 @@ class ArDetector {
let blackPixelCount = 0;
const bfImageData = this.blackframeContext.getImageData(0, 0, cols, rows).data;
const blackTreshold = this.blackLevel + this.settings.active.arDetect.blackbar.frameThreshold;
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
// with sufficient level of certainty due to small sample resolution, it can still give us some hints for later
@ -957,7 +984,7 @@ class ArDetector {
max_g = max_g > bfImageData[i+1] ? max_g : bfImageData[i+1];
max_b = max_b > bfImageData[i+2] ? max_b : bfImageData[i+2];
}
r = ~~(i/rows);
c = i % cols;
@ -1041,19 +1068,19 @@ class ArDetector {
/**
* Does a quick test to see if the aspect ratio is correct
* Returns 'true' if there's a chance of letterbox existing, false if not.
* @param imageData
* @param sampleCols
* @returns
* @param imageData
* @param sampleCols
* @returns
*/
fastLetterboxPresenceTest(imageData, sampleCols) {
// fast test to see if aspect ratio is correct.
// returns 'true' if presence of letterbox is possible.
// returns 'false' if we found a non-black edge pixel.
// returns 'false' if we found a non-black edge pixel.
// If we detect anything darker than blackLevel, we modify blackLevel to the new lowest value
const rowOffset = this.canvas.width * (this.canvas.height - 1);
let currentMin = 255, currentMax = 0, colOffset_r, colOffset_g, colOffset_b, colOffset_rb, colOffset_gb, colOffset_bb, blthreshold = this.settings.active.arDetect.blackbar.threshold;
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
for (let i = 0; i < sampleCols.length; ++i){

View File

@ -29,7 +29,7 @@ class PageInfo {
logger: Logger;
settings: Settings;
comms: CommsClient;
videos: VideoData[] = [];
videos: {videoData: VideoData, element: HTMLVideoElement}[] = [];
//#endregion
//#region misc stuff
@ -113,8 +113,8 @@ class PageInfo {
}
for (let video of this.videos) {
try {
(this.comms.unregisterVideo as any)(video.vdid)
video.destroy();
(this.comms.unregisterVideo as any)(video.videoData.vdid)
video.videoData.destroy();
} catch (e) {
this.logger.log('error', ['debug', 'init'], '[PageInfo::destroy] unable to destroy video! Error:', e);
}
@ -135,8 +135,10 @@ class PageInfo {
reset() {
for(let video of this.videos) {
video.destroy();
video.videoData.destroy();
video.videoData = null;
}
this.videos = [];
this.rescan(RescanReason.MANUAL);
}
@ -159,7 +161,7 @@ class PageInfo {
getVideos(host) {
if (this.settings.active.sites[host]?.DOM?.video?.manual
&& this.settings.active.sites[host]?.DOM?.video?.querySelectors){
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelectors);
const videos = document.querySelectorAll(this.settings.active.sites[host].DOM.video.querySelectors) as NodeListOf<HTMLVideoElement>;
if (videos.length) {
return videos;
@ -172,9 +174,25 @@ class PageInfo {
return this.readOnly ? this.hasVideos : this.videos.length;
}
rescan(rescanReason){
const oldVideoCount = this.videos.length;
/**
* Re-scans the page for videos. Removes any videos that no longer exist from our list
* of videos. Destroys all videoData objects for all the videos that don't have their
* own <video> html element on the page.
* @param rescanReason Why was the rescan triggered. Mostly used for logging.
* @returns
*/
rescan(rescanReason?: RescanReason){
// is there any video data objects that had their HTML elements removed but not yet
// destroyed? We clean that up here.
const orphans = this.videos.filter(x => !document.body.contains(x.element));
for (const orphan of orphans) {
orphan.videoData.destroy();
}
// remove all destroyed videos.
this.videos = this.videos.filter(x => !x.videoData.destroyed);
// add new videos
try{
let vids = this.getVideos(window.location.hostname);
@ -190,65 +208,49 @@ class PageInfo {
// add new videos
this.hasVideos = false;
let videoExists = false;
let video, v;
let videoExists = false;
for (const videoElement of vids) {
// do not re-add videos that we already track:
if (this.videos.find(x => x.element.isEqualNode(videoElement))) {
continue;
}
for (video of vids) {
// če najdemo samo en video z višino in širino, to pomeni, da imamo na strani veljavne videe
// če trenutni video nima definiranih teh vrednostih, preskočimo vse nadaljnja preverjanja
// <===[:::::::]===>
// if we find even a single video with width and height, that means the page has valid videos
// if video lacks either of the two properties, we skip all further checks cos pointless
if(video.offsetWidth && video.offsetHeight){
this.hasVideos = true;
if (this.readOnly) {
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
// things that interfere with the website. We still want to be running a rescan, tho.
if(rescanReason == RescanReason.PERIODIC){
this.scheduleRescan(RescanReason.PERIODIC);
}
return;
}
} else {
if(!videoElement.offsetWidth || !videoElement.offsetHeight) {
continue;
}
// at this point, we're certain that we found new videos. Let's update some properties:
this.hasVideos = true;
videoExists = false;
// if PageInfo is marked as "readOnly", we actually aren't adding any videos to anything because
// that's super haram. We're only interested in whether
if (this.readOnly) {
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
// things that interfere with the website. We still want to be running a rescan, tho.
for (v of this.videos) {
if (v.destroyed) {
continue; //TODO: if destroyed video is same as current video, copy aspect ratio settings to current video
}
if (v.video == video) {
videoExists = true;
break;
if(rescanReason == RescanReason.PERIODIC){
this.scheduleRescan(RescanReason.PERIODIC);
}
return;
}
if (videoExists) {
continue;
} else {
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", video, "NOTE:: Video initialization starts here:\n--------------------------------\n")
try {
v = new VideoData(video, this.settings, this);
this.videos.push(v);
} catch (e) {
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
}
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", videoElement, "NOTE:: Video initialization starts here:\n--------------------------------\n")
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
try {
const newVideo = new VideoData(videoElement, this.settings, this);
this.videos.push({videoData: newVideo, element: videoElement});
} catch (e) {
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
}
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
}
this.removeDestroyed();
// če smo ostali brez videev, potem odregistriraj stran.
// če nismo ostali brez videev, potem registriraj stran.
//
// if we're left without videos on the current page, we unregister the page.
// if we have videos, we call register.
if (this.comms) {
@ -280,27 +282,23 @@ class PageInfo {
}
} catch(e) {
// če pride do zajeba, potem lahko domnevamo da na strani ni nobenega videa. Uničimo vse objekte videoData
// da preprečimo večkratno inicializacijo. Če smo se z našim ugibom zmotili, potem se bodo vsi videi ponovno
// našli ob naslednjem preiskovanju
//
// if we encounter a fuckup, we can assume that no videos were found on the page. We destroy all videoData
// objects to prevent multiple initialization (which happened, but I don't know why). No biggie if we destroyed
// videoData objects in error — they'll be back in the next rescan
this.logger.log('error', 'debug', "rescan error: — destroying all videoData objects",e);
for (const v of this.videos) {
v.destroy();
v.videoData.destroy();
}
this.videos = [];
return;
}
if(rescanReason == RescanReason.PERIODIC){
this.scheduleRescan(RescanReason.PERIODIC);
}
}
removeDestroyed(){
this.videos = this.videos.filter( vid => vid.destroyed === false);
this.videos = this.videos.filter( vid => vid.videoData.destroyed === false);
}
scheduleRescan(rescanReason){
@ -358,14 +356,14 @@ class PageInfo {
initArDetection(playingOnly){
if (playingOnly) {
for(let vd of this.videos){
if(vd.isPlaying()) {
vd.initArDetection();
if(vd.videoData.isPlaying()) {
vd.videoData.initArDetection();
}
}
return;
} else {
for(let vd of this.videos){
vd.initArDetection();
vd.videoData.initArDetection();
}
}
}
@ -376,13 +374,13 @@ class PageInfo {
pauseProcessing(playingOnly){
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.pause();
if (vd.videoData.isPlaying()) {
vd.videoData.pause();
}
}
} else {
for(let vd of this.videos){
vd.pause();
vd.videoData.pause();
}
}
}
@ -390,18 +388,18 @@ class PageInfo {
resumeProcessing(resumeAutoar = false, playingOnly = false){
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.resume();
if (vd.videoData.isPlaying()) {
vd.videoData.resume();
if(resumeAutoar){
vd.resumeAutoAr();
vd.videoData.resumeAutoAr();
}
}
}
} else {
for(let vd of this.videos){
vd.resume();
vd.videoData.resume();
if(resumeAutoar){
vd.resumeAutoAr();
vd.videoData.resumeAutoAr();
}
}
}
@ -414,13 +412,13 @@ class PageInfo {
}
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.startArDetection();
if (vd.videoData.isPlaying()) {
vd.videoData.startArDetection();
}
}
} else {
for(let vd of this.videos){
vd.startArDetection();
vd.videoData.startArDetection();
}
}
}
@ -428,13 +426,13 @@ class PageInfo {
stopArDetection(playingOnly){
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.stopArDetection();
if (vd.videoData.isPlaying()) {
vd.videoData.stopArDetection();
}
}
} else {
for(let vd of this.videos){
vd.stopArDetection();
vd.videoData.stopArDetection();
}
}
}
@ -449,8 +447,8 @@ class PageInfo {
try {
for (let vd of this.videos) {
if (!playingOnly || vd.isPlaying()) {
vd.resetLastAr();
if (!playingOnly || vd.videoData.isPlaying()) {
vd.videoData.resetLastAr();
}
}
} catch (e) {
@ -464,14 +462,14 @@ class PageInfo {
// TODO: find a way to only change aspect ratio for one video
if (ar === AspectRatioType.Reset) {
for (let vd of this.videos) {
if (!playingOnly || vd.isPlaying()) {
vd.resetAr();
if (!playingOnly || vd.videoData.isPlaying()) {
vd.videoData.resetAr();
}
}
} else {
for (let vd of this.videos) {
if (!playingOnly || vd.isPlaying()) {
vd.setAr(ar)
if (!playingOnly || vd.videoData.isPlaying()) {
vd.videoData.setAr(ar)
}
}
}
@ -480,13 +478,13 @@ class PageInfo {
setVideoAlignment(videoAlignment, playingOnly) {
if (playingOnly) {
for(let vd of this.videos) {
if (vd.isPlaying()) {
vd.setVideoAlignment(videoAlignment)
if (vd.videoData.isPlaying()) {
vd.videoData.setVideoAlignment(videoAlignment)
}
}
} else {
for(let vd of this.videos) {
vd.setVideoAlignment(videoAlignment)
vd.videoData.setVideoAlignment(videoAlignment)
}
}
}
@ -494,13 +492,13 @@ class PageInfo {
setPanMode(mode, playingOnly?: boolean) {
if (playingOnly) {
for(let vd of this.videos) {
if (vd.isPlaying()) {
vd.setPanMode(mode);
if (vd.videoData.isPlaying()) {
vd.videoData.setPanMode(mode);
}
}
} else {
for(let vd of this.videos) {
vd.setPanMode(mode);
vd.videoData.setPanMode(mode);
}
}
}
@ -508,13 +506,13 @@ class PageInfo {
restoreAr(playingOnly?: boolean) {
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.restoreAr();
if (vd.videoData.isPlaying()) {
vd.videoData.restoreAr();
}
}
} else {
for(let vd of this.videos){
vd.restoreAr();
vd.videoData.restoreAr();
}
}
}
@ -524,13 +522,13 @@ class PageInfo {
if (playingOnly) {
for(let vd of this.videos){
if (vd.isPlaying()) {
vd.setStretchMode(stretchMode, fixedStretchRatio)
if (vd.videoData.isPlaying()) {
vd.videoData.setStretchMode(stretchMode, fixedStretchRatio)
}
}
} else {
for(let vd of this.videos){
vd.setStretchMode(stretchMode, fixedStretchRatio)
vd.videoData.setStretchMode(stretchMode, fixedStretchRatio)
}
}
}
@ -538,33 +536,33 @@ class PageInfo {
setZoom(zoomLevel, no_announce?: boolean, playingOnly?: boolean) {
if (playingOnly) {
for(let vd of this.videos) {
if (vd.isPlaying()) {
vd.setZoom(zoomLevel, no_announce);
if (vd.videoData.isPlaying()) {
vd.videoData.setZoom(zoomLevel, no_announce);
}
}
} else {
for(let vd of this.videos) {
vd.setZoom(zoomLevel, no_announce);
vd.videoData.setZoom(zoomLevel, no_announce);
}
}
}
zoomStep(step, playingOnly?: boolean) {
for(let vd of this.videos){
if (!playingOnly || vd.isPlaying()) {
vd.zoomStep(step);
if (!playingOnly || vd.videoData.isPlaying()) {
vd.videoData.zoomStep(step);
}
}
}
markPlayer(name, color) {
for (let vd of this.videos) {
vd.markPlayer(name,color);
vd.videoData.markPlayer(name,color);
}
}
unmarkPlayer() {
for (let vd of this.videos) {
vd.unmarkPlayer();
vd.videoData.unmarkPlayer();
}
}
@ -579,13 +577,13 @@ class PageInfo {
setManualTick(manualTick) {
for(let vd of this.videos) {
vd.setManualTick(manualTick);
vd.videoData.setManualTick(manualTick);
}
}
tick() {
for(let vd of this.videos) {
vd.tick();
vd.videoData.tick();
}
}

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "5.1.0",
"version": "5.1.1",
"applications": {
"gecko": {
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"

View File

@ -2,20 +2,23 @@
<div>
<h2>What's new</h2>
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md">is available here</a>.</p>
<p class="label">5.1.0</p>
<p class="label">5.1.1</p>
<p>
In last patch notes:
</p>
<pre>
> Hopefully that didn't break anything too much
</pre>
<p>
Well, about that ...
</p>
<ul>
<li>
Under the hood changes: aspect ratio autodetection now uses requestAnimationFrame instead of a setTimeout/setInterval-based loop.
</li>
<li>
Logger is sorta fixed.
Fixed the problem where autodetection wouldn't work for most people that aren't me.
</li>
</ul>
<p>
Hopefully that didn't break anything too much.
</p>
<p><small>
<b>Known issues:</b> zooming is limited in Chromium-based browsers. This is a browser bug that no extension can fix. See <a href="https://github.com/tamius-han/ultrawidify/discussions/161" target="_blank">this</a> for more info.
<b>Known issues:</b> zooming is limited in Chromium-based browsers. This is a browser bug that no extension can fix. Technical details about this can be found <a href="https://stuff.tamius.net/sacred-texts/2021/08/19/ultrawidify-and-chrome-2021-edition-episode-2/" target="_blank">on my blog.</a>.
</small></p>
</div>
</template>