From 3d7b50a2e3a3ccdba4a607027e08ca8e11b87b73 Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Sat, 26 Apr 2025 04:23:57 +0200 Subject: [PATCH] Get zoom options to mostly work --- src/common/interfaces/ArInterface.ts | 8 +- .../src/popup/panels/PopupVideoSettings.vue | 8 ++ src/ext/lib/aard/Aard.ts | 9 +- src/ext/lib/video-transform/Resizer.ts | 93 ++++++++++++------- src/ext/lib/video-transform/Scaler.ts | 25 +++-- 5 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/common/interfaces/ArInterface.ts b/src/common/interfaces/ArInterface.ts index d9e25ed..0a2b91c 100644 --- a/src/common/interfaces/ArInterface.ts +++ b/src/common/interfaces/ArInterface.ts @@ -1,6 +1,12 @@ import AspectRatioType from '../enums/AspectRatioType.enum'; +export enum ArVariant { + Crop = undefined, + Zoom = 1 +} + export interface Ar { type: AspectRatioType, - ratio?: number + ratio?: number, + variant?: ArVariant } diff --git a/src/csui/src/popup/panels/PopupVideoSettings.vue b/src/csui/src/popup/panels/PopupVideoSettings.vue index a986747..d993f10 100644 --- a/src/csui/src/popup/panels/PopupVideoSettings.vue +++ b/src/csui/src/popup/panels/PopupVideoSettings.vue @@ -43,6 +43,14 @@

Zoom:

