Merge branch 'master' into stable

This commit is contained in:
Tamius Han 2019-11-02 02:47:59 +01:00
commit b6ff740b84
31 changed files with 723 additions and 174 deletions

View File

@ -5,10 +5,12 @@
"blackbar",
"blackframe",
"canvas",
"comms",
"equalish",
"insta",
"recursing",
"reddit",
"rescan",
"resizer",
"textbox",
"videodata",

View File

@ -12,7 +12,16 @@ QoL improvements for me:
* logging: allow to enable logging at will and export said logs to a file
### v4.3.1 (current)
### v.4.4.0 (current)
* Russian users (and users of other non-latin keyboard layouts) can now use keyboard shortcuts by default, without having to rebind them manually. (NOTE: if you've changed keyboard shortcuts manually, this change will ***NOT*** be applied to your configuration.)
* NOTE: when using non-latin layouts, 'zoom' shortcut (`z` by default) uses the position of 'Y' on QWERTY layout.
* Ability to preserve aspect ratio between different videos (applies to current page and doesn't survive proper page reloads)
* Changing aspect ratio now resets zooming and panning.
* Fixed bug where keyboard shortcuts would work while typing in certain text fields
* Fixed minor bug with autodetection
### v4.3.1
* Minor rework of settings page (actions & shortcuts section)
* Fixed bug that prevented settings page from opening

View File

@ -1,6 +1,6 @@
{
"name": "ultravidify",
"version": "4.3.1",
"version": "4.4.0",
"description": "Aspect ratio fixer for youtube that works around some people's disability to properly encode 21:9 (and sometimes, 16:9) videos.",
"author": "Tamius Han <tamius.han@gmail.com>",
"scripts": {

View File

@ -5,7 +5,7 @@
</div>
<div class="flex action-name">
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.cmd[0].arg === AspectRatio.Fixed" class="icon red" @click="removeAction()">🗙</span> &nbsp; &nbsp;
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.cmd[0].arg === AspectRatio.Fixed" class="icon red" @click="removeAction()">🗙</span>
<span v-else class="icon transparent">🗙</span> &nbsp; &nbsp;
<span class="icon" @click="editAction()">🖉</span> &nbsp; &nbsp;
{{action.name}}

View File

@ -0,0 +1,9 @@
var CropModePersistence = Object.freeze({
Default: -1,
Disabled: 0,
UntilPageReload: 1,
CurrentSession: 2,
Forever: 3,
});
export default CropModePersistence;

View File

@ -1,6 +1,6 @@
class KeyboardShortcutParser {
static parseShortcut(keypress) {
var shortcutCombo = '';
let shortcutCombo = '';
if (keypress.ctrlKey) {
shortcutCombo += 'Ctrl + ';

View File

@ -1,7 +1,13 @@
export default {
computed: {
scopeActions: function() {
return this.settings.active.actions.filter(x => x.scopes[this.scope] && x.scopes[this.scope].show) || [];
return this.settings.active.actions.filter(x => {
if (! x.scopes) {
console.error('This action does not have a scope.', x);
return false;
}
return x.scopes[this.scope] && x.scopes[this.scope].show
}) || [];
},
extensionActions: function(){
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-extension-mode') || [];
@ -12,6 +18,9 @@ export default {
aspectRatioActions: function(){
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar') || [];
},
cropModePersistenceActions: function() {
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar-persistence') || [];
},
stretchActions: function(){
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-stretch') || [];
},

View File

@ -2,6 +2,7 @@ import VideoAlignment from '../../common/enums/video-alignment.enum';
import Stretch from '../../common/enums/stretch.enum';
import ExtensionMode from '../../common/enums/extension-mode.enum';
import AspectRatio from '../../common/enums/aspect-ratio.enum';
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
var ActionList = {
'set-ar': {
@ -30,6 +31,33 @@ var ActionList = {
page: true,
}
},
'set-ar-persistence': {
name: 'Set crop mode persistence',
args: [{
name: 'Never persist',
arg: CropModePersistence.Disabled,
},{
name: 'While on page',
arg: CropModePersistence.UntilPageReload,
},{
name: 'Current session',
arg: CropModePersistence.CurrentSession,
},{
name: 'Always persist',
arg: CropModePersistence.Forever,
}, {
name: 'Default',
arg: CropModePersistence.Default,
scopes: {
site: true,
}
}],
scopes: {
global: true,
site: true,
page: false,
}
},
'set-stretch': {
name: 'Set stretch',
args: [{

View File

@ -117,7 +117,132 @@ const ExtensionConfPatch = [
}
}
}
}, {
forVersion: '4.4.0',
updateFn: (userOptions, defaultOptions) => {
// remove 'press P to toggle panning mode' thing
const togglePan = userOptions.actions.find(x => x.cmd && x.cmd.length === 1 && x.cmd[0].action === 'toggle-pan');
if (togglePan) {
togglePan.scopes = {};
}
// add new actions
userOptions.actions.push({
name: 'Don\'t persist crop',
label: 'Never persist',
cmd: [{
action: 'set-ar-persistence',
arg: 0,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist crop while on page',
label: 'Until page load',
cmd: [{
action: 'set-ar-persistence',
arg: 1,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist crop for current session',
label: 'Current session',
cmd: [{
action: 'set-ar-persistence',
arg: 2,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist until manually reset',
label: 'Always persist',
cmd: [{
action: 'set-ar-persistence',
arg: 3,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Default crop persistence',
label: 'Default',
cmd: [{
action: 'set-ar-persistence',
arg: -1,
}],
scopes: {
site: {
show: true,
},
},
playerUi: {
show: true,
}
});
// patch shortcuts for non-latin layouts, but only if the user hasn't changed default keys
for (const action of userOptions.actions) {
if (!action.cmd || action.cmd.length !== 1) {
continue;
}
try {
// if this fails, then action doesn't have keyboard shortcut associated with it, so we skip it
const actionDefaults = defaultOptions.actions.find(x => x.cmd && x.cmd.length === 1 // (redundant, default actions have exactly 1 cmd in array)
&& x.cmd[0].action === action.cmd[0].action
&& x.scopes.page
&& x.scopes.page.shortcut
&& x.scopes.page.shortcut.length === 1
&& x.scopes.page.shortcut[0].key === action.scopes.page.shortcut[0].key // this can throw exception, and it's okay
);
if (actionDefaults === undefined) {
continue;
}
// update 'code' property for shortcut
action.scopes.page.shortcut[0]['code'] = actionDefaults.scopes.page.shortcut[0].code;
} catch (e) {
continue;
}
}
}
}
];
export default ExtensionConfPatch;

View File

@ -5,6 +5,7 @@ import Stretch from '../../common/enums/stretch.enum';
import ExtensionMode from '../../common/enums/extension-mode.enum';
import AntiGradientMode from '../../common/enums/anti-gradient-mode.enum';
import AspectRatio from '../../common/enums/aspect-ratio.enum';
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
if(Debug.debug)
console.log("Loading: ExtensionConf.js");
@ -191,6 +192,7 @@ var ExtensionConf = {
label: 'Automatic', // example override, takes precedence over default label
shortcut: [{
key: 'a',
code: 'KeyA',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -216,6 +218,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 'r',
code: 'KeyR',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -241,6 +244,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 'w',
code: 'KeyW',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -266,6 +270,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 'e',
code: 'KeyE',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -292,6 +297,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 's',
code: 'KeyS',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -318,6 +324,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 'd',
code: 'KeyD',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -344,6 +351,7 @@ var ExtensionConf = {
show: true,
shortcut: [{
key: 'x',
code: 'KeyX',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -357,6 +365,93 @@ var ExtensionConf = {
show: true,
path: 'crop',
}
}, {
name: 'Don\'t persist crop',
label: 'Never persist',
cmd: [{
action: 'set-ar-persistence',
arg: CropModePersistence.Never,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist crop while on page',
label: 'Until page load',
cmd: [{
action: 'set-ar-persistence',
arg: CropModePersistence.UntilPageReload,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist crop for current session',
label: 'Current session',
cmd: [{
action: 'set-ar-persistence',
arg: CropModePersistence.CurrentSession,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Persist until manually reset',
label: 'Always persist',
cmd: [{
action: 'set-ar-persistence',
arg: CropModePersistence.Forever,
}],
scopes: {
site: {
show: true,
},
global: {
show: true,
}
},
playerUi: {
show: true,
}
}, {
name: 'Default crop persistence',
label: 'Default',
cmd: [{
action: 'set-ar-persistence',
arg: CropModePersistence.Default,
}],
scopes: {
site: {
show: true,
},
},
playerUi: {
show: true,
}
}, {
name: 'Zoom in',
label: 'Zoom',
@ -369,6 +464,7 @@ var ExtensionConf = {
show: false,
shortcut: [{
key: 'z',
code: 'KeyY',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -393,6 +489,7 @@ var ExtensionConf = {
show: false,
shortcut: [{
key: 'u',
code: 'KeyU',
ctrlKey: false,
metaKey: false,
altKey: false,
@ -412,23 +509,11 @@ var ExtensionConf = {
action: 'toggle-pan',
arg: 'toggle'
}],
scopes: {
page: {
show: true,
shortcut: [{
key: 'p',
ctrlKey: false,
metaKey: false,
altKey: false,
shiftKey: false,
onKeyUp: true,
onKeyDown: false,
}]
}
},
playerUi: {
show: true,
path: 'zoom'
},
scopes: {
}
}, {
name: 'Hold to pan',

View File

@ -133,28 +133,31 @@ class ActionHandler {
// don't do shit on invalid value of state
}
preventAction() {
preventAction(event) {
var activeElement = document.activeElement;
if(this.logger.canLog('keyboard')) {
this.logger.pause(); // temp disable to avoid recursing;
const preventAction = this.preventAction();
this.logger.resume(); // undisable
this.logger.log('info', 'keyboard', "[ActionHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
"is full screen? (yes->allow):", PlayerData.isFullScreen(),
"\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
"\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
"\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
"\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable,
"\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
"\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
"\nwill the action be prevented? (yes -> prevent)", this.preventAction(),
"\nwill the action be prevented? (yes -> prevent)", preventAction,
"\n-----------------{ extra debug info }-------------------",
"\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
"\nrole:", activeElement.getAttribute('role'),
"\ntype:", activeElement.getAttribute('type'),
"insta-fail inputs:", this.inputs
"\ninsta-fail inputs:", this.inputs,
"\nevent:", event,
"\nevent.target:", event.target
);
this.logger.resume(); // undisable
}
// lately youtube has allowed you to read and write comments while watching video in
@ -175,25 +178,49 @@ class ActionHandler {
if (activeElement.getAttribute("role") === "textbox") {
return true;
}
if (event.target.isContentEditable) {
return true;
}
if (activeElement.getAttribute("type") === "text") {
return true;
}
return false;
}
isActionMatch(shortcut, event) {
isLatin(key) {
return 'abcdefghijklmnopqrstuvwxyz,.-+1234567890'.indexOf(key.toLocaleLowerCase()) !== -1;
}
isActionMatchStandard(shortcut, event) {
return shortcut.key === event.key &&
shortcut.ctrlKey === event.ctrlKey &&
shortcut.metaKey === event.metaKey &&
shortcut.altKey === event.altKey &&
shortcut.shiftKey === event.shiftKey
}
isActionMatchKeyCode(shortcut, event) {
return shortcut.code === event.code &&
shortcut.ctrlKey === event.ctrlKey &&
shortcut.metaKey === event.metaKey &&
shortcut.altKey === event.altKey &&
shortcut.shiftKey === event.shiftKey
}
isActionMatch(shortcut, event, isLatin = true) {
// ASCII and symbols fall back to key code matching, because we don't know for sure that
// regular matching by key is going to work
return isLatin ?
this.isActionMatchStandard(shortcut, event) :
this.isActionMatchStandard(shortcut, event) || this.isActionMatchKeyCode(shortcut, event);
}
execAction(actions, event, videoData) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::execAction] Trying to find and execute action for event. Actions/event: ", "color: #ff0", actions, event);
const isLatin = event.key ? this.isLatin(event.key) : true;
for (var action of actions) {
if (this.isActionMatch(action.shortcut, event)) {
if (this.isActionMatch(action.shortcut, event, isLatin)) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::execAction] found an action associated with keypress/event: ", "color: #ff0", action);
for (var cmd of action.cmd) {
@ -228,10 +255,17 @@ class ActionHandler {
this.settings.active.sites[site].arStatus = cmd.arg;
} else if (cmd.action === 'set-keyboard') {
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
} else if (cmd.action === 'set-ar-persistence') {
this.settings.active.sites[site]['cropModePersistence'] = cmd.arg;
this.pageInfo.setArPersistence(cmd.arg);
this.settings.saveWithoutReload();
}
if (cmd.action !== 'set-ar-persistence') {
this.settings.save();
}
}
}
// če smo našli dejanje za to tipko, potem ne preiskujemo naprej
// if we found an action for this key, we stop searching for a match
@ -244,7 +278,7 @@ class ActionHandler {
handleKeyup(event) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeyup] we pressed a key: ", "color: #ff0", event.key , " | keyup: ", event.keyup, "event:", event);
if (this.preventAction()) {
if (this.preventAction(event)) {
this.logger.log('info', 'keyboard', "[ActionHandler::handleKeyup] we are in a text box or something. Doing nothing.");
return;
}
@ -255,7 +289,7 @@ class ActionHandler {
handleKeydown(event) {
this.logger.log('info', 'keyboard', "%c[ActionHandler::handleKeydown] we pressed a key: ", "color: #ff0", event.key , " | keydown: ", event.keydown, "event:", event)
if (this.preventAction()) {
if (this.preventAction(event)) {
this.logger.log('info', 'keyboard', "[ActionHandler::handleKeydown] we are in a text box or something. Doing nothing.");
return;
}

View File

@ -6,6 +6,7 @@ import ObjectCopy from '../lib/ObjectCopy';
import Stretch from '../../common/enums/stretch.enum';
import VideoAlignment from '../../common/enums/video-alignment.enum';
import ExtensionConfPatch from '../conf/ExtConfPatches';
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
@ -27,42 +28,29 @@ class Settings {
const ths = this;
if (currentBrowser.firefox) {
browser.storage.onChanged.addListener( (changes, area) => {
this.logger.log('info', 'settings', "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
this.logger.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
}
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
ths.setActive(JSON.parse(changes.uwSettings.newValue));
}
if(this.updateCallback) {
try {
updateCallback(ths);
} catch (e) {
this.logger.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED.")
}
}
});
browser.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
} else if (currentBrowser.chrome) {
chrome.storage.onChanged.addListener( (changes, area) => {
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
}
}
storageChangeListener(changes, area) {
this.logger.log('info', 'settings', "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
if (changes['uwSettings'] && changes['uwSettings'].newValue) {
this.logger.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
}
const parsedSettings = JSON.parse(changes.uwSettings.newValue);
if(changes['uwSettings'] && changes['uwSettings'].newValue) {
ths.setActive(JSON.parse(changes.uwSettings.newValue));
this.setActive(parsedSettings);
}
if(this.updateCallback) {
if(!parsedSettings.preventReload && this.updateCallback) {
try {
updateCallback(ths);
} catch (e) {
this.logger.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED.")
}
}
});
}
}
getExtensionVersion() {
@ -156,8 +144,23 @@ class Settings {
// apply all remaining patches
this.logger.log('info', 'settings', `[Settings::applySettingsPatches] There are ${patches.length - index} settings patches to apply`);
while (index < patches.length) {
const updateFn = patches[index].updateFn;
delete patches[index].forVersion;
delete patches[index].updateFn;
if (Object.keys(patches[index]).length > 0) {
ObjectCopy.overwrite(this.active, patches[index]);
}
if (updateFn) {
try {
updateFn(this.active, this.getDefaultSettings());
} catch (e) {
console.log("!!!!", e)
this.logger.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e);
}
}
index++;
}
}
@ -171,11 +174,12 @@ class Settings {
// | needed. In this case, we assume we're on the current version
const oldVersion = (settings && settings.version) || this.version;
if(Debug.debug) {
if (settings) {
this.logger.log('info', 'settings', "[Settings::init] Configuration fetched from storage:", settings,
"\nlast saved with:", settings.version,
"\ncurrent version:", this.version
);
}
// if (Debug.flushStoredSettings) {
// this.logger.log('info', 'settings', "%c[Settings::init] Debug.flushStoredSettings is true. Using default settings", "background: #d00; color: #ffd");
@ -185,7 +189,6 @@ class Settings {
// this.set(this.active);
// return this.active;
// }
}
// if there's no settings saved, return default settings.
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
@ -279,8 +282,10 @@ class Settings {
}
}
async set(extensionConf) {
async set(extensionConf, options) {
if (!options || !options.forcePreserveVersion) {
extensionConf.version = this.version;
}
this.logger.log('info', 'settings', "[Settings::set] setting new settings:", extensionConf)
@ -299,11 +304,17 @@ class Settings {
this.active[prop] = value;
}
async save() {
async save(options) {
if (Debug.debug && Debug.storage) {
console.log("[Settings::save] Saving active settings:", this.active);
}
this.active.preventReload = undefined;
await this.set(this.active, options);
}
async saveWithoutReload() {
this.active.preventReload = true;
await this.set(this.active);
}
@ -522,6 +533,15 @@ class Settings {
return this.active.sites['@global'].stretch;
}
getDefaultCropPersistenceMode(site) {
if (site && this.active.sites[site] && this.active.sites[site].cropModePersistence !== Stretch.Default) {
return this.active.sites[site].cropModePersistence;
}
// persistence mode thing is missing from settings by default
return this.active.sites['@global'].cropModePersistence || CropModePersistence.Disabled;
}
getDefaultVideoAlignment(site) {
if (site && this.active.sites[site] && this.active.sites[site].videoAlignment !== VideoAlignment.Default) {
return this.active.sites[site].videoAlignment;

View File

@ -492,6 +492,13 @@ class ArDetector {
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: trueAr}, {type: AspectRatio.Automatic, ratio: trueAr});
}
clearImageData(id) {
if (ArrayBuffer.transfer) {
ArrayBuffer.transfer(id, 0);
}
id = undefined;
}
frameCheck(){
if(! this.video){
this.logger.log('error', 'debug', `%c[ArDetect::frameCheck] <@${this.arid}> Video went missing. Destroying current instance of videoData.`);
@ -581,6 +588,7 @@ class ArDetector {
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done.`, "color: #fa3");
this.clearImageData(imageData);
return;
}
@ -598,6 +606,7 @@ class ArDetector {
// if both succeed, then aspect ratio hasn't changed.
if (!guardLineOut.imageFail && !guardLineOut.blackbarFail) {
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail)\n`, "color: #afa", guardLineOut);
this.clearImageData(imageData);
return;
}
@ -617,6 +626,7 @@ class ArDetector {
this.guardLine.reset();
this.noLetterboxCanvasReset = true;
this.clearImageData(imageData);
return;
}
@ -643,6 +653,7 @@ class ArDetector {
triggerTimeout = this.getTimeout(baseTimeout, startTime);
this.scheduleFrameCheck(triggerTimeout);
this.clearImageData(imageData);
return;
}
}
@ -664,6 +675,8 @@ class ArDetector {
// rob ni bil zaznan, zato ne naredimo ničesar.
// no edge was detected. Let's leave things as they were
this.logger.log('info', 'arDetect_verbose', `%c[ArDetect::frameCheck] Edge wasn't detected with findBars`, "color: #fa3", edgePost, "EdgeStatus.AR_KNOWN:", EdgeStatus.AR_KNOWN);
this.clearImageData(imageData);
return;
}
@ -696,14 +709,16 @@ class ArDetector {
} catch (e) {
// edges weren't gucci, so we'll just reset
// the aspect ratio to defaults
try {
this.logger.log('error', 'arDetect', `%c[ArDetect::frameCheck] There was a problem setting blackbar. Doing nothing. Error:`, e);
this.guardline.reset();
} catch (e) {
// guardline wasn't gucci either, but we'll just ignore
// that and reset the aspect ratio anyway
}
this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
// (eg. here: https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410)
//
// this.conf.resizer.setAr({type: AspectRatio.Automatic, ratio: this.getDefaultAr()});
}
this.clearImageData(imageData);
}
resetBlackLevel(){

View File

@ -34,7 +34,7 @@ class GuardLine {
// to odstrani vse neveljavne nastavitve in vse možnosti, ki niso smiselne
// this removes any configs with invalid values or values that dont make sense
if (bbTop < 0 || bbBottom >= this.conf.canvas.height ){
throw "INVALID_SETTINGS_IN_GUARDLINE"
throw {error: "INVALID_SETTINGS_IN_GUARDLINE", bbTop, bbBottom}
}
this.blackbar = {

View File

@ -92,6 +92,8 @@ class CommsClient {
this.pageInfo.setManualTick(message.arg);
} else if (message.cmd === 'autoar-tick') {
this.pageInfo.tick();
} else if (message.cmd === 'set-ar-persistence') {
this.pageInfo.setArPersistence(message.arg);
}
}

View File

@ -2,6 +2,7 @@ import Debug from '../../conf/Debug';
import VideoData from './VideoData';
import RescanReason from './enums/RescanReason';
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
import CropModePersistence from '../../../common/enums/crop-mode-persistence.enum';
if(Debug.debug)
console.log("Loading: PageInfo.js");
@ -20,23 +21,38 @@ class PageInfo {
this.lastUrl = window.location.href;
this.extensionMode = extensionMode;
this.readOnly = readOnly;
this.defaultCrop = undefined;
this.currentCrop = undefined;
if (comms){
this.comms = comms;
}
// request inject css immediately
try {
// request inject css immediately
const playerStyleString = this.settings.active.sites[window.location.host].css.replace('\\n', '');
this.comms.sendMessage({
cmd: 'inject-css',
cssString: playerStyleString
});
} catch (e) {
// do nothing. It's ok if there's no special settings for the player element
// do nothing. It's ok if there's no special settings for the player element or crop persistence
}
// try getting default crop immediately.
const cropModePersistence = this.settings.getDefaultCropPersistenceMode(window.location.host);
// try {
// if (cropModePersistence === CropModePersistence.Forever) {
// this.defaultCrop = this.settings.active.sites[window.location.host].defaultCrop;
// } else if (cropModePersistence === CropModePersistence.CurrentSession) {
// this.defaultCrop = JSON.parse(sessionStorage.getItem('uw-crop-mode-session-persistence'));
// }
// } catch (e) {
// // do nothing. It's ok if there's no special settings for the player element or crop persistence
// }
this.currentCrop = this.defaultCrop;
this.rescan(RescanReason.PERIODIC);
this.scheduleUrlCheck();
@ -197,11 +213,17 @@ class PageInfo {
try {
v = new VideoData(video, this.settings, this);
if (!this.defaultCrop) {
if (!v.invalid) {
v.initArDetection();
} else {
this.logger.log('error', 'debug', 'Video is invalid. Aard not started.', video);
}
} else {
this.logger.log('info', 'debug', 'Default crop is specified for this site. Not starting aard.');
}
this.videos.push(v);
} catch (e) {
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
@ -216,7 +238,7 @@ class PageInfo {
// če smo ostali brez videev, potem odregistriraj stran.
// če nismo ostali brez videev, potem registriraj stran.
//
// if we're left withotu videos on the current page, we unregister the page.
// if we're left without videos on the current page, we unregister the page.
// if we have videos, we call register.
if (this.comms) {
if (this.videos.length != oldVideoCount) { // only if number of videos changed, tho
@ -551,6 +573,53 @@ class PageInfo {
setKeyboardShortcutsEnabled(state) {
this.actionHandler.setKeybordLocal(state);
}
setArPersistence(persistenceMode) {
// name of this function is mildly misleading — we don't really _set_ ar persistence. (Ar persistence
// mode is set and saved via popup or keyboard shortcuts, if user defined them) We just save the current
// aspect ratio whenever aspect ratio persistence mode changes.
if (persistenceMode === CropModePersistence.CurrentSession) {
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(this.currentCrop));
} else if (persistenceMode === CropModePersistence.Forever) {
if (this.settings.active.sites[window.location.host]) {
// | key may be missing, so we do this
this.settings.active.sites[window.location.host]['defaultAr'] = this.currentCrop;
} else {
this.settings.active.sites[window.location.host] = this.settings.getDefaultOption();
this.settings.active.sites[window.location.host]['defaultAr'] = this.currentCrop;
}
this.settings.saveWithoutReload();
}
}
updateCurrentCrop(ar) {
this.currentCrop = ar;
// This means crop persistance is disabled. If crop persistance is enabled, then settings for current
// site MUST exist (crop persistence mode is disabled by default)
const cropModePersistence = this.settings.getDefaultCropPersistenceMode(window.location.host);
if (cropModePersistence === CropModePersistence.Disabled) {
return;
}
this.defaultCrop = ar;
if (cropModePersistence === CropModePersistence.CurrentSession) {
sessionStorage.setItem('uw-crop-mode-session-persistence', JSON.stringify(ar));
} else if (cropModePersistence === CropModePersistence.Forever) {
if (this.settings.active.sites[window.location.host]) {
// | key may be missing, so we do this
this.settings.active.sites[window.location.host]['defaultAr'] = ar;
} else {
this.settings.active.sites[window.location.host] = this.settings.getDefaultOption();
this.settings.active.sites[window.location.host]['defaultAr'] = ar;
}
this.settings.saveWithoutReload();
}
}
}
export default PageInfo;

View File

@ -44,6 +44,7 @@ class VideoData {
};
this.resizer = new Resizer(this);
this.arDetector = new ArDetector(this); // this starts Ar detection. needs optional parameter that prevets ardetdctor from starting
// player dimensions need to be in:
// this.player.dimensions
@ -59,6 +60,11 @@ class VideoData {
// start fallback video/player size detection
this.fallbackChangeDetection();
// force reload last aspect ratio (if default crop ratio exists)
if (this.pageInfo.defaultCrop) {
this.resizer.setAr(this.pageInfo.defaultCrop);
}
}
async fallbackChangeDetection() {
@ -80,19 +86,31 @@ class VideoData {
}
return;
}
let confirmAspectRatioRestore = false;
for (let mutation of mutationList) {
if (mutation.type === 'attributes'
&& mutation.attributeName === 'class'
&& !context.video.classList.contains(this.userCssClassName) ) {
if (mutation.type === 'attributes') {
if (mutation.attributeName === 'class') {
if(!context.video.classList.contains(this.userCssClassName) ) {
// force the page to include our class in classlist, if the classlist has been removed
// while classList.add() doesn't duplicate classes (does nothing if class is already added),
// we still only need to make sure we're only adding our class to classlist if it has been
// removed. classList.add() will _still_ trigger mutation (even if classlist wouldn't change).
// This is a problem because INFINITE RECURSION TIME, and we _really_ don't want that.
context.video.classList.add(this.userCssClassName);
break;
}
// always trigger refresh on class changes, since change of classname might trigger change
// of the player size as well.
confirmAspectRatioRestore = true;
}
if (mutation.attributeName === 'style') {
confirmAspectRatioRestore = true;
}
}
}
if (!confirmAspectRatioRestore) {
return;
}
// adding player observer taught us that if element size gets triggered by a class, then
@ -131,8 +149,9 @@ class VideoData {
const pw = +(pcs.width.split('px')[0]);
// TODO: check & account for panning and alignment
if (this.isWithin(vh, (ph - (translateY / 2)), 2)
&& this.isWithin(vw, (pw - (translateX / 2)), 2)) {
if (transformMatrix[0] !== 'none'
&& this.isWithin(vh, (ph - (translateY * 2)), 2)
&& this.isWithin(vw, (pw - (translateX * 2)), 2)) {
} else {
this.player.forceRefreshPlayerElement();
this.restoreAr();
@ -178,7 +197,7 @@ class VideoData {
return;
}
if (!this.arDetector) {
this.arDetector.init();
this.initArDetection();
}
this.arDetector.start();
}

View File

@ -7,6 +7,7 @@ import ExtensionMode from '../../../common/enums/extension-mode.enum';
import Stretch from '../../../common/enums/stretch.enum';
import VideoAlignment from '../../../common/enums/video-alignment.enum';
import AspectRatio from '../../../common/enums/aspect-ratio.enum';
import CropModePersistance from '../../../common/enums/crop-mode-persistence.enum';
if(Debug.debug) {
console.log("Loading: Resizer.js");
@ -130,12 +131,36 @@ class Resizer {
return;
}
const siteSettings = this.settings.active.sites[window.location.host];
// reset zoom, but only on aspect ratio switch. We also know that aspect ratio gets converted to
// AspectRatio.Fixed when zooming, so let's keep that in mind
if (ar.type !== AspectRatio.Fixed) {
this.zoom.reset();
this.resetPan();
} else if (ar.ratio !== this.lastAr.ratio) {
// we must check against this.lastAR.ratio because some calls provide same value for ar and lastAr
this.zoom.reset();
this.resetPan();
}
// most everything that could go wrong went wrong by this stage, and returns can happen afterwards
// this means here's the optimal place to set or forget aspect ratio. Saving of current crop ratio
// is handled in pageInfo.updateCurrentCrop(), which also makes sure to persist aspect ratio if ar
// is set to persist between videos / through current session / until manual reset.
if (ar.type === AspectRatio.Automatic ||
ar.type === AspectRatio.Reset ||
ar.type === AspectRatio.Initial ) {
// reset/undo default
this.conf.pageInfo.updateCurrentCrop(undefined);
} else {
this.conf.pageInfo.updateCurrentCrop(ar);
}
if (ar.type === AspectRatio.Automatic ||
ar.type === AspectRatio.Reset && this.lastAr.type === AspectRatio.Initial) {
// some sites do things that interfere with our site (and aspect ratio setting in general)
// first, we check whether video contains anything we don't like
const siteSettings = this.settings.active.sites[window.location.host];
if (siteSettings && siteSettings.autoarPreventConditions) {
if (siteSettings.autoarPreventConditions.videoStyleString) {
const styleString = (this.video.getAttribute('style') || '').split(';');
@ -170,6 +195,8 @@ class Resizer {
}
}
if (lastAr) {
this.lastAr = this.calculateRatioForLegacyOptions(lastAr);
ar = this.calculateRatioForLegacyOptions(ar);
@ -247,6 +274,10 @@ class Resizer {
}
toFixedAr() {
this.lastAr.type = AspectRatio.Fixed;
}
resetLastAr() {
this.lastAr = {type: AspectRatio.Initial};
}
@ -272,6 +303,9 @@ class Resizer {
// dont allow weird floats
this.videoAlignment = VideoAlignment.Center;
// because non-fixed aspect ratios reset panning:
this.toFixedAr();
const player = this.conf.player.element;
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
@ -283,6 +317,11 @@ class Resizer {
}
}
resetPan() {
this.pan = {};
this.videoAlignment = this.settings.getDefaultVideoAlignment(window.location.host);
}
setPan(relativeMousePosX, relativeMousePosY){
// relativeMousePos[X|Y] - on scale from 0 to 1, how close is the mouse to player edges.
// use these values: top, left: 0, bottom, right: 1

View File

@ -120,14 +120,31 @@ class Scaler {
actualHeight: 0, // height of the video (excluding letterbox) when <video> tag height is equal to height
}
if (fileAr < playerAr) {
if (fileAr < ar.ratio){
// imamo letterbox zgoraj in spodaj -> spremenimo velikost videa (a nikoli širše od ekrana)
// letterbox -> change video size (but never to wider than monitor width)
// in this situation we have to crop letterbox on top/bottom of the player
// we cut it, but never more than the player
videoDimensions.xFactor = Math.min(ar.ratio, playerAr) / fileAr;
videoDimensions.yFactor = videoDimensions.xFactor;
} else {
// in this situation, we would be cutting pillarbox. Inside horizontal player.
// I don't think so. Except exceptions, we'll wait for bug reports.
videoDimensions.xFactor = 1;
videoDimensions.yFactor = 1;
}
} else {
if (fileAr < ar.ratio || playerAr < ar.ratio){
// in this situation, we need to add extra letterbox on top of our letterbox
// this means we simply don't crop anything _at all_
videoDimensions.xFactor = 1;
videoDimensions.yFactor = 1;
} else {
// meant for handling pillarbox crop. not quite implemented.
videoDimensions.xFactor = fileAr / Math.min(ar.ratio, playerAr);
videoDimensions.yFactor = videoDimensions.xFactor;
// videoDimensions.xFactor = Math.max(ar.ratio, playerAr) * fileAr;
// videoDimensions.yFactor = videoDimensions.xFactor;
}
}
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] Crop factor calculated — ", videoDimensions.xFactor);

View File

@ -18,6 +18,7 @@ class Zoom {
reset(){
this.scale = 1;
this.logScale = 0;
}
zoomStep(amount){
@ -34,6 +35,8 @@ class Zoom {
this.logger.log('info', 'debug', "[Zoom::zoomStep] changing zoom by", amount, ". New zoom level:", this.scale);
this.conf.resizer.toFixedAr();
this.conf.restoreAr();
this.conf.announceZoom(this.scale);
}

View File

@ -44,26 +44,26 @@ class UW {
if (!this.logger) {
const loggingOptions = {
logToFile: false,
logToConsole: false,
logToConsole: true,
fileOptions: {
// really the same stuff as consoleOptions
},
consoleOptions: {
enabled: true, // if logging is enabled at all
'debug': true,
'init': true,
'settings': true,
'keyboard': false,
'mousemove': false,
'actionHandler': false,
'comms': false,
'playerDetect': false,
// 'debug': true,
// 'init': true,
// 'settings': true,
// 'keyboard': true,
// 'mousemove': false,
// 'actionHandler': false,
// 'comms': false,
// 'playerDetect': false,
// 'resizer': true,
// 'scaler': true,
// 'stretcher': true,
'videoRescan': false,
'arDetect': false,
'arDetect_verbose': false,
// 'videoRescan': false,
// 'arDetect': true,
// 'arDetect_verbose': true,
}
};
this.logger = new Logger(loggingOptions);

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "4.3.1.1",
"version": "4.4.0",
"applications": {
"gecko": {
"id": "{cf02b1a7-a01a-4e37-a609-516a283f1ed3}"

View File

@ -70,7 +70,7 @@ export default {
if (this.hasError) {
return;
}
this.settings.save();
this.settings.save({forcePreserveVersion: true});
// this.parsedSettings = JSON.stringify(this.settings.active, null, 2);
// this.lastSettings = JSON.parse(JSON.stringify(this.settings.active));
const ths = this;

View File

@ -39,12 +39,11 @@
</template>
<script>
import SetShortcutButton from './SetShortcutButton.vue';
import SetShortcutButton from './SetShortcutButton';
export default {
components: {
SetShortcutButton
SetShortcutButton,
},
props: {
scopeOptions: Object,

View File

@ -7,7 +7,7 @@
@focus="initiateKeypress"
@keyup="processKeyup"
/>
<span v-if="shortcut" @click="$emit('set-shortcut')">(Clear shortcut)</span>
<span v-if="shortcut" @click="clearShortcut()">(Clear shortcut)</span>
</div>
</template>
@ -38,6 +38,7 @@ export default {
if (this.waitingForPress) {
const shortcut = {
key: event.key,
code: event.code,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
altKey: event.altKey,
@ -50,6 +51,11 @@ export default {
this.shortcutText = KeyboardShortcutParser.parseShortcut(shortcut);
}
this.waitingForPress = false;
},
clearShortcut() {
this.shortcutText = '[click to add shortcut]';
this.shortcut = undefined;
this.$emit('set-shortcut');
}
}
}

View File

@ -13,7 +13,7 @@ class ExecAction {
this.site = site;
}
exec(action, scope, frame) {
async exec(action, scope, frame) {
for (var cmd of action.cmd) {
if (scope === 'page') {
const message = {
@ -27,6 +27,23 @@ class ExecAction {
Comms.sendMessage(message);
} else {
// set-ar-persistence sends stuff to content scripts as well (!)
// it's important to do that BEFORE the save step
if (cmd.action === 'set-ar-persistence') {
// even when setting global defaults, we only send message to the current tab in
// order to avoid problems related to
const message = {
forwardToActive: true,
targetFrame: frame,
frame: frame,
cmd: cmd.action,
arg: cmd.arg,
}
// this hopefully delays settings.save() until current crops are saved on the site
// and thus avoid any fucky-wuckies
await Comms.sendMessage(message);
}
let site = this.site;
if (scope === 'global') {
site = '@global';
@ -48,11 +65,17 @@ class ExecAction {
this.settings.active.sites[site].autoar = cmd.arg;
} else if (cmd.action === 'set-keyboard') {
this.settings.active.sites[site].keyboardShortcutsEnabled = cmd.arg;
} else if (cmd.action === 'set-ar-persistence') {
this.settings.active.sites[site]['cropModePersistence'] = cmd.arg;
this.settings.saveWithoutReload();
}
if (cmd.action !== 'set-ar-persistence') {
this.settings.save();
}
}
}
}
}
export default ExecAction;

View File

@ -42,9 +42,27 @@
</div>
</div>
<!-- CROP MODE PERSISTENCE -->
<div v-if="cropModePersistenceActions.length"
class="w100"
>
<div class="label">Persists crop mode <template v-if="scope === 'site'">for {{site}}</template>:</div>
<div class="flex flex-row flex-wrap">
<ShortcutButton v-for="(action, index) of cropModePersistenceActions"
class="flex flex-grow button b3"
:class="{'setting-selected': getCurrent('cropModePersistence') === action.cmd[0].arg}"
:key="index"
:label="(action.scopes[scope] && action.scopes[scope].label) ? action.scopes[scope].label : action.label"
:shortcut="parseShortcut(action)"
@click.native="execAction(action)"
>
</ShortcutButton>
</div>
</div>
<!-- DEFAULT SETTINGS -->
<div v-if="stretchActions.length">
<div class="label">Default stretching mode:</div>
<div class="label experimental">Default stretching mode:</div>
<div class="flex flex-row flex-wrap">
<ShortcutButton v-for="(action, index) of stretchActions"
class="flex b3 flex-grow button"

View File

@ -5,6 +5,13 @@
</div>
<div v-if="aspectRatioActions.length">
<div class="label">Cropping mode:</div>
<div v-if="cropModePersistence && cropModePersistence > CropModePersistence.Disabled" class="info">
Cropping mode will persist
<template v-if="cropModePersistence === CropModePersistence.UntilPageReload">until page reload (this includes page navigation!).</template>
<template v-if="cropModePersistence === CropModePersistence.CurrentSession">for current session.</template>
<template v-if="cropModePersistence === CropModePersistence.Forever">forever (or at least until you change aspect ratio manually).</template>
This can be changed in the 'site settings' or 'extension settings' tab.
</div>
<div class="flex flex-row flex-wrap">
<ShortcutButton v-for="(action, index) of aspectRatioActions"
class="flex b3 flex-grow button"
@ -117,11 +124,13 @@ import ExecAction from '../js/ExecAction';
import KeyboardShortcutParser from '../../common/js/KeyboardShortcutParser';
import ShortcutButton from '../../common/components/ShortcutButton';
import ComputeActionsMixin from '../../common/mixins/ComputeActionsMixin';
import CropModePersistence from '../../common/enums/crop-mode-persistence.enum';
export default {
data() {
return {
scope: 'page',
CropModePersistence: CropModePersistence,
}
},
mixins: [
@ -131,7 +140,8 @@ export default {
'settings',
'frame',
'zoom',
'someSitesDisabledWarning'
'someSitesDisabledWarning',
'cropModePersistence',
],
created() {
this.exec = new ExecAction(this.settings);

View File

@ -2,11 +2,17 @@
<div>
<h2>What's new</h2>
<p>Full changelog for older versions <a href="https://github.com/xternal7/ultrawidify/blob/master/CHANGELOG.md">is available here</a>.</p>
<p class="label">4.3.1.1</p>
<p class="label">4.4.0</p>
<ul>
<li><b>[4.3.3.1]</b> Patched twitch.tv</li>
<li>Minor rework of settings page (actions & shortcuts section)</li>
<li>Fixed bug that prevented settings page from opening</li>
<li>Russian users (and users of other non-latin keyboard layouts) can now use keyboard shortcuts by default,
without having to rebind them manually. <b>NOTE: this change will only be applied to users who have <i>NOT</i>
modified their keyboard shortcuts.</b></li>
<li>NOTE: when using non-latin layouts, 'zoom' shortcut (`z` by default) uses the position of 'Y' on QWERTY layout.</li>
<li>Ability to preserve aspect ratio between different videos (applies to current page and doesn't survive proper
page reloads)</li>
<li>Changing aspect ratio now resets zooming and panning.</li>
<li>Fixed bug where keyboard shortcuts would work while typing in certain text fields</li>
<li>Fixed a minor bug with autodetection</li>
</ul>
</div>
</template>

View File

@ -1,6 +1,6 @@
# List of test/sample videos
A quick list of videos where letterbox is encoded in the file.
A quick list of videos where letterbox is encoded in the file. Inclusion of a video on this list does not imply endorsement of the content expressed by the video or the views expressed by its creator.
## Vimeo:
@ -34,11 +34,13 @@ https://www.youtube.com/watch?v=L_u97PqWX6g (also dark at the start)
https://www.youtube.com/watch?v=Xr9Oubxw1gA (triggers autocorrection every now and then)
https://www.youtube.com/watch?v=NaTGwlfRB_c (dark, triggers minor corrections)
https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410 (@ts, dark, resets aspect ratio for no reason within 15s)
### Watermark stopping AR
https://www.youtube.com/watch?v=tXTFdDrd7pA
### HARD MODE
For situations that would be _really_ hard to fix (if fix is even possible)