Compare commits

..

No commits in common. "095a3daadd8539e1da15fd4731a7d86d56fe5e84" and "ddffbe92a0f3b664a2cf37b54a1a0637d7519cef" have entirely different histories.

29 changed files with 552 additions and 3133 deletions

View File

@ -8,7 +8,6 @@
* Setting inheritance/overriding is not thoroughly tested and may be full of edge cases.
* Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.
* Autodetection can now scan for subtitles.
* New experimental autodetection (which is secretly just slightly modified subtitle check)
### v6.3.0
* Added zoom segment to in-player UI and popup.

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "6.3.993",
"version": "6.3.97",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "6.3.993",
"version": "6.3.97",
"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

@ -90,7 +90,6 @@ export interface AardSubtitleScanOptions {
export interface AardSettings {
aardType: 'webgl' | 'legacy' | 'auto';
useLegacy: boolean,
earlyStopOptions: {
stopAfterFirstDetection: boolean;
@ -184,131 +183,6 @@ export interface AardSettings {
}
}
export interface AardLegacySettings {
aardType: 'webgl' | 'legacy' | 'auto';
earlyStopOptions: {
stopAfterFirstDetection: boolean;
stopAfterTimeout: boolean;
stopTimeout: number;
},
polling: {
runInBackgroundTabs: AardPollingOptions;
runOnSmallVideos: AardPollingOptions;
}
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
// Any more and we don't adjust ar.
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency
playing: number, // while playing
playingReduced: number, // while video/player element has insufficient size
paused: number, // while paused
error: number, // after error
minimumTimeout: number,
tickrate: number, // 1 tick every this many milliseconds
},
subtitles: AardSubtitleScanOptions,
autoDisable: { // settings for automatically disabling the extension
onFirstChange: boolean,
ifNotChanged: boolean,
ifNotChangedTimeout: number,
ifSubtitles: boolean;
},
canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
// it's not used to detect aspect ratio by itself, so it can be tiny af
width: number,
height: number,
},
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
// at the expense of performance
width: number,
height: number,
},
},
blackLevels: {
defaultBlack: number, // By default, pixels darker than this are considered black.
// (If detection algorithm detects darker blacks, black is considered darkest detected pixel)
blackTolerance: number, // If pixel is more than this much brighter than blackLevel, it's considered not black
// It is not considered a valid image detection if gradient detection is enabled
imageDelta: number, // When gradient detection is enabled, pixels this much brighter than black skip gradient detection
}
sampling: {
edgePosition: number; // % of width (max 0.33). Pixels up to this far away from either edge may contain logo.
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
randomCols: number, // we add this many randomly selected columns to the static columns
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks,
},
// pls deprecate and move things used
edgeDetection: {
slopeTestWidth: number,
gradientTestSamples: number, // we check this many pixels below (or above) the suspected edge to check for gradient
gradientTestBlackThreshold: number, // if pixel in test sample is brighter than that, we aren't looking at gradient
gradientTestDeltaThreshold: number, // if delta between two adjacent pixels in gradient test exceeds this, it's not gradient
thresholds: {
edgeDetectionLimit: number, // during scanning of the edge, quit after edge gets detected at this many points
minQualitySingleEdge: number, // At least one of the detected must reach this quality
minQualitySecondEdge: number, // The other edge must reach this quality (must be smaller or equal to single edge quality)
}
gradientThreshold: number, // if more than this percentage (0-1) is detected as gradient, we mark edge as gradient
gradientTestMinDelta: number, // if difference between test row and before row is MORE than this -> not gradient
gradientTestMinDeltaAfter: number, // if difference between test row and after row is LESS than this -> not gradient
gradientTestMaxDeltaAfter: number, // if difference between test row and after row is MORE than this -> not gradient
maxLetterboxOffset: number, // Upper and lower letterbox can be different by this many (% of height)
// Previous iteration variables VVVV
sampleWidth: number, // we take a sample this wide for edge detection
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
confirmationThreshold: number, //
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
// to confirm an edge in case there's no edges on top or bottom (other
// than logo, of course)
logoThreshold: number, // if edge candidate sits with count greater than this*all_samples, it can't be logo
// or watermark.
edgeTolerancePx?: number, // we check for black edge violation this far from detection point
edgeTolerancePercent?: number, // we check for black edge detection this % of height from detection point. unused
middleIgnoredArea: number, // we ignore this % of canvas height towards edges while detecting aspect ratios
minColsForSearch: number, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
// continue with search. It's pointless, because black edge is higher/lower than we
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
edgeMismatchTolerancePx: number,// corners and center are considered equal if they differ by at most this many px
minValidImage: number, // if more than this % (0-1) of row is image, we confirm image regardless of other criteria except gradient
maxEdgeSegments: number, // if edge has more than this many segments, we consider it unreliable
minEdgeSegmentSize: number,
averageEdgeThreshold: number, // average edge must be this many px
},
letterboxOrientationScan: {
letterboxLimit: number, // how many non-black pixels we can detect before ruling out letterbox
pillarboxLimit: number, // how many non-black pixels we can detect before ruling out pillarbox
}
pillarTest: {
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
allowMisaligned: number // left and right edge can vary this much (%)
},
textLineTest: {
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
// with text. This value is relative to canvas width (%)
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
// is over 50% of the canvas width
testRowOffset: number // we test this % of height from detected edge
}
}
interface DevSettings {
loadFromSnapshot: boolean,
}
@ -320,8 +194,7 @@ interface SettingsInterface {
}
dev: DevSettings,
aardLegacy: AardLegacySettings,
aard: AardSettings,
arDetect: AardSettings,
ui: {
inPlayer: {

View File

@ -11,29 +11,19 @@
<div class="settings-segment">
<div class="field">
<div class="label">Autodetection mode (requires page reload)</div>
<div class="select">
<select v-model="settings.active.aard.useLegacy" @change="aardLegacyModeChanged">
<option :value="true">Legacy</option>
<option :value="false">Experimental</option>
</select>
</div>
</div>
<div class="field">
<div class="label">Autodetection frequency (time between samples)</div>
<div class="range-input">
<input
type="range"
:value="Math.log(aardSettings.timers.playing)"
:value="Math.log(settings.active.arDetect.timers.playing)"
@change="setArCheckFrequency($event.target.value)"
min="2.3"
max="9.3"
step="0.01"
/>
<input
v-model="aardSettings.timers.playing"
v-model="settings.active.arDetect.timers.playing"
@change="setArCheckFrequency($event.target.value)"
class="input"
type="text"
@ -47,14 +37,14 @@
<div class="range-input">
<input
type="range"
:value="Math.log(aardSettings.timers.playingReduced)"
:value="Math.log(settings.active.arDetect.timers.playingReduced)"
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
min="2.3"
max="9.3"
step="0.01"
/>
<input
v-model="aardSettings.timers.playingReduced"
v-model="settings.active.arDetect.timers.playingReduced"
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
class="input"
type="text"
@ -66,64 +56,64 @@
<div class="field">
<div class="label">Poll for aspect ratio changes in background tabs:</div>
<div class="select">
<select v-model="aardSettings.polling.runInBackgroundTabs" @change="settings.saveWithoutReload">
<select v-model="settings.active.arDetect.polling.runInBackgroundTabs" @change="settings.saveWithoutReload">
<option :value="AardPollingOptions.No">Never</option>
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
</select>
</div>
</div>
<div v-if="aardSettings.polling.runInBackgroundTabs === AardPollingOptions.Full" class="hint warn">
<div v-if="settings.active.arDetect.polling.runInBackgroundTabs === AardPollingOptions.Full" class="hint warn">
Using normal polling rate in background tabs is NOT recommended.
</div>
<div class="field">
<div class="label">Poll for aspect ratio changes in small players:</div>
<div class="select">
<select v-model="aardSettings.polling.runOnSmallVideos" @change="settings.saveWithoutReload">
<select v-model="settings.active.arDetect.polling.runOnSmallVideos" @change="settings.saveWithoutReload">
<option :value="AardPollingOptions.No">Never</option>
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
</select>
</div>
</div>
<div v-if="aardSettings.polling.runOnSmallVideos === AardPollingOptions.Full" class="hint warn">
<div v-if="settings.active.arDetect.polling.runOnSmallVideos === AardPollingOptions.Full" class="hint warn">
Using normal polling rate on small videos is NOT recommended.
</div>
<div class="field">
<div class="label">Stop autodetection after first detection:</div>
<div class="select">
<select v-model="aardSettings.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
<select v-model="settings.active.arDetect.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select>
</div>
</div>
<div class="field" :class="{disabled: aardSettings.autoDisable.onFirstChange}">
<div class="field" :class="{disabled: settings.active.arDetect.autoDisable.onFirstChange}">
<div class="label">Stop autodetection if aspect ratio doesn't change for some time:</div>
<div class="select">
<select v-model="aardSettings.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
<select v-model="settings.active.arDetect.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
<option :value="true">Yes</option>
<option :value="false">No</option>
</select>
</div>
</div>
<div v-if="aardSettings.autoDisable.ifNotChanged" class="field">
<div v-if="settings.active.arDetect.autoDisable.ifNotChanged" class="field">
<div class="label">Stop autodetection if aspect ratio doesn't change for:</div>
<div class="range-input">
<input
type="range"
:value="Math.log(aardSettings.autoDisable.ifNotChangedTimeout / 10)"
:value="Math.log(settings.active.arDetect.autoDisable.ifNotChangedTimeout / 10)"
@change="setAutoDisableTimeout($event.target.value, 10)"
min="2.3"
max="9.3"
step="0.01"
/>
<input
:value="aardSettings.autoDisable.ifNotChangedTimeout / 1000"
:value="settings.active.arDetect.autoDisable.ifNotChangedTimeout / 1000"
@change="setAutoDisableTimeout($event.target.value, 1000)"
class="input"
type="text"
@ -135,7 +125,7 @@
<div class="field">
<div class="label">Autodetection canvas type:</div>
<div class="select">
<select v-model="aardSettings.aardType" @change="settings.saveWithoutReload">
<select v-model="settings.active.arDetect.aardType" @change="settings.saveWithoutReload">
<option value="auto">Automatic</option>
<option value="webgl">WebGL only</option>
<option value="legacy">Legacy / fallback</option>
@ -146,7 +136,7 @@
<div class="field">
<div class="label">Maximum allowed vertical video misalignment:</div>
<div class="input">
<input v-model="aardSettings.allowedMisaligned" />
<input v-model="settings.active.arDetect.allowedMisaligned" />
</div>
</div>
<div class="hint">
@ -164,8 +154,8 @@
<div class="field">
<div class="label">Subtitle detection:</div>
<div class="select">
<select v-model="aardSettings.subtitles.subtitleCropMode" @change="settings.saveWithoutReload">
<!-- <option :value="AardSubtitleCropMode.DisableScan">Do not detect subtitles</option> -->
<select v-model="settings.active.arDetect.subtitles.subtitleCropMode" @change="settings.saveWithoutReload">
<option :value="AardSubtitleCropMode.DisableScan">Do not detect subtitles</option>
<option :value="AardSubtitleCropMode.ResetAR">Do not crop while subtitles are on screen</option>
<option :value="AardSubtitleCropMode.ResetAndDisable">Stop autodetection if subtitles are detected</option>
<option :value="AardSubtitleCropMode.CropSubtitles">Always crop subtitles</option>
@ -173,19 +163,19 @@
</div>
</div>
<div v-if="aardSettings.subtitles.subtitleCropMode === AardSubtitleCropMode.ResetAR" class="field">
<div v-if="settings.active.arDetect.subtitles.subtitleCropMode === AardSubtitleCropMode.ResetAR" class="field">
<div class="label">Wait before resuming detection:</div>
<div class="range-input">
<input
type="range"
:value="Math.log(aardSettings.subtitles.resumeAfter / 10)"
:value="Math.log(settings.active.arDetect.subtitles.resumeAfter / 10)"
@change="setSubtitleTimeout($event.target.value, 10)"
min="2.3"
max="9.3"
step="0.01"
/>
<input
:value="aardSettings.subtitles.resumeAfter / 1000"
:value="settings.active.arDetect.subtitles.resumeAfter / 1000"
@change="setSubtitleTimeout($event.target.value, 1000)"
class="input"
type="text"
@ -271,9 +261,6 @@ export default {
}
},
computed: {
aardSettings() {
return this.settings.active.aard.useLegacy ? this.settings.active.aardLegacy : this.settings.active.aard;
}
},
created() {
this.eventBus.subscribe(
@ -298,15 +285,15 @@ export default {
BrowserDetect.runtime.openOptionsPage();
},
setArCheckFrequency(event, timer) {
this.aardSettings.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
this.settings.active.arDetect.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
this.settings.saveWithoutReload();
},
setAutoDisableTimeout(event, multiplier) {
this.aardSettings.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
this.settings.active.arDetect.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
this.settings.saveWithoutReload();
},
setSubtitleTimeout(event, multiplier) {
this.aardSettings.subtitles.resumeAfter = Math.floor(Math.pow(Math.E, event)) * multiplier;
this.settings.active.arDetect.subtitles.resumeAfter = Math.floor(Math.pow(Math.E, event)) * multiplier;
this.settings.saveWithoutReload();
},
refreshGraph() {
@ -324,9 +311,6 @@ export default {
saveDebugUiSettings() {
this.settings.active.ui.dev.aardDebugOverlay = JSON.parse(JSON.stringify(this.settingsJson));
this.settings.saveWithoutReload();
},
aardLegacyModeChanged() {
this.settings.save();
}
},

View File

@ -11,10 +11,6 @@
<li>Embedded sites now inherit settings of the parent frame. <small>However, this hasn't been tested for all edge cases and may contain bugs.</small></li>
<li>Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.</li>
<li>Autodetection can be set to stop after first aspect ratio detection, or after a period of no changes.</li>
<li>
There is a new, experiental mode for autodetection. At this point, it can be manually enabled in the autodetection settings. It will become the default option in 2026.<br/>
If you enable experimental mode, please consider reporting problems <a href="https://github.com/tamius-han/ultrawidify/issues/291" target="_blank">in this thread</a> on Github.
</li>
</ul>
</div>

View File

@ -255,25 +255,17 @@ const ExtensionConfPatch = Object.freeze([
{
forVersion: '6.3.93',
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
(userOptions as any).arDetect.polling = defaultOptions.aard.polling;
(userOptions as any).arDetect.subtitles = defaultOptions.aard.subtitles;
(userOptions as any).arDetect.autoDisable = defaultOptions.aard.autoDisable;
userOptions.arDetect.polling = defaultOptions.arDetect.polling;
userOptions.arDetect.subtitles = defaultOptions.arDetect.subtitles;
userOptions.arDetect.autoDisable = defaultOptions.arDetect.autoDisable;
}
},
{
forVersion: '6.3.98',
forVersion: '6.3.97',
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
(userOptions as any).arDetect.letterboxOrientationScan = defaultOptions.aard.letterboxOrientationScan;
(userOptions as any).arDetect.edgeDetection = defaultOptions.aard.edgeDetection;
(userOptions as any).arDetect.subtitles = defaultOptions.aard.subtitles;
}
},
{
forVersion: '6.3.98',
upgradeFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
userOptions.aard = defaultOptions.aard;
userOptions.aardLegacy = defaultOptions.aardLegacy;
delete (userOptions as any).arDetect;
userOptions.arDetect.letterboxOrientationScan = defaultOptions.arDetect.letterboxOrientationScan;
userOptions.arDetect.edgeDetection = defaultOptions.arDetect.edgeDetection;
userOptions.arDetect.subtitles = defaultOptions.arDetect.subtitles;
}
}

View File

@ -21,7 +21,7 @@ const ExtensionConf: SettingsInterface = {
loadFromSnapshot: false,
},
aardLegacy: {
arDetect: {
aardType: 'auto',
polling: {
@ -88,140 +88,6 @@ const ExtensionConf: SettingsInterface = {
},
},
blackLevels: {
defaultBlack: 16,
blackTolerance: 4,
imageDelta: 16,
},
sampling: {
edgePosition: 0.25,
staticCols: 16, // we take a column at [0-n]/n-th parts along the width and sample it
randomCols: 0, // we add this many randomly selected columns to the static columns
staticRows: 9, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
},
edgeDetection: {
slopeTestWidth: 8,
gradientTestSamples: 8,
gradientTestBlackThreshold: 16,
gradientTestDeltaThreshold: 32,
gradientTestMinDelta: 8,
thresholds: {
edgeDetectionLimit: 12,
minQualitySingleEdge: 6,
minQualitySecondEdge: 3,
},
maxLetterboxOffset: 0.1,
sampleWidth: 8, // we take a sample this wide for edge detection
detectionThreshold: 4, // sample needs to have this many non-black pixels to be a valid edge
confirmationThreshold: 1, //
singleSideConfirmationThreshold: 3, // we need this much edges (out of all samples, not just edges) in order
// to confirm an edge in case there's no edges on top or bottom (other
// than logo, of course)
logoThreshold: 0.15, // if edge candidate sits with count greater than this*all_samples, it can't be logo
// or watermark.
edgeTolerancePx: 1, // we check for black edge violation this far from detection point
edgeTolerancePercent: null, // we check for black edge detection this % of height from detection point. unused
middleIgnoredArea: 0.2, // we ignore this % of canvas height towards edges while detecting aspect ratios
minColsForSearch: 0.5, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
// continue with search. It's pointless, because black edge is higher/lower than we
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
edgeMismatchTolerancePx: 3, // corners and center are considered equal if they differ by at most this many px
gradientThreshold: 0.5, // if more than this percentage (0-1) is detected as gradient, we mark edge as gradient
gradientTestMinDeltaAfter: 2, // if difference between test row and after row is LESS than this -> not gradient
gradientTestMaxDeltaAfter: 12, // if difference between test row and after row is MORE than this -> not gradient
minValidImage: 0.7, // if more than this % (0-1) of row is image, we confirm image regardless of other criteria except gradient
maxEdgeSegments: 8, // if edge has more than this many segments, we consider it unreliable
minEdgeSegmentSize: 2,
averageEdgeThreshold: 16, // average(ish) edge must be this many px
},
pillarTest: {
ignoreThinPillarsPx: 5, // ignore pillars that are less than this many pixels thick.
allowMisaligned: 0.05 // left and right edge can vary this much (%)
},
textLineTest: {
nonTextPulse: 0.10, // if a single continuous pulse has this many non-black pixels, we aren't dealing
// with text. This value is relative to canvas width (%)
pulsesToConfirm: 10, // this is a threshold to confirm we're seeing text.
pulsesToConfirmIfHalfBlack: 5, // this is the threshold to confirm we're seeing text if longest black pulse
// is over 50% of the canvas width
testRowOffset: 0.02 // we test this % of height from detected edge
}
},
aard: {
aardType: 'auto',
useLegacy: true,
polling: {
runInBackgroundTabs: AardPollingOptions.Reduced,
runOnSmallVideos: AardPollingOptions.Reduced
},
letterboxOrientationScan: {
letterboxLimit: 8,
pillarboxLimit: 8
},
subtitles: {
subtitleCropMode: AardSubtitleCropMode.ResetAR,
resumeAfter: 5000,
scanSpacing: 5,
scanMargin: 0.25,
maxValidLetter: 24,
subtitleSubpixelThresholdOff: 8,
subtitleSubpixelThresholdOn: 192,
minDetections: 8,
minImageLineDetections: 8,
refiningScanSpacing: 8,
refiningScanInitialIterations: 12,
maxPotentialSubtitleMisalignment: 32,
},
earlyStopOptions: {
stopAfterFirstDetection: false,
stopAfterTimeout: false,
stopTimeout: 30,
},
disabledReason: "", // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much.
// Any more and we don't adjust ar.
allowedArVariance: 0.0125,// amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency
playing: 333, // while playing
playingReduced: 5000, // while playing at small sizes
paused: 3000, // while paused
error: 3000, // after error
minimumTimeout: 5,
tickrate: 10, // 1 tick every this many milliseconds
},
autoDisable: { // settings for automatically disabling the extension
onFirstChange: false, // disable once we have a stable aspect ratio
ifNotChanged: false, // disable if Ar hasn't changed for this long
ifNotChangedTimeout: 20000, // if user enables ifNotChangedTimeout, we default to 20s
ifSubtitles: false, // disable if subtitles are detected
},
canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
// it's not used to detect aspect ratio by itself, so it can be tiny af
width: 16,
height: 9,
},
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
// at the expense of performance
width: 640,
height: 360,
},
},
blackLevels: {
defaultBlack: 16,
blackTolerance: 4,

View File

@ -97,8 +97,6 @@ class Logger {
}
static saveConfig(conf: LoggerConfig) {
console.warn('LEGACY LOGGER IS STILL BEING CALLED FROM SOMEWHERE!', new Error().stack);
if (process.env.CHANNEL === 'dev') {
console.info('Saving logger conf:', conf)
}

View File

@ -152,20 +152,20 @@ export class Aard {
this.canvasSamples = {
top: generateSampleArray(
this.settings.active.aard.sampling.staticCols,
this.settings.active.aard.canvasDimensions.sampleCanvas.width
this.settings.active.arDetect.sampling.staticCols,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width
),
bottom: generateSampleArray(
this.settings.active.aard.sampling.staticCols,
this.settings.active.aard.canvasDimensions.sampleCanvas.width
this.settings.active.arDetect.sampling.staticCols,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width
),
left: generateSampleArray(
this.settings.active.aard.sampling.staticCols,
this.settings.active.aard.canvasDimensions.sampleCanvas.height
this.settings.active.arDetect.sampling.staticCols,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
),
right: generateSampleArray(
this.settings.active.aard.sampling.staticCols,
this.settings.active.aard.canvasDimensions.sampleCanvas.height
this.settings.active.arDetect.sampling.staticCols,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
)
};
@ -188,14 +188,14 @@ export class Aard {
}
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'legacy') {
ROW_SIZE = this.settings.active.aard.canvasDimensions.sampleCanvas.width * PIXEL_SIZE;
ROW_SIZE = this.settings.active.arDetect.canvasDimensions.sampleCanvas.width * PIXEL_SIZE;
if (canvasType) {
if (canvasType === this.settings.active.aard.aardType || this.settings.active.aard.aardType === 'auto') {
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
if (canvasType === 'webgl') {
return new GlCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-gl'});
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} else if (canvasType === 'legacy') {
return new FallbackCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-legacy'});
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
} else {
// TODO: throw error
}
@ -205,21 +205,21 @@ export class Aard {
}
if (['auto', 'webgl'].includes(this.settings.active.aard.aardType)) {
if (['auto', 'webgl'].includes(this.settings.active.arDetect.aardType)) {
try {
return new GlCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-gl'});
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} catch (e) {
if (this.settings.active.aard.aardType !== 'webgl') {
return new FallbackCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-legacy'});
if (this.settings.active.arDetect.aardType !== 'webgl') {
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
}
this.logger.error('createCanvas', 'could not create webgl canvas:', e);
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {webglError: true}});
throw e;
}
} else if (this.settings.active.aard.aardType === 'legacy') {
return new FallbackCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-legacy'});
} else if (this.settings.active.arDetect.aardType === 'legacy') {
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
} else {
this.logger.error('createCanvas', 'invalid value in settings.arDetect.aardType:', this.settings.active.aard.aardType);
this.logger.error('createCanvas', 'invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {invalidSettings: true}});
throw 'AARD_INVALID_SETTINGS';
}
@ -231,7 +231,7 @@ export class Aard {
*/
private showDebugCanvas() {
if (!this.canvasStore.debug) {
this.canvasStore.debug = new GlDebugCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'uw-debug-gl'});
this.canvasStore.debug = new GlDebugCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'uw-debug-gl'});
}
this.canvasStore.debug.enableFx();
if (!this.debugConfig.debugUi) {
@ -290,8 +290,8 @@ export class Aard {
}
// do full reset of test samples
this.testResults = initAardTestResults(this.settings.active.aard);
this.verticalTestResults = initAardTestResults(this.settings.active.aard);
this.testResults = initAardTestResults(this.settings.active.arDetect);
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
if (this.animationFrame) {
window.cancelAnimationFrame(this.animationFrame);
@ -301,8 +301,8 @@ export class Aard {
this.animationFrame = window.requestAnimationFrame( (ts: DOMHighResTimeStamp) => this.onAnimationFrame(ts));
// set auto-disable timer if detection timeout is set
if (this.settings.active.aard.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.aard.autoDisable.ifNotChangedTimeout;
if (this.settings.active.arDetect.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
}
}
@ -314,8 +314,8 @@ export class Aard {
this.stop();
if (options?.noCache) {
this.testResults = initAardTestResults(this.settings.active.aard);
this.verticalTestResults = initAardTestResults(this.settings.active.aard);
this.testResults = initAardTestResults(this.settings.active.arDetect);
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
}
this.main();
@ -342,7 +342,7 @@ export class Aard {
// if video was paused & we know that we already checked that frame,
// we will not check it again.
const videoState = this.getVideoPlaybackState();
const polling = this.settings.active.aard.polling;
const polling = this.settings.active.arDetect.polling;
const now = Date.now();
if (videoState !== VideoPlaybackState.Playing) {
@ -375,8 +375,8 @@ export class Aard {
return false;
}
this.timers.nextFrameCheckTime = now + this.settings.active.aard.timers.playing;
this.timers.reducedPollingNextCheckTime = now + this.settings.active.aard.timers.playingReduced;
this.timers.nextFrameCheckTime = now + this.settings.active.arDetect.timers.playing;
this.timers.reducedPollingNextCheckTime = now + this.settings.active.arDetect.timers.playingReduced;
return true;
}
@ -403,7 +403,7 @@ export class Aard {
* Main loop for scanning aspect ratio changes
*/
private async main() {
const arConf = this.settings.active.aard;
const arConf = this.settings.active.arDetect;
try {
this.timer.next();
@ -745,8 +745,8 @@ export class Aard {
const lastPixelOffset = ROW_SIZE - PIXEL_SIZE;
const imageSize = ROW_SIZE * height;
const xLimit = this.settings.active.aard.letterboxOrientationScan.letterboxLimit;
const yLimit = this.settings.active.aard.letterboxOrientationScan.pillarboxLimit;
const xLimit = this.settings.active.arDetect.letterboxOrientationScan.letterboxLimit;
const yLimit = this.settings.active.arDetect.letterboxOrientationScan.pillarboxLimit;
let letterbox = true, pillarbox = true;
let xCount = 0, yCount = 0;
@ -844,7 +844,7 @@ export class Aard {
*/
private updateLetterboxEdgeCandidates(crossDimension: number, topCandidate: number, bottomCandidate: number) {
const bottomDistance = (crossDimension - bottomCandidate);
const maxOffset = ~~(crossDimension * this.settings.active.aard.edgeDetection.maxLetterboxOffset);
const maxOffset = ~~(crossDimension * this.settings.active.arDetect.edgeDetection.maxLetterboxOffset);
const diff = Math.abs(topCandidate - bottomDistance);
const candidateAvg = ~~((topCandidate + bottomDistance) * 0.5);
@ -869,7 +869,7 @@ export class Aard {
* @returns
*/
private subtitleScan(imageData: Uint8Array, width: number, height: number, skipAdvancedScan: boolean) {
const scanConf = this.settings.active.aard.subtitles;
const scanConf = this.settings.active.arDetect.subtitles;
this.testResults.subtitleDetected = false;
@ -931,8 +931,8 @@ export class Aard {
) {
results.uncertain = false;
const scanConf = this.settings.active.aard.subtitles;
const arConf = this.settings.active.aard;
const scanConf = this.settings.active.arDetect.subtitles;
const arConf = this.settings.active.arDetect;
let letterCount, imageSegmentCount, potentialFadedLetterCount, potentialFadedLetterCountInvalidated, nonGradientPixelCount, letterSize, imageSize, imageSegmentSize, imageWeightedSize, segmentWeights, imageSegmentAlignment, imageSegmentAlignmentSamples,
isOnLetter, isOnImage, isBlank,
@ -940,7 +940,7 @@ export class Aard {
let rowStart, rowEnd, rowMid, rowGTA, rowGTB; // GT = gradient test
let imageConfirmPass = false, subtitleConfirmPass = false;
let outerIteration = 0;
let innerIteration, outerIteration = 0;
const rowMargin = Math.floor(scanConf.scanMargin * ROW_SIZE);
const imageThreshold = Math.floor((ROW_SIZE - (rowMargin * 2)) * arConf.edgeDetection.minValidImage * PIXEL_SIZE_FRACTION);
@ -953,11 +953,8 @@ export class Aard {
(scanSpacing > 0 && searchRow < endRow) || (scanSpacing < 0 && searchRow > endRow);
searchRow += scanSpacing
) {
if (++outerIteration > height) {
// console.warn('[ultrawidify|aard::subtitleScanRegionLinear] — scan got stuck in an infinite loop. This shouldn\'t happen.');
results.uncertain;
break outerLoop;
}
innerIteration = 0;
outerIteration++;
letterCount = 0;
potentialFadedLetterCount = 0;
@ -1022,6 +1019,8 @@ export class Aard {
*/
while (rowStart < rowEnd) {
innerIteration++;
const r = imageData[rowStart], g = imageData[rowStart + 1], b = imageData[rowStart + 2];
const on = r > scanConf.subtitleSubpixelThresholdOn
@ -1317,7 +1316,7 @@ export class Aard {
const maxRatio = Math.max(ar, this.testResults.activeAspectRatio);
const diff = Math.abs(ar - this.testResults.activeAspectRatio);
if ((diff / maxRatio) > this.settings.active.aard.allowedArVariance || options?.forceReset) {
if ((diff / maxRatio) > this.settings.active.arDetect.allowedArVariance || options?.forceReset) {
this.videoData.resizer.updateAr({
type: AspectRatioType.AutomaticUpdate,
ratio: ar,
@ -1327,11 +1326,11 @@ export class Aard {
this.testResults.activeAspectRatio = ar;
if (!options?.uncertainDetection) {
if (this.settings.active.aard.autoDisable.onFirstChange) {
if (this.settings.active.arDetect.autoDisable.onFirstChange) {
this.status.autoDisabled = true;
}
if (this.settings.active.aard.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.aard.autoDisable.ifNotChangedTimeout;
if (this.settings.active.arDetect.autoDisable.ifNotChanged) {
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
}
}

View File

@ -1,7 +1,4 @@
import { Aard } from './Aard';
import { AardLegacy } from './AardLegacy';
import { AardPerformanceData } from './AardTimers';
import { FallbackCanvas } from './gl/FallbackCanvas';
export class AardDebugUi {
@ -79,11 +76,37 @@ export class AardDebugUi {
</div>
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%; margin-top: 8rem;">
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%">
<div style="">
<div id="uw-aard-debug_aard-sample-canvas" style="min-width: 640px"></div>
<div style="background: black; color: #fff"; font-size: 24px;">AARD IN</div>
<div style="background: black; color: #ccc; padding: 1rem">
<div>
<span style="color: rgb(0.1, 0.1, 0.35)"></span>
Black level sample
</div>
<div>
<span style="color: rgb(0.3, 1.0, 0.6)"></span>
<span style="color: rgb(0.1, 0.5, 0.3)"></span>
Guard line (middle/corner) OK
</div>
<div>
<span style="color: rgb(1.0, 0.1, 0.1)"></span>
<span style="color: rgb(0.5, 0.0, 0.0)"></span>
Guard line (middle/corner) violation
</div>
<div>
Image line <span style="color: rgb(0.7, 0.7, 0.7)"></span> image, <span style="color: rgb(0.2, 0.2, 0.6)"></span> no image
</div>
<div>
Edge scan <span style="color: rgb(0.1, 0.1, 0.4)"></span> probe, <span style="color: rgb(0.4, 0.4, 1.0)"></span> hit
</div>
<div>
Slope test <span style="color: rgb(0.4, 0.4, 1.0)"></span> ok, <span style="color: rgb(1.0, 0.0, 0.0)"></span> fail
</div>
</div>
<div style="pointer-events: all">
<button id="uw-aard-debug-ui_enable-stop-on-change" style="">Pause video on aspect ratio change</button>
<button id="uw-aard-debug-ui_disable-stop-on-change" style="display: none">Stop pausing video on aspect ratio change</button>
@ -102,9 +125,10 @@ export class AardDebugUi {
<pre id="uw-aard-results"></pre>
</div>
</div>
<div style="width: 1920px; border: 2px dotted #142; margin-right:2rem;">
<div style="width: 1920px">
<div id="uw-aard-debug_aard-output" style="zoom: 3; image-rendering: pixelated;"></div>
<div style="background: black; color: #fff; font-size: 24px;">AARD RESULT</div>
</div>
</div>
</div>
@ -170,19 +194,9 @@ export class AardDebugUi {
const popupContent = `
<h2 style="color: #fa6; margin-bottom: 1rem">Detailed performance analysis:</h2>
<div>
<span style="color: #fff">About:</span><br/>
Aard version: <span style="color: #fa6">${this.aard instanceof AardLegacy ? 'legacy' : this.aard instanceof Aard ? 'experimental' : 'unknown'}</span> <small>(instanceof AardLegacy? ${this.aard instanceof AardLegacy}, Aard? ${this.aard instanceof Aard})</small><br/>
canvas type: <span style="color: #fa6">${!this.aard.canvasStore.main ? '<canvas not initialized>' : this.aard.canvasStore.main instanceof FallbackCanvas ? '2dCanvas' : 'webgl'}</span> |
legacy default: <span style="color: #fa6">${this.aard.settings.active.aardLegacy.aardType}</span>,
experimental default: <span style="color: #fa6">${this.aard.settings.active.aard.aardType}</span>
</div>
<br/>
<div style="width: 100%; display: flex; flex-direction: column">
<div style="margin-bottom: 1rem;">
${this.generateRawTimes(this.aard.timer.current)}
${this.generateRawTimes(this.aard.timer.average)}
</div>
<div style="color: #fff">Stage times (not cumulative):</div>
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
@ -200,14 +214,12 @@ export class AardDebugUi {
</div>
<div style="margin-bottom: 1rem;">
${this.generateRawTimes(this.aard.timer.lastChange)}
${this.generateRawTimes(this.aard.timer.average)}
</div>
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
<div style="width: 160px; text-align: right; padding-right: 4px;">Last change:</div>
<div style="flex-grow: 1;">${this.generateMiniGraphBar(this.aard.timer.lastChange, true)}</div>
</div>
<!-- <pre>${JSON.stringify({current: this.aard.timer.current, average: this.aard.timer.average, lastChange: this.aard.timer.lastChange}, null, 2)}</pre> -->
</div>
`
@ -286,23 +298,23 @@ export class AardDebugUi {
total += fastBlackLevel;
const guardLineStart = fastBlackLevelStart + fastBlackLevel;
const guardLine = (perf.guardLine !== undefined && perf.guardLine !== -1) ? Math.max(perf.guardLine - total, 0) : 0;
const guardLine = Math.max(perf.guardLine - total, 0);
total += guardLine;
const edgeScanStart = guardLineStart + guardLine;
const edgeScan = (perf.edgeScan !== undefined && perf.edgeScan !== -1) ? Math.max(perf.edgeScan - total, 0) : 0;
const edgeScan = Math.max(perf.edgeScan - total, 0);
total += edgeScan;
const gradientStart = edgeScanStart + edgeScan;
const gradient = (perf.gradient !== undefined && perf.gradient !== -1) ? Math.max(perf.gradient - total, 0) : 0;
const gradient = Math.max(perf.gradient - total, 0);
total += gradient;
const scanResultsStart = gradientStart + gradient;
const scanResults = (perf.scanResults !== undefined && perf.scanResults !== -1) ? Math.max(perf.scanResults - total, 0) : 0;
const scanResults = Math.max(perf.scanResults - total, 0);
total += scanResults;
const subtitleScanStart = scanResultsStart + scanResults;
const subtitleScan = Math.max(perf.subtitleScan - total, 0);
const subtitleScan = Math.max(perf.scanResults - total, 0);
total += subtitleScan;
return `
@ -332,10 +344,12 @@ export class AardDebugUi {
<div style="position: absolute; top: ${detailed ? '74px' : '2px'}; left: ${scanResultsStart}%; min-width: 1px; width: ${scanResults}%; background: #80f; height: 12px;"></div>
${this.getBarLabel(scanResults, scanResultsStart, 74, `scan results processing: ${scanResults.toFixed(2)} ms`, detailed)}
<div style="position: absolute; top: ${detailed ? '86px' : '2px'}; left: ${subtitleScanStart}%; min-width: 1px; width: ${subtitleScan}%; background: rgba(234, 204, 84, 1); height: 12px;"></div>
${this.getBarLabel(subtitleScan, subtitleScanStart, 86, `subtitle scan: ${subtitleScan.toFixed(2)} ms`, detailed)}
${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
${this.getBarLabel(0, subtitleScan + subtitleScanStart, 98, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
<div style="position: absolute; top: ${detailed ? '74px' : '2px'}; left: ${subtitleScanStart}%; min-width: 1px; width: ${subtitleScan}%; background: rgba(234, 204, 84, 1); height: 12px;"></div>
${this.getBarLabel(scanResults, scanResultsStart, 74, `scan results processing: ${subtitleScan.toFixed(2)} ms`, detailed)}
${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
<!-- 60/30 fps markers -->
<div style="position: absolute; top: ${detailed ? '-12px' : '0'}; left: 16.666%; width: 1px; border-left: 1px dashed #4f9; height: ${detailed ? '112px' : '12px'}; padding-left: 2px; background-color: rgba(0,0,0,0.5); z-index: ${detailed ? '5' : '2'}000;">60fps</div>
@ -345,7 +359,7 @@ export class AardDebugUi {
}
_lastAr: undefined;
updateTestResults(testResults, timers) {
updateTestResults(testResults) {
this.updatePerformanceResults();
if (testResults.aspectRatioUpdated && this.pauseOnArCheck) {
@ -364,13 +378,52 @@ export class AardDebugUi {
Active: ${ar}, changed since last check? ${testResults.aspectRatioUpdated} letterbox width: ${testResults.letterboxWidth} offset ${testResults.letterboxOffset}<br/>
<sup>(last: ${this._lastAr})</sup>
Paused until? ${Date.now() < timers.pauseUntil ? ((timers.pauseUntil - Date.now()) / 1000) + 's' : 'not paused'};
<sup>now: ${Date.now()}ms; until:${timers.pauseUntil}ms; diff: ${(+timers.pauseUntil - Date.now())} </sup>
image in black level probe (aka "not letterbox"): ${testResults.notLetterbox}
`;
this._lastAr = ar;
if (testResults.notLetterbox) {
resultsDiv.innerHTML = out;
return;
}
out = `${out}
-- UNCERTAIN FLAGS
AR: ${testResults.aspectRatioUncertain} (reason: ${testResults.aspectRatioUncertainReason ?? 'n/a'}); top row: ${testResults.topRowUncertain}; bottom row: ${testResults.bottomRowUncertain}${
testResults.aspectRatioInvalid ? `\nINVALID_AR (reason: ${testResults.aspectRatioInvalidReason ?? 'n/a'})` : ''}
-- GUARD & IMAGE LINE
bottom guard: ${testResults.guardLine.bottom} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.bottom}
top guard: ${testResults.guardLine.top} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.top}
guard line ${testResults.guardLine.invalidated ? 'INVALIDATED' : 'valid'} image line ${testResults.guardLine.invalidated ? '<skipped test>' : testResults.imageLine.invalidated ? 'INVALIDATED' : 'valid'}
corner invalidations (invalid pixels -> verdict)
LEFT CENTER RIGHT
bottom: ${testResults.guardLine.cornerPixelsViolated[0]} ${testResults.guardLine.cornerViolated[0] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[1]} ${testResults.guardLine.cornerViolated[1] ? '❌' : '◽'}
top: ${testResults.guardLine.cornerPixelsViolated[2]} ${testResults.guardLine.cornerViolated[2] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[3]} ${testResults.guardLine.cornerViolated[3] ? '❌' : '◽'}
-- AR SCAN ${testResults.lastStage < 1 ? `
DID NOT RUN THIS FRAME` : `
LEFT CENTER RIGHT CANDIDATE
BOTTOM
distance: ${testResults.aspectRatioCheck.bottomRows[0]} ${testResults.aspectRatioCheck.bottomRows[1]} ${testResults.aspectRatioCheck.bottomRows[2]} ${testResults.aspectRatioCheck.bottomCandidate}
quality: ${testResults.aspectRatioCheck.bottomQuality[0]} ${testResults.aspectRatioCheck.bottomQuality[1]} ${testResults.aspectRatioCheck.bottomQuality[2]} ${testResults.aspectRatioCheck.bottomCandidateQuality}
TOP
distance: ${testResults.aspectRatioCheck.topRows[0]} ${testResults.aspectRatioCheck.topRows[1]} ${testResults.aspectRatioCheck.topRows[2]} ${testResults.aspectRatioCheck.topCandidate}
quality: ${testResults.aspectRatioCheck.topQuality[0]} ${testResults.aspectRatioCheck.topQuality[1]} ${testResults.aspectRatioCheck.topQuality[2]} ${testResults.aspectRatioCheck.topCandidateQuality}
Diff matrix:
R-L C-R C-L
bottom: ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[2]}
top: ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[2]}
`}
`;
resultsDiv.innerHTML = out;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,11 @@ export interface AardPerformanceData {
getImage: number;
fastBlackLevel: number;
// optional ones are only available in legacy aard
guardLine?: number; // actually times both guard line and image line checks
edgeScan?: number; // includes validation step
gradient?: number;
scanResults?: number;
guardLine: number; // actually times both guard line and image line checks
edgeScan: number; // includes validation step
gradient: number;
subtitleScan: number;
scanResults: number;
}
@ -84,52 +82,36 @@ export class AardTimer {
getAverage() {
for (let i = 0; i < this.aardPerformanceDataBuffer.length; i++) {
const sample = this.aardPerformanceDataBuffer[i];
if (sample.draw !== -1) {
this.average.draw += sample.draw;
}
if (sample.getImage !== -1) {
this.average.getImage += sample.getImage;
}
if (sample.fastBlackLevel !== -1 && this.average.fastBlackLevel !== null) {
if (sample.fastBlackLevel !== -1) {
this.average.fastBlackLevel += sample.fastBlackLevel;
} else {
this.average.fastBlackLevel = null;
}
if (sample.guardLine !== -1 && this.average.guardLine !== null) {
if (sample.guardLine !== -1) {
this.average.guardLine += sample.guardLine;
} else {
this.average.guardLine = null;
}
if (sample.edgeScan !== -1 && this.average.edgeScan !== null) {
if (sample.edgeScan !== -1) {
this.average.edgeScan += sample.edgeScan;
} else {
this.average.edgeScan = null;
}
if (sample.gradient !== -1 && this.average.gradient !== null) {
if (sample.gradient !== -1) {
this.average.gradient += sample.gradient;
} else {
this.average.edgeScan = null;
}
if (sample.scanResults !== -1 && this.average.scanResults !== null) {
if (sample.scanResults !== -1) {
this.average.scanResults += sample.scanResults;
} else {
this.average.scanResults = null;
}
if (sample.subtitleScan !== -1 && this.average.subtitleScan !== null) {
this.average.subtitleScan += sample.subtitleScan;
} else {
this.average.subtitleScan = null;
}
}
this.average.draw /= this.aardPerformanceDataBuffer.length;
this.average.getImage /= this.aardPerformanceDataBuffer.length;
this.average.fastBlackLevel /= this.aardPerformanceDataBuffer.length;
this.average.guardLine = this.average.guardLine === null ? -1 : (this.average.guardLine / this.aardPerformanceDataBuffer.length);
this.average.edgeScan = this.average.edgeScan === null ? -1 : (this.average.guardLine / this.aardPerformanceDataBuffer.length);
this.average.gradient = this.average.gradient === null ? -1 : (this.average.guardLine / this.aardPerformanceDataBuffer.length);
this.average.scanResults = this.average.scanResults === null ? -1 : (this.average.guardLine / this.aardPerformanceDataBuffer.length);
this.average.subtitleScan = this.average.subtitleScan === null ? -1 : (this.average.subtitleScan /this.aardPerformanceDataBuffer.length);
this.average.guardLine /= this.aardPerformanceDataBuffer.length;
this.average.edgeScan /= this.aardPerformanceDataBuffer.length;
this.average.gradient /= this.aardPerformanceDataBuffer.length;
this.average.scanResults /= this.aardPerformanceDataBuffer.length;
this.average.subtitleScan /= this.aardPerformanceDataBuffer.length;
}
}

View File

@ -1,189 +0,0 @@
import { AardLegacySettings, AardSettings } from '../../../../common/interfaces/SettingsInterface'
import { AardUncertainReason } from '../enums/aard-letterbox-uncertain-reason.enum'
import { LetterboxOrientation } from '../enums/letterbox-orientation.enum'
export interface AardTestResult_SubtitleRegion {
firstBlank: number,
lastBlank: number,
firstSubtitle: number,
lastSubtitle: number,
firstImage: number,
lastImage: number,
uncertain: boolean,
}
export interface AardLegacyTestResults {
isFinished: boolean,
lastStage: number,
letterboxOrientation: LetterboxOrientation,
lastValidLetterboxOrientation: LetterboxOrientation,
subtitleDetected: boolean,
blackLevel: number, // is cumulative
blackThreshold: number, // is cumulative
guardLine: {
top: number, // is cumulative
bottom: number, // is cumulative
invalidated: boolean,
cornerViolated: [boolean, boolean, boolean, boolean],
cornerPixelsViolated: [0,0,0,0],
front?: number,
back?: number,
},
imageLine: {
top: number, // is cumulative
bottom: number, // is cumulative
invalidated: boolean
}
aspectRatioCheck: {
topRows: [number, number, number],
topQuality: [number, number, number],
bottomRows: [number, number, number],
bottomQuality: [number, number, number],
topCandidate: number,
topCandidateQuality: number,
bottomCandidate: number,
bottomCandidateDistance: number,
bottomCandidateQuality: number,
topRowsDifferenceMatrix: [number, number, number],
bottomRowsDifferenceMatrix: [number, number, number],
frontCandidate: number,
backCandidate: number,
},
aspectRatioUncertain: boolean,
topRowUncertain: boolean,
bottomRowUncertain: boolean,
aspectRatioUpdated: boolean,
activeAspectRatio: number, // is cumulative
letterboxSize: number,
letterboxOffset: number,
logoDetected: [boolean, boolean, boolean, boolean],
aspectRatioInvalid: boolean,
subtitleScan: {
top: number,
bottom: number,
regions: {
top: AardTestResult_SubtitleRegion,
bottom: AardTestResult_SubtitleRegion
}
},
notLetterbox: boolean,
aspectRatioUncertainEdges: number,
aspectRatioUncertainReason?: AardUncertainReason,
aspectRatioInvalidReason?: string,
}
export function initAardTestResults(settings: AardLegacySettings): AardLegacyTestResults {
return {
isFinished: true,
lastStage: 0,
letterboxOrientation: LetterboxOrientation.NotKnown,
lastValidLetterboxOrientation: LetterboxOrientation.NotKnown,
blackLevel: settings.blackLevels.defaultBlack,
blackThreshold: 16,
guardLine: {
top: -1,
bottom: -1,
invalidated: false,
cornerViolated: [false, false, false, false],
cornerPixelsViolated: [0,0,0,0],
front: -1,
back: -1,
},
imageLine: {
top: -1,
bottom: -1,
invalidated: false,
},
aspectRatioCheck: {
topRows: [-1, -1, -1],
topQuality: [0, 0, 0],
bottomRows: [-1, -1, -1],
bottomQuality: [0, 0, 0],
topCandidate: 0,
topCandidateQuality: 0,
bottomCandidate: 0,
bottomCandidateDistance: 0,
bottomCandidateQuality: 0,
topRowsDifferenceMatrix: [0, 0, 0],
bottomRowsDifferenceMatrix: [0, 0, 0],
frontCandidate: 0,
backCandidate: 0,
},
aspectRatioUncertain: false,
aspectRatioUncertainEdges: 0,
topRowUncertain: false,
bottomRowUncertain: false,
subtitleDetected: false,
subtitleScan: {
top: -1,
bottom: -1,
regions: {
top: {
firstBlank: -1,
lastBlank: -1,
firstSubtitle: -1,
lastSubtitle: -1,
firstImage: -1,
lastImage: -1,
uncertain: false,
},
bottom: {
firstBlank: -1,
lastBlank: -1,
firstSubtitle: -1,
lastSubtitle: -1,
firstImage: -1,
lastImage: -1,
uncertain: false,
}
}
},
aspectRatioUpdated: false,
activeAspectRatio: 0,
letterboxSize: 0,
letterboxOffset: 0,
logoDetected: [false, false, false, false],
aspectRatioInvalid: false,
notLetterbox: false,
}
}
export function resetGuardLine(results: AardLegacyTestResults) {
results.guardLine.front = -1;
results.guardLine.back = -1;
}
export function resetAardTestResults(results: AardLegacyTestResults): void {
results.isFinished = false;
results.lastStage = 0;
results.aspectRatioUpdated = false;
results.aspectRatioUncertainReason = null;
results.aspectRatioInvalid = false;
results.letterboxOrientation = LetterboxOrientation.NotKnown;
}
export function resetSubtitleScanResults(results: AardLegacyTestResults): void {
results.subtitleScan.top = -1;
results.subtitleScan.bottom = -1;
results.subtitleScan.regions.top.firstBlank = -1;
results.subtitleScan.regions.top.lastBlank = -1;
results.subtitleScan.regions.top.firstSubtitle = -1;
results.subtitleScan.regions.top.lastSubtitle = -1;
results.subtitleScan.regions.top.firstImage = -1;
results.subtitleScan.regions.top.lastImage = -1;
results.subtitleScan.regions.bottom.firstBlank = -1;
results.subtitleScan.regions.bottom.lastBlank = -1;
results.subtitleScan.regions.bottom.firstSubtitle = -1;
results.subtitleScan.regions.bottom.lastSubtitle = -1;
results.subtitleScan.regions.bottom.firstImage = -1;
results.subtitleScan.regions.bottom.lastImage = -1;
}

View File

@ -113,7 +113,7 @@ export class LogAggregator {
private storageChangeListener(changes, area) {
if (!changes[STORAGE_LOG_SETTINGS_KEY]) {
// console.info('We dont have any logging settings, not processing frther', changes);
console.info('We dont have any logging settings, not processing frther', changes);
return;
}

View File

@ -213,8 +213,6 @@ class Settings {
}
);
// this.active.newFeatureTracker = {};
// apply all remaining patches
this.logger?.info('applySettingsPatches', `There are ${ExtensionConfPatch.length - index} settings patches to apply`);
@ -290,6 +288,19 @@ class Settings {
return this.active;
}
// This means extension update happened.
// btw fun fact — we can do version rollbacks, which might come in handy while testing
this.active.version = this.version;
// if extension has been updated, update existing settings with any options added in the
// new version. In addition to that, we remove old keys that are no longer used.
const patched = ObjectCopy.addNew(settings, this.default);
this.logger?.info('init',"Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default);
if (patched) {
this.active = patched;
}
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch
this.applySettingsPatches(oldVersion);

View File

@ -11,7 +11,6 @@ import PageInfo from './PageInfo';
import { RunLevel } from '../../enum/run-level.enum';
import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface';
import { ComponentLogger } from '../logging/ComponentLogger';
import { collectionHas, equalish } from '../../util/comparators';
if (process.env.CHANNEL !== 'stable'){
console.info("Loading: PlayerData.js");
@ -97,7 +96,6 @@ class PlayerData {
isTooSmall: boolean = true;
//#endregion
//#region misc stuff
extensionMode: any;
dimensions: PlayerDimensions;
@ -146,12 +144,11 @@ class PlayerData {
private dimensionChangeListener = {
that: this,
handleEvent: function(event: Event) {
this.that.requestTick();
this.that.trackEnvironmentChanges(event);
this.that.trackDimensionChanges()
}
}
private fallbackDimensionChangeInterval: any;
/**
* Gets player aspect ratio. If in full screen, it returns screen aspect ratio unless settings say otherwise.
*/
@ -161,7 +158,8 @@ class PlayerData {
return window.innerWidth / window.innerHeight;
}
if (!this.dimensions) {
return (this.element?.scrollWidth ?? this.videoElement.videoWidth) / (this.element?.scrollHeight ?? this.videoElement.videoHeight);
this.trackDimensionChanges();
this.trackEnvironmentChanges();
}
return this.dimensions.width / this.dimensions.height;
@ -227,6 +225,7 @@ class PlayerData {
}
this.startChangeDetection();
document.addEventListener('fullscreenchange', this.dimensionChangeListener);
// we want to reload on storage changes
@ -237,6 +236,35 @@ class PlayerData {
}
}
/**
* Checks if player dimensions are too small for autodetection to run.
* Full screen is never too small, even if it's
* @param playerDimensions
* @returns
*/
private checkIfTooSmall(playerDimensions?: PlayerDimensions) {
if (playerDimensions) {
return !playerDimensions.fullscreen && (playerDimensions.width < 1208 || playerDimensions.height < 720);
} else {
return !document.fullscreenElement && (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
}
}
private reloadPlayerDataConfig(siteConfUpdate) {
// this.siteSettings = siteConfUpdate;
this.updatePlayer();
this.periodicallyRefreshPlayerElement = false;
try {
this.periodicallyRefreshPlayerElement = this.siteSettings.data.currentDOMConfig.periodicallyRefreshPlayerElement;
} catch (e) {
// no biggie — that means we don't have any special settings for this site.
}
// because this is often caused by the UI
this.handlePlayerTreeRequest();
}
/**
* Initializes event bus
*/
@ -248,27 +276,18 @@ class PlayerData {
}
}
private initFallbackDimensionMonitor() {
this.stopFallbackDimensionMonitor();
this.fallbackDimensionChangeInterval = setInterval( () => {
this.processTick();
}, 2000);
}
/**
* Stops manually scanning for dimension changes
* Completely stops everything the extension is doing
*/
stopFallbackDimensionMonitor() {
clearImmediate(this.fallbackDimensionChangeInterval);
this.fallbackDimensionChangeInterval = undefined;
destroy() {
document.removeEventListener('fullscreenchange', this.dimensionChangeListener);
this.stopChangeDetection();
this.ui?.destroy();
this.notificationService?.destroy();
}
//#endregion
/**
* Initialized player UI at a later time.
* @param playerDimensions
* @returns
*/
deferredUiInitialization(playerDimensions = this.dimensions) {
deferredUiInitialization(playerDimensions) {
if (this.ui || this.siteSettings.data.enableUI.fullscreen === ExtensionMode.Disabled) {
return;
}
@ -306,44 +325,6 @@ class PlayerData {
}
}
/**
* Completely stops everything the extension is doing
*/
destroy() {
document.removeEventListener('fullscreenchange', this.dimensionChangeListener);
this.stopChangeDetection();
this.ui?.destroy();
this.notificationService?.destroy();
}
//#endregion
//#region utils
/**
* Checks if player dimensions are too small for autodetection to run.
* Full screen is never too small, even if it's
* @param playerDimensions
* @returns
*/
private checkIfTooSmall() {
return !document.fullscreenElement && (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
}
private reloadPlayerDataConfig(siteConfUpdate) {
// this.siteSettings = siteConfUpdate;
this.updatePlayer();
this.periodicallyRefreshPlayerElement = false;
try {
this.periodicallyRefreshPlayerElement = this.siteSettings.data.currentDOMConfig.periodicallyRefreshPlayerElement;
} catch (e) {
// no biggie — that means we don't have any special settings for this site.
}
// because this is often caused by the UI
this.handlePlayerTreeRequest();
}
/**
* Sets extension runLevel and sets or unsets appropriate css classes as necessary
* @param runLevel
@ -375,12 +356,161 @@ class PlayerData {
this.runLevel = runLevel;
}
//#endregion
/**
* Detects whether player element is in theater mode or not.
* If theater mode changed, emits event.
* @returns whether player is in theater mode
*/
private detectTheaterMode() {
const oldTheaterMode = this.isTheaterMode;
const newTheaterMode = this.equalish(window.innerWidth, this.element.offsetWidth, 32);
this.isTheaterMode = newTheaterMode;
// theater mode changed
if (oldTheaterMode !== newTheaterMode) {
if (newTheaterMode) {
this.eventBus.send('player-theater-enter', {});
} else {
this.eventBus.send('player-theater-exit', {});
}
}
return newTheaterMode;
}
trackEnvironmentChanges() {
if (this.environment !== this.lastEnvironment) {
this.lastEnvironment = this.environment;
this.eventBus.send('uw-environment-change', {newEnvironment: this.environment});
}
}
/**
*
*/
trackDimensionChanges() {
if (this._isTrackDimensionChangesActive) {
// this shouldn't really get called, _ever_ ... but sometimes it happens
console.warn('[PlayerData::trackDimensionChanges] trackDimensionChanges is already active!');
return;
}
this._isTrackDimensionChangesActive = true;
try {
// get player dimensions _once_
let currentPlayerDimensions;
let fsChanged = this.isFullscreen !== !!document.fullscreenElement;
this.isFullscreen = !!document.fullscreenElement;
if (this.isFullscreen) {
currentPlayerDimensions = {
width: window.innerWidth,
height: window.innerHeight,
};
} else {
currentPlayerDimensions = {
width: this.element.offsetWidth,
height: this.element.offsetHeight
};
this.detectTheaterMode();
}
// defer creating UI
this.deferredUiInitialization(currentPlayerDimensions);
// if dimensions of the player box are the same as the last known
// dimensions, we don't have to do anything ... in theory. In practice,
// sometimes restore-ar doesn't appear to register the first time, and
// this function doesn't really run often enough to warrant finding a
// real, optimized fix.
if (
this.dimensions?.width == currentPlayerDimensions.width
&& this.dimensions?.height == currentPlayerDimensions.height
) {
this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500});
this.dimensions = currentPlayerDimensions;
this._isTrackDimensionChangesActive = false;
return;
}
// in every other case, we need to check if the player is still
// big enough to warrant our extension running.
this.handleSizeConstraints(currentPlayerDimensions);
// this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
// Save current dimensions to avoid triggering this function pointlessly
this.dimensions = currentPlayerDimensions;
if (fsChanged) {
this.updatePlayer();
}
} catch (e) {
}
this._isTrackDimensionChangesActive = false;
}
/**
* Checks if extension is allowed to run in current environment.
* @param currentPlayerDimensions
*/
private handleSizeConstraints(currentPlayerDimensions: PlayerDimensions) {
// Check if extension is allowed to run in current combination of theater + full screen
const canEnable = this.siteSettings.isEnabledForEnvironment(this.isFullscreen, this.isTheaterMode) === ExtensionMode.Enabled;
if (this.runLevel === RunLevel.Off && canEnable) {
this.eventBus.send('restore-ar', null);
// must be called after
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
} else if (!canEnable && this.runLevel !== RunLevel.Off) {
// must be called before
this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
this.setRunLevel(RunLevel.Off);
}
}
private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) {
if (this.runLevel === RunLevel.Off ) {
this.logger.info('handleDimensionChanges', "player size changed, but PlayerDetect is in disabled state. The player element is probably too small.");
return;
}
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
// ignore the contents of this if (unless we need to change how logging works)
this.logger.info('handleDimensionChanges', "player size potentially changed.\n\nold dimensions:", oldDimensions, '\nnew dimensions:', newDimensions);
// if size doesn't match, trigger onPlayerDimensionChange
if (
newDimensions?.width != oldDimensions?.width
|| newDimensions?.height != oldDimensions?.height
|| newDimensions?.fullscreen != oldDimensions?.fullscreen
){
// If player size changes, we restore aspect ratio
this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500});
// this.videoData.resizer?.restore();
this.eventBus.send('uw-config-broadcast', {
type: 'player-dimensions',
data: newDimensions
});
this.isTooSmall = this.checkIfTooSmall(newDimensions);
}
}
onPlayerDimensionsChanged: ResizeObserverCallback = _.debounce(
(mutationList?, observer?) => {
this._requestTick(true);
this.trackDimensionChanges();
this.trackEnvironmentChanges();
},
250, // do it once per this many ms
{
@ -389,112 +519,6 @@ class PlayerData {
}
);
private _requestTick(immediately?: boolean) {
if (immediately) {
this.processTick();
}
}
// Currently just a dummy, but at some point we could try to avoid running
// processTick on every tick — instead, running it only when needed.
// But today is not that day.
requestTick() {
this._requestTick();
}
/**
* Checks if anything changed
* @returns
*/
private processTick() {
let changeDetected = false;
let changes = {
player: false,
fullScreen: false,
theater: false,
dimensions: false,
}
let fs, newPlayerCandidate, isTheaterMode, currentDimensions;
fullScreenCheck:
{
fs = !!document.fullscreenElement;
if (fs !== this.isFullscreen) {
changes.fullScreen = true;
changeDetected = true;
}
}
playerCheck:
{
newPlayerCandidate = this.getPlayer();
if (!newPlayerCandidate) {
console.warn('[uw->PlayerData::processTick()] — player not detected — dimensions will not be checked')
return;
}
if (newPlayerCandidate !== this.element) {
changes.player = true;
changeDetected = true;
}
}
theaterModeCheck:
{
isTheaterMode = equalish(window.innerWidth, this.element.offsetWidth, 32);
if (isTheaterMode !== this.isTheaterMode) {
changes.theater = true;
changeDetected = true;
}
}
dimensionCheck:
{
if (this.isFullscreen) {
currentDimensions = {
width: window.innerWidth,
height: window.innerHeight,
};
} else {
currentDimensions = {
width: this.element.offsetWidth,
height: this.element.offsetHeight
};
}
if (currentDimensions.width !== this.dimensions?.width || currentDimensions.height !== this.dimensions?.height) {
changes.dimensions = true;
changeDetected = true;
}
}
// we only commit changes if no errors were encountered
commitChanges:
{
if (changes.fullScreen) this.isFullscreen = fs;
if (changes.player) this.element = newPlayerCandidate;
if (changes.theater) this.isTheaterMode = isTheaterMode;
if (changes.dimensions) this.dimensions = currentDimensions;
this.isTooSmall = this.checkIfTooSmall();
}
const canEnable = this.siteSettings.isEnabledForEnvironment(this.isFullscreen, this.isTheaterMode) === ExtensionMode.Enabled;
if (this.runLevel === RunLevel.Off && canEnable) {
this.eventBus.send('restore-ar', null);
} else if (!canEnable && this.runLevel !== RunLevel.Off) {
// must be called before
this.eventBus.send('restore-ar', null);
this.setRunLevel(RunLevel.Off);
}
if (changeDetected) {
this.deferredUiInitialization();
this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500});
}
}
//#region player element change detection
/**
@ -522,13 +546,46 @@ class PlayerData {
// legacy mode still exists, but acts as a fallback for observers and is triggered less
// frequently in order to avoid too many pointless checks
this.initFallbackDimensionMonitor();
this.legacyChangeDetection();
}
async legacyChangeDetection() {
while (!this.halted) {
await sleep(1000);
try {
this.forceRefreshPlayerElement();
} catch (e) {
console.error('[PlayerData::legacycd] this message is pretty high on the list of messages you shouldn\'t see', e);
}
}
}
doPeriodicPlayerElementChangeCheck() {
if (this.periodicallyRefreshPlayerElement) {
this.forceRefreshPlayerElement();
}
}
stopChangeDetection(){
this.observer.disconnect();
}
//#region helper functions
collectionHas(collection, element) {
for (let i = 0, len = collection.length; i < len; i++) {
if (collection[i] == element) {
return true;
}
}
return false;
}
equalish(a,b, tolerance) {
return a > b - tolerance && a < b + tolerance;
}
//#endregion
private getElementStack(): ElementStack {
const elementStack: ElementStack = [{
element: this.videoElement,
@ -560,22 +617,19 @@ class PlayerData {
return this.elementStack;
}
updatePlayer(options?: {verbose?: boolean, newElement?: HTMLElement}) {
const newPlayer = options?.newElement ?? this.getPlayer(options);
if (newPlayer === this.element || !newPlayer) {
if (newPlayer === this.element) {
return;
}
this.observer?.unobserve(this.element);
// clean up and re-initialize UI
this.ui?.destroy();
delete this.ui;
// Don't forget to also move observer to the new player
// (if observer exists)
this.element = newPlayer;
this.observer?.observe(this.element);
this.ui = new UI(
'ultrawidifyUi',
@ -588,7 +642,8 @@ class PlayerData {
}
);
this.requestTick();
this.trackDimensionChanges();
this.trackEnvironmentChanges();
}
/**
@ -640,12 +695,8 @@ class PlayerData {
return playerCandidate.element;
} else {
const playerCandidate = this.getPlayerAuto(elementStack, videoWidth, videoHeight);
if (playerCandidate === null) {
console.warn('[uw::getPlayer] getPlayerAuto returned null — no player detected?');
} else {
playerCandidate.heuristics['activePlayer'] = true;
return playerCandidate.element;
}
playerCandidate.heuristics['activePlayer'] = true;
return playerCandidate.element;
}
}
@ -662,8 +713,8 @@ class PlayerData {
const elementStack = this.getElementStack();
if (
equalish(elementStack[currentIndex].element.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
&& equalish(elementStack[currentIndex].element.offsetHeight, elementStack[nextIndex].element.offsetHeight, 2)
this.equalish(elementStack[currentIndex].element.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
&& this.equalish(elementStack[currentIndex].element.offsetHeight, elementStack[nextIndex].element.offsetHeight, 2)
) {
// this.siteSettings.set('playerAutoConfig.initialIndex', this.siteSettings.data.playerAutoConfig.initialIndex + 1, {noSave: true});
// this.siteSettings.set('playerAutoConfig.modified', true);
@ -714,9 +765,8 @@ class PlayerData {
// Don't bother thinking about this too much, as any "thinking" was quickly
// corrected by bugs caused by various edge cases.
if (
equalish(elementData.height, videoHeight, 5)
|| equalish(elementData.width, videoWidth, 5)
this.equalish(elementData.height, videoHeight, 5)
|| this.equalish(elementData.width, videoWidth, 5)
) {
let score = 1000;
@ -827,14 +877,14 @@ class PlayerData {
const allSelectors = document.querySelectorAll(queryString);
for (const element of elementStack) {
if (collectionHas(allSelectors, element.element)) {
if (this.collectionHas(allSelectors, element.element)) {
let score = 100;
// we award points to elements which match video size in one
// dimension and exceed it in the other
if (
(element.width >= videoWidth && equalish(element.height, videoHeight, 2))
|| (element.height >= videoHeight && equalish(element.width, videoWidth, 2))
(element.width >= videoWidth && this.equalish(element.height, videoHeight, 2))
|| (element.height >= videoHeight && this.equalish(element.width, videoWidth, 2))
) {
score += 75;
}
@ -867,6 +917,8 @@ class PlayerData {
private handlePlayerTreeRequest() {
// this populates this.elementStack fully
this.updatePlayer({verbose: true});
console.log('tree:', JSON.parse(JSON.stringify(this.elementStack)));
console.log('————————————————————— handling player tree request!')
this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))});
}
@ -881,7 +933,7 @@ class PlayerData {
this.markedElement.remove();
}
const elementBB = this.elementStack[data.parentIndex].element.getBoundingClientRect();;
const elementBB = this.elementStack[data.parentIndex].element.getBoundingClientRect();
// console.log('element bounding box:', elementBB);

View File

@ -17,7 +17,6 @@ import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface';
import { LogAggregator } from '../logging/LogAggregator';
import { ComponentLogger } from '../logging/ComponentLogger';
import { AardLegacy } from '../aard/AardLegacy';
/**
* VideoData handles CSS for the video element.
@ -74,7 +73,7 @@ class VideoData {
player: PlayerData;
resizer: Resizer;
aard: Aard | AardLegacy;
aard: Aard;
eventBus: EventBus;
extensionStatus: ExtensionStatus;
@ -260,7 +259,7 @@ class VideoData {
this.resizer = new Resizer(this);
try {
this.aard = this.settings.active.aard.useLegacy ? new AardLegacy(this) : new Aard(this); // this starts Ar detection. needs optional parameter that prevents ArDetector from starting
this.aard = new Aard(this); // this starts Ar detection. needs optional parameter that prevents ArDetector from starting
} catch (e) {
console.error('Failed to start Aard!', e);
}
@ -703,7 +702,7 @@ class VideoData {
return;
}
if (! this.aard){
this.aard = this.settings.active.aard.useLegacy ? new AardLegacy(this): new Aard(this);
this.aard = new Aard(this);
}
}

View File

@ -65,8 +65,6 @@ class Resizer {
private effectiveZoom: {x: number, y: number} = {x: 1, y: 1};
private pendingAr?: {ar: Ar, lastAr?: Ar};
_lastAr: Ar = {type: AspectRatioType.Initial};
set lastAr(x: Ar) {
// emit updates for UI when setting lastAr, but only if AR really changed
@ -334,19 +332,6 @@ class Resizer {
return;
}
// If we are missing some data that's necessary for crop calculations,
// set pendingAr and quit
if (!this.videoData.player.dimensions?.width || !this.videoData.player.dimensions?.height) {
this.pendingAr = {ar, lastAr};
this.videoData.player.requestTick();
return;
}
this.pendingAr = null;
if (! this.videoData.player.dimensions) {
this.videoData.player.updatePlayer();
}
// If no aspect ratio is applied AND if no stretch mode is active,
// we disable our CSS in order to prevent breaking websites by default,
// without any human interaction
@ -480,12 +465,8 @@ class Resizer {
// this.videoData.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
// }
try {
const translate = this.computeOffsets(stretchFactors, options?.ar);
this.applyCss(stretchFactors, translate);
} catch (e) {
// don't apply CSS if there's an error
}
let translate = this.computeOffsets(stretchFactors, options?.ar);
this.applyCss(stretchFactors, translate);
}
toFixedAr() {
@ -586,10 +567,6 @@ class Resizer {
* @returns
*/
restore() {
if (this.pendingAr) {
this.setAr(this.pendingAr.ar, this.pendingAr.lastAr);
return;
}
if (!this.manualZoom) {
this.logger.info('restore', `<rid:${this.resizerId}> attempting to restore aspect ratio`, {'lastAr': this.lastAr} );
@ -881,15 +858,16 @@ class Resizer {
) {
this.logger.warn('computeOffsets', `<rid:${this.resizerId}> 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`,
`Something is probably undefined:\n`,
`\n videoData.video offset w x h:`, this.videoData.video.offsetWidth, 'x', this.videoData.video.offsetHeight,
`\n videoData.player.dimensions w x h:`, this.videoData.player.dimensions.width, 'x', this.videoData.player.dimensions.height,
`Player element needs to be checked.`
);
// request dimension change tick.
// this will cause
this.videoData.player.requestTick();
throw 'DIMENSIONS_ERROR';
// sometimes this appears to randomly recurse.
// There seems to be no way to reproduce it.
if (! this._computeOffsetsRecursionGuard) {
this._computeOffsetsRecursionGuard = true;
this.videoData.player.trackDimensionChanges();
this._computeOffsetsRecursionGuard = false;
}
}
return translate;

View File

@ -1,12 +0,0 @@
export function collectionHas(collection, element): boolean {
for (let i = 0, len = collection.length; i < len; i++) {
if (collection[i] == element) {
return true;
}
}
return false;
}
export function equalish(a: number,b: number, tolerance: number): boolean {
return a > b - tolerance && a < b + tolerance;
}

View File

@ -4,29 +4,21 @@
Ultrawidify has been updated.
</div>
<div class="body flex-grow">
<h1>Where should Ultrawidify run by default</h1>
<h1>Where do you want to use Ultrawidify?</h1>
<div class="flex flex-row">
<div class=""
@click="() => {}"
@click="() => {}"
>
All websites<br/>
<small>(Some sites are disabled by default.)</small>
All sites<br/>
<small>(Some sites are disabled by default. Requires access to all sites)</small>
</div>
<div>
On sites that people say are working<br/>
<small>(And the sites I explicitly allow)</small>
</div>
<div>
Officially supported sites*<br/>
<small>(And the sites I explicitly allow)</small>
Default sites and sites I explicitly allow
</div>
<div>
Only the sites I explicitly allow
</div>
</div>
<div>
*Ultrawidify still needs access to all websites for technical and historical technical reasons.
</div>
<h1>Try to automatically detect aspect ratio?</h1>
<div class="flex flex-row">

View File

@ -1,76 +1,86 @@
<template>
<div class="flex flex-col h100 justify-center items-center">
<template v-if="!settingsInitialized">Please wait ...</template>
<template v-else>
<div class="body flex-grow">
<h1>Ultrawidify has been updated</h1>
<br/>
<p>This update introduces some new experimental features:</p>
<b>
What do you want to if there are subtitles in the video?
</b>
<div class="select">
<select v-model="placeholderSubtitleCrop">
<option :value="AardSubtitleCropMode.ResetAR">Reset aspect ratio while subtitles are visible</option>
<option :value="AardSubtitleCropMode.ResetAndDisable">Reset aspect ratio and stop autodetection for the video</option>
<option :value="AardSubtitleCropMode.CropSubtitles">Crop subtitles</option>
</select>
<div class="flex flex-col h100">
<div class="header flex-nogrow flex-noshrink">
Thank you for installing Ultrawidify.
</div>
<div class="body flex-grow">
<p>Before we're ready to go, there are three quick questions. You will be able to change these later.</p>
<h1>Where do you want to use Ultrawidify?</h1>
<div class="flex flex-row">
<div class=""
@click="() => {}"
>
All sites<br/>
<small>(Some sites are disabled by default. Requires access to all sites)</small>
</div>
<br/>
<b>Use experimental aspect ratio detection?</b>
<div class="select">
<select v-model="settings.active.aard.useLegacy">
<option :value="true">Use legacy detection</option>
<option :value="false">Use experimental detection</option>
</select>
<div>
Default sites and sites I explicitly allow
</div>
<p>Experimental aspect ratio detection should be more accurate, but it hasn't been extensively tested yet.</p>
<p>If you enable experimental mode, please consider reporting problems <a href="https://github.com/tamius-han/ultrawidify/issues/291" target="_blank">in this thread</a> on Github.</p>
<p>Experimental detection will become the default in 2026 unless people report issues.</p>
<br/>
<br/>
<div class="flex flex-row w-full justify-center items-center">
<button v-if="!settingsSaved" class="button primary" @click="saveSettings">
Save preferences
</button>
<template v-else>Your settings have been saved.</template>
<div>
Only the sites I explicitly allow
</div>
<br/>
<br/>
<p>You can always change your settings later.</p>
</div>
<div class="footer flex-nogrow flex-noshrink">
<h1>Should Ultrawidify automatically detect aspect ratio where possible?</h1>
<div class="flex flex-row">
<div class="">
Yes
</div>
<div>
Only on sites I allow
</div>
<div class="">
Never
</div>
</div>
</template>
<h1>Do you want to see update notes when extension receives updates?</h1>
<p>Update notes will open a new tab, just like this one.</p>
<div class="flex flex-row">
<div class="">
Yes, even for the tiniest changes
</div>
<div class="">
Yes, but only for the big/important ones
</div>
<div class="">
No, never.
</div>
</div>
</div>
<div class="footer flex-nogrow flex-noshrink">
</div>
</div>
</template>
<script>
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import BrowserDetect from '../../ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
import Settings from '@src/ext/lib/settings/Settings';
import { AardSubtitleCropMode } from '@src/ext/lib/aard/enums/aard-subtitle-crop-mode.enum';
export default {
data () {
return {
AardSubtitleCropMode,
selectedTab: 'video',
selectedFrame: '__all',
selectedSite: '',
activeFrames: [],
activeSites: [],
port: BrowserDetect.firefox ? chrome.runtime.connect({name: 'popup-port'}) : chrome.runtime.connect({name: 'popup-port'}),
comms: new Comms(),
frameStore: {},
frameStoreCount: 0,
performance: {},
site: null,
currentZoom: 1,
settings: {},
settingsInitialized: false,
logAggregator: {},
logger: {},
placeholderSubtitleCrop: AardSubtitleCropMode.ResetAR,
settingsSaved: false
siteTabDisabled: false,
videoTabDisabled: false,
canShowVideoTab: {canShow: true, warning: true},
showWhatsNew: false,
}
},
async created() {
@ -79,36 +89,26 @@ export default {
this.settings = new Settings({updateCallback: () => this.updateConfig(), logAggregator: this.logAggregator});
await this.settings.init();
this.placeholderSubtitleCrop = (this.settings.active.aard.useLegacy ? this.settings.active.aardLegacy.subtitles?.subtitleCropMode : this.settings.active.aard.subtitles?.subtitleCropMode) ?? AardSubtitleCropMode.ResetAR;
this.settingsInitialized = true;
},
components: {
},
methods: {
async updateConfig() {
await this.settings.init();
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
},
saveSettings() {
this.settings.active[this.settings.active.aard.useLegacy ? 'aardLegacy' : 'aard'].subtitles.subtitleCropMode = this.placeholderSubtitleCrop;
this.settings.save();
this.settingsSaved = true;
}
}
}
</script>
<style src="@csui/res/css/font/overpass.css"></style>
<style src="@csui/res/css/font/overpass-mono.css"></style>
<style src="@csui/res/css/flex.scss"></style>
<style src="@csui/res/css/common.scss"></style>
<style src="../../res/css/font/overpass.css"></style>
<style src="../../res/css/font/overpass-mono.css"></style>
<style src="../../res/css/flex.scss"></style>
<style src="../../res/css/common.scss"></style>
<style lang="scss" scoped>
p {
font-size: 0.9rem !important;
}
body {
html, body {
width: 800px !important;
max-width: 800px !important;
padding: 0px;

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="popup.css">
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head>
<body style="width: 800px; height: 600px; overflow: hidden !important">
<div id="app">
</div>
<script src="first-time.js"></script>
</body>
</html>

View File

@ -0,0 +1,11 @@
import Vue from 'vue'
import App from './App'
// global.browser = require('webextension-polyfill')
// Vue.prototype.$browser = global.browser
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App)
})

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ultrawidify updated</title>
</head>
<body style="width: 100dvw; height: 100dvh">
<div id="app">
app should load here
</div>
<script src="updated.js" type="module"></script>
</body>
</html>

View File

@ -1,13 +0,0 @@
import { createApp } from 'vue';
import App from './App';
import mdiVue from 'mdi-vue/v3';
import * as mdijs from '@mdi/js';
// import '@src/res-common/common.scss';
// NOTE — this is in-player interface for ultrawidify
// it is injected into the page in UI.init()
createApp(App)
.use(mdiVue, {icons: mdijs})
.mount('#app');

View File

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.3.993",
"version": "6.3.97",
"icons": {
"32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png"
@ -41,8 +41,7 @@
"res/img/settings/about-bg.png",
"res/icons/*",
"res/img/*",
"csui/*",
"install/*"
"csui/*"
],
"matches": [
"*://*/*"

View File

@ -12,12 +12,3 @@ var BgVars = {
}
const server = new UWServer();
// add update listener
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "update") {
chrome.tabs.create({
url: chrome.runtime.getURL("install/updated/updated.html")
});
}
});

View File

@ -20,7 +20,6 @@ const config = {
'csui/csui-popup': './csui/csui-popup.js',
'csui/csui': './csui/csui.js',
// 'install/first-time/first-time':'./install/first-time/first-time.js',
'install/updated/updated': './install/updated/updated.js',
},
output: {
path: __dirname + `/dist-${process.env.BROWSER == 'firefox' ? 'ff' : process.env.BROWSER}`,
@ -126,7 +125,6 @@ const config = {
{ from: 'res', to: 'res', ignore: ['css', 'css/**']},
{ from: 'ext', to: 'ext', ignore: ['conf/*', 'lib/**']},
{ from: 'csui', to: 'csui', ignore: ['src']},
{ from: 'install', to: 'install' },
// we need to get webextension-polyfill and put it in common/lib
{ from: '../node_modules/webextension-polyfill/dist/browser-polyfill.js', to: 'common/lib/browser-polyfill.js'},
@ -140,7 +138,6 @@ const config = {
{ from: 'csui/csui-overlay-dark.html', to: 'csui/csui-dark.html', transform: transformHtml },
{ from: 'csui/csui-overlay-light.html', to: 'csui/csui-light.html', transform: transformHtml },
// { from: 'install/first-time/first-time.html', to: 'install/first-time/first-time.html', transform: transformHtml},
{ from: 'install/updated/updated.html', to: 'install/updated/updated.html', transform: transformHtml },
{
from: 'manifest.json',
to: 'manifest.json',