Compare commits

...

10 Commits

19 changed files with 482 additions and 84 deletions

2
package-lock.json generated
View File

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

View File

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

View File

@ -12,6 +12,13 @@ export enum ExtensionEnvironment {
Fullscreen = 'fullscreen', Fullscreen = 'fullscreen',
} }
export interface DevUiConfig {
aardDebugOverlay: {
showOnStartup: boolean,
showDetectionDetails: boolean,
}
}
export interface KeyboardShortcutInterface { export interface KeyboardShortcutInterface {
key?: string, key?: string,
code?: string, code?: string,
@ -161,11 +168,16 @@ export interface AardSettings {
} }
} }
interface DevSettings {
loadFromSnapshot: boolean,
}
interface SettingsInterface { interface SettingsInterface {
_updateFlags?: { _updateFlags?: {
requireReload?: SettingsReloadFlags, requireReload?: SettingsReloadFlags,
forSite?: string forSite?: string
} }
dev: DevSettings,
arDetect: AardSettings, arDetect: AardSettings,
@ -185,6 +197,7 @@ interface SettingsInterface {
}, },
}, },
devMode?: boolean, devMode?: boolean,
dev: DevUiConfig,
} }
restrictions?: RestrictionsSettings; restrictions?: RestrictionsSettings;

View File

@ -144,11 +144,13 @@
Site compatibility: Site compatibility:
<SupportLevelIndicator <SupportLevelIndicator
:siteSupportLevel="siteSupportLevel" :siteSupportLevel="siteSupportLevel"
supportLevelStyle="font-size: 0.69rem !important;"
tooltipStyle="font-size: 0.8rem;"
> >
</SupportLevelIndicator> </SupportLevelIndicator>
<div v-if="statusFlags.hasDrm" class="aard-blocked"> <div v-if="statusFlags.hasDrm" class="aard-blocked">
Autodetection potentially<br/> Autodetection blocked<br/>
unavailable due to <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>. by <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>.
</div> </div>
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked"> <div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
Autodetection blocked<br/> Autodetection blocked<br/>
@ -156,7 +158,7 @@
</div> </div>
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked"> <div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
Autodetection unavailable<br/> Autodetection unavailable<br/>
due to webgl error. (webgl error)
</div> </div>
</GhettoContextMenuItem> </GhettoContextMenuItem>
</div> </div>
@ -483,7 +485,8 @@ export default {
}, },
acknowledgeNewFeature(featureKey) { acknowledgeNewFeature(featureKey) {
delete this.settings.active.newFeatureTracker[featureKey]; this.settings.active.newFeatureTracker[featureKey].show = 0;
this.settings.active.newFeatureTracker[featureKey].acknowledged = true;
this.settings.saveWithoutReload(); this.settings.saveWithoutReload();
}, },
newFeatureViewUpdate(featureKey) { newFeatureViewUpdate(featureKey) {
@ -721,6 +724,7 @@ export default {
} }
.aard-blocked { .aard-blocked {
font-size: 0.8rem;
color: #fa6; color: #fa6;
} }

View File

@ -10,7 +10,7 @@
<div class="w-[1/2]" style="width: 50%"> <div class="w-[1/2]" style="width: 50%">
<h2>Report a problem</h2> <h2>Report a problem</h2>
<p> <p>
You may report <strike>undocumented features</strike> bugs using one of the following options (in order of preference): Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
</p> </p>
<ul> <ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li> <li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>

View File

@ -269,8 +269,27 @@
<p> <p>
<b>Debug options</b> <b>Debug options</b>
</p> </p>
<div> <div class="flex flex-row">
<button @click="eventBus.sendToTunnel('aard-enable-debug', true)">Show debug overlay</button> <div>
<div>
<button @click="eventBus.sendToTunnel('aard-enable-debug', true)">Show debug overlay</button>
</div>
<div>
<div class="label">Show debug overlay on startup</div>
<input
type="checkbox"
v-model="settings.active.ui.dev.aardDebugOverlay.showOnStartup"
@change="settings.saveWithoutReload"
>
</div>
</div>
<div>
<JsonEditor
v-model="settingsJson"
>
</JsonEditor>
<button @click="saveDebugUiSettings">Save debug UI settings</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -289,14 +308,15 @@ import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import StretchType from '../../../common/enums/StretchType.enum'; import StretchType from '../../../common/enums/StretchType.enum';
import CropModePersistence from '../../../common/enums/CropModePersistence.enum'; import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue'; import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
import JsonEditor from '@csui/src/components/JsonEditor';
export default { export default {
data() { components: {
return { ShortcutButton,
exec: null, EditShortcutButton,
performanceData: {}, Button,
graphRefreshInterval: undefined, AlignmentOptionsControlComponent,
} JsonEditor
}, },
mixins: [ mixins: [
], ],
@ -306,6 +326,16 @@ export default {
'eventBus', 'eventBus',
'site' 'site'
], ],
data() {
return {
exec: null,
performanceData: {},
graphRefreshInterval: undefined,
settingsJson: {},
}
},
computed: {
},
created() { created() {
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-config-broadcast', 'uw-config-broadcast',
@ -315,24 +345,15 @@ export default {
} }
); );
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
mounted() { mounted() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');
this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500); this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
this.resetSettingsEditor();
}, },
destroyed() { destroyed() {
this.eventBus.unsubscribeAll(this);
clearInterval(this.graphRefreshInterval); clearInterval(this.graphRefreshInterval);
}, },
components: {
ShortcutButton,
EditShortcutButton,
Button,
AlignmentOptionsControlComponent
},
computed: {
},
methods: { methods: {
async openOptionsPage() { async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage(); BrowserDetect.runtime.openOptionsPage();
@ -349,7 +370,15 @@ export default {
this.$nextTick( () => this.$forceUpdate() ); this.$nextTick( () => this.$forceUpdate() );
} }
}, },
} resetSettingsEditor() {
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active.ui.dev.aardDebugOverlay ?? {}));
},
saveDebugUiSettings() {
this.settings.active.ui.dev.aardDebugOverlay = JSON.parse(JSON.stringify(this.settingsJson));
this.settings.saveWithoutReload();
}
},
} }
</script> </script>