+ + + diff --git a/src/ext/lib/aard/Aard.ts b/src/ext/lib/aard/Aard.ts index 63eee00..313082f 100644 --- a/src/ext/lib/aard/Aard.ts +++ b/src/ext/lib/aard/Aard.ts @@ -1,6 +1,7 @@ -import AspectRatioType from '../../../common/enums/AspectRatioType.enum'; -import ExtensionMode from '../../../common/enums/ExtensionMode.enum'; -import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface'; +import AspectRatioType from '@src/common/enums/AspectRatioType.enum'; +import ExtensionMode from '@src/common/enums/ExtensionMode.enum'; +import { ArVariant } from '@src/common/interfaces/ArInterface'; +import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface'; import EventBus from '../EventBus'; import Logger from '../Logger'; import Settings from '../Settings'; @@ -413,7 +414,7 @@ export class Aard { /** * Checks whether autodetection can run */ - startCheck() { + startCheck(arVariant?: ArVariant) { console.log('aard - starting checks') if (!this.videoData.player) { console.warn('Player not detected!'); diff --git a/src/ext/lib/video-transform/Resizer.ts b/src/ext/lib/video-transform/Resizer.ts index b6a380e..031e6ce 100644 --- a/src/ext/lib/video-transform/Resizer.ts +++ b/src/ext/lib/video-transform/Resizer.ts @@ -16,7 +16,7 @@ import VideoData from '../video-data/VideoData'; import EventBus from '../EventBus'; import { _cp } from '../../../common/js/utils'; import Settings from '../Settings'; -import { Ar } from '../../../common/interfaces/ArInterface'; +import { Ar, ArVariant } from '../../../common/interfaces/ArInterface'; import { RunLevel } from '../../enum/run-level.enum'; import * as _ from 'lodash'; import getElementStyles from '../../util/getElementStyles'; @@ -26,6 +26,11 @@ if(Debug.debug) { console.log("Loading: Resizer.js"); } +enum ResizerMode { + Crop = 1, + Zoom = 2 +}; + /** * Resizer is the top class and is responsible for figuring out which component needs to crop, which * component needs to zoom, and which component needs to stretch. @@ -83,9 +88,9 @@ class Resizer { //#endregion cycleableAspectRatios: Ar[]; + cycleableZoomAspectRatios: Ar[]; nextCycleOptionIndex = 0; - //#region event bus configuration private eventBusCommands = { 'set-ar': [{ @@ -93,7 +98,7 @@ class Resizer { this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe. if (config.type !== AspectRatioType.Cycle) { - this.setAr(config); + this.setAr({...config, variant: ArVariant.Crop}); } else { // if we manually switched to a different aspect ratio, cycle from that ratio forward const lastArIndex = this.cycleableAspectRatios.findIndex(x => x.type === this.lastAr.type && x.ratio === this.lastAr.ratio); @@ -101,11 +106,29 @@ class Resizer { this.nextCycleOptionIndex = (lastArIndex + 1) % this.cycleableAspectRatios.length; } - this.setAr(this.cycleableAspectRatios[this.nextCycleOptionIndex]); + this.setAr({...this.cycleableAspectRatios[this.nextCycleOptionIndex], variant: ArVariant.Crop}); this.nextCycleOptionIndex = (this.nextCycleOptionIndex + 1) % this.cycleableAspectRatios.length; } } }], + 'set-ar-zoom': [{ + function: (config: any) => { + this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe. + + if (config.type !== AspectRatioType.Cycle) { + this.setAr({...config, variant: ArVariant.Zoom}); + } else { + // if we manually switched to a different aspect ratio, cycle from that ratio forward + const lastArIndex = this.cycleableZoomAspectRatios.findIndex(x => x.type === this.lastAr.type && x.ratio === this.lastAr.ratio); + if (lastArIndex !== -1) { + this.nextCycleOptionIndex = (lastArIndex + 1) % this.cycleableZoomAspectRatios.length; + } + + this.setAr({...this.cycleableZoomAspectRatios[this.nextCycleOptionIndex], variant: ArVariant.Zoom}); + this.nextCycleOptionIndex = (this.nextCycleOptionIndex + 1) % this.cycleableZoomAspectRatios.length; + } + } + }], 'set-alignment': [{ function: (config: any) => { this.setVideoAlignment(config.x, config.y); @@ -188,6 +211,11 @@ class Resizer { .filter(x => [AspectRatioType.FitHeight, AspectRatioType.FitWidth, AspectRatioType.Fixed, AspectRatioType.Reset].includes(x?.arguments?.type)) .map(x => x.arguments) as Ar[]; + this.cycleableZoomAspectRatios = + (this.settings?.active?.commands?.zoom ?? []) + .filter(x => x.action === 'set-ar-zoom' && x.arguments?.type !== AspectRatioType.Cycle) + .map(x => x.arguments) as Ar[]; + this.nextCycleOptionIndex = 0; this.userCssClassName = videoData.userCssClassName; } @@ -276,8 +304,27 @@ class Resizer { } } + /** + * Starts and stops Aard as necessary. Returns 'true' if we can + * stop setting aspect ratio early. + * @param ar + * @param resizerMode + * @returns + */ + private handleAard(ar: Ar): boolean { + if (ar.type === AspectRatioType.Automatic) { + this.videoData.aard?.startCheck(ar.variant); + return true; + } else if (ar.type !== AspectRatioType.AutomaticUpdate) { + this.videoData.aard?.stop(); + } else if (this.stretcher.stretch.type === StretchType.Basic) { + this.videoData?.aard?.stop(); + } + + } + async setAr(ar: Ar, lastAr?: Ar) { - if (this.destroyed) { + if (this.destroyed || ar == null) { return; } @@ -294,11 +341,8 @@ class Resizer { } // handle autodetection stuff - if (ar.type === AspectRatioType.Automatic) { - this.videoData.aard?.startCheck(); + if (this.handleAard(ar)) { return; - } else if (ar.type !== AspectRatioType.AutomaticUpdate) { - this.videoData.aard?.stop(); } if (ar.type !== AspectRatioType.AutomaticUpdate) { @@ -312,10 +356,6 @@ class Resizer { this.logger.log('info', 'debug', '%c[Resizer::setAr] trying to set ar. New ar:', 'background-color: #4c3a2f, color: #ffa349', ar); - if (ar == null) { - return; - } - let stretchFactors: VideoDimensions | any; // reset zoom, but only on aspect ratio switch. We also know that aspect ratio gets converted to @@ -324,6 +364,7 @@ class Resizer { (ar.type !== AspectRatioType.Fixed && ar.type !== AspectRatioType.Manual) // anything not these two _always_ changes AR || ar.type !== this.lastAr.type // this also means aspect ratio has changed || ar.ratio !== this.lastAr.ratio // this also means aspect ratio has changed + || ar.variant !== this.lastAr.variant ) { this.zoom.reset(); this.resetPan(); @@ -360,26 +401,11 @@ class Resizer { this.videoData.destroy(); } - // pause AR on: - // * ar.type NOT automatic - // * ar.type is auto, but stretch is set to basic basic stretch - // - // unpause when using other modes - if ((ar.type !== AspectRatioType.Automatic && ar.type !== AspectRatioType.AutomaticUpdate) || this.stretcher.stretch.type === StretchType.Basic) { - this.videoData?.aard?.stop(); - } else { - if (ar.type !== AspectRatioType.AutomaticUpdate) { - if (this.lastAr.type === AspectRatioType.Automatic || this.lastAr.type === AspectRatioType.AutomaticUpdate) { - this.videoData?.aard?.stop(); - } - } - } - // do stretch thingy if ([StretchType.NoStretch, StretchType.Conditional, StretchType.FixedSource].includes(this.stretcher.stretch.type)) { stretchFactors = this.scaler.calculateCrop(ar); - if(! stretchFactors || stretchFactors.error){ + if (!stretchFactors || stretchFactors.error){ this.logger.log('error', 'debug', `[Resizer::setAr] failed to set AR due to problem with calculating crop. Error:`, stretchFactors?.error); if (stretchFactors?.error === 'no_video'){ this.videoData.destroy(); @@ -406,12 +432,11 @@ class Resizer { this.stretcher.stretch.type === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch', 'Stretch factors are:', stretchFactors ); - } else if (this.stretcher.stretch.type === StretchType.Hybrid) { stretchFactors = this.stretcher.calculateStretch(ar.ratio); this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors); } else if (this.stretcher.stretch.type === StretchType.Fixed) { - stretchFactors = this.stretcher.calculateStretchFixed(ar.ratio) + stretchFactors = this.stretcher.calculateStretchFixed(ar.ratio); } else if (this.stretcher.stretch.type === StretchType.Basic) { stretchFactors = this.stretcher.calculateBasicStretch(); this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for basic StretchType. Stretch factors are:', stretchFactors); @@ -794,8 +819,10 @@ class Resizer { // conditions are true at the same time, we need to go 'chiny reckon' and recheck our player // element. Chances are our video is not getting aligned correctly if ( - (this.videoData.video.offsetWidth > this.videoData.player.dimensions.width && this.videoData.video.offsetHeight > this.videoData.player.dimensions.height) || - (this.videoData.video.offsetWidth < this.videoData.player.dimensions.width && this.videoData.video.offsetHeight < this.videoData.player.dimensions.height) + ( + (this.videoData.video.offsetWidth > this.videoData.player.dimensions.width && this.videoData.video.offsetHeight > this.videoData.player.dimensions.height) || + (this.videoData.video.offsetWidth < this.videoData.player.dimensions.width && this.videoData.video.offsetHeight < this.videoData.player.dimensions.height) + ) && ar?.variant !== ArVariant.Zoom ) { this.logger.log('warn', ['debugger', 'resizer'], `[Resizer::_res_computeOffsets] We are getting some incredibly funny results here.\n\n`, `Video seems to be both wider and taller (or shorter and narrower) than player element at the same time. This is super duper not supposed to happen.\n\n`, diff --git a/src/ext/lib/video-transform/Scaler.ts b/src/ext/lib/video-transform/Scaler.ts index ca48d2b..a5a8728 100644 --- a/src/ext/lib/video-transform/Scaler.ts +++ b/src/ext/lib/video-transform/Scaler.ts @@ -3,6 +3,7 @@ import AspectRatioType from '../../../common/enums/AspectRatioType.enum'; import BrowserDetect from '../../conf/BrowserDetect'; import VideoData from '../video-data/VideoData'; import Logger from '../Logger'; +import { Ar, ArVariant } from '../../../common/interfaces/ArInterface'; export enum CropStrategy { @@ -100,7 +101,7 @@ class Scaler { return null; } - calculateCrop(ar: {type: AspectRatioType, ratio?: number}): VideoDimensions | {error: string, [x: string]: any} { + calculateCrop(ar: Ar): VideoDimensions | {error: string, [x: string]: any} { /** * STEP 1: NORMALIZE ASPECT RATIO * @@ -170,9 +171,15 @@ class Scaler { this.logger.log('info', 'scaler', "[Scaler::calculateCrop] trying to set ar. args are: ar->",ar.ratio,"; this.conf.player.dimensions->",this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions); + // If we encounter invalid players, we try to update its dimensions + // ONCE before throwing an error if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){ this.logger.log('error', 'scaler', "[Scaler::calculateCrop] ERROR — no (or invalid) this.conf.player.dimensions:",this.conf.player.dimensions); - return {error: "this.conf.player.dimensions_error"}; + this.conf.player.updatePlayer(); + + if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){ + return {error: "this.conf.player.dimensions_error"}; + } } // we can finally start computing required video dimensions now: @@ -184,7 +191,7 @@ class Scaler { } - this.logger.log('info', 'scaler', "[Scaler::calculateCrop] ar is " ,ar.ratio, ", file ar is", streamAr, ", this.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions); + this.logger.log('info', 'scaler', "[Scaler::calculateCrop] ar is " ,ar.ratio, ", file ar is", streamAr, ",ar variant", ar.variant ,"\nthis.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions, this.conf.player.element); const videoDimensions: VideoDimensions = { xFactor: 1, @@ -199,7 +206,7 @@ class Scaler { } } - this.calculateCropCore(videoDimensions, ar.ratio, streamAr, playerAr) + this.calculateCropCore(videoDimensions, ar.ratio, streamAr, playerAr, ar.variant) return videoDimensions; } @@ -212,7 +219,11 @@ class Scaler { * @param {*} streamAr * @param {*} playerAr */ - calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number) { + calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number, variant?: ArVariant) { + if (variant === ArVariant.Zoom) { + playerAr = ar; + } + if (streamAr < playerAr) { if (streamAr < ar){ // in this situation we have to crop letterbox on top/bottom of the player @@ -254,8 +265,8 @@ class Scaler { const letterboxRatio = (1 - (playerAr / ar)); videoDimensions.relativeCropLimits = { - top: ar > streamAr ? ( ar > playerAr ? (letterboxRatio * -0.5) : 0) : 0, - left: ar < streamAr ? ( ar < playerAr ? (-0.5 / letterboxRatio) : 0) : 0, + top: ar > streamAr ? ( ar >= playerAr ? (letterboxRatio * -0.5) : 0) : 0, + left: ar < streamAr ? ( ar <= playerAr ? (-0.5 / letterboxRatio) : 0) : 0, } videoDimensions.preventAlignment = { x: ar > playerAr, // video is wider than player, so it's full width already