Re-add legacy AR detect
This commit is contained in:
parent
ddffbe92a0
commit
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.98",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.97",
|
||||
"version": "6.3.98",
|
||||
"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,7 +164,7 @@
|
||||
<div class="field">
|
||||
<div class="label">Subtitle detection:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.subtitles.subtitleCropMode" @change="settings.saveWithoutReload">
|
||||
<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>
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user