View File

@ -113,7 +113,7 @@
class="danger" class="danger"
:class="{'disabled': !allowSettingsEditing}" :class="{'disabled': !allowSettingsEditing}"
:disabled="!allowSettingsEditing" :disabled="!allowSettingsEditing"
@click="saveSettingsChanges" @click="() => saveSettingsChanges()"
> >
Save Save
</button> </button>
@ -129,6 +129,31 @@
> >
</JsonEditor> </JsonEditor>
</div> </div>
<h2>Settings migration report</h2>
<pre>
{{settings.migrationReport}}
</pre>
<h2>Settings snapshots</h2>
<div class="flex flex-col">
<div v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt">
<small>{{snapshot.createdAt.toISOString()}}</small>
<div class="flex flex-row">
<div class="grow">
{{snapshot.name}}
</div>
<div v-if="settings.isAutomatic">(auto)</div>
<div v-if="settings.isAutomatic && settings.isProtected">(protected)</div>
<div v-if="settings.default">(default)</div>
</div>
<div>
<button @click="() => markDefaultSnapshot(index)"><template v-if="settings.isDefault">Revoke default</template><template v-else>Make default</template></button>
<button v-if="settings.isAutomatic" @click="() => toggleSnapshotProtection(index)">Toggle protection</button>
<button @click="() => deleteSnapshot(index)">Delete snapshot</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -151,6 +176,7 @@ export default {
allowSettingsEditing: false, allowSettingsEditing: false,
editorSaveFinished: false, editorSaveFinished: false,
settingsJson: {}, settingsJson: {},
settingsSnapshots: []
} }
}, },
mixins: [ mixins: [
@ -236,10 +262,18 @@ export default {
this.resetSettingsEditor(); this.resetSettingsEditor();
}, },
saveSettingsChanges() { async saveSettingsChanges() {
if (this.allowSettingsEditing) { if (this.allowSettingsEditing) {
const currentVersion = this.settings.active?.version;
this.settings.active = this.settingsJson; this.settings.active = this.settingsJson;
this.settings.saveWithoutReload();
if (currentVersion !== this.settingsJson.version) {
await this.settings.save({forcePreserveVersion: true});
return;
} else {
await this.settings.saveWithoutReload();
}
this.resetSettingsEditor(); this.resetSettingsEditor();
this.editorSaveFinished = true; this.editorSaveFinished = true;
@ -251,10 +285,32 @@ export default {
resetSettingsEditor() { resetSettingsEditor() {
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active ?? {})); this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active ?? {}));
} },
//#endregion //#endregion
}
//#region settings snapshot management
async loadSettingsSnapshots() {
this.settingsSnapshots = await this.settings.snapshotManager.listSnapshots();
},
async markDefaultSnapshot(index) {
await this.settings.snapshotManager.setDefaultSnapshot(index, !this.settingsSnapshots[index].isDefault);
await this.loadSettingsSnapshots();
this.settings.active.dev.loadFromSnapshot = this.settingsSnapshots[index].isDefault;
this.saveSettingsChanges();
},
async toggleSnapshotProtection(index) {
await this.settings.snapshotManager.setSnapshotAsProtected(index, !this.settingsSnapshots[index].isProtected);
await this.loadSettingsSnapshots();
},
async deleteSnapshot(index) {
await this.settings.snapshotManager.deleteSnapshot(index);
await this.loadSettingsSnapshots();
},
}
//#endregion
} }
</script> </script>

View File

