Compare commits
4 Commits
ddffbe92a0
...
095a3daadd
| Author | SHA1 | Date | |
|---|---|---|---|
| 095a3daadd | |||
| baa7c79f6c | |||
| f5e308c5b4 | |||
| c0c29759fa |
@ -8,6 +8,7 @@
|
||||
* 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
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.97",
|
||||
"version": "6.3.993",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.97",
|
||||
"version": "6.3.993",
|
||||
"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": {
|
||||
|
||||
@ -90,6 +90,7 @@ export interface AardSubtitleScanOptions {
|
||||
|
||||
export interface AardSettings {
|
||||
aardType: 'webgl' | 'legacy' | 'auto';
|
||||
useLegacy: boolean,
|
||||
|
||||
earlyStopOptions: {
|
||||
stopAfterFirstDetection: boolean;
|
||||
@ -183,6 +184,131 @@ 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,
|
||||
}
|
||||
@ -194,7 +320,8 @@ interface SettingsInterface {
|
||||
}
|
||||
dev: DevSettings,
|
||||
|
||||
arDetect: AardSettings,
|
||||
aardLegacy: AardLegacySettings,
|
||||
aard: AardSettings,
|
||||
|
||||
ui: {
|
||||
inPlayer: {
|
||||
|
||||
@ -11,19 +11,29 @@
|
||||
|
||||
<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(settings.active.arDetect.timers.playing)"
|
||||
:value="Math.log(aardSettings.timers.playing)"
|
||||
@change="setArCheckFrequency($event.target.value)"
|
||||
min="2.3"
|
||||
max="9.3"
|
||||
step="0.01"
|
||||
/>
|
||||
<input
|
||||
v-model="settings.active.arDetect.timers.playing"
|
||||
v-model="aardSettings.timers.playing"
|
||||
@change="setArCheckFrequency($event.target.value)"
|
||||
class="input"
|
||||
type="text"
|
||||
@ -37,14 +47,14 @@
|
||||
<div class="range-input">
|
||||
<input
|
||||
type="range"
|
||||
:value="Math.log(settings.active.arDetect.timers.playingReduced)"
|
||||
:value="Math.log(aardSettings.timers.playingReduced)"
|
||||
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
|
||||
min="2.3"
|
||||
max="9.3"
|
||||
step="0.01"
|
||||
/>
|
||||
<input
|
||||
v-model="settings.active.arDetect.timers.playingReduced"
|
||||
v-model="aardSettings.timers.playingReduced"
|
||||
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
|
||||
class="input"
|
||||
type="text"
|
||||
@ -56,64 +66,64 @@
|
||||
<div class="field">
|
||||
<div class="label">Poll for aspect ratio changes in background tabs:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.polling.runInBackgroundTabs" @change="settings.saveWithoutReload">
|
||||
<select v-model="aardSettings.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="settings.active.arDetect.polling.runInBackgroundTabs === AardPollingOptions.Full" class="hint warn">
|
||||
<div v-if="aardSettings.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="settings.active.arDetect.polling.runOnSmallVideos" @change="settings.saveWithoutReload">
|
||||
<select v-model="aardSettings.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="settings.active.arDetect.polling.runOnSmallVideos === AardPollingOptions.Full" class="hint warn">
|
||||
<div v-if="aardSettings.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="settings.active.arDetect.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
|
||||
<select v-model="aardSettings.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
|
||||
<option :value="true">Yes</option>
|
||||
<option :value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field" :class="{disabled: settings.active.arDetect.autoDisable.onFirstChange}">
|
||||
<div class="field" :class="{disabled: aardSettings.autoDisable.onFirstChange}">
|
||||
<div class="label">Stop autodetection if aspect ratio doesn't change for some time:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
|
||||
<select v-model="aardSettings.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
|
||||
<option :value="true">Yes</option>
|
||||
<option :value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="settings.active.arDetect.autoDisable.ifNotChanged" class="field">
|
||||
<div v-if="aardSettings.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(settings.active.arDetect.autoDisable.ifNotChangedTimeout / 10)"
|
||||
:value="Math.log(aardSettings.autoDisable.ifNotChangedTimeout / 10)"
|
||||
@change="setAutoDisableTimeout($event.target.value, 10)"
|
||||
min="2.3"
|
||||
max="9.3"
|
||||
step="0.01"
|
||||
/>
|
||||
<input
|
||||
:value="settings.active.arDetect.autoDisable.ifNotChangedTimeout / 1000"
|
||||
:value="aardSettings.autoDisable.ifNotChangedTimeout / 1000"
|
||||
@change="setAutoDisableTimeout($event.target.value, 1000)"
|
||||
class="input"
|
||||
type="text"
|
||||
@ -125,7 +135,7 @@
|
||||
<div class="field">
|
||||
<div class="label">Autodetection canvas type:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.aardType" @change="settings.saveWithoutReload">
|
||||
<select v-model="aardSettings.aardType" @change="settings.saveWithoutReload">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="webgl">WebGL only</option>
|
||||
<option value="legacy">Legacy / fallback</option>
|
||||
@ -136,7 +146,7 @@
|
||||
<div class="field">
|
||||
<div class="label">Maximum allowed vertical video misalignment:</div>
|
||||
<div class="input">
|
||||
<input v-model="settings.active.arDetect.allowedMisaligned" />
|
||||
<input v-model="aardSettings.allowedMisaligned" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
@ -154,8 +164,8 @@
|
||||
<div class="field">
|
||||
<div class="label">Subtitle detection:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.subtitles.subtitleCropMode" @change="settings.saveWithoutReload">
|
||||
<option :value="AardSubtitleCropMode.DisableScan">Do not detect subtitles</option>
|
||||
<select v-model="aardSettings.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>
|
||||
@ -163,19 +173,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="settings.active.arDetect.subtitles.subtitleCropMode === AardSubtitleCropMode.ResetAR" class="field">
|
||||
<div v-if="aardSettings.subtitles.subtitleCropMode === AardSubtitleCropMode.ResetAR" class="field">
|
||||
<div class="label">Wait before resuming detection:</div>
|
||||
<div class="range-input">
|
||||
<input
|
||||
type="range"
|
||||
:value="Math.log(settings.active.arDetect.subtitles.resumeAfter / 10)"
|
||||
:value="Math.log(aardSettings.subtitles.resumeAfter / 10)"
|
||||
@change="setSubtitleTimeout($event.target.value, 10)"
|
||||
min="2.3"
|
||||
max="9.3"
|
||||
step="0.01"
|
||||
/>
|
||||
<input
|
||||
:value="settings.active.arDetect.subtitles.resumeAfter / 1000"
|
||||
:value="aardSettings.subtitles.resumeAfter / 1000"
|
||||
@change="setSubtitleTimeout($event.target.value, 1000)"
|
||||
class="input"
|
||||
type="text"
|
||||
@ -261,6 +271,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
aardSettings() {
|
||||
return this.settings.active.aard.useLegacy ? this.settings.active.aardLegacy : this.settings.active.aard;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.eventBus.subscribe(
|
||||
@ -285,15 +298,15 @@ export default {
|
||||
BrowserDetect.runtime.openOptionsPage();
|
||||
},
|
||||
setArCheckFrequency(event, timer) {
|
||||
this.settings.active.arDetect.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
|
||||
this.aardSettings.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
setAutoDisableTimeout(event, multiplier) {
|
||||
this.settings.active.arDetect.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
||||
this.aardSettings.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
setSubtitleTimeout(event, multiplier) {
|
||||
this.settings.active.arDetect.subtitles.resumeAfter = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
||||
this.aardSettings.subtitles.resumeAfter = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
refreshGraph() {
|
||||
@ -311,6 +324,9 @@ export default {
|
||||
saveDebugUiSettings() {
|
||||
this.settings.active.ui.dev.aardDebugOverlay = JSON.parse(JSON.stringify(this.settingsJson));
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
aardLegacyModeChanged() {
|
||||
this.settings.save();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
<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>
|
||||
|
||||
@ -255,17 +255,25 @@ const ExtensionConfPatch = Object.freeze([
|
||||
{
|
||||
forVersion: '6.3.93',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
|
||||
userOptions.arDetect.polling = defaultOptions.arDetect.polling;
|
||||
userOptions.arDetect.subtitles = defaultOptions.arDetect.subtitles;
|
||||
userOptions.arDetect.autoDisable = defaultOptions.arDetect.autoDisable;
|
||||
(userOptions as any).arDetect.polling = defaultOptions.aard.polling;
|
||||
(userOptions as any).arDetect.subtitles = defaultOptions.aard.subtitles;
|
||||
(userOptions as any).arDetect.autoDisable = defaultOptions.aard.autoDisable;
|
||||
}
|
||||
},
|
||||
{
|
||||
forVersion: '6.3.97',
|
||||
forVersion: '6.3.98',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
|
||||
userOptions.arDetect.letterboxOrientationScan = defaultOptions.arDetect.letterboxOrientationScan;
|
||||
userOptions.arDetect.edgeDetection = defaultOptions.arDetect.edgeDetection;
|
||||
userOptions.arDetect.subtitles = defaultOptions.arDetect.subtitles;
|
||||
(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ const ExtensionConf: SettingsInterface = {
|
||||
loadFromSnapshot: false,
|
||||
},
|
||||
|
||||
arDetect: {
|
||||
aardLegacy: {
|
||||
aardType: 'auto',
|
||||
|
||||
polling: {
|
||||
@ -88,6 +88,140 @@ 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,
|
||||
|
||||
@ -97,6 +97,8 @@ 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)
|
||||
}
|
||||
|
||||
@ -152,20 +152,20 @@ export class Aard {
|
||||
|
||||
this.canvasSamples = {
|
||||
top: generateSampleArray(
|
||||
this.settings.active.arDetect.sampling.staticCols,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width
|
||||
this.settings.active.aard.sampling.staticCols,
|
||||
this.settings.active.aard.canvasDimensions.sampleCanvas.width
|
||||
),
|
||||
bottom: generateSampleArray(
|
||||
this.settings.active.arDetect.sampling.staticCols,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width
|
||||
this.settings.active.aard.sampling.staticCols,
|
||||
this.settings.active.aard.canvasDimensions.sampleCanvas.width
|
||||
),
|
||||
left: generateSampleArray(
|
||||
this.settings.active.arDetect.sampling.staticCols,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
|
||||
this.settings.active.aard.sampling.staticCols,
|
||||
this.settings.active.aard.canvasDimensions.sampleCanvas.height
|
||||
),
|
||||
right: generateSampleArray(
|
||||
this.settings.active.arDetect.sampling.staticCols,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
|
||||
this.settings.active.aard.sampling.staticCols,
|
||||
this.settings.active.aard.canvasDimensions.sampleCanvas.height
|
||||
)
|
||||
};
|
||||
|
||||
@ -188,14 +188,14 @@ export class Aard {
|
||||
}
|
||||
|
||||
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'legacy') {
|
||||
ROW_SIZE = this.settings.active.arDetect.canvasDimensions.sampleCanvas.width * PIXEL_SIZE;
|
||||
ROW_SIZE = this.settings.active.aard.canvasDimensions.sampleCanvas.width * PIXEL_SIZE;
|
||||
|
||||
if (canvasType) {
|
||||
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
|
||||
if (canvasType === this.settings.active.aard.aardType || this.settings.active.aard.aardType === 'auto') {
|
||||
if (canvasType === 'webgl') {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
return new GlCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} else if (canvasType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
return new FallbackCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
} else {
|
||||
// TODO: throw error
|
||||
}
|
||||
@ -205,21 +205,21 @@ export class Aard {
|
||||
|
||||
}
|
||||
|
||||
if (['auto', 'webgl'].includes(this.settings.active.arDetect.aardType)) {
|
||||
if (['auto', 'webgl'].includes(this.settings.active.aard.aardType)) {
|
||||
try {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
return new GlCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} catch (e) {
|
||||
if (this.settings.active.arDetect.aardType !== 'webgl') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
if (this.settings.active.aard.aardType !== 'webgl') {
|
||||
return new FallbackCanvas({...this.settings.active.aard.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.arDetect.aardType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
} else if (this.settings.active.aard.aardType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.aard.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
} else {
|
||||
this.logger.error('createCanvas', 'invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
|
||||
this.logger.error('createCanvas', 'invalid value in settings.arDetect.aardType:', this.settings.active.aard.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.arDetect.canvasDimensions.sampleCanvas, id: 'uw-debug-gl'});
|
||||
this.canvasStore.debug = new GlDebugCanvas({...this.settings.active.aard.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.arDetect);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
|
||||
this.testResults = initAardTestResults(this.settings.active.aard);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.aard);
|
||||
|
||||
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.arDetect.autoDisable.ifNotChanged) {
|
||||
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
|
||||
if (this.settings.active.aard.autoDisable.ifNotChanged) {
|
||||
this.timers.autoDisableAt = Date.now() + this.settings.active.aard.autoDisable.ifNotChangedTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,8 +314,8 @@ export class Aard {
|
||||
this.stop();
|
||||
|
||||
if (options?.noCache) {
|
||||
this.testResults = initAardTestResults(this.settings.active.arDetect);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
|
||||
this.testResults = initAardTestResults(this.settings.active.aard);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.aard);
|
||||
}
|
||||
|
||||
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.arDetect.polling;
|
||||
const polling = this.settings.active.aard.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.arDetect.timers.playing;
|
||||
this.timers.reducedPollingNextCheckTime = now + this.settings.active.arDetect.timers.playingReduced;
|
||||
this.timers.nextFrameCheckTime = now + this.settings.active.aard.timers.playing;
|
||||
this.timers.reducedPollingNextCheckTime = now + this.settings.active.aard.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.arDetect;
|
||||
const arConf = this.settings.active.aard;
|
||||
|
||||
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.arDetect.letterboxOrientationScan.letterboxLimit;
|
||||
const yLimit = this.settings.active.arDetect.letterboxOrientationScan.pillarboxLimit;
|
||||
const xLimit = this.settings.active.aard.letterboxOrientationScan.letterboxLimit;
|
||||
const yLimit = this.settings.active.aard.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.arDetect.edgeDetection.maxLetterboxOffset);
|
||||
const maxOffset = ~~(crossDimension * this.settings.active.aard.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.arDetect.subtitles;
|
||||
const scanConf = this.settings.active.aard.subtitles;
|
||||
|
||||
this.testResults.subtitleDetected = false;
|
||||
|
||||
@ -931,8 +931,8 @@ export class Aard {
|
||||
) {
|
||||
results.uncertain = false;
|
||||
|
||||
const scanConf = this.settings.active.arDetect.subtitles;
|
||||
const arConf = this.settings.active.arDetect;
|
||||
const scanConf = this.settings.active.aard.subtitles;
|
||||
const arConf = this.settings.active.aard;
|
||||
|
||||
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 innerIteration, outerIteration = 0;
|
||||
let 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,8 +953,11 @@ export class Aard {
|
||||
(scanSpacing > 0 && searchRow < endRow) || (scanSpacing < 0 && searchRow > endRow);
|
||||
searchRow += scanSpacing
|
||||
) {
|
||||
innerIteration = 0;
|
||||
outerIteration++;
|
||||
if (++outerIteration > height) {
|
||||
// console.warn('[ultrawidify|aard::subtitleScanRegionLinear] — scan got stuck in an infinite loop. This shouldn\'t happen.');
|
||||
results.uncertain;
|
||||
break outerLoop;
|
||||
}
|
||||
|
||||
letterCount = 0;
|
||||
potentialFadedLetterCount = 0;
|
||||
@ -1019,8 +1022,6 @@ export class Aard {
|
||||
*/
|
||||
|
||||
while (rowStart < rowEnd) {
|
||||
innerIteration++;
|
||||
|
||||
const r = imageData[rowStart], g = imageData[rowStart + 1], b = imageData[rowStart + 2];
|
||||
|
||||
const on = r > scanConf.subtitleSubpixelThresholdOn
|
||||
@ -1316,7 +1317,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.arDetect.allowedArVariance || options?.forceReset) {
|
||||
if ((diff / maxRatio) > this.settings.active.aard.allowedArVariance || options?.forceReset) {
|
||||
this.videoData.resizer.updateAr({
|
||||
type: AspectRatioType.AutomaticUpdate,
|
||||
ratio: ar,
|
||||
@ -1326,11 +1327,11 @@ export class Aard {
|
||||
this.testResults.activeAspectRatio = ar;
|
||||
|
||||
if (!options?.uncertainDetection) {
|
||||
if (this.settings.active.arDetect.autoDisable.onFirstChange) {
|
||||
if (this.settings.active.aard.autoDisable.onFirstChange) {
|
||||
this.status.autoDisabled = true;
|
||||
}
|
||||
if (this.settings.active.arDetect.autoDisable.ifNotChanged) {
|
||||
this.timers.autoDisableAt = Date.now() + this.settings.active.arDetect.autoDisable.ifNotChangedTimeout;
|
||||
if (this.settings.active.aard.autoDisable.ifNotChanged) {
|
||||
this.timers.autoDisableAt = Date.now() + this.settings.active.aard.autoDisable.ifNotChangedTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { Aard } from './Aard';
|
||||
import { AardLegacy } from './AardLegacy';
|
||||
import { AardPerformanceData } from './AardTimers';
|
||||
import { FallbackCanvas } from './gl/FallbackCanvas';
|
||||
|
||||
export class AardDebugUi {
|
||||
|
||||
@ -76,37 +79,11 @@ export class AardDebugUi {
|
||||
</div>
|
||||
|
||||
|
||||
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%">
|
||||
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%; margin-top: 8rem;">
|
||||
<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>
|
||||
@ -125,10 +102,9 @@ export class AardDebugUi {
|
||||
<pre id="uw-aard-results"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 1920px">
|
||||
<div style="width: 1920px; border: 2px dotted #142; margin-right:2rem;">
|
||||
<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>
|
||||
@ -194,9 +170,19 @@ 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.average)}
|
||||
${this.generateRawTimes(this.aard.timer.current)}
|
||||
</div>
|
||||
<div style="color: #fff">Stage times (not cumulative):</div>
|
||||
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
|
||||
@ -214,12 +200,14 @@ export class AardDebugUi {
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
${this.generateRawTimes(this.aard.timer.average)}
|
||||
${this.generateRawTimes(this.aard.timer.lastChange)}
|
||||
</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>
|
||||
`
|
||||
|
||||
@ -298,23 +286,23 @@ export class AardDebugUi {
|
||||
total += fastBlackLevel;
|
||||
|
||||
const guardLineStart = fastBlackLevelStart + fastBlackLevel;
|
||||
const guardLine = Math.max(perf.guardLine - total, 0);
|
||||
const guardLine = (perf.guardLine !== undefined && perf.guardLine !== -1) ? Math.max(perf.guardLine - total, 0) : 0;
|
||||
total += guardLine;
|
||||
|
||||
const edgeScanStart = guardLineStart + guardLine;
|
||||
const edgeScan = Math.max(perf.edgeScan - total, 0);
|
||||
const edgeScan = (perf.edgeScan !== undefined && perf.edgeScan !== -1) ? Math.max(perf.edgeScan - total, 0) : 0;
|
||||
total += edgeScan;
|
||||
|
||||
const gradientStart = edgeScanStart + edgeScan;
|
||||
const gradient = Math.max(perf.gradient - total, 0);
|
||||
const gradient = (perf.gradient !== undefined && perf.gradient !== -1) ? Math.max(perf.gradient - total, 0) : 0;
|
||||
total += gradient;
|
||||
|
||||
const scanResultsStart = gradientStart + gradient;
|
||||
const scanResults = Math.max(perf.scanResults - total, 0);
|
||||
const scanResults = (perf.scanResults !== undefined && perf.scanResults !== -1) ? Math.max(perf.scanResults - total, 0) : 0;
|
||||
total += scanResults;
|
||||
|
||||
const subtitleScanStart = scanResultsStart + scanResults;
|
||||
const subtitleScan = Math.max(perf.scanResults - total, 0);
|
||||
const subtitleScan = Math.max(perf.subtitleScan - total, 0);
|
||||
total += subtitleScan;
|
||||
|
||||
return `
|
||||
@ -344,12 +332,10 @@ 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)}
|
||||
|
||||
${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
|
||||
<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)}
|
||||
|
||||
<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;')}
|
||||
${this.getBarLabel(0, subtitleScan + subtitleScanStart, 98, `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>
|
||||
@ -359,7 +345,7 @@ export class AardDebugUi {
|
||||
}
|
||||
|
||||
_lastAr: undefined;
|
||||
updateTestResults(testResults) {
|
||||
updateTestResults(testResults, timers) {
|
||||
this.updatePerformanceResults();
|
||||
|
||||
if (testResults.aspectRatioUpdated && this.pauseOnArCheck) {
|
||||
@ -378,52 +364,13 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
2144
src/ext/lib/aard/AardLegacy.ts
Normal file
2144
src/ext/lib/aard/AardLegacy.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,11 +4,13 @@ export interface AardPerformanceData {
|
||||
getImage: number;
|
||||
|
||||
fastBlackLevel: number;
|
||||
guardLine: number; // actually times both guard line and image line checks
|
||||
edgeScan: number; // includes validation step
|
||||
gradient: 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;
|
||||
subtitleScan: number;
|
||||
scanResults: number;
|
||||
}
|
||||
|
||||
|
||||
@ -82,36 +84,52 @@ 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) {
|
||||
if (sample.fastBlackLevel !== -1 && this.average.fastBlackLevel !== null) {
|
||||
this.average.fastBlackLevel += sample.fastBlackLevel;
|
||||
} else {
|
||||
this.average.fastBlackLevel = null;
|
||||
}
|
||||
if (sample.guardLine !== -1) {
|
||||
if (sample.guardLine !== -1 && this.average.guardLine !== null) {
|
||||
this.average.guardLine += sample.guardLine;
|
||||
} else {
|
||||
this.average.guardLine = null;
|
||||
}
|
||||
if (sample.edgeScan !== -1) {
|
||||
if (sample.edgeScan !== -1 && this.average.edgeScan !== null) {
|
||||
this.average.edgeScan += sample.edgeScan;
|
||||
} else {
|
||||
this.average.edgeScan = null;
|
||||
}
|
||||
if (sample.gradient !== -1) {
|
||||
if (sample.gradient !== -1 && this.average.gradient !== null) {
|
||||
this.average.gradient += sample.gradient;
|
||||
} else {
|
||||
this.average.edgeScan = null;
|
||||
}
|
||||
if (sample.scanResults !== -1) {
|
||||
if (sample.scanResults !== -1 && this.average.scanResults !== null) {
|
||||
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.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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,189 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -213,6 +213,8 @@ class Settings {
|
||||
}
|
||||
);
|
||||
|
||||
// this.active.newFeatureTracker = {};
|
||||
|
||||
|
||||
// apply all remaining patches
|
||||
this.logger?.info('applySettingsPatches', `There are ${ExtensionConfPatch.length - index} settings patches to apply`);
|
||||
@ -288,19 +290,6 @@ 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);
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ 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");
|
||||
@ -96,6 +97,7 @@ class PlayerData {
|
||||
isTooSmall: boolean = true;
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region misc stuff
|
||||
extensionMode: any;
|
||||
dimensions: PlayerDimensions;
|
||||
@ -144,11 +146,12 @@ class PlayerData {
|
||||
private dimensionChangeListener = {
|
||||
that: this,
|
||||
handleEvent: function(event: Event) {
|
||||
this.that.trackEnvironmentChanges(event);
|
||||
this.that.trackDimensionChanges()
|
||||
this.that.requestTick();
|
||||
}
|
||||
}
|
||||
|
||||
private fallbackDimensionChangeInterval: any;
|
||||
|
||||
/**
|
||||
* Gets player aspect ratio. If in full screen, it returns screen aspect ratio unless settings say otherwise.
|
||||
*/
|
||||
@ -158,8 +161,7 @@ class PlayerData {
|
||||
return window.innerWidth / window.innerHeight;
|
||||
}
|
||||
if (!this.dimensions) {
|
||||
this.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
return (this.element?.scrollWidth ?? this.videoElement.videoWidth) / (this.element?.scrollHeight ?? this.videoElement.videoHeight);
|
||||
}
|
||||
|
||||
return this.dimensions.width / this.dimensions.height;
|
||||
@ -225,7 +227,6 @@ class PlayerData {
|
||||
}
|
||||
|
||||
this.startChangeDetection();
|
||||
|
||||
document.addEventListener('fullscreenchange', this.dimensionChangeListener);
|
||||
|
||||
// we want to reload on storage changes
|
||||
@ -236,35 +237,6 @@ 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
|
||||
*/
|
||||
@ -276,18 +248,27 @@ class PlayerData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely stops everything the extension is doing
|
||||
*/
|
||||
destroy() {
|
||||
document.removeEventListener('fullscreenchange', this.dimensionChangeListener);
|
||||
this.stopChangeDetection();
|
||||
this.ui?.destroy();
|
||||
this.notificationService?.destroy();
|
||||
private initFallbackDimensionMonitor() {
|
||||
this.stopFallbackDimensionMonitor();
|
||||
this.fallbackDimensionChangeInterval = setInterval( () => {
|
||||
this.processTick();
|
||||
}, 2000);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
deferredUiInitialization(playerDimensions) {
|
||||
/**
|
||||
* Stops manually scanning for dimension changes
|
||||
*/
|
||||
stopFallbackDimensionMonitor() {
|
||||
clearImmediate(this.fallbackDimensionChangeInterval);
|
||||
this.fallbackDimensionChangeInterval = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialized player UI at a later time.
|
||||
* @param playerDimensions
|
||||
* @returns
|
||||
*/
|
||||
deferredUiInitialization(playerDimensions = this.dimensions) {
|
||||
if (this.ui || this.siteSettings.data.enableUI.fullscreen === ExtensionMode.Disabled) {
|
||||
return;
|
||||
}
|
||||
@ -325,6 +306,44 @@ 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
|
||||
@ -356,161 +375,12 @@ class PlayerData {
|
||||
this.runLevel = runLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
//#endregion
|
||||
|
||||
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.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
this._requestTick(true);
|
||||
},
|
||||
250, // do it once per this many ms
|
||||
{
|
||||
@ -519,6 +389,112 @@ 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
|
||||
/**
|
||||
@ -546,46 +522,13 @@ 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.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();
|
||||
}
|
||||
this.initFallbackDimensionMonitor();
|
||||
}
|
||||
|
||||
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,
|
||||
@ -617,19 +560,22 @@ class PlayerData {
|
||||
return this.elementStack;
|
||||
}
|
||||
|
||||
|
||||
updatePlayer(options?: {verbose?: boolean, newElement?: HTMLElement}) {
|
||||
const newPlayer = options?.newElement ?? this.getPlayer(options);
|
||||
|
||||
if (newPlayer === this.element) {
|
||||
if (newPlayer === this.element || !newPlayer) {
|
||||
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',
|
||||
@ -642,8 +588,7 @@ class PlayerData {
|
||||
}
|
||||
);
|
||||
|
||||
this.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
this.requestTick();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -695,10 +640,14 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private nextPlayerElement() {
|
||||
@ -713,8 +662,8 @@ class PlayerData {
|
||||
const elementStack = this.getElementStack();
|
||||
|
||||
if (
|
||||
this.equalish(elementStack[currentIndex].element.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
|
||||
&& this.equalish(elementStack[currentIndex].element.offsetHeight, elementStack[nextIndex].element.offsetHeight, 2)
|
||||
equalish(elementStack[currentIndex].element.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
|
||||
&& 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);
|
||||
@ -765,8 +714,9 @@ class PlayerData {
|
||||
// Don't bother thinking about this too much, as any "thinking" was quickly
|
||||
// corrected by bugs caused by various edge cases.
|
||||
if (
|
||||
this.equalish(elementData.height, videoHeight, 5)
|
||||
|| this.equalish(elementData.width, videoWidth, 5)
|
||||
|
||||
equalish(elementData.height, videoHeight, 5)
|
||||
|| equalish(elementData.width, videoWidth, 5)
|
||||
) {
|
||||
let score = 1000;
|
||||
|
||||
@ -877,14 +827,14 @@ class PlayerData {
|
||||
const allSelectors = document.querySelectorAll(queryString);
|
||||
|
||||
for (const element of elementStack) {
|
||||
if (this.collectionHas(allSelectors, element.element)) {
|
||||
if (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 && this.equalish(element.height, videoHeight, 2))
|
||||
|| (element.height >= videoHeight && this.equalish(element.width, videoWidth, 2))
|
||||
(element.width >= videoWidth && equalish(element.height, videoHeight, 2))
|
||||
|| (element.height >= videoHeight && equalish(element.width, videoWidth, 2))
|
||||
) {
|
||||
score += 75;
|
||||
}
|
||||
@ -917,8 +867,6 @@ 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))});
|
||||
}
|
||||
|
||||
@ -933,7 +881,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);
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ 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.
|
||||
@ -73,7 +74,7 @@ class VideoData {
|
||||
player: PlayerData;
|
||||
resizer: Resizer;
|
||||
|
||||
aard: Aard;
|
||||
aard: Aard | AardLegacy;
|
||||
|
||||
eventBus: EventBus;
|
||||
extensionStatus: ExtensionStatus;
|
||||
@ -259,7 +260,7 @@ class VideoData {
|
||||
this.resizer = new Resizer(this);
|
||||
|
||||
try {
|
||||
this.aard = new Aard(this); // this starts Ar detection. needs optional parameter that prevents ArDetector from starting
|
||||
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
|
||||
} catch (e) {
|
||||
console.error('Failed to start Aard!', e);
|
||||
}
|
||||
@ -702,7 +703,7 @@ class VideoData {
|
||||
return;
|
||||
}
|
||||
if (! this.aard){
|
||||
this.aard = new Aard(this);
|
||||
this.aard = this.settings.active.aard.useLegacy ? new AardLegacy(this): new Aard(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,8 @@ 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
|
||||
@ -332,6 +334,19 @@ 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
|
||||
@ -465,8 +480,12 @@ class Resizer {
|
||||
// this.videoData.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
|
||||
// }
|
||||
|
||||
let translate = this.computeOffsets(stretchFactors, options?.ar);
|
||||
try {
|
||||
const translate = this.computeOffsets(stretchFactors, options?.ar);
|
||||
this.applyCss(stretchFactors, translate);
|
||||
} catch (e) {
|
||||
// don't apply CSS if there's an error
|
||||
}
|
||||
}
|
||||
|
||||
toFixedAr() {
|
||||
@ -567,6 +586,10 @@ 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} );
|
||||
|
||||
@ -858,16 +881,15 @@ 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.`
|
||||
);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// request dimension change tick.
|
||||
// this will cause
|
||||
this.videoData.player.requestTick();
|
||||
throw 'DIMENSIONS_ERROR';
|
||||
}
|
||||
|
||||
return translate;
|
||||
|
||||
12
src/ext/util/comparators.ts
Normal file
12
src/ext/util/comparators.ts
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
@ -4,21 +4,29 @@
|
||||
Ultrawidify has been updated.
|
||||
</div>
|
||||
<div class="body flex-grow">
|
||||
<h1>Where do you want to use Ultrawidify?</h1>
|
||||
<h1>Where should Ultrawidify run by default</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>
|
||||
All websites<br/>
|
||||
<small>(Some sites are disabled by default.)</small>
|
||||
</div>
|
||||
<div>
|
||||
Default sites and sites I explicitly allow
|
||||
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>
|
||||
</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">
|
||||
|
||||
@ -1,86 +1,76 @@
|
||||
<template>
|
||||
<div class="flex flex-col h100">
|
||||
<div class="header flex-nogrow flex-noshrink">
|
||||
Thank you for installing Ultrawidify.
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
<div>
|
||||
Default sites and sites I explicitly allow
|
||||
</div>
|
||||
<div>
|
||||
Only the sites I explicitly allow
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<p>You can always change your settings later.</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer flex-nogrow flex-noshrink">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
import BrowserDetect from '@src/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 {
|
||||
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,
|
||||
AardSubtitleCropMode,
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
logAggregator: {},
|
||||
logger: {},
|
||||
siteTabDisabled: false,
|
||||
videoTabDisabled: false,
|
||||
canShowVideoTab: {canShow: true, warning: true},
|
||||
showWhatsNew: false,
|
||||
placeholderSubtitleCrop: AardSubtitleCropMode.ResetAR,
|
||||
settingsSaved: false
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@ -89,26 +79,36 @@ 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: {
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
async updateConfig() {
|
||||
await 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="../../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 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 lang="scss" scoped>
|
||||
html, body {
|
||||
p {
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 800px !important;
|
||||
max-width: 800px !important;
|
||||
padding: 0px;
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
<!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>
|
||||
@ -1,11 +0,0 @@
|
||||
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)
|
||||
})
|
||||
13
src/install/updated/updated.html
Normal file
13
src/install/updated/updated.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!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>
|
||||
13
src/install/updated/updated.js
Normal file
13
src/install/updated/updated.js
Normal file
@ -0,0 +1,13 @@
|
||||
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');
|
||||
@ -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.97",
|
||||
"version": "6.3.993",
|
||||
"icons": {
|
||||
"32":"res/icons/uw-32.png",
|
||||
"64":"res/icons/uw-64.png"
|
||||
@ -41,7 +41,8 @@
|
||||
"res/img/settings/about-bg.png",
|
||||
"res/icons/*",
|
||||
"res/img/*",
|
||||
"csui/*"
|
||||
"csui/*",
|
||||
"install/*"
|
||||
],
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
|
||||
@ -12,3 +12,12 @@ 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")
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -20,6 +20,7 @@ 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}`,
|
||||
@ -125,6 +126,7 @@ 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'},
|
||||
@ -138,6 +140,7 @@ 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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user