@ -1,23 +1,33 @@
<template> <template>
<div class="flex flex-col w-full h-full gap-2"> <div class="flex flex-col w-full h-full gap-2">
<div class="flex flex-row gap-8 bg-black flex-wrap"> <div class="flex flex-row gap-8 bg-black flex-wrap w-full">
<div class="min-w-[400px] max-w-[520px] grow-1 shrink-1"> <div class="min-w-[400px] max-w-[520px] grow shrink">
<h1>What's new</h1> <h1>What's new</h1>
<!-- <p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p> --> <!-- <p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p> -->
<h2>6.2.5</h2> <h2>6.2.6</h2>
<ul> <ul>
<li>'Show UI' button was moved to popup header. Extension popup now defaults to 'crop options' tab</li> <li>Automatic aspect ratio detection: do not apply negative aspect ratios</li>
<li>Fixed the bug where current extension settings wouldn't be displayed correctly in the popup</li> <li>Keyboard zoom now works</li>
<li>Fixed the issue where extension options using the "Extension default" mode would always be disabled</li> <li><code>www.youtube-nocookie.com</code> has been added to the "officially supported" list</li>
<li>Added the ability to import and export settings (ft. developer mode editor)</li> <li>Fixed the bug where UI would sometimes refuse to stay hidden</li>
<li>Added some toys not intended for general audience (Aard now has debug tools).</li>
<li>Fixed an issue where aspect ratio wouldn't get calculated correctly on youtube videos with native aspect ratios other than 16:9</li>
<li>Fixed an issue that would crash the extension if video element didn't have a player element associated with it</li>
<li>Fixed an issue where extension sometimes wouldn't work if video element was grafted/re-parented to a different element</li>
</ul> </ul>
</div> </div>
<div class="min-w-[400px] max-w-[520px] grow-1 shrink-1"> <div style="width: 1rem; height: 0px;"></div>
<div class="min-w-[400px] max-w-[520px] grow shrink">
<h2>Report a problem</h2>
<p>
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
</p>
<ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
</ul>
<p>
When reporting bugs, please include extension version, whether you installed the extension from, and description of your problem.
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Thank you monies</h2> <h2>Thank you monies</h2>
<p> <p>
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction. If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
@ -30,11 +40,23 @@
</div> </div>
</template> </template>
<script> <script>
import BrowserDetect from '@src/ext/conf/BrowserDetect';
export default({ export default({
props: [ props: [
'settings' 'settings'
], ],
data() {
return {
BrowserDetect: BrowserDetect,
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
addonSource: BrowserDetect.processEnvVersion,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
}
},
mounted() { mounted() {
this.settings.active.whatsNewChecked = true; this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload(); this.settings.saveWithoutReload();
@ -49,6 +71,10 @@ export default({
flex-direction: row; flex-direction: row;
} }
.grow {
flex-grow: 1;
}
p, li { p, li {
font-size: 1rem; font-size: 1rem;
} }

View File

@ -1,44 +1,44 @@
<template> <template>
<div v-if="siteSupportLevel === 'official'" class="site-support official"> <div v-if="siteSupportLevel === 'official'" class="site-support official" :style="supportLevelStyle">
<mdicon name="check-decagram" /> <mdicon name="check-decagram" />
<div v-if="!small">Verified</div> <div v-if="!small">Verified</div>
<div class="tooltip"> <div class="tooltip" :style="tooltipStyle">
<template v-if="small">Verified&nbsp;&nbsp;</template> <template v-if="small">Verified&nbsp;&nbsp;</template>
The extension is being tested and should work on this site. The extension is being tested and should work on this site.
</div> </div>
</div> </div>
<div v-if="siteSupportLevel === 'community'" class="site-support community"> <div v-if="siteSupportLevel === 'community'" class="site-support community" :style="supportLevelStyle">
<mdicon name="account-group" /> <mdicon name="account-group" />
<div v-if="!small">Community</div> <div v-if="!small">Community</div>
<div class="tooltip"> <div class="tooltip" :style="tooltipStyle">
<template v-if="small">Community&nbsp;&nbsp;</template> <template v-if="small">Community&nbsp;&nbsp;</template>
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/> People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
is geoblocked. is geoblocked.
</div> </div>
</div> </div>
<div v-if="siteSupportLevel === 'no-support' || siteSupportLevel === 'unknown'" class="site-support no-support"> <div v-if="siteSupportLevel === 'no-support' || siteSupportLevel === 'unknown'" class="site-support no-support" :style="supportLevelStyle">
<mdicon name="help-circle-outline" /> <mdicon name="help-circle-outline" />
<div v-if="!small">Unknown</div> <div v-if="!small">Unknown</div>
<div class="tooltip"> <div class="tooltip" :style="tooltipStyle">
<template v-if="small">Unknown&nbsp;&nbsp;</template> <template v-if="small">Unknown&nbsp;&nbsp;</template>
Not officially supported. Extension will try to fix things, but no promises.<br/><br/> Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
Tamius (the dev) does not test the extension on this site for various reasons Tamius (the dev) does not test the extension on this site for various reasons
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use). (unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
</div> </div>
</div> </div>
<div v-if="siteSupportLevel === 'user-added' || siteSupportLevel === 'user-defined'" class="site-support user-added"> <div v-if="siteSupportLevel === 'user-added' || siteSupportLevel === 'user-defined'" class="site-support user-added" :style="supportLevelStyle">
<mdicon name="account" /> <mdicon name="account" />
<div v-if="!small">Modified by you</div> <div v-if="!small">Modified by you</div>
<div class="tooltip"> <div class="tooltip" :style="tooltipStyle">
<template v-if="small">Modified by you&nbsp;&nbsp;</template> <template v-if="small">Modified by you&nbsp;&nbsp;</template>
You have manually changed settings for this site. The extension is doing what you told it to do. You have manually changed settings for this site. The extension is doing what you told it to do.
</div> </div>
</div> </div>
<div v-if="siteSupportLevel === 'officially-disabled'" class="site-support officially-disabled"> <div v-if="siteSupportLevel === 'officially-disabled'" class="site-support officially-disabled" :style="supportLevelStyle">
<mdicon class="site-support no-support" name="checkbox-marked-circle" /> <mdicon class="site-support no-support" name="checkbox-marked-circle" />
<div v-if="!small">Not supported</div> <div v-if="!small">Not supported</div>
<div class="tooltip"> <div class="tooltip" :style="tooltipStyle">
<template v-if="small">Not supported&nbsp;&nbsp;</template> <template v-if="small">Not supported&nbsp;&nbsp;</template>
Extension is known to not work with this site. Extension is known to not work with this site.
</div> </div>
@ -50,6 +50,8 @@ export default {
props: { props: {
siteSupportLevel: String, siteSupportLevel: String,
small: Boolean, small: Boolean,
supportLevelStyle: String,
tooltipStyle: String,
} }
} }
</script> </script>

View File

@ -21,7 +21,6 @@ const ExtensionConfPatch = [
normal: ExtensionMode.Default, normal: ExtensionMode.Default,
} }
} }
const uiEnabled =
userOptions.sites['@global'].enableUI = { userOptions.sites['@global'].enableUI = {
fullscreen: userOptions.ui.inPlayer.enabled ? ExtensionMode.Enabled : ExtensionMode.Disabled, fullscreen: userOptions.ui.inPlayer.enabled ? ExtensionMode.Enabled : ExtensionMode.Disabled,
theater: ExtensionMode.Enabled, theater: ExtensionMode.Enabled,
@ -83,6 +82,16 @@ const ExtensionConfPatch = [
}]; }];
delete (userOptions as any).actions; delete (userOptions as any).actions;
userOptions.dev = {
loadFromSnapshot: false
};
userOptions.ui.dev = {
aardDebugOverlay: {
showOnStartup: false,
showDetectionDetails: true
}
}
} }
} }
]; ];

View File

@ -14,6 +14,10 @@ if(Debug.debug)
console.log("Loading: ExtensionConf.js"); console.log("Loading: ExtensionConf.js");
const ExtensionConf: SettingsInterface = { const ExtensionConf: SettingsInterface = {
dev: {
loadFromSnapshot: false,
},
arDetect: { arDetect: {
aardType: 'auto', aardType: 'auto',
@ -127,6 +131,12 @@ const ExtensionConf: SettingsInterface = {
offsetX: -50, offsetX: -50,
offsetY: 0 offsetY: 0
} }
},
dev: {
aardDebugOverlay: {
showOnStartup: false,
showDetectionDetails: true
}
} }
}, },
@ -722,6 +732,46 @@ const ExtensionConf: SettingsInterface = {
} }
} }
}, },
"www.youtube-nocookie.com": {
enable: {
fullscreen: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
normal: ExtensionMode.Enabled,
},
enableAard: {
fullscreen: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
normal: ExtensionMode.Enabled,
},
enableKeyboard: {
fullscreen: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
normal: ExtensionMode.Enabled
},
enableUI: {
fullscreen: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
normal: ExtensionMode.Disabled
},
override: false, // ignore value localStorage in favour of this
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined')
defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'.
// We still want to know what the original type was, hence defaultType
activeDOMConfig: 'official',
DOMConfig: {
'official': {
type: 'official',
elements: {
player: {
manual: true,
querySelectors: "#movie_player, #player, #c4-player",
}
}
}
}
},
"www.netflix.com" : { "www.netflix.com" : {
enable: { enable: {
fullscreen: ExtensionMode.Enabled, fullscreen: ExtensionMode.Enabled,

View File

@ -12,11 +12,15 @@ import Logger from './Logger';
import SettingsInterface from '../../common/interfaces/SettingsInterface'; import SettingsInterface from '../../common/interfaces/SettingsInterface';
import AspectRatioType from '../../common/enums/AspectRatioType.enum'; import AspectRatioType from '../../common/enums/AspectRatioType.enum';
import { SiteSettings } from './settings/SiteSettings'; import { SiteSettings } from './settings/SiteSettings';
import { SettingsSnapshotManager } from './settings/SettingsSnapshotManager';
if(process.env.CHANNEL !== 'stable'){ if(process.env.CHANNEL !== 'stable'){
console.info("Loading Settings"); console.info("Loading Settings");
} }
interface SetSettingsOptions {
forcePreserveVersion?: boolean,
}
class Settings { class Settings {
//#region flags //#region flags
@ -41,6 +45,17 @@ class Settings {
afterSettingsChangedCallbacks: (() => void)[] = []; afterSettingsChangedCallbacks: (() => void)[] = [];
private sortedPatches: any[]; private sortedPatches: any[];
public snapshotManager: SettingsSnapshotManager;
private _migrationReport: string = '';
private set migrationReport(report: string) {
this._migrationReport = report;
}
public get migrationReport(): string {
return this._migrationReport;
}
//#endregion //#endregion
constructor(options) { constructor(options) {
@ -50,11 +65,14 @@ class Settings {
this.afterSettingsSaved = options?.afterSettingsSaved; this.afterSettingsSaved = options?.afterSettingsSaved;
this.active = options?.activeSettings ?? undefined; this.active = options?.activeSettings ?? undefined;
this.default = ExtensionConf; this.default = ExtensionConf;
this.snapshotManager = new SettingsSnapshotManager();
this.default['version'] = this.getExtensionVersion(); this.default['version'] = this.getExtensionVersion();
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)}); chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
this.sortedPatches = this.sortConfPatches(ExtensionConfPatch); this.sortedPatches = this.sortConfPatches(ExtensionConfPatch);
} }
private storageChangeListener(changes, area) { private storageChangeListener(changes, area) {
@ -186,6 +204,18 @@ class Settings {
return; return;
} }
// save current settings object
const currentSettings = this.active;
this.snapshotManager.createSnapshot(
JSON.parse(JSON.stringify(currentSettings)),
{
label: 'Pre-migration snapshot',
isAutomatic: true
}
);
// apply all remaining patches // apply all remaining patches
this.logger?.log('info', 'settings', `[Settings::applySettingsPatches] There are ${this.sortedPatches.length - index} settings patches to apply`); this.logger?.log('info', 'settings', `[Settings::applySettingsPatches] There are ${this.sortedPatches.length - index} settings patches to apply`);
while (index < this.sortedPatches.length) { while (index < this.sortedPatches.length) {
@ -209,7 +239,16 @@ class Settings {
} }
async init() { async init() {
const settings = await this.get(); let settings = await this.get();
if (settings?.dev?.loadFromSnapshot) {
this.logger?.log('info', 'settings', '[Settings::init] Dev mode is enabled, Loading settings from snapshot:', settings.dev.loadFromSnapshot);
const snapshot = await this.snapshotManager.getSnapshot();
if (snapshot) {
settings = snapshot.settings;
}
}
this.version = this.getExtensionVersion(); this.version = this.getExtensionVersion();
// |—> on first setup, settings is undefined & settings.version is haram // |—> on first setup, settings is undefined & settings.version is haram
@ -282,7 +321,7 @@ class Settings {
return this.active; return this.active;
} }
async get() { async get(): Promise<SettingsInterface | undefined> {
let ret; let ret;
ret = await chrome.storage.local.get('uwSettings'); ret = await chrome.storage.local.get('uwSettings');
@ -290,13 +329,13 @@ class Settings {
this.logger?.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings)); this.logger?.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings));
try { try {
return JSON.parse(ret.uwSettings); return JSON.parse(ret.uwSettings) as SettingsInterface;
} catch(e) { } catch(e) {
return undefined; return undefined;
} }
} }
async set(extensionConf, options?) { async set(extensionConf, options?: SetSettingsOptions) {
if (!options || !options.forcePreserveVersion) { if (!options || !options.forcePreserveVersion) {
extensionConf.version = this.version; extensionConf.version = this.version;
} }
@ -344,7 +383,7 @@ class Settings {
} }
} }
async save(options?) { async save(options?: SetSettingsOptions) {
if (Debug.debug && Debug.storage) { if (Debug.debug && Debug.storage) {
console.log("[Settings::save] Saving active settings:", this.active); console.log("[Settings::save] Saving active settings:", this.active);
} }
@ -354,10 +393,10 @@ class Settings {
} }
async saveWithoutReload() { async saveWithoutReload(options?: SetSettingsOptions) {
this.active.preventReload = true; this.active.preventReload = true;
this.active.lastModified = new Date(); this.active.lastModified = new Date();
await this.set(this.active); await this.set(this.active, options);
} }
async rollback() { async rollback() {

View File

@ -219,7 +219,6 @@ import { AardTimers, initAardTimers } from './interfaces/aard-timers.interface';
* *
*/ */
export class Aard { export class Aard {
//#region configuration parameters //#region configuration parameters
private logger: Logger; private logger: Logger;
private videoData: VideoData; private videoData: VideoData;
@ -269,6 +268,7 @@ export class Aard {
private debugConfig: any = {}; private debugConfig: any = {};
private timer: AardTimer; private timer: AardTimer;
private lastAnimationFrameTime: number = Infinity;
//#endregion //#endregion
//#region getters //#region getters
@ -304,8 +304,7 @@ export class Aard {
// we can tick manually, for debugging // we can tick manually, for debugging
this.logger.log('info', 'init', `[ArDetector::ctor] creating new ArDetector. arid: ${this.arid}`); this.logger.log('info', 'init', `[ArDetector::ctor] creating new ArDetector. arid: ${this.arid}`);
this.timer = new AardTimer() this.timer = new AardTimer();
this.init(); this.init();
} }
@ -337,6 +336,14 @@ export class Aard {
// console.error('FALIED TO CREATE DEBUGG CANVAS', e); // console.error('FALIED TO CREATE DEBUGG CANVAS', e);
// } // }
try {
if (this.settings.active.ui.dev?.aardDebugOverlay?.showOnStartup) {
this.showDebugCanvas();
}
} catch (e) {
console.error(`[uw::aard] failed to create debug UI:`, e);
}
this.startCheck(); this.startCheck();
} }
@ -407,6 +414,7 @@ export class Aard {
* Checks whether autodetection can run * Checks whether autodetection can run
*/ */
startCheck() { startCheck() {
console.log('aard - starting checks')
if (!this.videoData.player) { if (!this.videoData.player) {
console.warn('Player not detected!'); console.warn('Player not detected!');
console.log('--- video data: ---\n', this.videoData); console.log('--- video data: ---\n', this.videoData);
@ -453,7 +461,6 @@ export class Aard {
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect); this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
} }
this.main(); this.main();
} }
@ -648,7 +655,7 @@ export class Aard {
do { do {
if (this.testResults.notLetterbox) { if (this.testResults.notLetterbox) {
// console.log('————not letterbox') // console.log('————not letterbox')
console.warn('DETECTED NOT LETTERBOX! (resetting)') // console.warn('DETECTED NOT LETTERBOX! (resetting)')
this.timer.arChanged(); this.timer.arChanged();
this.updateAspectRatio(this.defaultAr); this.updateAspectRatio(this.defaultAr);
break; break;
@ -659,7 +666,7 @@ export class Aard {
// console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason); // console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason);
// console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'); // console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
console.warn('ASPECT RATIO UNCERTAIN, GUARD LINE INVALIDATED (resetting)') // console.warn('ASPECT RATIO UNCERTAIN, GUARD LINE INVALIDATED (resetting)')
this.timer.arChanged(); this.timer.arChanged();
this.updateAspectRatio(this.defaultAr); this.updateAspectRatio(this.defaultAr);
@ -669,18 +676,24 @@ export class Aard {
// TODO: emit debug values if debugging is enabled // TODO: emit debug values if debugging is enabled
this.testResults.isFinished = true; this.testResults.isFinished = true;
console.warn( // console.warn(
`[${(+new Date() % 10000) / 100} | ${this.arid}]`,'check finished — aspect ratio updated:', this.testResults.aspectRatioUpdated, // `[${(+new Date() % 10000) / 100} | ${this.arid}]`,'check finished — aspect ratio updated:', this.testResults.aspectRatioUpdated,
'\ndetected ar:', this.testResults.activeAspectRatio, '->', this.getAr(), // '\ndetected ar:', this.testResults.activeAspectRatio, '->', this.getAr(),
'\nis video playing?', this.getVideoPlaybackState() === VideoPlaybackState.Playing, // '\nis video playing?', this.getVideoPlaybackState() === VideoPlaybackState.Playing,
'\n\n', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'); // '\n\n', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
// if edge width changed, emit update event. // if edge width changed, emit update event.
// except aspectRatioUpdated doesn't get set reliably, so we just call update every time, and update // except aspectRatioUpdated doesn't get set reliably, so we just call update every time, and update
// if detected aspect ratio is different from the current aspect ratio // if detected aspect ratio is different from the current aspect ratio
// if (this.testResults.aspectRatioUpdated) { // if (this.testResults.aspectRatioUpdated) {
// this.timer.arChanged(); // this.timer.arChanged();
this.updateAspectRatio(); const finalAr = this.getAr();
if (finalAr > 0) {
this.updateAspectRatio(finalAr);
} else {
this.testResults.aspectRatioInvalid = true;
this.testResults.aspectRatioInvalidReason = finalAr.toFixed(3);
}
// } // }
// if we got "no letterbox" OR aspectRatioUpdated // if we got "no letterbox" OR aspectRatioUpdated

View File

@ -7,9 +7,15 @@ export class AardDebugUi {
uiAnchorElement: HTMLDivElement; uiAnchorElement: HTMLDivElement;
pauseOnArCheck: boolean = false; pauseOnArCheck: boolean = false;
uiVisibility: any = {};
constructor(aard: any) { constructor(aard: any) {
this.aard = aard; this.aard = aard;
this.uiVisibility = {
detectionDetails: aard.settings.active.ui.dev.aardDebugOverlay.showDetectionDetails
};
(window as any).ultrawidify_uw_aard_debug_tools = { (window as any).ultrawidify_uw_aard_debug_tools = {
enableStopOnChange: () => this.changePauseOnCheck(true), enableStopOnChange: () => this.changePauseOnCheck(true),
disableStopOnChange: () => this.changePauseOnCheck(false), disableStopOnChange: () => this.changePauseOnCheck(false),
@ -26,7 +32,13 @@ export class AardDebugUi {
position: fixed; top: 0; left: 0; width: 100vw; height: 100dvh; display: flex; flex-direction: column; pointer-events: none; z-index: 9999; font-size: 16px; font-family: 'Overpass Mono', monospace; position: fixed; top: 0; left: 0; width: 100vw; height: 100dvh; display: flex; flex-direction: column; pointer-events: none; z-index: 9999; font-size: 16px; font-family: 'Overpass Mono', monospace;
"> ">
<div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between; backdrop-filter: blur(0.5rem) brightness(0.5);"> <div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between; backdrop-filter: blur(0.5rem) brightness(0.5);">
<div style="padding: 1rem; color: #fff"><h1>Aard debug overlay</h1></div> <div style="padding: 1rem; color: #fff">
<h1>Aard debug overlay</h1>
</div>
<div style="pointer-events: all; display: flex; flex-direction: column; margin-right: 1rem;">
<button id="uw-aard-debug_show-detection-details">Show det. details</button>
<button id="uw-aard-debug_hide-detection-details">Hide det. details</button>
</div>
<style> <style>
#uw-aard-debug_performance-container #uw-aard-debug_performance-popup { #uw-aard-debug_performance-container #uw-aard-debug_performance-popup {
@ -62,7 +74,9 @@ export class AardDebugUi {
<button id="uw-aard-debug-ui_close-overlay">Close overlay</button> <button id="uw-aard-debug-ui_close-overlay">Close overlay</button>
</div> </div>
</div> </div>
<div style="display: flex; flex-direction: row; width: 100%">
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%">
<div style=""> <div style="">
<div id="uw-aard-debug_aard-sample-canvas" style="min-width: 640px"></div> <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: #fff"; font-size: 24px;">AARD IN</div>
@ -129,6 +143,10 @@ export class AardDebugUi {
document.getElementById('uw-aard-debug-ui_enable-step').onclick = () => this.aard.step(); document.getElementById('uw-aard-debug-ui_enable-step').onclick = () => this.aard.step();
document.getElementById('uw-aard-debug-ui_enable-step-nocache').onclick = () => this.aard.step({noCache: true}); document.getElementById('uw-aard-debug-ui_enable-step-nocache').onclick = () => this.aard.step({noCache: true});
document.getElementById('uw-aard-debug-ui_close-overlay').onclick = () => (this.aard as any).hideDebugCanvas(); document.getElementById('uw-aard-debug-ui_close-overlay').onclick = () => (this.aard as any).hideDebugCanvas();
document.getElementById('uw-aard-debug_show-detection-details').onclick = () => {this.uiVisibility.detectionDetails = true; this.setOverlayVisibility();};
document.getElementById('uw-aard-debug_hide-detection-details').onclick = () => {this.uiVisibility.detectionDetails = false; this.setOverlayVisibility();};
this.setOverlayVisibility();
} }
changePauseOnCheck(pauseOnChange: boolean) { changePauseOnCheck(pauseOnChange: boolean) {
@ -352,7 +370,8 @@ export class AardDebugUi {
out = `${out} out = `${out}
-- UNCERTAIN FLAGS -- UNCERTAIN FLAGS
AR: ${testResults.aspectRatioUncertain} (reason: ${testResults.aspectRatioUncertainReason ?? 'n/a'}); top row: ${testResults.topRowUncertain}; bottom row: ${testResults.bottomRowUncertain} 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 -- GUARD & IMAGE LINE
bottom guard: ${testResults.guardLine.bottom} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.bottom} bottom guard: ${testResults.guardLine.bottom} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.bottom}
@ -388,5 +407,10 @@ export class AardDebugUi {
resultsDiv.textContent = out; resultsDiv.textContent = out;
} }
private setOverlayVisibility() {
document.getElementById('uw-aard-debug-ui_body').style.display = this.uiVisibility.detectionDetails ? 'flex' : 'none';
document.getElementById('uw-aard-debug_hide-detection-details').style.display = this.uiVisibility.detectionDetails ? '' : 'none';
document.getElementById('uw-aard-debug_show-detection-details').style.display = this.uiVisibility.detectionDetails ? 'none' : '';
}
} }

View File

@ -39,7 +39,9 @@ export interface AardTestResults {
letterboxWidth: number, letterboxWidth: number,
letterboxOffset: number, letterboxOffset: number,
logoDetected: [boolean, boolean, boolean, boolean] logoDetected: [boolean, boolean, boolean, boolean]
aspectRatioInvalid: boolean
aspectRatioUncertainReason?: string aspectRatioUncertainReason?: string
aspectRatioInvalidReason?: string
} }
export function initAardTestResults(settings: AardSettings): AardTestResults { export function initAardTestResults(settings: AardSettings): AardTestResults {
@ -81,7 +83,8 @@ export function initAardTestResults(settings: AardSettings): AardTestResults {
activeAspectRatio: 0, activeAspectRatio: 0,
letterboxWidth: 0, letterboxWidth: 0,
letterboxOffset: 0, letterboxOffset: 0,
logoDetected: [false, false, false, false] logoDetected: [false, false, false, false],
aspectRatioInvalid: false,
} }
} }
@ -120,4 +123,5 @@ export function resetAardTestResults(results: AardTestResults): void {
results.aspectRatioUncertainReason = null; results.aspectRatioUncertainReason = null;
results.topRowUncertain = false; results.topRowUncertain = false;
results.bottomRowUncertain = false; results.bottomRowUncertain = false;
results.aspectRatioInvalid = false;
} }

View File

@ -0,0 +1,106 @@
import { settings } from 'cluster'
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
export interface SettingsSnapshot {
isAutomatic?: boolean;
isProtected?: boolean;
isDefault?: boolean;
forVersion: string;
label: string;
settings: SettingsInterface;
createdAt: Date;
}
export interface SettingsSnapshotOptions {
isAutomatic?: boolean,
isProtected?: boolean,
isDefault?: boolean,
label?: string,
forVersion?: string
}
export class SettingsSnapshotManager {
private MAX_AUTOMATIC_SNAPSHOTS = 5;
async getSnapshot(index?: number) {
const snapshots = await this.listSnapshots();
if (!index) {
return snapshots.find(x => x.isDefault);
} else {
if (index < 0 || index >= snapshots.length) {
throw new Error('Invalid index');
}
return snapshots[index];
}
}
async createSnapshot(settings: SettingsInterface, options?: SettingsSnapshotOptions) {
const snapshot = {
...options,
label: options.label ?? 'Automatic snapshot',
forVersion: options.forVersion || settings.version,
settings: JSON.parse(JSON.stringify(settings)),
createdAt: new Date(),
};
const snapshots = await this.listSnapshots();
const automaticSnapshots = snapshots.filter((s) => s.isAutomatic && !s.isProtected);
if (options.isAutomatic && automaticSnapshots.length >= this.MAX_AUTOMATIC_SNAPSHOTS) {
const firstAutomaticIndex = snapshots.findIndex((s) => s.isAutomatic && !s.isProtected);
snapshots.splice(firstAutomaticIndex, 1);
}
snapshots.push(snapshot);
this.set(snapshots);
}
async setDefaultSnapshot(index: number, isDefault: boolean) {
const snapshots = await this.listSnapshots();
if (index < 0 || index >= snapshots.length) {
throw new Error('Invalid index');
}
if (isDefault) {
for (const snapshot of snapshots) {
snapshot.isDefault = false;
}
}
snapshots[index].isDefault = isDefault;
this.set(snapshots);
}
async markSnapshotAsProtected(index: number, isProtected: boolean) {
const snapshots = await this.listSnapshots();
if (index < 0 || index >= snapshots.length) {
throw new Error('Invalid index');
}
snapshots[index].isProtected = isProtected;
this.set(snapshots);
}
async deleteSnapshot(index: number) {
const snapshots = await this.listSnapshots();
if (index < 0 || index >= snapshots.length) {
throw new Error('Invalid index');
}
snapshots.splice(index, 1);
this.set(snapshots);
}
async listSnapshots(): Promise<SettingsSnapshot[]> {
const ret = await chrome.storage.local.get('uwSettings-snapshots');
try {
JSON.parse(ret['uwSettings-snapshots']) as SettingsSnapshot[];
} catch (e) {
return [] as SettingsSnapshot[];
}
}
private async set(snapshots: SettingsSnapshot[]) {
await chrome.storage.local.set({
'uwSettings-snapshots': JSON.stringify(snapshots),
});
}
}

View File

@ -1,3 +1,4 @@
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import { EventBusConnector } from '../EventBus'; import { EventBusConnector } from '../EventBus';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
@ -29,6 +30,7 @@ class UI {
this.saveState = undefined; this.saveState = undefined;
this.playerData = uiConfig.playerData; this.playerData = uiConfig.playerData;
this.uiSettings = uiConfig.uiSettings; this.uiSettings = uiConfig.uiSettings;
this.siteSettings = uiConfig.siteSettings;
this.iframeErrorCount = 0; this.iframeErrorCount = 0;
this.iframeConfirmed = false; this.iframeConfirmed = false;
@ -43,10 +45,28 @@ class UI {
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
// UI will be initialized when setUiVisibility is called // UI will be initialized when setUiVisibility is called
console.log('ui config:', uiConfig);
this.init(); this.init();
} }
canRun() {
if (this.isGlobal) {
return true;
}
return this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled
|| this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled
|| this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled;
}
async init() { async init() {
if (!this.canRun()) {
console.log('ui config: canRun returned false', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
return;
}
console.log('ui config: canRun returned truie', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
this.initUIContainer(); this.initUIContainer();
this.loadIframe(); this.loadIframe();
this.initMessaging(); this.initMessaging();
@ -315,6 +335,10 @@ class UI {
canShowUI: false, canShowUI: false,
} }
if (this.playerData?.environment && this.siteSettings.data.enableUI[this.playerData?.environment] !== ExtensionMode.Enabled) {
return result;
}
if (this.playerData?.dimensions) { if (this.playerData?.dimensions) {
result.playerDimensions = this.playerData.dimensions; result.playerDimensions = this.playerData.dimensions;
} }

View File

@ -291,7 +291,8 @@ class PlayerData {
parentElement: this.element, parentElement: this.element,
eventBus: this.eventBus, eventBus: this.eventBus,
playerData: this, playerData: this,
uiSettings: this.videoData.settings.active.ui uiSettings: this.videoData.settings.active.ui,
siteSettings: this.siteSettings,
} }
); );
@ -615,7 +616,8 @@ class PlayerData {
parentElement: this.element, parentElement: this.element,
eventBus: this.eventBus, eventBus: this.eventBus,
playerData: this, playerData: this,
uiSettings: this.videoData.settings.active.ui uiSettings: this.videoData.settings.active.ui,
siteSettings: this.siteSettings,
} }
); );
@ -627,8 +629,6 @@ class PlayerData {
* Finds and returns HTML element of the player * Finds and returns HTML element of the player
*/ */
private getPlayer(options?: {verbose?: boolean}): HTMLElement { private getPlayer(options?: {verbose?: boolean}): HTMLElement {
const host = window.location.hostname;
let element = this.videoElement.parentNode;
const videoWidth = this.videoElement.offsetWidth; const videoWidth = this.videoElement.offsetWidth;
const videoHeight = this.videoElement.offsetHeight; const videoHeight = this.videoElement.offsetHeight;
let playerCandidate; let playerCandidate;
@ -649,7 +649,6 @@ class PlayerData {
} }
// if mode is given, we follow the preference // if mode is given, we follow the preference
if (this.siteSettings.data.currentDOMConfig?.elements?.player?.manual && this.siteSettings.data.currentDOMConfig?.elements?.player?.mode) { if (this.siteSettings.data.currentDOMConfig?.elements?.player?.manual && this.siteSettings.data.currentDOMConfig?.elements?.player?.mode) {
if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') { if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') {
playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight);

View File

@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Ultrawidify", "name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.", "description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.2.5", "version": "6.2.6",
"icons": { "icons": {
"32":"res/icons/uw-32.png", "32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png" "64":"res/icons/uw-64.png"