Github messages for voidlinux
 help / color / mirror / Atom feed
* [PR PATCH] openshot: update to 2.6.1.
@ 2022-02-23  0:01 tibequadorian
  2022-02-23  0:12 ` [PR PATCH] [Updated] " tibequadorian
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: tibequadorian @ 2022-02-23  0:01 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1228 bytes --]

There is a new pull request by tibequadorian against master on the void-packages repository

https://github.com/tibequadorian/void-packages openshot
https://github.com/void-linux/void-packages/pull/35798

openshot: update to 2.6.1.
<!-- Uncomment relevant sections and delete options which are not applicable -->

#### Testing the changes
- I tested the changes in this PR: **briefly**

<!--
#### New package
- This new package conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements): **YES**|**NO**
-->

<!-- Note: If the build is likely to take more than 2 hours, please [skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration)
and test at least one native build and, if supported, at least one cross build.
Ignore this section if this PR is not skipping CI.
-->
<!-- 
#### Local build testing
- I built this PR locally for my native architecture, (ARCH-LIBC)
- I built this PR locally for these architectures (if supported. mark crossbuilds):
  - aarch64-musl
  - armv7l
  - armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/35798.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-openshot-35798.patch --]
[-- Type: text/x-diff, Size: 124025 bytes --]

From 5ef92256989275841e85f297f5bf0b0e4e2b1f4d Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:41:38 +0100
Subject: [PATCH 1/3] libopenshot-audio: update to 0.2.2.

---
 common/shlibs                                 |  2 +-
 .../libopenshot-audio/patches/fix-musl.patch  | 86 ++++++++++---------
 srcpkgs/libopenshot-audio/template            |  7 +-
 3 files changed, 51 insertions(+), 44 deletions(-)

diff --git a/common/shlibs b/common/shlibs
index 32befc92d641..c66b4fbe6700 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2597,7 +2597,7 @@ libax25io.so.0 libax25-0.0.12rc4_1
 libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
-libopenshot-audio.so.7 libopenshot-audio-0.2.0_1
+libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
 libopenshot.so.19 libopenshot-0.2.5_3
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
diff --git a/srcpkgs/libopenshot-audio/patches/fix-musl.patch b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
index b5f178d92d8a..532248c86217 100644
--- a/srcpkgs/libopenshot-audio/patches/fix-musl.patch
+++ b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
@@ -1,40 +1,46 @@
---- a/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-12-12 14:53:23.532613378 +0100
-@@ -97,7 +97,7 @@
-  #include <net/if.h>
-  #include <sys/ioctl.h>
- 
-- #if ! JUCE_ANDROID
-+ #if ! JUCE_ANDROID && defined(__GLIBC__)
-   #include <execinfo.h>
-  #endif
- #endif
---- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-12-12 14:58:35.988986030 +0100
-@@ -134,6 +134,8 @@
-         }
-     }
- 
-+   #elif !defined(__GLIBC__)
-+    jassertfalse; // sorry, not implemented yet!
-    #else
-     void* stack[128];
-     int frames = backtrace (stack, numElementsInArray (stack));
---- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-12-12 15:07:35.046607788 +0100
-@@ -142,8 +142,15 @@
-     return result;
- }
- 
-+#if defined(__GLIBC__)
- String SystemStats::getUserLanguage()    { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
- String SystemStats::getUserRegion()      { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
-+#else
-+// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
-+// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
-+String SystemStats::getUserLanguage()    { return String("en"); }
-+String SystemStats::getUserRegion()      { return String("US"); }
-+#endif
- String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); }
- 
- //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/juce_core.cpp b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+index 8bac812..e23b422 100644
+--- a/JuceLibraryCode/modules/juce_core/juce_core.cpp
++++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+@@ -92,7 +92,7 @@
+  #include <net/if.h>
+  #include <sys/ioctl.h>
+ 
+- #if ! JUCE_ANDROID
++ #if ! JUCE_ANDROID && defined(__GLIBC__)
+   #include <execinfo.h>
+  #endif
+ #endif
+diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+index 2d7faa3..f132405 100644
+--- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+@@ -139,8 +139,15 @@ static String getLocaleValue (nl_item key)
+     return result;
+ }
+ 
++#if defined(__GLIBC__)
+ String SystemStats::getUserLanguage()     { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
+ String SystemStats::getUserRegion()       { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
++#else
++// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
++// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
++String SystemStats::getUserLanguage()     { return String("en"); }
++String SystemStats::getUserRegion()       { return String("US"); }
++#endif
+ String SystemStats::getDisplayLanguage()  { return getUserLanguage() + "-" + getUserRegion(); }
+ 
+ //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+index 757ea24..6b61e16 100644
+--- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+@@ -138,7 +138,7 @@ String SystemStats::getStackBacktrace()
+ {
+     String result;
+ 
+-   #if JUCE_ANDROID || JUCE_MINGW
++   #if JUCE_ANDROID || JUCE_MINGW || !defined(__GLIBC__)
+     jassertfalse; // sorry, not implemented yet!
+ 
+    #elif JUCE_WINDOWS
diff --git a/srcpkgs/libopenshot-audio/template b/srcpkgs/libopenshot-audio/template
index 56c330eafcf1..254f8b6283bd 100644
--- a/srcpkgs/libopenshot-audio/template
+++ b/srcpkgs/libopenshot-audio/template
@@ -1,6 +1,6 @@
 # Template file for 'libopenshot-audio'
 pkgname=libopenshot-audio
-version=0.2.0
+version=0.2.2
 revision=1
 build_style=cmake
 hostmakedepends="doxygen"
@@ -11,7 +11,7 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot-audio"
 distfiles="https://github.com/OpenShot/libopenshot-audio/archive/v${version}.tar.gz"
-checksum=937ff4f1c2dfb8ab5d56ad85beacaa29dfd5a79af0d9cf647386034fe9882309
+checksum=66bedfda0d8d430598b21bc2dde6c0016a758a6c83467d0273a9d692de10baaf
 
 if [ "$XBPS_TARGET_NO_ATOMIC8" ]; then
 	makedepends+=" libatomic-devel"
@@ -20,9 +20,10 @@ fi
 
 libopenshot-audio-devel_package() {
 	short_desc+=" - development files"
-	depends+=" ${sourcepkg}>=${version}_${revision}"
+	depends+=" ${sourcepkg}>=${version}_${revision} alsa-lib-devel zlib-devel"
 	pkg_install() {
 		vmove usr/include
+		vmove usr/lib/cmake
 		vmove "usr/lib/*.so"
 	}
 }

From 75f8145b3f01182645f1df55791a5ed864b347d6 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:37 +0100
Subject: [PATCH 2/3] libopenshot: update to 0.2.7.

---
 common/shlibs                                 |  2 +-
 .../patches/AV_GET_CODEC_CONTEXT-macro.patch  | 34 -------------------
 srcpkgs/libopenshot/patches/fix-musl.patch    | 34 +++++++++++++++++++
 srcpkgs/libopenshot/template                  | 16 +++++----
 4 files changed, 45 insertions(+), 41 deletions(-)
 delete mode 100644 srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
 create mode 100644 srcpkgs/libopenshot/patches/fix-musl.patch

diff --git a/common/shlibs b/common/shlibs
index c66b4fbe6700..d339115dda39 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2598,7 +2598,7 @@ libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
 libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
-libopenshot.so.19 libopenshot-0.2.5_3
+libopenshot.so.21 libopenshot-0.2.7_1
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
 liblog.so android-studio-3.0.1_1
diff --git a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch b/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
deleted file mode 100644
index e6c640a8e11e..000000000000
--- a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/include/FFmpegUtilities.h	2020-03-03 09:00:06.000000000 +0100
-+++ b/include/FFmpegUtilities.h	2020-08-19 17:04:58.535806744 +0200
-@@ -163,11 +163,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
-@@ -199,11 +198,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
diff --git a/srcpkgs/libopenshot/patches/fix-musl.patch b/srcpkgs/libopenshot/patches/fix-musl.patch
new file mode 100644
index 000000000000..67424a848086
--- /dev/null
+++ b/srcpkgs/libopenshot/patches/fix-musl.patch
@@ -0,0 +1,34 @@
+diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
+index bde744d7..d07924fb 100644
+--- a/examples/CMakeLists.txt
++++ b/examples/CMakeLists.txt
+@@ -39,11 +39,19 @@ file(TO_NATIVE_PATH "${PROJECT_SOURCE_DIR}/examples/" TEST_MEDIA_PATH)
+ target_compile_definitions(openshot-example PRIVATE
+ 	-DTEST_MEDIA_PATH="${TEST_MEDIA_PATH}" )
+ 
++find_library(EXECINFO_FOUND execinfo)
++
+ # Link test executable to the new library
+ target_link_libraries(openshot-example openshot)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-example execinfo)
++endif()
+ 
+ add_executable(openshot-html-example ExampleHtml.cpp)
+ target_link_libraries(openshot-html-example openshot Qt5::Gui)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-html-example execinfo)
++endif()
+ 
+ ############### PLAYER EXECUTABLE ################
+ # Create test executable
+@@ -53,6 +61,9 @@ set_target_properties(openshot-player PROPERTIES AUTOMOC ON)
+ 
+ # Link test executable to the new library
+ target_link_libraries(openshot-player openshot)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-player execinfo)
++endif()
+ 
+ ############### TEST BLACKMAGIC CAPTURE APP ################
+ if (BLACKMAGIC_FOUND)
diff --git a/srcpkgs/libopenshot/template b/srcpkgs/libopenshot/template
index b59472105b35..274f08e99251 100644
--- a/srcpkgs/libopenshot/template
+++ b/srcpkgs/libopenshot/template
@@ -1,11 +1,11 @@
 # Template file for 'libopenshot'
 pkgname=libopenshot
-version=0.2.5
-revision=6
-archs="i686 x86_64 ppc64le"
+version=0.2.7
+revision=1
 build_style=cmake
-configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON" # Builds fail with Ruby-2.4.1
-hostmakedepends="swig doxygen ruby python3 pkg-config"
+# Builds fail with Ruby-2.4.1
+configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON"
+hostmakedepends="swig doxygen ruby python3 pkg-config qt5-qmake qt5-host-tools"
 makedepends="python3-devel ffmpeg-devel libmagick-devel qt5-devel libgomp-devel
  libopenshot-audio-devel qt5-multimedia-devel unittest-cpp zeromq-devel cppzmq
  jsoncpp-devel"
@@ -15,10 +15,14 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="LGPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot"
 distfiles="https://github.com/OpenShot/libopenshot/archive/v${version}.tar.gz"
-checksum=8ae7d226fbd2efbc84da4f7d9d8c7f3cc9616e4de46e1233e3b0a84ac0a429bc
+checksum=568eab6d69d469c5f745f0e25387ca5e000f7c28be48417b0d7770577ac74a28
 # FIXME: tests segfault
 make_check=extended
 
+if [ "$XBPS_TARGET_LIBC" = musl ]; then
+	makedepends+=" libexecinfo-devel"
+fi
+
 libopenshot-devel_package() {
 	short_desc+=" - development files"
 	pkg_install() {

From b0fed979202b5ff4e389407bd314d7c9c6ad0aff Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:51 +0100
Subject: [PATCH 3/3] openshot: update to 2.6.1.

---
 .../openshot/patches/0001-video_widget.patch  |   74 +
 .../patches/0002-python3.10_int.patch         | 1993 +++++++++++++++++
 srcpkgs/openshot/template                     |   12 +-
 3 files changed, 2073 insertions(+), 6 deletions(-)
 create mode 100644 srcpkgs/openshot/patches/0001-video_widget.patch
 create mode 100644 srcpkgs/openshot/patches/0002-python3.10_int.patch

diff --git a/srcpkgs/openshot/patches/0001-video_widget.patch b/srcpkgs/openshot/patches/0001-video_widget.patch
new file mode 100644
index 000000000000..5e93d764c4bf
--- /dev/null
+++ b/srcpkgs/openshot/patches/0001-video_widget.patch
@@ -0,0 +1,74 @@
+From 9748a13268d66a5949aebc970637b5903756d018 Mon Sep 17 00:00:00 2001
+From: Jonathan Thomas <jonathan@openshot.org>
+Date: Thu, 7 Oct 2021 13:53:09 -0500
+Subject: [PATCH] Support for previewing anamorphic video profiles, including a
+ few code clean-ups.
+
+---
+ src/windows/video_widget.py | 22 +++++++---------------
+ 1 file changed, 7 insertions(+), 15 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 7326598d3..842deb3ba 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -77,28 +77,20 @@ def changed(self, action):
+             if action.type == "load" and action.values.get("pixel_ratio"):
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("pixel_ratio").get("num", 16),
+-                    action.values.get("pixel_ratio").get("den", 9))
++                    action.values.get("pixel_ratio").get("num", 1),
++                    action.values.get("pixel_ratio").get("den", 1))
+                 log.info(
+                     "Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+             elif action.key and action.key[0] == "pixel_ratio":
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("num", 16),
+-                    action.values.get("den", 9))
++                    action.values.get("num", 1),
++                    action.values.get("den", 1))
+                 log.info(
+                     "Update: Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+ 
+-            # Update max size (to size of video preview viewport)
+-            if display_ratio_changed or pixel_ratio_changed:
+-                timeline = get_app().window.timeline_sync.timeline
+-                timeline.SetMaxSize(
+-                    round(self.width() * self.pixel_ratio.ToFloat()),
+-                    round(self.height() * self.pixel_ratio.ToFloat())
+-                    )
+-
+ 
+     def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+      x1=None, y1=None, x2=None, y2=None, rotation = None):
+@@ -236,7 +228,7 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height)
++            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+@@ -405,7 +397,7 @@ def paintEvent(self, event, *args):
+         self.mutex.unlock()
+ 
+     def centeredViewport(self, width, height):
+-        """ Calculate size of viewport to maintain apsect ratio """
++        """ Calculate size of viewport to maintain aspect ratio """
+ 
+         # Calculate padding
+         top_padding = (height - (height * self.zoom)) / 2.0
+@@ -416,7 +408,7 @@ def centeredViewport(self, width, height):
+         height = height * self.zoom
+ 
+         # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        aspectRatio = self.aspect_ratio.ToFloat()
+         heightFromWidth = width / aspectRatio
+         widthFromHeight = height * aspectRatio
+ 
diff --git a/srcpkgs/openshot/patches/0002-python3.10_int.patch b/srcpkgs/openshot/patches/0002-python3.10_int.patch
new file mode 100644
index 000000000000..8f6dc53dbf40
--- /dev/null
+++ b/srcpkgs/openshot/patches/0002-python3.10_int.patch
@@ -0,0 +1,1993 @@
+From a3088503500e79877ce985e4784f75478d9b792e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Mon, 20 Sep 2021 05:36:19 -0400
+Subject: [PATCH 1/6] Preferences: Fix logging calls
+
+---
+ src/launch.py              |  2 +-
+ src/windows/preferences.py | 18 +++++++++++-------
+ 2 files changed, 12 insertions(+), 8 deletions(-)
+
+diff --git a/src/launch.py b/src/launch.py
+index 9fc600b23..b614e9bc7 100755
+--- a/src/launch.py
++++ b/src/launch.py
+@@ -41,7 +41,7 @@
+  """
+ 
+ import sys
+-import os.path
++import os
+ import argparse
+ 
+ from PyQt5.QtCore import Qt
+diff --git a/src/windows/preferences.py b/src/windows/preferences.py
+index bf7d67b0c..325f496b3 100644
+--- a/src/windows/preferences.py
++++ b/src/windows/preferences.py
+@@ -102,7 +102,7 @@ def __init__(self):
+ 
+     def txtSearch_changed(self):
+         """textChanged event handler for search box"""
+-        log.info("Search for %s" % self.txtSearch.text())
++        log.info("Search for %s", self.txtSearch.text())
+ 
+         # Populate preferences
+         self.Populate(filter=self.txtSearch.text())
+@@ -317,7 +317,7 @@ def Populate(self, filter=""):
+                                 value_list.remove(value_item)
+ 
+                         # Remove hardware mode items which cannot decode the example video
+-                        log.debug("Preparing to test hardware decoding: %s" % (value_list))
++                        log.debug("Preparing to test hardware decoding: %s", value_list)
+                         for value_item in list(value_list):
+                             v = value_item["value"]
+                             if (not self.testHardwareDecode(value_list, v, 0)
+@@ -470,7 +470,7 @@ def bool_value_changed(self, widget, param, state):
+         # Trigger specific actions
+         if param["setting"] == "debug-mode":
+             # Update debug setting of timeline
+-            log.info("Setting debug-mode to %s" % (state == Qt.Checked))
++            log.info("Setting debug-mode to %s", state == Qt.Checked)
+             debug_enabled = (state == Qt.Checked)
+ 
+             # Enable / Disable logger
+@@ -528,7 +528,9 @@ def text_value_changed(self, widget, param, value=None):
+         if param.get("category") == "Keyboard":
+             previous_value = value
+             value = QKeySequence(value).toString()
+-            log.info("Parsing keyboard mapping via QKeySequence from %s to %s" % (previous_value, value))
++            log.info(
++                "Parsing keyboard mapping via QKeySequence from %s to %s",
++                previous_value, value)
+ 
+         # Save setting
+         self.s.set(param["setting"], value)
+@@ -604,11 +606,13 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+             if reader.GetFrame(0).CheckPixel(0, 0, 2, 133, 255, 255, 5):
+                 is_supported = True
+                 self.hardware_tests_cards[decoder_card].append(int(decoder))
+-                log.debug("Successful hardware decoder! %s (%s-%s)" % (decoder_name, decoder, decoder_card))
++                log.debug(
++                    "Successful hardware decoder! %s (%s-%s)",
++                    decoder_name, decoder, decoder_card)
+             else:
+                 log.debug(
+                     "CheckPixel failed testing hardware decoding (i.e. wrong color found): %s (%s-%s)",
+-                    (decoder_name, decoder, decoder_card))
++                    decoder_name, decoder, decoder_card)
+ 
+             reader.Close()
+             clip.Close()
+@@ -616,7 +620,7 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+         except Exception:
+             log.debug(
+                 "Exception trying to test hardware decoding (this is expected): %s (%s-%s)",
+-                (decoder_name, decoder, decoder_card))
++                decoder_name, decoder, decoder_card)
+ 
+         # Resume current settings
+         openshot.Settings.Instance().HARDWARE_DECODER = current_decoder
+
+From 1b14896d2057df80b0b20ba22e1380ba9e9bd6e6 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:01:39 -0400
+Subject: [PATCH 2/6] Enforce integer function arguments
+
+---
+ src/windows/export.py                     |   8 +-
+ src/windows/models/properties_model.py    |  34 +-
+ src/windows/process_effect.py             |   2 +-
+ src/windows/video_widget.py               | 596 ++++++++++++++--------
+ src/windows/views/effects_listview.py     |   7 +-
+ src/windows/views/effects_treeview.py     |   7 +-
+ src/windows/views/emojis_listview.py      |   7 +-
+ src/windows/views/files_listview.py       |   7 +-
+ src/windows/views/files_treeview.py       |   7 +-
+ src/windows/views/properties_tableview.py |  38 +-
+ src/windows/views/transitions_listview.py |   7 +-
+ src/windows/views/transitions_treeview.py |   7 +-
+ src/windows/views/tutorial.py             |  14 +-
+ 13 files changed, 476 insertions(+), 265 deletions(-)
+
+diff --git a/src/windows/export.py b/src/windows/export.py
+index a624eb2e2..6461afb25 100644
+--- a/src/windows/export.py
++++ b/src/windows/export.py
+@@ -290,7 +290,7 @@ def updateProgressBar(self, title_message, start_frame, end_frame, current_frame
+             percentage_string = format_of_progress_string % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
+         else:
+             percentage_string = "100%"
+-        self.progressExportVideo.setValue(current_frame)
++        self.progressExportVideo.setValue(int(current_frame))
+         self.progressExportVideo.setFormat(percentage_string)
+         self.setWindowTitle("%s %s" % (percentage_string, title_message))
+ 
+@@ -690,9 +690,9 @@ def titlestring(sec, fps, mess):
+         fps_encode = 0
+ 
+         # Init progress bar
+-        self.progressExportVideo.setMinimum(self.txtStartFrame.value())
+-        self.progressExportVideo.setMaximum(self.txtEndFrame.value())
+-        self.progressExportVideo.setValue(self.txtStartFrame.value())
++        self.progressExportVideo.setMinimum(int(self.txtStartFrame.value()))
++        self.progressExportVideo.setMaximum(int(self.txtEndFrame.value()))
++        self.progressExportVideo.setValue(int(self.txtStartFrame.value()))
+ 
+         # Prompt error message
+         if self.txtStartFrame.value() == self.txtEndFrame.value():
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index c3236ed84..40897f642 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -414,8 +414,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+             new_value = None
+ 
+         log.info(
+-            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s"
+-            % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x))
++            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s",
++            property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x)
+ 
+         # Find this clip
+         c = None
+@@ -518,35 +518,35 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     try:
+                         clip_data[property_key] = int(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Integer value passed to property: %s' % ex)
++                        log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Float value passed to property: %s' % ex)
++                        log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Boolean value passed to property: %s' % ex)
++                        log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid String value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid String value passed to property', exc_info=1)
+ 
+                 elif property_type in ["font", "caption"]:
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid Font/Caption value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid Font/Caption value passed to property', exc_info=1)
+ 
+                 elif property_type == "reader":
+                     # Transition
+@@ -557,8 +557,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_data[property_key] = json.loads(clip_object.Reader().Json())
+                         clip_object.Close()
+                         clip_object = None
+-                    except Exception as ex:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)' % (value, ex))
++                    except Exception:
++                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
+@@ -688,9 +688,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Color needs to be handled special
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             if readonly or type in ["color", "font", "caption"] or choices or label == "Track":
+@@ -789,9 +789,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Update the color based on the color curves
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             # Update helper dictionary
+diff --git a/src/windows/process_effect.py b/src/windows/process_effect.py
+index e4f3120c0..ea0c2946e 100644
+--- a/src/windows/process_effect.py
++++ b/src/windows/process_effect.py
+@@ -352,7 +352,7 @@ def accept(self):
+         while(not processing.IsDone() ):
+             # update progressbar
+             progressionStatus = processing.GetProgress()
+-            self.progressBar.setValue(progressionStatus)
++            self.progressBar.setValue(int(progressionStatus))
+             time.sleep(0.01)
+ 
+             # Process any queued events
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index d5c89a204..f33696c5c 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -25,8 +25,11 @@
+  along with OpenShot Library.  If not, see <http://www.gnu.org/licenses/>.
+  """
+ 
++import json
++
+ from PyQt5.QtCore import (
+-    Qt, QCoreApplication, QPointF, QPoint, QRect, QRectF, QSize, QMutex, QTimer
++    Qt, QCoreApplication, QMutex, QTimer,
++    QPoint, QPointF, QSize, QSizeF, QRect, QRectF,
+ )
+ from PyQt5.QtGui import (
+     QTransform, QPainter, QIcon, QColor, QPen, QBrush, QCursor, QImage, QRegion
+@@ -41,8 +44,6 @@
+ from classes.app import get_app
+ from classes.query import Clip, Effect
+ 
+-import json
+-
+ 
+ class VideoWidget(QWidget, updates.UpdateInterface):
+     """ A QWidget used on the video display widget """
+@@ -86,37 +87,68 @@ def changed(self, action):
+                     self.pixel_ratio.ToFloat())
+ 
+ 
+-    def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-     x1=None, y1=None, x2=None, y2=None, rotation = None):
++    def drawTransformHandler(
++        self, painter, sx, sy, source_width, source_height,
++        origin_x, origin_y,
++        x1=None, y1=None, x2=None, y2=None, rotation = None
++    ):
+         # Draw transform corners and center origin circle
+         # Corner size
+         cs = self.cs
+         os = 12.0
+ 
++        csx = cs / sx
++        csy = cs / sy
++
+         # Rotate the transform handler
+         if rotation:
+-            bbox_center_x = (((x1*source_width + x2*source_width) / 2.0) ) - ( (os/2) /sx)
+-            bbox_center_y = (((y1*source_height + y2*source_height) / 2.0) ) - ( (os/2) /sy)
++            bbox_center_x = ((x1*source_width + x2*source_width) / 2.0) - ((os / 2) / sx)
++            bbox_center_y = ((y1*source_height + y2*source_height) / 2.0) - ((os / 2) / sy)
+             painter.translate(bbox_center_x, bbox_center_y)
+             painter.rotate(rotation)
+             painter.translate(-bbox_center_x, -bbox_center_y)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(x1*source_width, y1*source_height), QPointF(x2*source_width, y2*source_height))
++            self.clipBounds = QRectF(
++                QPointF(x1 * source_width, y1 * source_height),
++                QPointF(x2 * source_width, y2 * source_height)
++            )
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.topRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
+         else:
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(0.0, 0.0), QPointF(source_width, source_height))
++            self.clipBounds = QRectF(
++                QPointF(0.0, 0.0),
++                QPointF(source_width, source_height))
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(-cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(-cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                -csx / 2.0, -csy / 2.0, csx, csy)
++            self.topRightHandle = QRectF(
++                source_width - csx / 2.0, -csy / 2.0, csx, csy)
++            self.bottomLeftHandle = QRectF(
++                -csx / 2.0, source_height - csy / 2.0, csx, csy)
++            self.bottomRightHandle = QRectF(
++                source_width - csx / 2.0, source_height - csy / 2.0, csx, csy)
+ 
+         # Draw 4 corners
+         pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+@@ -127,47 +159,110 @@ def drawTransformHandler(self, painter, sx, sy, source_width, source_height, ori
+         painter.drawRect(self.bottomLeftHandle)
+         painter.drawRect(self.bottomRightHandle)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y1*source_height)-cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y2*source_height)-( cs/sy/2.0), cs/sx, cs/sy)
+-            self.leftHandle = QRectF((x1*source_width)-(cs/sx/2.0), ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF((x2*source_width) - (cs/sx) + cs/sx/2.0, ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y1 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y2 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                (x1 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                (x2 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx, csy)
+ 
+         else:
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.leftHandle = QRectF(-cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                (source_width - csx) / 2.0,
++                -csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                (source_width - csx) / 2.0,
++                source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                -csx / 2.0,
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                source_width - (csx / 2.0),
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
+ 
+         # Calculate shear handles
+-        self.topShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
+-        self.leftShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.topLeftHandle.width(), self.clipBounds.height())
+-        self.rightShearHandle = QRectF(self.topRightHandle.x(), self.topRightHandle.y(), self.topRightHandle.width(), self.clipBounds.height())
+-        self.bottomShearHandle = QRectF(self.bottomLeftHandle.x(), self.bottomLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
++        self.topShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
++        self.leftShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.topLeftHandle.width(),
++            self.clipBounds.height())
++        self.rightShearHandle = QRectF(
++            self.topRightHandle.x(),
++            self.topRightHandle.y(),
++            self.topRightHandle.width(),
++            self.clipBounds.height())
++        self.bottomShearHandle = QRectF(
++            self.bottomLeftHandle.x(),
++            self.bottomLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
+ 
+         # Draw 4 sides (centered)
+-        painter.drawRect(self.topHandle)
+-        painter.drawRect(self.bottomHandle)
+-        painter.drawRect(self.leftHandle)
+-        painter.drawRect(self.rightHandle)
+-        painter.drawRect(self.clipBounds)
++        painter.drawRects([
++            self.topHandle,
++            self.bottomHandle,
++            self.leftHandle,
++            self.rightHandle,
++            self.clipBounds,
++            ])
+ 
+         # Calculate center coordinate
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             cs = 5.0
+             os = 7.0
+-            self.centerHandle = QRectF( (((x1*source_width+x2*source_width) / 2.0) ) - (os/sx), (((y1*source_height+y2*source_height) / 2.0) ) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                ((x1 + x2) * source_width / 2.0) - (os / sx),
++                ((y1 + y2) * source_height / 2.0) - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0
++            )
+         else:
+-            self.centerHandle = QRectF((source_width * origin_x) - (os/sx), (source_height * origin_y) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                source_width * origin_x - (os / sx),
++                source_height * origin_y - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0)
+ 
+         # Draw origin
+         painter.drawEllipse(self.centerHandle)
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) - self.centerHandle.height(),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) + self.centerHandle.height())
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0) - self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0) + self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0))
++
++        # Draw cross at origin center, extending beyond ellipse by 25%
++        center = self.centerHandle.center()
++        halfW = QPointF(self.centerHandle.width() * 0.75, 0)
++        halfH = QPointF(0, self.centerHandle.height() * 0.75)
++        painter.drawLines(
++            center - halfW, center + halfW,
++            center - halfH, center + halfH,
++        )
+ 
+         # Remove transform
+         painter.resetTransform()
+@@ -179,7 +274,11 @@ def paintEvent(self, event, *args):
+ 
+         # Paint custom frame image on QWidget
+         painter = QPainter(self)
+-        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++        painter.setRenderHints(
++            QPainter.Antialiasing
++            | QPainter.SmoothPixmapTransform
++            | QPainter.TextAntialiasing,
++            True)
+ 
+         # Fill the whole widget with the solid color
+         painter.fillRect(event.rect(), QColor("#191919"))
+@@ -191,7 +290,7 @@ def paintEvent(self, event, *args):
+             # DRAW FRAME
+             # Calculate new frame image size, maintaining aspect ratio
+             pixSize = self.current_image.size()
+-            pixSize.scale(event.rect().width(), event.rect().height(), Qt.KeepAspectRatio)
++            pixSize.scale(event.rect().size(), Qt.KeepAspectRatio)
+             self.curr_frame_size = pixSize
+ 
+             # Scale image (take into account display scaling for High DPI monitors)
+@@ -223,13 +322,19 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
++            pixel_adjust = self.pixel_ratio.Reciprocal().ToDouble()
++            source_size = QSize(
++                int(source_width),
++                int(source_height * pixel_adjust))
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+ 
+             # Set scale as STRETCH if the clip is attached to an object
+-            if (raw_properties.get('parentObjectId').get('memo') != 'None' and len(raw_properties.get('parentObjectId').get('memo')) > 0 ):
++            if (
++                    raw_properties.get('parentObjectId').get('memo') != 'None'
++                    and len(raw_properties.get('parentObjectId').get('memo')) > 0
++            ):
+                 scale = openshot.SCALE_STRETCH
+ 
+             if scale == openshot.SCALE_FIT:
+@@ -239,12 +344,7 @@ def paintEvent(self, event, *args):
+                 source_size.scale(player_width, player_height, Qt.IgnoreAspectRatio)
+ 
+             elif scale == openshot.SCALE_CROP:
+-                width_size = QSize(player_width, round(player_width / (float(source_width) / float(source_height))))
+-                height_size = QSize(round(player_height / (float(source_height) / float(source_width))), player_height)
+-                if width_size.width() >= player_width and width_size.height() >= player_height:
+-                    source_size.scale(width_size.width(), width_size.height(), Qt.KeepAspectRatio)
+-                else:
+-                    source_size.scale(height_size.width(), height_size.height(), Qt.KeepAspectRatio)
++                source_size.scale(player_width, player_height, Qt.KeepAspectRatioByExpanding)
+ 
+             # Get new source width / height (after scaling mode applied)
+             source_width = source_size.width()
+@@ -285,9 +385,6 @@ def paintEvent(self, event, *args):
+                 x += player_width - scaled_source_width  # right
+                 y += (player_height - scaled_source_height)  # bottom
+ 
+-            # Track gravity starting coordinate
+-            self.gravity_point = QPointF(x, y)
+-
+             # Adjust x,y for location
+             x_offset = raw_properties.get('location_x').get('value')
+             y_offset = raw_properties.get('location_y').get('value')
+@@ -329,7 +426,6 @@ def paintEvent(self, event, *args):
+                     raw_properties_effect = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+                     # Get properties for the first object in dict. PropertiesJSON should return one object at the time
+                     tmp = raw_properties_effect.get('objects')
+-                    tmp2 = tmp.keys()
+                     obj_id = list(tmp.keys())[0]
+                     raw_properties_effect = raw_properties_effect.get('objects').get(obj_id)
+ 
+@@ -342,10 +438,19 @@ def paintEvent(self, event, *args):
+                             y1 = raw_properties_effect['y1']['value']
+                             x2 = raw_properties_effect['x2']['value']
+                             y2 = raw_properties_effect['y2']['value']
+-                            self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-                                x1, y1, x2, y2, rotation)
++                            self.drawTransformHandler(
++                                painter,
++                                sx, sy,
++                                source_width, source_height,
++                                origin_x, origin_y,
++                                x1, y1, x2, y2,
++                                rotation)
+             else:
+-                self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y)
++                self.drawTransformHandler(
++                    painter,
++                    sx, sy,
++                    source_width, source_height,
++                    origin_x, origin_y)
+ 
+         if self.region_enabled:
+             # Paint region selector onto video preview
+@@ -376,11 +481,21 @@ def paintEvent(self, event, *args):
+                 pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+                 pen.setCosmetic(True)
+                 painter.setPen(pen)
+-                painter.drawRect(self.regionTopLeftHandle.x() - (cs/2.0/self.zoom), self.regionTopLeftHandle.y() - (cs/2.0/self.zoom), self.regionTopLeftHandle.width() / self.zoom, self.regionTopLeftHandle.height() / self.zoom)
+-                painter.drawRect(self.regionBottomRightHandle.x() - (cs/2.0/self.zoom), self.regionBottomRightHandle.y() - (cs/2.0/self.zoom), self.regionBottomRightHandle.width() / self.zoom, self.regionBottomRightHandle.height() / self.zoom)
+-                region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                        self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                        self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
++                painter.drawRect(
++                    self.regionTopLeftHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.width() / self.zoom,
++                    self.regionTopLeftHandle.height() / self.zoom)
++                painter.drawRect(
++                    self.regionBottomRightHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.width() / self.zoom,
++                    self.regionBottomRightHandle.height() / self.zoom)
++                region_rect = QRectF(
++                    self.regionTopLeftHandle.x(),
++                    self.regionTopLeftHandle.y(),
++                    self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                    self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
+                 painter.drawRect(region_rect)
+ 
+             # Remove transform
+@@ -394,23 +509,15 @@ def paintEvent(self, event, *args):
+     def centeredViewport(self, width, height):
+         """ Calculate size of viewport to maintain aspect ratio """
+ 
+-        # Calculate padding
+-        top_padding = (height - (height * self.zoom)) / 2.0
+-        left_padding = (width - (width * self.zoom)) / 2.0
++        window_size = QSizeF(width, height)
++        window_rect = QRectF(QPointF(0, 0), window_size)
+ 
+-        # Adjust parameters to zoom
+-        width = width * self.zoom
+-        height = height * self.zoom
++        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        viewport_size = QSizeF(aspectRatio, 1).scaled(window_size, Qt.KeepAspectRatio)
++        viewport_rect = QRectF(QPointF(0, 0), viewport_size)
++        viewport_rect.moveCenter(window_rect.center())
+ 
+-        # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat()
+-        heightFromWidth = width / aspectRatio
+-        widthFromHeight = height * aspectRatio
+-
+-        if heightFromWidth <= height:
+-            return QRect(left_padding, ((height - heightFromWidth) / 2) + top_padding, width, heightFromWidth)
+-        else:
+-            return QRect(((width - widthFromHeight) / 2.0) + left_padding, top_padding, widthFromHeight, height)
++        return viewport_rect.toRect()
+ 
+     def present(self, image, *args):
+         """ Present the current frame """
+@@ -448,12 +555,16 @@ def mouseReleaseEvent(self, event):
+         # This can be used other widgets to display the selected region
+         if self.region_enabled:
+             # Get region coordinates
+-            region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                 self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                 self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()).normalized()
++            region_rect = QRectF(
++                self.regionTopLeftHandle.x(),
++                self.regionTopLeftHandle.y(),
++                self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()
++            ).normalized()
+ 
+             # Map region (due to zooming)
+-            mapped_region_rect = self.region_transform.mapToPolygon(region_rect.toRect()).boundingRect()
++            mapped_region_rect = self.region_transform.mapToPolygon(
++                region_rect.toRect()).boundingRect()
+ 
+             # Render a scaled version of the region (as a QImage)
+             # TODO: Grab higher quality pixmap from the QWidget, as this method seems to be 1/2 resolution
+@@ -461,14 +572,25 @@ def mouseReleaseEvent(self, event):
+             scale = 3.0
+ 
+             # Map rect to transform (for scaling video elements)
+-            mapped_region_rect = QRect(mapped_region_rect.x(), mapped_region_rect.y(), mapped_region_rect.width() * scale, mapped_region_rect.height() * scale)
++            mapped_region_rect = QRect(
++                mapped_region_rect.x(),
++                mapped_region_rect.y(),
++                int(mapped_region_rect.width() * scale),
++                int(mapped_region_rect.height() * scale))
+ 
+             # Render QWidget onto scaled QImage
+-            self.region_qimage = QImage(mapped_region_rect.width(), mapped_region_rect.height(), QImage.Format_RGBA8888)
++            self.region_qimage = QImage(
++                mapped_region_rect.size(), QImage.Format_RGBA8888)
+             region_painter = QPainter(self.region_qimage)
+-            region_painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++            region_painter.setRenderHints(
++                QPainter.Antialiasing
++                | QPainter.SmoothPixmapTransform
++                | QPainter.TextAntialiasing,
++                True)
+             region_painter.scale(scale, scale)
+-            self.render(region_painter, QPoint(0,0), QRegion(mapped_region_rect, QRegion.Rectangle))
++            self.render(
++                region_painter, QPoint(0, 0),
++                QRegion(mapped_region_rect, QRegion.Rectangle))
+             region_painter.end()
+ 
+         # Inform UpdateManager to accept updates, and only store our final update
+@@ -484,7 +606,8 @@ def mouseReleaseEvent(self, event):
+     def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+         """Rotate cursor based on the current transform"""
+         rotated_pixmap = pixmap.transformed(
+-            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8), Qt.SmoothTransformation)
++            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8),
++            Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+     def getTransformMode(self, rotation, shear_x, shear_y, event):
+@@ -627,6 +750,10 @@ def mouseMoveEvent(self, event):
+ 
+             # Transform clip object
+             if self.transform_mode:
++
++                x_motion = event.pos().x() - self.mouse_position.x()
++                y_motion = event.pos().y() - self.mouse_position.y()
++
+                 if self.transform_mode == 'origin':
+                     # Get current keyframe value
+                     origin_x = raw_properties.get('origin_x').get('value')
+@@ -635,8 +762,8 @@ def mouseMoveEvent(self, event):
+                     scale_y = raw_properties.get('scale_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    origin_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() * scale_x)
+-                    origin_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() * scale_y)
++                    origin_x += x_motion / (self.clipBounds.width() * scale_x)
++                    origin_y += y_motion / (self.clipBounds.height() * scale_y)
+ 
+                     # Constrain to clip
+                     if origin_x < 0.0:
+@@ -648,8 +775,13 @@ def mouseMoveEvent(self, event):
+                     if origin_y > 1.0:
+                         origin_y = 1.0
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_x', origin_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_y', origin_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_x', origin_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_y', origin_y)
+ 
+                 elif self.transform_mode == 'location':
+                     # Get current keyframe value
+@@ -657,12 +789,17 @@ def mouseMoveEvent(self, event):
+                     location_y = raw_properties.get('location_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                    location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                    location_x += x_motion / viewport_rect.width()
++                    location_y += y_motion / viewport_rect.height()
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_x', location_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_y', location_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_x', location_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_y', location_y)
+ 
+                 elif self.transform_mode == 'shear_top':
+                     # Get current keyframe shear value
+@@ -672,11 +809,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x -= (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_bottom':
+                     # Get current keyframe shear value
+@@ -686,11 +825,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x += (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_left':
+                     # Get current keyframe shear value
+@@ -701,11 +842,13 @@ def mouseMoveEvent(self, event):
+                     aspect_ratio = (
+                         self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y -= (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'shear_right':
+                     # Get current keyframe shear value
+@@ -713,13 +856,16 @@ def mouseMoveEvent(self, event):
+                     shear_y = raw_properties.get('shear_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    aspect_ratio = (self.clipBounds.height() / self.clipBounds.width()) * 2.0
++                    aspect_ratio = (
++                        self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y += (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'rotation':
+                     # Get current rotation keyframe value
+@@ -728,71 +874,68 @@ def mouseMoveEvent(self, event):
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                     # Calculate new location coordinates
+-                    is_on_left = event.pos().x() < self.originHandle.x()
++                    is_on_right = event.pos().x() > self.originHandle.x()
+                     is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                    if is_on_top:
+-                        rotation += (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-                    else:
+-                        rotation -= (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-
+-                    if is_on_left:
+-                        rotation -= (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
+-                    else:
+-                        rotation += (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
++                    x_adjust = x_motion / ((self.clipBounds.width() * scale_x) / 90)
++                    rotation += (x_adjust if is_on_top else -x_adjust)
++
++                    y_adjust = y_motion / ((self.clipBounds.height() * scale_y) / 90)
++                    rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'rotation', rotation)
++                    self.updateClipProperty(
++                        self.transforming_clip.id,
++                        clip_frame_number,
++                        'rotation', rotation)
+ 
+                 elif self.transform_mode.startswith('scale_'):
+                     # Get current scale keyframe value
+                     scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                    half_w = self.clipBounds.width() / 2.0
++                    half_h = self.clipBounds.height() / 2.0
++
+                     if self.transform_mode == 'scale_top_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top':
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom':
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x -= x_motion / half_w
+                     elif self.transform_mode == 'scale_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x += x_motion / half_w
+ 
+                     if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+                         # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                        if scale_x and scale_y:
++                        if scale_x:
+                             scale_y = scale_x
+                         elif scale_y:
+                             scale_x = scale_y
+-                        elif scale_x:
+-                            scale_y = scale_x
+ 
+                     # Update keyframe value (or create new one)
+                     both_scaled = scale_x != 0.001 and scale_y != 0.001
+                     if scale_x != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_x', scale_x, refresh=(not both_scaled))
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_x', scale_x,
++                            refresh=(not both_scaled))
+                     if scale_y != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_y', scale_y)
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -803,16 +946,29 @@ def mouseMoveEvent(self, event):
+             cs = self.cs
+ 
+             # Adjust existing region coordinates (if any)
+-            if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++            if (not self.mouse_dragging
++                and self.resize_button.isVisible()
++                and self.resize_button.rect().contains(event.pos())
++            ):
+                 # Mouse over resize button (and not currently dragging)
+                 self.setCursor(Qt.ArrowCursor)
+-            elif self.region_transform and self.regionTopLeftHandle and self.region_transform.mapToPolygon(self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionTopLeftHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_top_left':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+                 if self.mouse_dragging and not self.region_mode:
+                     self.region_mode = 'scale_top_left'
+-            elif self.region_transform and self.regionBottomRightHandle and self.region_transform.mapToPolygon(self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionBottomRightHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_bottom_right':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+@@ -824,13 +980,25 @@ def mouseMoveEvent(self, event):
+             # Initialize new region coordinates at current event.pos()
+             if self.mouse_dragging and not self.region_mode:
+                 self.region_mode = 'scale_bottom_right'
+-                self.regionTopLeftHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
+-                self.regionBottomRightHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
++                self.regionTopLeftHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
++                self.regionBottomRightHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
+ 
+             # Move existing region coordinates
+             if self.mouse_dragging:
+-                diff_x = self.region_transform_inverted.map(event.pos()).x() - self.region_transform_inverted.map(self.mouse_position).x()
+-                diff_y = self.region_transform_inverted.map(event.pos()).y() - self.region_transform_inverted.map(self.mouse_position).y()
++                diff_x = int(
++                    self.region_transform_inverted.map(event.pos()).x()
++                    - self.region_transform_inverted.map(self.mouse_position).x()
++                )
++                diff_y = int(
++                    self.region_transform_inverted.map(event.pos()).y()
++                    - self.region_transform_inverted.map(self.mouse_position).y()
++                )
+                 if self.region_mode == 'scale_top_left':
+                     self.regionTopLeftHandle.adjust(diff_x, diff_y, diff_x, diff_y)
+                 elif self.region_mode == 'scale_bottom_right':
+@@ -859,12 +1027,11 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-
+-
+             if self.transforming_effect_object.info.has_tracked_object:
+                 # Get properties of effect at current frame
+                 raw_properties = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+-                 # Get properties for the first object in dict. PropertiesJSON should return one object at the time
++                # Get properties for the first object in dict.
++                # PropertiesJSON should return one object at the time
+                 obj_id = list(raw_properties.get('objects').keys())[0]
+                 raw_properties = raw_properties.get('objects').get(obj_id)
+ 
+@@ -878,18 +1045,28 @@ def mouseMoveEvent(self, event):
+                 # Transform effect object
+                 if self.transform_mode:
+ 
++                    x_motion = event.pos().x() - self.mouse_position.x()
++                    y_motion = event.pos().y() - self.mouse_position.y()
++
+                     if self.transform_mode == 'location':
+                         # Get current keyframe value
+                         location_x = raw_properties.get('delta_x').get('value')
+                         location_y = raw_properties.get('delta_y').get('value')
+ 
+                         # Calculate new location coordinates
+-                        location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                        location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                        location_x += x_motion / viewport_rect.width()
++                        location_y += y_motion / viewport_rect.height()
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_x', location_x, refresh=False)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_y', location_y)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_x', location_x,
++                            refresh=False)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_y', location_y)
+ 
+                     elif self.transform_mode == 'rotation':
+                         # Get current rotation keyframe value
+@@ -898,63 +1075,70 @@ def mouseMoveEvent(self, event):
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                         # Calculate new location coordinates
+-                        is_on_left = event.pos().x() < self.originHandle.x()
++                        is_on_right = event.pos().x() > self.originHandle.x()
+                         is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                        if is_on_top:
+-                            rotation += (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
+-                        else:
+-                            rotation -= (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
++                        x_adjust = x_motion / (self.clipBounds.width() * scale_x / 90)
++                        rotation += (x_adjust if is_on_top else -x_adjust)
+ 
+-                        if is_on_left:
+-                            rotation -= (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
+-                        else:
+-                            rotation += (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
++                        y_adjust = y_motion / (self.clipBounds.height() * scale_y / 90)
++                        rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'rotation', rotation)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id,
++                            clip_frame_number, obj_id,
++                            'rotation', rotation)
+ 
+                     elif self.transform_mode.startswith('scale_'):
+                         # Get current scale keyframe value
+                         scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                        half_w = self.clipBounds.width() / 2.0
++                        half_h = self.clipBounds.height() / 2.0
++
+                         if self.transform_mode == 'scale_top_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top':
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom':
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x -= x_motion / half_w
+                         elif self.transform_mode == 'scale_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x += x_motion / half_w
+ 
+                         if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+-                            # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                            if scale_x and scale_y:
++                            # If CTRL key is pressed, fix the scale_y to the correct aspect ratio
++                            if scale_x:
+                                 scale_y = scale_x
+                             elif scale_y:
+                                 scale_x = scale_y
+-                            elif scale_x:
+-                                scale_y = scale_x
+ 
+                         # Update keyframe value (or create new one)
+                         both_scaled = scale_x != 0.001 and scale_y != 0.001
+                         if scale_x != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_x', scale_x, refresh=(not both_scaled))
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_x', scale_x,
++                                refresh=(not both_scaled))
+                         if scale_y != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_y', scale_y)
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -1012,8 +1196,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             # No clip found
+             return
+ 
+-        for point in c.data['objects'][obj_id][property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        try:
++            props = c.data['objects'][obj_id]
++            points_list = props[property_key]["Points"]
++        except (TypeError, KeyError):
++            log.error("Corrupted project data!", exc_info=1)
++            return
++
++        for point in points_list:
++            log.info("looping points: co.X = %s", point["co"]["X"])
+ 
+             if point["co"]["X"] == frame_number:
+                 found_point = True
+@@ -1023,12 +1214,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+ 
+         if not found_point and new_value != None:
+             effect_updated = True
+-            log.info("Created new point at X=%s" % frame_number)
+-            c.data['objects'][obj_id][property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER})
++            log.info("Created new point at X=%s", frame_number)
++            points_list.append({
++                'co': { 'X': frame_number, 'Y': new_value },
++                'interpolation': openshot.BEZIER,
++                })
+ 
+         # Reduce # of clip properties we are saving (performance boost)
+         #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects').get(obj_id)}}
++        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+ 
+         if effect_updated:
+             c.save()
+@@ -1040,10 +1234,10 @@ def refreshTriggered(self):
+         """Signal to refresh viewport (i.e. a property might have changed that effects the preview)"""
+ 
+         # Update reference to clip
+-        if self and self.transforming_clip:
++        if self.transforming_clip:
+             self.transforming_clip = Clip.get(id=self.transforming_clip.id)
+ 
+-        if self and self.transforming_effect:
++        if self.transforming_effect:
+             self.transforming_effect = Effect.get(id=self.transforming_effect.id)
+ 
+     def transformTriggered(self, clip_id):
+@@ -1053,7 +1247,7 @@ def transformTriggered(self, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_clip and not clip_id:
++        if self.transforming_clip and not clip_id:
+             # Clear transform
+             self.transforming_clip = None
+             need_refresh = True
+@@ -1078,7 +1272,7 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_effect and not effect_id:
++        if self.transforming_effect and not effect_id:
+             # Clear transform
+             self.transforming_effect = None
+             self.transforming_clip = None
+@@ -1102,12 +1296,8 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+     def regionTriggered(self, clip_id):
+         """Handle the 'select region' signal when it's emitted"""
+-        if self and not clip_id:
+-            # Clear transform
+-            self.region_enabled = False
+-        else:
+-            self.region_enabled = True
+-
++        # Clear transform
++        self.region_enabled = bool(not clip_id)
+         get_app().window.refreshFrameSignal.emit()
+ 
+     def resizeEvent(self, event):
+@@ -1135,7 +1325,7 @@ def delayed_resize_callback(self):
+             ratio = float(project_size.width()) / float(project_size.height())
+             even_width = round(project_size.width() / 2.0) * 2
+             even_height = round(round(even_width / ratio) / 2.0) * 2
+-            project_size = QSize(even_width, even_height)
++            project_size = QSize(int(even_width), int(even_height))
+ 
+         # Emit signal that video widget changed size
+         self.win.MaxSizeChanged.emit(project_size)
+@@ -1199,7 +1389,6 @@ def __init__(self, watch_project=True, *args):
+         self.mouse_dragging = False
+         self.mouse_position = None
+         self.transform_mode = None
+-        self.gravity_point = None
+         self.original_clip_data = None
+         self.region_qimage = None
+         self.region_transform = None
+@@ -1208,8 +1397,8 @@ def __init__(self, watch_project=True, *args):
+         self.regionTopLeftHandle = None
+         self.regionBottomRightHandle = None
+         self.curr_frame_size = None # Frame size
+-        self.zoom = 1.0 # Zoom of widget (does not affect video, only workspace)
+-        self.cs = 14.0 # Corner size of Transform Handler rectangles
++        self.zoom = 1.0  # Zoom of widget (does not affect video, only workspace)
++        self.cs = 14.0  # Corner size of Transform Handler rectangles
+         self.resize_button = QPushButton(_('Reset Zoom'), self)
+         self.resize_button.hide()
+         self.resize_button.setStyleSheet('QPushButton { margin: 10px; padding: 2px; }')
+@@ -1251,7 +1440,6 @@ def __init__(self, watch_project=True, *args):
+ 
+         # Show Property timer
+         # Timer to use a delay before sending MaxSizeChanged signals (so we don't spam libopenshot)
+-        self.delayed_size = None
+         self.delayed_resize_timer = QTimer(self)
+         self.delayed_resize_timer.setInterval(200)
+         self.delayed_resize_timer.setSingleShot(True)
+diff --git a/src/windows/views/effects_listview.py b/src/windows/views/effects_listview.py
+index b7da28dc4..3ce3b4164 100644
+--- a/src/windows/views/effects_listview.py
++++ b/src/windows/views/effects_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsListView(QListView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -69,8 +70,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/effects_treeview.py b/src/windows/views/effects_treeview.py
+index 6a5ab79f4..910593524 100644
+--- a/src/windows/views/effects_treeview.py
++++ b/src/windows/views/effects_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/emojis_listview.py b/src/windows/views/emojis_listview.py
+index 6f09bc562..cb13c35a7 100644
+--- a/src/windows/views/emojis_listview.py
++++ b/src/windows/views/emojis_listview.py
+@@ -39,7 +39,8 @@
+ 
+ class EmojisListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def dragEnterEvent(self, event):
+         # If dragging urls onto widget, accept
+@@ -57,8 +58,8 @@ def startDrag(self, event):
+         drag = QDrag(self)
+         drag.setMimeData(self.model.mimeData(selected))
+         icon = self.model.data(selected[0], Qt.DecorationRole)
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+ 
+         # Create emoji file before drag starts
+         data = json.loads(drag.mimeData().text())
+diff --git a/src/windows/views/files_listview.py b/src/windows/views/files_listview.py
+index 83cdd0272..0e67306ae 100644
+--- a/src/windows/views/files_listview.py
++++ b/src/windows/views/files_listview.py
+@@ -38,7 +38,8 @@
+ 
+ class FilesListView(QListView):
+     """ A ListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -113,8 +114,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/files_treeview.py b/src/windows/views/files_treeview.py
+index d3fe74d88..776b71cc2 100644
+--- a/src/windows/views/files_treeview.py
++++ b/src/windows/views/files_treeview.py
+@@ -41,7 +41,8 @@
+ 
+ class FilesTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+ 
+@@ -114,8 +115,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/properties_tableview.py b/src/windows/views/properties_tableview.py
+index e79b13644..fdaa7ee24 100644
+--- a/src/windows/views/properties_tableview.py
++++ b/src/windows/views/properties_tableview.py
+@@ -53,8 +53,13 @@
+ 
+ 
+ class PropertyDelegate(QItemDelegate):
+-    def __init__(self, parent=None, *args):
+-        QItemDelegate.__init__(self, parent, *args)
++    def __init__(self, parent=None, *args, **kwargs):
++
++        self.model = kwargs.pop("model", None)
++        if not self.model:
++            log.error("Cannot create delegate without data model!")
++
++        super().__init__(parent, *args, **kwargs)
+ 
+         # pixmaps for curve icons
+         self.curve_pixmaps = {
+@@ -68,7 +73,7 @@ def paint(self, painter, option, index):
+         painter.setRenderHint(QPainter.Antialiasing)
+ 
+         # Get data model and selection
+-        model = get_app().window.propertyTableView.clip_properties_model.model
++        model = self.model
+         row = model.itemFromIndex(index).row()
+         selected_label = model.item(row, 0)
+         selected_value = model.item(row, 1)
+@@ -104,16 +109,16 @@ def paint(self, painter, option, index):
+         painter.setPen(QPen(Qt.NoPen))
+         if property_type == "color":
+             # Color keyframe
+-            red = cur_property[1]["red"]["value"]
+-            green = cur_property[1]["green"]["value"]
+-            blue = cur_property[1]["blue"]["value"]
+-            painter.setBrush(QBrush(QColor(QColor(red, green, blue))))
++            red = int(cur_property[1]["red"]["value"])
++            green = int(cur_property[1]["green"]["value"])
++            blue = int(cur_property[1]["blue"]["value"])
++            painter.setBrush(QColor(red, green, blue))
+         else:
+             # Normal Keyframe
+             if option.state & QStyle.State_Selected:
+-                painter.setBrush(QBrush(QColor("#575757")))
++                painter.setBrush(QColor("#575757"))
+             else:
+-                painter.setBrush(QBrush(QColor("#3e3e3e")))
++                painter.setBrush(QColor("#3e3e3e"))
+ 
+         if readonly:
+             # Set text color for read only fields
+@@ -146,7 +151,10 @@ def paint(self, painter, option, index):
+ 
+             if points > 1:
+                 # Draw interpolation icon on top
+-                painter.drawPixmap(option.rect.x() + option.rect.width() - 30.0, option.rect.y() + 4, self.curve_pixmaps[interpolation])
++                painter.drawPixmap(
++                    int(option.rect.x() + option.rect.width() - 30.0),
++                    int(option.rect.y() + 4),
++                    self.curve_pixmaps[interpolation])
+ 
+             # Set text color
+             painter.setPen(QPen(Qt.white))
+@@ -818,9 +826,9 @@ def Color_Picker_Triggered(self, cur_property):
+         _ = get_app()._tr
+ 
+         # Get current value of color
+-        red = cur_property[1]["red"]["value"]
+-        green = cur_property[1]["green"]["value"]
+-        blue = cur_property[1]["blue"]["value"]
++        red = int(cur_property[1]["red"]["value"])
++        green = int(cur_property[1]["green"]["value"])
++        blue = int(cur_property[1]["blue"]["value"])
+ 
+         # Show color dialog
+         currentColor = QColor(red, green, blue)
+@@ -865,7 +873,7 @@ def __init__(self, *args):
+         self.files_model = self.win.files_model.model
+ 
+         # Connect to update signals, so our menus stay current
+-        self.win.files_model.ModelRefreshed.connect(self.refresh_menu)
++        self.files_model.dataChanged.connect(self.refresh_menu)
+         self.win.transition_model.ModelRefreshed.connect(self.refresh_menu)
+         self.menu_reset = False
+ 
+@@ -890,7 +898,7 @@ def __init__(self, *args):
+         self.setWordWrap(True)
+ 
+         # Set delegate
+-        delegate = PropertyDelegate()
++        delegate = PropertyDelegate(model=self.clip_properties_model.model)
+         self.setItemDelegateForColumn(1, delegate)
+         self.previous_x = -1
+ 
+diff --git a/src/windows/views/transitions_listview.py b/src/windows/views/transitions_listview.py
+index 09af86b2b..3ba3346f5 100644
+--- a/src/windows/views/transitions_listview.py
++++ b/src/windows/views/transitions_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/transitions_treeview.py b/src/windows/views/transitions_treeview.py
+index 6f903e913..7aa0cd481 100644
+--- a/src/windows/views/transitions_treeview.py
++++ b/src/windows/views/transitions_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -68,8 +69,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/tutorial.py b/src/windows/views/tutorial.py
+index e2d7fb861..24124799f 100644
+--- a/src/windows/views/tutorial.py
++++ b/src/windows/views/tutorial.py
+@@ -53,7 +53,12 @@ def paintEvent(self, event, *args):
+ 
+         painter.setPen(QPen(frameColor, 2))
+         painter.setBrush(self.palette().color(QPalette.Window))
+-        painter.drawRoundedRect(QRectF(31, 0, self.width() - 31, self.height()), 10, 10)
++        painter.drawRoundedRect(
++            QRectF(31, 0,
++                   self.width() - 31,
++                   self.height()
++                   ),
++            10, 10)
+ 
+         # Paint blue triangle (if needed)
+         if self.arrow:
+@@ -61,7 +66,8 @@ def paintEvent(self, event, *args):
+             path = QPainterPath()
+             path.moveTo(0, 35)
+             path.lineTo(31, 35 - arrow_height)
+-            path.lineTo(31, (35 - arrow_height) + (arrow_height * 2))
++            path.lineTo(
++                31, int((35 - arrow_height) + (arrow_height * 2)))
+             path.lineTo(0, 35)
+             painter.fillPath(path, frameColor)
+ 
+@@ -199,7 +205,9 @@ def process(self, parent_name=None):
+ 
+             # Create tutorial
+             self.position_widget = tutorial_object
+-            self.offset = QPoint(tutorial_details["x"], tutorial_details["y"])
++            self.offset = QPoint(
++                int(tutorial_details["x"]),
++                int(tutorial_details["y"]))
+             tutorial_dialog = TutorialDialog(tutorial_id, tutorial_details["text"], tutorial_details["arrow"], self)
+ 
+             # Connect signals
+
+From 9fc55120f36c36b2b6b67e499128993c25e535cf Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:02:56 -0400
+Subject: [PATCH 3/6] VideoWidget: New checkTransformMode
+
+Replacement for getTransformMode with less repetitious code
+---
+ src/windows/video_widget.py | 160 ++++++++++++++----------------------
+ 1 file changed, 61 insertions(+), 99 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index f33696c5c..1b9c35b54 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -610,106 +610,68 @@ def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+             Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+-    def getTransformMode(self, rotation, shear_x, shear_y, event):
++    def checkTransformMode(self, rotation, shear_x, shear_y, event):
++        handle_uis = [
++            {"handle": self.centerHandle, "mode": 'origin', "cursor": 'hand'},
++            {"handle": self.topRightHandle, "mode": 'scale_top_right', "cursor": 'resize_bdiag'},
++            {"handle": self.topHandle, "mode": 'scale_top', "cursor": 'resize_y'},
++            {"handle": self.topLeftHandle, "mode": 'scale_top_left', "cursor": 'resize_fdiag'},
++            {"handle": self.leftHandle, "mode": 'scale_left', "cursor": 'resize_x'},
++            {"handle": self.rightHandle, "mode": 'scale_right', "cursor": 'resize_x'},
++            {"handle": self.bottomLeftHandle, "mode": 'scale_bottom_left', "cursor": 'resize_bdiag'},
++            {"handle": self.bottomHandle, "mode": 'scale_bottom', "cursor": 'resize_y'},
++            {"handle": self.bottomRightHandle, "mode": 'scale_bottom_right', "cursor": 'resize_fdiag'},
++            {"handle": self.topShearHandle, "mode": 'shear_top', "cursor": 'shear_x'},
++            {"handle": self.leftShearHandle, "mode": 'shear_left', "cursor": 'shear_y'},
++            {"handle": self.rightShearHandle, "mode": 'shear_right', "cursor": 'shear_y'},
++            {"handle": self.bottomShearHandle, "mode": 'shear_bottom', "cursor": 'shear_x'},
++        ]
++        non_handle_uis = {
++            "region": self.clipBounds,
++            "inside": {"mode": 'location', "cursor": 'move'},
++            "outside": {"mode": 'rotation', "cursor": "rotate"}
++            }
++
+         # Mouse over resize button (and not currently dragging)
+-        if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++        if (not self.mouse_dragging
++            and self.resize_button.isVisible()
++            and self.resize_button.rect().contains(event.pos()
++        ):
+             self.setCursor(Qt.ArrowCursor)
+-        # Determine if cursor is over a handle
+-        elif self.transform.mapToPolygon(self.centerHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'origin':
+-                self.setCursor(self.rotateCursor(self.cursors.get('hand'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'origin'
+-        elif self.transform.mapToPolygon(self.topRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_right'
+-        elif self.transform.mapToPolygon(self.topHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top'
+-        elif self.transform.mapToPolygon(self.topLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_left'
+-        elif self.transform.mapToPolygon(self.leftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_left'
+-        elif self.transform.mapToPolygon(self.rightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_right'
+-        elif self.transform.mapToPolygon(self.bottomLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_left'
+-        elif self.transform.mapToPolygon(self.bottomHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom'
+-        elif self.transform.mapToPolygon(self.bottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_right'
+-        elif self.transform.mapToPolygon(self.topShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_top'
+-        elif self.transform.mapToPolygon(self.leftShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_left'
+-        elif self.transform.mapToPolygon(self.rightShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_right'
+-        elif self.transform.mapToPolygon(self.bottomShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_bottom'
+-        elif self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'location':
+-                self.setCursor(self.rotateCursor(self.cursors.get('move'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'location'
+-        elif not self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'rotation':
+-                self.setCursor(self.rotateCursor(self.cursors.get('rotate'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'rotation'
+-        elif not self.transform_mode:
+-            # Reset cursor when not over a handle
+-            self.setCursor(QCursor(Qt.ArrowCursor))
++            self.transform_mode = None
++            return
++
++        # If mouse is over a handle, set corresponding pointer/mode
++        for h in handle_uis:
++            if self.transform.mapToPolygon(
++                    h["handle"].toRect()
++                    ).containsPoint(event.pos(), Qt.OddEvenFill):
++                # Handle contains cursor
++                if self.transform_mode and self.transform_mode != h["mode"]:
++                    # We're in different xform mode, skip
++                    continue
++                if self.mouse_dragging:
++                    self.transform_mode = h["mode"]
++                self.setCursor(self.rotateCursor(
++                    self.cursors.get(h["cursor"]), rotation, shear_x, shear_y))
++                return
++
++        # If not over any handles, determne inside/outside clip rectangle
++        r = non_handle_uis.get("region")
++        if self.transform.mapToPolygon(r.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            nh = non_handle_uis.get("inside", {})
++        else:
++            nh = non_handle_uis.get("outside", {})
++        if self.mouse_dragging and not self.transform_mode:
++            self.transform_mode = nh.get("mode")
++        if not self.transform_mode or self.transform_mode == nh.get("mode"):
++            self.setCursor(self.rotateCursor(
++                self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-        return True
++
++        # If we got this far and we don't have a transform mode, reset the cursor
++        if not self.transform_mode:
++            self.setCursor(QCursor(Qt.ArrowCursor))
+ 
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+@@ -746,7 +708,7 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-            _ = self.getTransformMode(rotation, shear_x, shear_y, event)
++            self.checkTransformMode(rotation, shear_x, shear_y, event)
+ 
+             # Transform clip object
+             if self.transform_mode:
+@@ -1040,7 +1002,7 @@ def mouseMoveEvent(self, event):
+                     self.mutex.unlock()
+                     return
+ 
+-                _ = self.getTransformMode(0, 0, 0, event)
++                self.checkTransformMode(0, 0, 0, event)
+ 
+                 # Transform effect object
+                 if self.transform_mode:
+
+From 1f058f730bb36068bce5c645ecea45b98279b614 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:42:48 -0400
+Subject: [PATCH 4/6] VideoWidget: Protect property accesses
+
+---
+ src/windows/video_widget.py | 61 +++++++++++++++++++++----------------
+ 1 file changed, 34 insertions(+), 27 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 1b9c35b54..a4d42969f 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -635,7 +635,7 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+         # Mouse over resize button (and not currently dragging)
+         if (not self.mouse_dragging
+             and self.resize_button.isVisible()
+-            and self.resize_button.rect().contains(event.pos()
++            and self.resize_button.rect().contains(event.pos())
+         ):
+             self.setCursor(Qt.ArrowCursor)
+             self.transform_mode = None
+@@ -668,11 +668,6 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+             self.setCursor(self.rotateCursor(
+                 self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-
+-        # If we got this far and we don't have a transform mode, reset the cursor
+-        if not self.transform_mode:
+-            self.setCursor(QCursor(Qt.ArrowCursor))
+-
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+         self.mutex.lock()
+@@ -1121,27 +1116,37 @@ def updateClipProperty(self, clip_id, frame_number, property_key, new_value, ref
+             # No clip found
+             return
+ 
+-        for point in c.data[property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        # Property missing? Create it!
++        if property_key not in c.data:
++            c.data[property_key] = {"Points": []}
++            log.warning(
++                "%s: Added missing '%s' to property data",
++                clip_id, property_key)
+ 
+-            if point["co"]["X"] == frame_number:
++        points = c.data.get(property_key).get("Points")
++        for point in points:
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s" % co.get("X"))
++
++            if co.get("X") == frame_number:
+                 found_point = True
+                 clip_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+         if not found_point and new_value is not None:
+             clip_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             c.data[property_key]["Points"].append({
+-                'co': {'X': frame_number, 'Y': new_value},
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        c.data = {property_key: c.data.get(property_key)}
+-
+         if clip_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            c.data = {property_key: c.data.get(property_key)}
+             c.save()
+             # Update the preview
+             if refresh:
+@@ -1166,27 +1171,29 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             return
+ 
+         for point in points_list:
+-            log.info("looping points: co.X = %s", point["co"]["X"])
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s", co.get("X"))
+ 
+-            if point["co"]["X"] == frame_number:
++            if co.get("X") == frame_number:
+                 found_point = True
+                 effect_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+-        if not found_point and new_value != None:
++        if not found_point and new_value is not None:
+             effect_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             points_list.append({
+-                'co': { 'X': frame_number, 'Y': new_value },
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER,
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+-
+         if effect_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            #TODO: This is too slow when draging transform handlers
++            c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+             c.save()
+             # Update the preview
+             if refresh:
+
+From 7df87ddc189c5e5031fbb19df13378428fab951e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:52:19 -0400
+Subject: [PATCH 5/6] classes/thumbnail: Fix dangling filehandles
+
+---
+ src/classes/thumbnail.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/classes/thumbnail.py b/src/classes/thumbnail.py
+index cca47d68f..dac7422a1 100644
+--- a/src/classes/thumbnail.py
++++ b/src/classes/thumbnail.py
+@@ -209,13 +209,13 @@ def do_GET(self):
+ 
+         # Send message back to client
+         if os.path.exists(thumb_path):
+-            if not only_path:
+-                self.wfile.write(open(thumb_path, 'rb').read())
+-            else:
++            if only_path:
+                 self.wfile.write(bytes(thumb_path, "utf-8"))
++            else:
++                with open(thumb_path, 'rb') as f:
++                    self.wfile.write(f.read())
+ 
+         # Pause processing of request (since we don't currently use thread pooling, this allows
+         # the threads to be processed without choking the CPU as much
+         # TODO: Make HTTPServer work with a limited thread pool and remove this sleep() hack.
+         time.sleep(0.01)
+-
+
+From 33cf68ca0b1ea57edd5dec3dbb8ba06d6a3f8fa4 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 25 Nov 2021 02:18:32 -0500
+Subject: [PATCH 6/6] properties_model: Fix bad logging call, Codacy flags
+
+---
+ src/windows/models/properties_model.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index 40897f642..695e9f39c 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -444,7 +444,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                 log.debug("%s: update property %s. %s", log_id, property_key, clip_data.get(property_key))
+ 
+                 # Check the type of property (some are keyframe, and some are not)
+-                if property_type != "reader" and type(clip_data[property_key]) == dict:
++                if property_type != "reader" and isinstance(clip_data[property_key], dict):
+                     # Keyframe
+                     # Loop through points, find a matching points on this frame
+                     found_point = False
+@@ -517,21 +517,21 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = int(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+@@ -558,7 +558,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_object.Close()
+                         clip_object = None
+                     except Exception:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
++                        log.warn('Invalid Reader value passed to property: %s', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
diff --git a/srcpkgs/openshot/template b/srcpkgs/openshot/template
index d16ea925d223..4248baa81e39 100644
--- a/srcpkgs/openshot/template
+++ b/srcpkgs/openshot/template
@@ -1,12 +1,11 @@
 # Template file for 'openshot'
 pkgname=openshot
-version=2.5.1
-revision=3
-archs="i686 x86_64 ppc64le"
+version=2.6.1
+revision=1
 wrksrc="${pkgname}-qt-${version}"
 build_style=python3-module
-hostmakedepends="python3"
-makedepends="ffmpeg-devel python3-PyQt5 python3-setuptools"
+hostmakedepends="python3 python3-setuptools"
+makedepends="ffmpeg-devel python3-PyQt5"
 depends="ImageMagick libopenshot mlt python3-PyQt5-svg
  python3-PyQt5-webkit python3-httplib2 python3-pyzmq python3-requests"
 short_desc="Open-source, non-linear video editor for Linux"
@@ -14,4 +13,5 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://www.openshot.org"
 distfiles="https://github.com/OpenShot/openshot-qt/archive/v${version}.tar.gz"
-checksum=4c25eb9a5de42e749de4c6ca2f7a313c60e1283fe52d70c121629dbb8bb2df7b
+checksum=11651d5e7287da3c766ce6b447aef8da5cdadaf5626a2816e9025c831d0e1a66
+make_check=no # tests are broken

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PR PATCH] [Updated] openshot: update to 2.6.1.
  2022-02-23  0:01 [PR PATCH] openshot: update to 2.6.1 tibequadorian
@ 2022-02-23  0:12 ` tibequadorian
  2022-02-24 14:47 ` tibequadorian
  2022-02-25 23:52 ` [PR PATCH] [Merged]: " Piraty
  2 siblings, 0 replies; 4+ messages in thread
From: tibequadorian @ 2022-02-23  0:12 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1313 bytes --]

There is an updated pull request by tibequadorian against master on the void-packages repository

https://github.com/tibequadorian/void-packages openshot
https://github.com/void-linux/void-packages/pull/35798

openshot: update to 2.6.1.
Patches for openshot are already upstream so maybe we should wait for 2.6.2.

<!-- Uncomment relevant sections and delete options which are not applicable -->

#### Testing the changes
- I tested the changes in this PR: **briefly**

<!--
#### New package
- This new package conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements): **YES**|**NO**
-->

<!-- Note: If the build is likely to take more than 2 hours, please [skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration)
and test at least one native build and, if supported, at least one cross build.
Ignore this section if this PR is not skipping CI.
-->
<!-- 
#### Local build testing
- I built this PR locally for my native architecture, (ARCH-LIBC)
- I built this PR locally for these architectures (if supported. mark crossbuilds):
  - aarch64-musl
  - armv7l
  - armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/35798.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-openshot-35798.patch --]
[-- Type: text/x-diff, Size: 124106 bytes --]

From c3c3357aca2851de9d12303413ddcdf1d8a12225 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:41:38 +0100
Subject: [PATCH 1/3] libopenshot-audio: update to 0.2.2.

---
 common/shlibs                                 |  2 +-
 .../libopenshot-audio/patches/fix-musl.patch  | 86 ++++++++++---------
 srcpkgs/libopenshot-audio/template            |  7 +-
 3 files changed, 51 insertions(+), 44 deletions(-)

diff --git a/common/shlibs b/common/shlibs
index 32befc92d641..c66b4fbe6700 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2597,7 +2597,7 @@ libax25io.so.0 libax25-0.0.12rc4_1
 libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
-libopenshot-audio.so.7 libopenshot-audio-0.2.0_1
+libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
 libopenshot.so.19 libopenshot-0.2.5_3
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
diff --git a/srcpkgs/libopenshot-audio/patches/fix-musl.patch b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
index b5f178d92d8a..532248c86217 100644
--- a/srcpkgs/libopenshot-audio/patches/fix-musl.patch
+++ b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
@@ -1,40 +1,46 @@
---- a/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-12-12 14:53:23.532613378 +0100
-@@ -97,7 +97,7 @@
-  #include <net/if.h>
-  #include <sys/ioctl.h>
- 
-- #if ! JUCE_ANDROID
-+ #if ! JUCE_ANDROID && defined(__GLIBC__)
-   #include <execinfo.h>
-  #endif
- #endif
---- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-12-12 14:58:35.988986030 +0100
-@@ -134,6 +134,8 @@
-         }
-     }
- 
-+   #elif !defined(__GLIBC__)
-+    jassertfalse; // sorry, not implemented yet!
-    #else
-     void* stack[128];
-     int frames = backtrace (stack, numElementsInArray (stack));
---- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-12-12 15:07:35.046607788 +0100
-@@ -142,8 +142,15 @@
-     return result;
- }
- 
-+#if defined(__GLIBC__)
- String SystemStats::getUserLanguage()    { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
- String SystemStats::getUserRegion()      { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
-+#else
-+// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
-+// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
-+String SystemStats::getUserLanguage()    { return String("en"); }
-+String SystemStats::getUserRegion()      { return String("US"); }
-+#endif
- String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); }
- 
- //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/juce_core.cpp b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+index 8bac812..e23b422 100644
+--- a/JuceLibraryCode/modules/juce_core/juce_core.cpp
++++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+@@ -92,7 +92,7 @@
+  #include <net/if.h>
+  #include <sys/ioctl.h>
+ 
+- #if ! JUCE_ANDROID
++ #if ! JUCE_ANDROID && defined(__GLIBC__)
+   #include <execinfo.h>
+  #endif
+ #endif
+diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+index 2d7faa3..f132405 100644
+--- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+@@ -139,8 +139,15 @@ static String getLocaleValue (nl_item key)
+     return result;
+ }
+ 
++#if defined(__GLIBC__)
+ String SystemStats::getUserLanguage()     { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
+ String SystemStats::getUserRegion()       { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
++#else
++// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
++// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
++String SystemStats::getUserLanguage()     { return String("en"); }
++String SystemStats::getUserRegion()       { return String("US"); }
++#endif
+ String SystemStats::getDisplayLanguage()  { return getUserLanguage() + "-" + getUserRegion(); }
+ 
+ //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+index 757ea24..6b61e16 100644
+--- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+@@ -138,7 +138,7 @@ String SystemStats::getStackBacktrace()
+ {
+     String result;
+ 
+-   #if JUCE_ANDROID || JUCE_MINGW
++   #if JUCE_ANDROID || JUCE_MINGW || !defined(__GLIBC__)
+     jassertfalse; // sorry, not implemented yet!
+ 
+    #elif JUCE_WINDOWS
diff --git a/srcpkgs/libopenshot-audio/template b/srcpkgs/libopenshot-audio/template
index 56c330eafcf1..254f8b6283bd 100644
--- a/srcpkgs/libopenshot-audio/template
+++ b/srcpkgs/libopenshot-audio/template
@@ -1,6 +1,6 @@
 # Template file for 'libopenshot-audio'
 pkgname=libopenshot-audio
-version=0.2.0
+version=0.2.2
 revision=1
 build_style=cmake
 hostmakedepends="doxygen"
@@ -11,7 +11,7 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot-audio"
 distfiles="https://github.com/OpenShot/libopenshot-audio/archive/v${version}.tar.gz"
-checksum=937ff4f1c2dfb8ab5d56ad85beacaa29dfd5a79af0d9cf647386034fe9882309
+checksum=66bedfda0d8d430598b21bc2dde6c0016a758a6c83467d0273a9d692de10baaf
 
 if [ "$XBPS_TARGET_NO_ATOMIC8" ]; then
 	makedepends+=" libatomic-devel"
@@ -20,9 +20,10 @@ fi
 
 libopenshot-audio-devel_package() {
 	short_desc+=" - development files"
-	depends+=" ${sourcepkg}>=${version}_${revision}"
+	depends+=" ${sourcepkg}>=${version}_${revision} alsa-lib-devel zlib-devel"
 	pkg_install() {
 		vmove usr/include
+		vmove usr/lib/cmake
 		vmove "usr/lib/*.so"
 	}
 }

From bb61a44b5ece18e2805a5b6e614d55ce0a9866ca Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:37 +0100
Subject: [PATCH 2/3] libopenshot: update to 0.2.7.

enable for all archs and libc
---
 common/shlibs                                 |  2 +-
 .../patches/AV_GET_CODEC_CONTEXT-macro.patch  | 34 -------------------
 srcpkgs/libopenshot/patches/fix-musl.patch    | 34 +++++++++++++++++++
 srcpkgs/libopenshot/template                  | 16 +++++----
 4 files changed, 45 insertions(+), 41 deletions(-)
 delete mode 100644 srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
 create mode 100644 srcpkgs/libopenshot/patches/fix-musl.patch

diff --git a/common/shlibs b/common/shlibs
index c66b4fbe6700..d339115dda39 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2598,7 +2598,7 @@ libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
 libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
-libopenshot.so.19 libopenshot-0.2.5_3
+libopenshot.so.21 libopenshot-0.2.7_1
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
 liblog.so android-studio-3.0.1_1
diff --git a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch b/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
deleted file mode 100644
index e6c640a8e11e..000000000000
--- a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/include/FFmpegUtilities.h	2020-03-03 09:00:06.000000000 +0100
-+++ b/include/FFmpegUtilities.h	2020-08-19 17:04:58.535806744 +0200
-@@ -163,11 +163,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
-@@ -199,11 +198,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
diff --git a/srcpkgs/libopenshot/patches/fix-musl.patch b/srcpkgs/libopenshot/patches/fix-musl.patch
new file mode 100644
index 000000000000..67424a848086
--- /dev/null
+++ b/srcpkgs/libopenshot/patches/fix-musl.patch
@@ -0,0 +1,34 @@
+diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
+index bde744d7..d07924fb 100644
+--- a/examples/CMakeLists.txt
++++ b/examples/CMakeLists.txt
+@@ -39,11 +39,19 @@ file(TO_NATIVE_PATH "${PROJECT_SOURCE_DIR}/examples/" TEST_MEDIA_PATH)
+ target_compile_definitions(openshot-example PRIVATE
+ 	-DTEST_MEDIA_PATH="${TEST_MEDIA_PATH}" )
+ 
++find_library(EXECINFO_FOUND execinfo)
++
+ # Link test executable to the new library
+ target_link_libraries(openshot-example openshot)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-example execinfo)
++endif()
+ 
+ add_executable(openshot-html-example ExampleHtml.cpp)
+ target_link_libraries(openshot-html-example openshot Qt5::Gui)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-html-example execinfo)
++endif()
+ 
+ ############### PLAYER EXECUTABLE ################
+ # Create test executable
+@@ -53,6 +61,9 @@ set_target_properties(openshot-player PROPERTIES AUTOMOC ON)
+ 
+ # Link test executable to the new library
+ target_link_libraries(openshot-player openshot)
++if(EXECINFO_FOUND)
++	target_link_libraries(openshot-player execinfo)
++endif()
+ 
+ ############### TEST BLACKMAGIC CAPTURE APP ################
+ if (BLACKMAGIC_FOUND)
diff --git a/srcpkgs/libopenshot/template b/srcpkgs/libopenshot/template
index b59472105b35..274f08e99251 100644
--- a/srcpkgs/libopenshot/template
+++ b/srcpkgs/libopenshot/template
@@ -1,11 +1,11 @@
 # Template file for 'libopenshot'
 pkgname=libopenshot
-version=0.2.5
-revision=6
-archs="i686 x86_64 ppc64le"
+version=0.2.7
+revision=1
 build_style=cmake
-configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON" # Builds fail with Ruby-2.4.1
-hostmakedepends="swig doxygen ruby python3 pkg-config"
+# Builds fail with Ruby-2.4.1
+configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON"
+hostmakedepends="swig doxygen ruby python3 pkg-config qt5-qmake qt5-host-tools"
 makedepends="python3-devel ffmpeg-devel libmagick-devel qt5-devel libgomp-devel
  libopenshot-audio-devel qt5-multimedia-devel unittest-cpp zeromq-devel cppzmq
  jsoncpp-devel"
@@ -15,10 +15,14 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="LGPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot"
 distfiles="https://github.com/OpenShot/libopenshot/archive/v${version}.tar.gz"
-checksum=8ae7d226fbd2efbc84da4f7d9d8c7f3cc9616e4de46e1233e3b0a84ac0a429bc
+checksum=568eab6d69d469c5f745f0e25387ca5e000f7c28be48417b0d7770577ac74a28
 # FIXME: tests segfault
 make_check=extended
 
+if [ "$XBPS_TARGET_LIBC" = musl ]; then
+	makedepends+=" libexecinfo-devel"
+fi
+
 libopenshot-devel_package() {
 	short_desc+=" - development files"
 	pkg_install() {

From 1224d38663b0ee5f27a2ed7a98148f26ae56f102 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:51 +0100
Subject: [PATCH 3/3] openshot: update to 2.6.1.

enable for all archs and libc
patches are upstream
---
 .../openshot/patches/0001-video_widget.patch  |   74 +
 .../patches/0002-python3.10_int.patch         | 1993 +++++++++++++++++
 srcpkgs/openshot/template                     |   12 +-
 3 files changed, 2073 insertions(+), 6 deletions(-)
 create mode 100644 srcpkgs/openshot/patches/0001-video_widget.patch
 create mode 100644 srcpkgs/openshot/patches/0002-python3.10_int.patch

diff --git a/srcpkgs/openshot/patches/0001-video_widget.patch b/srcpkgs/openshot/patches/0001-video_widget.patch
new file mode 100644
index 000000000000..5e93d764c4bf
--- /dev/null
+++ b/srcpkgs/openshot/patches/0001-video_widget.patch
@@ -0,0 +1,74 @@
+From 9748a13268d66a5949aebc970637b5903756d018 Mon Sep 17 00:00:00 2001
+From: Jonathan Thomas <jonathan@openshot.org>
+Date: Thu, 7 Oct 2021 13:53:09 -0500
+Subject: [PATCH] Support for previewing anamorphic video profiles, including a
+ few code clean-ups.
+
+---
+ src/windows/video_widget.py | 22 +++++++---------------
+ 1 file changed, 7 insertions(+), 15 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 7326598d3..842deb3ba 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -77,28 +77,20 @@ def changed(self, action):
+             if action.type == "load" and action.values.get("pixel_ratio"):
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("pixel_ratio").get("num", 16),
+-                    action.values.get("pixel_ratio").get("den", 9))
++                    action.values.get("pixel_ratio").get("num", 1),
++                    action.values.get("pixel_ratio").get("den", 1))
+                 log.info(
+                     "Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+             elif action.key and action.key[0] == "pixel_ratio":
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("num", 16),
+-                    action.values.get("den", 9))
++                    action.values.get("num", 1),
++                    action.values.get("den", 1))
+                 log.info(
+                     "Update: Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+ 
+-            # Update max size (to size of video preview viewport)
+-            if display_ratio_changed or pixel_ratio_changed:
+-                timeline = get_app().window.timeline_sync.timeline
+-                timeline.SetMaxSize(
+-                    round(self.width() * self.pixel_ratio.ToFloat()),
+-                    round(self.height() * self.pixel_ratio.ToFloat())
+-                    )
+-
+ 
+     def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+      x1=None, y1=None, x2=None, y2=None, rotation = None):
+@@ -236,7 +228,7 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height)
++            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+@@ -405,7 +397,7 @@ def paintEvent(self, event, *args):
+         self.mutex.unlock()
+ 
+     def centeredViewport(self, width, height):
+-        """ Calculate size of viewport to maintain apsect ratio """
++        """ Calculate size of viewport to maintain aspect ratio """
+ 
+         # Calculate padding
+         top_padding = (height - (height * self.zoom)) / 2.0
+@@ -416,7 +408,7 @@ def centeredViewport(self, width, height):
+         height = height * self.zoom
+ 
+         # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        aspectRatio = self.aspect_ratio.ToFloat()
+         heightFromWidth = width / aspectRatio
+         widthFromHeight = height * aspectRatio
+ 
diff --git a/srcpkgs/openshot/patches/0002-python3.10_int.patch b/srcpkgs/openshot/patches/0002-python3.10_int.patch
new file mode 100644
index 000000000000..8f6dc53dbf40
--- /dev/null
+++ b/srcpkgs/openshot/patches/0002-python3.10_int.patch
@@ -0,0 +1,1993 @@
+From a3088503500e79877ce985e4784f75478d9b792e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Mon, 20 Sep 2021 05:36:19 -0400
+Subject: [PATCH 1/6] Preferences: Fix logging calls
+
+---
+ src/launch.py              |  2 +-
+ src/windows/preferences.py | 18 +++++++++++-------
+ 2 files changed, 12 insertions(+), 8 deletions(-)
+
+diff --git a/src/launch.py b/src/launch.py
+index 9fc600b23..b614e9bc7 100755
+--- a/src/launch.py
++++ b/src/launch.py
+@@ -41,7 +41,7 @@
+  """
+ 
+ import sys
+-import os.path
++import os
+ import argparse
+ 
+ from PyQt5.QtCore import Qt
+diff --git a/src/windows/preferences.py b/src/windows/preferences.py
+index bf7d67b0c..325f496b3 100644
+--- a/src/windows/preferences.py
++++ b/src/windows/preferences.py
+@@ -102,7 +102,7 @@ def __init__(self):
+ 
+     def txtSearch_changed(self):
+         """textChanged event handler for search box"""
+-        log.info("Search for %s" % self.txtSearch.text())
++        log.info("Search for %s", self.txtSearch.text())
+ 
+         # Populate preferences
+         self.Populate(filter=self.txtSearch.text())
+@@ -317,7 +317,7 @@ def Populate(self, filter=""):
+                                 value_list.remove(value_item)
+ 
+                         # Remove hardware mode items which cannot decode the example video
+-                        log.debug("Preparing to test hardware decoding: %s" % (value_list))
++                        log.debug("Preparing to test hardware decoding: %s", value_list)
+                         for value_item in list(value_list):
+                             v = value_item["value"]
+                             if (not self.testHardwareDecode(value_list, v, 0)
+@@ -470,7 +470,7 @@ def bool_value_changed(self, widget, param, state):
+         # Trigger specific actions
+         if param["setting"] == "debug-mode":
+             # Update debug setting of timeline
+-            log.info("Setting debug-mode to %s" % (state == Qt.Checked))
++            log.info("Setting debug-mode to %s", state == Qt.Checked)
+             debug_enabled = (state == Qt.Checked)
+ 
+             # Enable / Disable logger
+@@ -528,7 +528,9 @@ def text_value_changed(self, widget, param, value=None):
+         if param.get("category") == "Keyboard":
+             previous_value = value
+             value = QKeySequence(value).toString()
+-            log.info("Parsing keyboard mapping via QKeySequence from %s to %s" % (previous_value, value))
++            log.info(
++                "Parsing keyboard mapping via QKeySequence from %s to %s",
++                previous_value, value)
+ 
+         # Save setting
+         self.s.set(param["setting"], value)
+@@ -604,11 +606,13 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+             if reader.GetFrame(0).CheckPixel(0, 0, 2, 133, 255, 255, 5):
+                 is_supported = True
+                 self.hardware_tests_cards[decoder_card].append(int(decoder))
+-                log.debug("Successful hardware decoder! %s (%s-%s)" % (decoder_name, decoder, decoder_card))
++                log.debug(
++                    "Successful hardware decoder! %s (%s-%s)",
++                    decoder_name, decoder, decoder_card)
+             else:
+                 log.debug(
+                     "CheckPixel failed testing hardware decoding (i.e. wrong color found): %s (%s-%s)",
+-                    (decoder_name, decoder, decoder_card))
++                    decoder_name, decoder, decoder_card)
+ 
+             reader.Close()
+             clip.Close()
+@@ -616,7 +620,7 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+         except Exception:
+             log.debug(
+                 "Exception trying to test hardware decoding (this is expected): %s (%s-%s)",
+-                (decoder_name, decoder, decoder_card))
++                decoder_name, decoder, decoder_card)
+ 
+         # Resume current settings
+         openshot.Settings.Instance().HARDWARE_DECODER = current_decoder
+
+From 1b14896d2057df80b0b20ba22e1380ba9e9bd6e6 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:01:39 -0400
+Subject: [PATCH 2/6] Enforce integer function arguments
+
+---
+ src/windows/export.py                     |   8 +-
+ src/windows/models/properties_model.py    |  34 +-
+ src/windows/process_effect.py             |   2 +-
+ src/windows/video_widget.py               | 596 ++++++++++++++--------
+ src/windows/views/effects_listview.py     |   7 +-
+ src/windows/views/effects_treeview.py     |   7 +-
+ src/windows/views/emojis_listview.py      |   7 +-
+ src/windows/views/files_listview.py       |   7 +-
+ src/windows/views/files_treeview.py       |   7 +-
+ src/windows/views/properties_tableview.py |  38 +-
+ src/windows/views/transitions_listview.py |   7 +-
+ src/windows/views/transitions_treeview.py |   7 +-
+ src/windows/views/tutorial.py             |  14 +-
+ 13 files changed, 476 insertions(+), 265 deletions(-)
+
+diff --git a/src/windows/export.py b/src/windows/export.py
+index a624eb2e2..6461afb25 100644
+--- a/src/windows/export.py
++++ b/src/windows/export.py
+@@ -290,7 +290,7 @@ def updateProgressBar(self, title_message, start_frame, end_frame, current_frame
+             percentage_string = format_of_progress_string % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
+         else:
+             percentage_string = "100%"
+-        self.progressExportVideo.setValue(current_frame)
++        self.progressExportVideo.setValue(int(current_frame))
+         self.progressExportVideo.setFormat(percentage_string)
+         self.setWindowTitle("%s %s" % (percentage_string, title_message))
+ 
+@@ -690,9 +690,9 @@ def titlestring(sec, fps, mess):
+         fps_encode = 0
+ 
+         # Init progress bar
+-        self.progressExportVideo.setMinimum(self.txtStartFrame.value())
+-        self.progressExportVideo.setMaximum(self.txtEndFrame.value())
+-        self.progressExportVideo.setValue(self.txtStartFrame.value())
++        self.progressExportVideo.setMinimum(int(self.txtStartFrame.value()))
++        self.progressExportVideo.setMaximum(int(self.txtEndFrame.value()))
++        self.progressExportVideo.setValue(int(self.txtStartFrame.value()))
+ 
+         # Prompt error message
+         if self.txtStartFrame.value() == self.txtEndFrame.value():
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index c3236ed84..40897f642 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -414,8 +414,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+             new_value = None
+ 
+         log.info(
+-            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s"
+-            % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x))
++            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s",
++            property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x)
+ 
+         # Find this clip
+         c = None
+@@ -518,35 +518,35 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     try:
+                         clip_data[property_key] = int(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Integer value passed to property: %s' % ex)
++                        log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Float value passed to property: %s' % ex)
++                        log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Boolean value passed to property: %s' % ex)
++                        log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid String value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid String value passed to property', exc_info=1)
+ 
+                 elif property_type in ["font", "caption"]:
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid Font/Caption value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid Font/Caption value passed to property', exc_info=1)
+ 
+                 elif property_type == "reader":
+                     # Transition
+@@ -557,8 +557,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_data[property_key] = json.loads(clip_object.Reader().Json())
+                         clip_object.Close()
+                         clip_object = None
+-                    except Exception as ex:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)' % (value, ex))
++                    except Exception:
++                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
+@@ -688,9 +688,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Color needs to be handled special
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             if readonly or type in ["color", "font", "caption"] or choices or label == "Track":
+@@ -789,9 +789,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Update the color based on the color curves
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             # Update helper dictionary
+diff --git a/src/windows/process_effect.py b/src/windows/process_effect.py
+index e4f3120c0..ea0c2946e 100644
+--- a/src/windows/process_effect.py
++++ b/src/windows/process_effect.py
+@@ -352,7 +352,7 @@ def accept(self):
+         while(not processing.IsDone() ):
+             # update progressbar
+             progressionStatus = processing.GetProgress()
+-            self.progressBar.setValue(progressionStatus)
++            self.progressBar.setValue(int(progressionStatus))
+             time.sleep(0.01)
+ 
+             # Process any queued events
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index d5c89a204..f33696c5c 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -25,8 +25,11 @@
+  along with OpenShot Library.  If not, see <http://www.gnu.org/licenses/>.
+  """
+ 
++import json
++
+ from PyQt5.QtCore import (
+-    Qt, QCoreApplication, QPointF, QPoint, QRect, QRectF, QSize, QMutex, QTimer
++    Qt, QCoreApplication, QMutex, QTimer,
++    QPoint, QPointF, QSize, QSizeF, QRect, QRectF,
+ )
+ from PyQt5.QtGui import (
+     QTransform, QPainter, QIcon, QColor, QPen, QBrush, QCursor, QImage, QRegion
+@@ -41,8 +44,6 @@
+ from classes.app import get_app
+ from classes.query import Clip, Effect
+ 
+-import json
+-
+ 
+ class VideoWidget(QWidget, updates.UpdateInterface):
+     """ A QWidget used on the video display widget """
+@@ -86,37 +87,68 @@ def changed(self, action):
+                     self.pixel_ratio.ToFloat())
+ 
+ 
+-    def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-     x1=None, y1=None, x2=None, y2=None, rotation = None):
++    def drawTransformHandler(
++        self, painter, sx, sy, source_width, source_height,
++        origin_x, origin_y,
++        x1=None, y1=None, x2=None, y2=None, rotation = None
++    ):
+         # Draw transform corners and center origin circle
+         # Corner size
+         cs = self.cs
+         os = 12.0
+ 
++        csx = cs / sx
++        csy = cs / sy
++
+         # Rotate the transform handler
+         if rotation:
+-            bbox_center_x = (((x1*source_width + x2*source_width) / 2.0) ) - ( (os/2) /sx)
+-            bbox_center_y = (((y1*source_height + y2*source_height) / 2.0) ) - ( (os/2) /sy)
++            bbox_center_x = ((x1*source_width + x2*source_width) / 2.0) - ((os / 2) / sx)
++            bbox_center_y = ((y1*source_height + y2*source_height) / 2.0) - ((os / 2) / sy)
+             painter.translate(bbox_center_x, bbox_center_y)
+             painter.rotate(rotation)
+             painter.translate(-bbox_center_x, -bbox_center_y)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(x1*source_width, y1*source_height), QPointF(x2*source_width, y2*source_height))
++            self.clipBounds = QRectF(
++                QPointF(x1 * source_width, y1 * source_height),
++                QPointF(x2 * source_width, y2 * source_height)
++            )
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.topRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
+         else:
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(0.0, 0.0), QPointF(source_width, source_height))
++            self.clipBounds = QRectF(
++                QPointF(0.0, 0.0),
++                QPointF(source_width, source_height))
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(-cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(-cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                -csx / 2.0, -csy / 2.0, csx, csy)
++            self.topRightHandle = QRectF(
++                source_width - csx / 2.0, -csy / 2.0, csx, csy)
++            self.bottomLeftHandle = QRectF(
++                -csx / 2.0, source_height - csy / 2.0, csx, csy)
++            self.bottomRightHandle = QRectF(
++                source_width - csx / 2.0, source_height - csy / 2.0, csx, csy)
+ 
+         # Draw 4 corners
+         pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+@@ -127,47 +159,110 @@ def drawTransformHandler(self, painter, sx, sy, source_width, source_height, ori
+         painter.drawRect(self.bottomLeftHandle)
+         painter.drawRect(self.bottomRightHandle)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y1*source_height)-cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y2*source_height)-( cs/sy/2.0), cs/sx, cs/sy)
+-            self.leftHandle = QRectF((x1*source_width)-(cs/sx/2.0), ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF((x2*source_width) - (cs/sx) + cs/sx/2.0, ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y1 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y2 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                (x1 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                (x2 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx, csy)
+ 
+         else:
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.leftHandle = QRectF(-cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                (source_width - csx) / 2.0,
++                -csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                (source_width - csx) / 2.0,
++                source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                -csx / 2.0,
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                source_width - (csx / 2.0),
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
+ 
+         # Calculate shear handles
+-        self.topShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
+-        self.leftShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.topLeftHandle.width(), self.clipBounds.height())
+-        self.rightShearHandle = QRectF(self.topRightHandle.x(), self.topRightHandle.y(), self.topRightHandle.width(), self.clipBounds.height())
+-        self.bottomShearHandle = QRectF(self.bottomLeftHandle.x(), self.bottomLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
++        self.topShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
++        self.leftShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.topLeftHandle.width(),
++            self.clipBounds.height())
++        self.rightShearHandle = QRectF(
++            self.topRightHandle.x(),
++            self.topRightHandle.y(),
++            self.topRightHandle.width(),
++            self.clipBounds.height())
++        self.bottomShearHandle = QRectF(
++            self.bottomLeftHandle.x(),
++            self.bottomLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
+ 
+         # Draw 4 sides (centered)
+-        painter.drawRect(self.topHandle)
+-        painter.drawRect(self.bottomHandle)
+-        painter.drawRect(self.leftHandle)
+-        painter.drawRect(self.rightHandle)
+-        painter.drawRect(self.clipBounds)
++        painter.drawRects([
++            self.topHandle,
++            self.bottomHandle,
++            self.leftHandle,
++            self.rightHandle,
++            self.clipBounds,
++            ])
+ 
+         # Calculate center coordinate
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             cs = 5.0
+             os = 7.0
+-            self.centerHandle = QRectF( (((x1*source_width+x2*source_width) / 2.0) ) - (os/sx), (((y1*source_height+y2*source_height) / 2.0) ) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                ((x1 + x2) * source_width / 2.0) - (os / sx),
++                ((y1 + y2) * source_height / 2.0) - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0
++            )
+         else:
+-            self.centerHandle = QRectF((source_width * origin_x) - (os/sx), (source_height * origin_y) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                source_width * origin_x - (os / sx),
++                source_height * origin_y - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0)
+ 
+         # Draw origin
+         painter.drawEllipse(self.centerHandle)
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) - self.centerHandle.height(),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) + self.centerHandle.height())
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0) - self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0) + self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0))
++
++        # Draw cross at origin center, extending beyond ellipse by 25%
++        center = self.centerHandle.center()
++        halfW = QPointF(self.centerHandle.width() * 0.75, 0)
++        halfH = QPointF(0, self.centerHandle.height() * 0.75)
++        painter.drawLines(
++            center - halfW, center + halfW,
++            center - halfH, center + halfH,
++        )
+ 
+         # Remove transform
+         painter.resetTransform()
+@@ -179,7 +274,11 @@ def paintEvent(self, event, *args):
+ 
+         # Paint custom frame image on QWidget
+         painter = QPainter(self)
+-        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++        painter.setRenderHints(
++            QPainter.Antialiasing
++            | QPainter.SmoothPixmapTransform
++            | QPainter.TextAntialiasing,
++            True)
+ 
+         # Fill the whole widget with the solid color
+         painter.fillRect(event.rect(), QColor("#191919"))
+@@ -191,7 +290,7 @@ def paintEvent(self, event, *args):
+             # DRAW FRAME
+             # Calculate new frame image size, maintaining aspect ratio
+             pixSize = self.current_image.size()
+-            pixSize.scale(event.rect().width(), event.rect().height(), Qt.KeepAspectRatio)
++            pixSize.scale(event.rect().size(), Qt.KeepAspectRatio)
+             self.curr_frame_size = pixSize
+ 
+             # Scale image (take into account display scaling for High DPI monitors)
+@@ -223,13 +322,19 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
++            pixel_adjust = self.pixel_ratio.Reciprocal().ToDouble()
++            source_size = QSize(
++                int(source_width),
++                int(source_height * pixel_adjust))
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+ 
+             # Set scale as STRETCH if the clip is attached to an object
+-            if (raw_properties.get('parentObjectId').get('memo') != 'None' and len(raw_properties.get('parentObjectId').get('memo')) > 0 ):
++            if (
++                    raw_properties.get('parentObjectId').get('memo') != 'None'
++                    and len(raw_properties.get('parentObjectId').get('memo')) > 0
++            ):
+                 scale = openshot.SCALE_STRETCH
+ 
+             if scale == openshot.SCALE_FIT:
+@@ -239,12 +344,7 @@ def paintEvent(self, event, *args):
+                 source_size.scale(player_width, player_height, Qt.IgnoreAspectRatio)
+ 
+             elif scale == openshot.SCALE_CROP:
+-                width_size = QSize(player_width, round(player_width / (float(source_width) / float(source_height))))
+-                height_size = QSize(round(player_height / (float(source_height) / float(source_width))), player_height)
+-                if width_size.width() >= player_width and width_size.height() >= player_height:
+-                    source_size.scale(width_size.width(), width_size.height(), Qt.KeepAspectRatio)
+-                else:
+-                    source_size.scale(height_size.width(), height_size.height(), Qt.KeepAspectRatio)
++                source_size.scale(player_width, player_height, Qt.KeepAspectRatioByExpanding)
+ 
+             # Get new source width / height (after scaling mode applied)
+             source_width = source_size.width()
+@@ -285,9 +385,6 @@ def paintEvent(self, event, *args):
+                 x += player_width - scaled_source_width  # right
+                 y += (player_height - scaled_source_height)  # bottom
+ 
+-            # Track gravity starting coordinate
+-            self.gravity_point = QPointF(x, y)
+-
+             # Adjust x,y for location
+             x_offset = raw_properties.get('location_x').get('value')
+             y_offset = raw_properties.get('location_y').get('value')
+@@ -329,7 +426,6 @@ def paintEvent(self, event, *args):
+                     raw_properties_effect = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+                     # Get properties for the first object in dict. PropertiesJSON should return one object at the time
+                     tmp = raw_properties_effect.get('objects')
+-                    tmp2 = tmp.keys()
+                     obj_id = list(tmp.keys())[0]
+                     raw_properties_effect = raw_properties_effect.get('objects').get(obj_id)
+ 
+@@ -342,10 +438,19 @@ def paintEvent(self, event, *args):
+                             y1 = raw_properties_effect['y1']['value']
+                             x2 = raw_properties_effect['x2']['value']
+                             y2 = raw_properties_effect['y2']['value']
+-                            self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-                                x1, y1, x2, y2, rotation)
++                            self.drawTransformHandler(
++                                painter,
++                                sx, sy,
++                                source_width, source_height,
++                                origin_x, origin_y,
++                                x1, y1, x2, y2,
++                                rotation)
+             else:
+-                self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y)
++                self.drawTransformHandler(
++                    painter,
++                    sx, sy,
++                    source_width, source_height,
++                    origin_x, origin_y)
+ 
+         if self.region_enabled:
+             # Paint region selector onto video preview
+@@ -376,11 +481,21 @@ def paintEvent(self, event, *args):
+                 pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+                 pen.setCosmetic(True)
+                 painter.setPen(pen)
+-                painter.drawRect(self.regionTopLeftHandle.x() - (cs/2.0/self.zoom), self.regionTopLeftHandle.y() - (cs/2.0/self.zoom), self.regionTopLeftHandle.width() / self.zoom, self.regionTopLeftHandle.height() / self.zoom)
+-                painter.drawRect(self.regionBottomRightHandle.x() - (cs/2.0/self.zoom), self.regionBottomRightHandle.y() - (cs/2.0/self.zoom), self.regionBottomRightHandle.width() / self.zoom, self.regionBottomRightHandle.height() / self.zoom)
+-                region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                        self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                        self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
++                painter.drawRect(
++                    self.regionTopLeftHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.width() / self.zoom,
++                    self.regionTopLeftHandle.height() / self.zoom)
++                painter.drawRect(
++                    self.regionBottomRightHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.width() / self.zoom,
++                    self.regionBottomRightHandle.height() / self.zoom)
++                region_rect = QRectF(
++                    self.regionTopLeftHandle.x(),
++                    self.regionTopLeftHandle.y(),
++                    self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                    self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
+                 painter.drawRect(region_rect)
+ 
+             # Remove transform
+@@ -394,23 +509,15 @@ def paintEvent(self, event, *args):
+     def centeredViewport(self, width, height):
+         """ Calculate size of viewport to maintain aspect ratio """
+ 
+-        # Calculate padding
+-        top_padding = (height - (height * self.zoom)) / 2.0
+-        left_padding = (width - (width * self.zoom)) / 2.0
++        window_size = QSizeF(width, height)
++        window_rect = QRectF(QPointF(0, 0), window_size)
+ 
+-        # Adjust parameters to zoom
+-        width = width * self.zoom
+-        height = height * self.zoom
++        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        viewport_size = QSizeF(aspectRatio, 1).scaled(window_size, Qt.KeepAspectRatio)
++        viewport_rect = QRectF(QPointF(0, 0), viewport_size)
++        viewport_rect.moveCenter(window_rect.center())
+ 
+-        # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat()
+-        heightFromWidth = width / aspectRatio
+-        widthFromHeight = height * aspectRatio
+-
+-        if heightFromWidth <= height:
+-            return QRect(left_padding, ((height - heightFromWidth) / 2) + top_padding, width, heightFromWidth)
+-        else:
+-            return QRect(((width - widthFromHeight) / 2.0) + left_padding, top_padding, widthFromHeight, height)
++        return viewport_rect.toRect()
+ 
+     def present(self, image, *args):
+         """ Present the current frame """
+@@ -448,12 +555,16 @@ def mouseReleaseEvent(self, event):
+         # This can be used other widgets to display the selected region
+         if self.region_enabled:
+             # Get region coordinates
+-            region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                 self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                 self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()).normalized()
++            region_rect = QRectF(
++                self.regionTopLeftHandle.x(),
++                self.regionTopLeftHandle.y(),
++                self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()
++            ).normalized()
+ 
+             # Map region (due to zooming)
+-            mapped_region_rect = self.region_transform.mapToPolygon(region_rect.toRect()).boundingRect()
++            mapped_region_rect = self.region_transform.mapToPolygon(
++                region_rect.toRect()).boundingRect()
+ 
+             # Render a scaled version of the region (as a QImage)
+             # TODO: Grab higher quality pixmap from the QWidget, as this method seems to be 1/2 resolution
+@@ -461,14 +572,25 @@ def mouseReleaseEvent(self, event):
+             scale = 3.0
+ 
+             # Map rect to transform (for scaling video elements)
+-            mapped_region_rect = QRect(mapped_region_rect.x(), mapped_region_rect.y(), mapped_region_rect.width() * scale, mapped_region_rect.height() * scale)
++            mapped_region_rect = QRect(
++                mapped_region_rect.x(),
++                mapped_region_rect.y(),
++                int(mapped_region_rect.width() * scale),
++                int(mapped_region_rect.height() * scale))
+ 
+             # Render QWidget onto scaled QImage
+-            self.region_qimage = QImage(mapped_region_rect.width(), mapped_region_rect.height(), QImage.Format_RGBA8888)
++            self.region_qimage = QImage(
++                mapped_region_rect.size(), QImage.Format_RGBA8888)
+             region_painter = QPainter(self.region_qimage)
+-            region_painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++            region_painter.setRenderHints(
++                QPainter.Antialiasing
++                | QPainter.SmoothPixmapTransform
++                | QPainter.TextAntialiasing,
++                True)
+             region_painter.scale(scale, scale)
+-            self.render(region_painter, QPoint(0,0), QRegion(mapped_region_rect, QRegion.Rectangle))
++            self.render(
++                region_painter, QPoint(0, 0),
++                QRegion(mapped_region_rect, QRegion.Rectangle))
+             region_painter.end()
+ 
+         # Inform UpdateManager to accept updates, and only store our final update
+@@ -484,7 +606,8 @@ def mouseReleaseEvent(self, event):
+     def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+         """Rotate cursor based on the current transform"""
+         rotated_pixmap = pixmap.transformed(
+-            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8), Qt.SmoothTransformation)
++            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8),
++            Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+     def getTransformMode(self, rotation, shear_x, shear_y, event):
+@@ -627,6 +750,10 @@ def mouseMoveEvent(self, event):
+ 
+             # Transform clip object
+             if self.transform_mode:
++
++                x_motion = event.pos().x() - self.mouse_position.x()
++                y_motion = event.pos().y() - self.mouse_position.y()
++
+                 if self.transform_mode == 'origin':
+                     # Get current keyframe value
+                     origin_x = raw_properties.get('origin_x').get('value')
+@@ -635,8 +762,8 @@ def mouseMoveEvent(self, event):
+                     scale_y = raw_properties.get('scale_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    origin_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() * scale_x)
+-                    origin_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() * scale_y)
++                    origin_x += x_motion / (self.clipBounds.width() * scale_x)
++                    origin_y += y_motion / (self.clipBounds.height() * scale_y)
+ 
+                     # Constrain to clip
+                     if origin_x < 0.0:
+@@ -648,8 +775,13 @@ def mouseMoveEvent(self, event):
+                     if origin_y > 1.0:
+                         origin_y = 1.0
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_x', origin_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_y', origin_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_x', origin_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_y', origin_y)
+ 
+                 elif self.transform_mode == 'location':
+                     # Get current keyframe value
+@@ -657,12 +789,17 @@ def mouseMoveEvent(self, event):
+                     location_y = raw_properties.get('location_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                    location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                    location_x += x_motion / viewport_rect.width()
++                    location_y += y_motion / viewport_rect.height()
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_x', location_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_y', location_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_x', location_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_y', location_y)
+ 
+                 elif self.transform_mode == 'shear_top':
+                     # Get current keyframe shear value
+@@ -672,11 +809,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x -= (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_bottom':
+                     # Get current keyframe shear value
+@@ -686,11 +825,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x += (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_left':
+                     # Get current keyframe shear value
+@@ -701,11 +842,13 @@ def mouseMoveEvent(self, event):
+                     aspect_ratio = (
+                         self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y -= (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'shear_right':
+                     # Get current keyframe shear value
+@@ -713,13 +856,16 @@ def mouseMoveEvent(self, event):
+                     shear_y = raw_properties.get('shear_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    aspect_ratio = (self.clipBounds.height() / self.clipBounds.width()) * 2.0
++                    aspect_ratio = (
++                        self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y += (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'rotation':
+                     # Get current rotation keyframe value
+@@ -728,71 +874,68 @@ def mouseMoveEvent(self, event):
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                     # Calculate new location coordinates
+-                    is_on_left = event.pos().x() < self.originHandle.x()
++                    is_on_right = event.pos().x() > self.originHandle.x()
+                     is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                    if is_on_top:
+-                        rotation += (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-                    else:
+-                        rotation -= (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-
+-                    if is_on_left:
+-                        rotation -= (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
+-                    else:
+-                        rotation += (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
++                    x_adjust = x_motion / ((self.clipBounds.width() * scale_x) / 90)
++                    rotation += (x_adjust if is_on_top else -x_adjust)
++
++                    y_adjust = y_motion / ((self.clipBounds.height() * scale_y) / 90)
++                    rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'rotation', rotation)
++                    self.updateClipProperty(
++                        self.transforming_clip.id,
++                        clip_frame_number,
++                        'rotation', rotation)
+ 
+                 elif self.transform_mode.startswith('scale_'):
+                     # Get current scale keyframe value
+                     scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                    half_w = self.clipBounds.width() / 2.0
++                    half_h = self.clipBounds.height() / 2.0
++
+                     if self.transform_mode == 'scale_top_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top':
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom':
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x -= x_motion / half_w
+                     elif self.transform_mode == 'scale_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x += x_motion / half_w
+ 
+                     if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+                         # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                        if scale_x and scale_y:
++                        if scale_x:
+                             scale_y = scale_x
+                         elif scale_y:
+                             scale_x = scale_y
+-                        elif scale_x:
+-                            scale_y = scale_x
+ 
+                     # Update keyframe value (or create new one)
+                     both_scaled = scale_x != 0.001 and scale_y != 0.001
+                     if scale_x != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_x', scale_x, refresh=(not both_scaled))
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_x', scale_x,
++                            refresh=(not both_scaled))
+                     if scale_y != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_y', scale_y)
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -803,16 +946,29 @@ def mouseMoveEvent(self, event):
+             cs = self.cs
+ 
+             # Adjust existing region coordinates (if any)
+-            if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++            if (not self.mouse_dragging
++                and self.resize_button.isVisible()
++                and self.resize_button.rect().contains(event.pos())
++            ):
+                 # Mouse over resize button (and not currently dragging)
+                 self.setCursor(Qt.ArrowCursor)
+-            elif self.region_transform and self.regionTopLeftHandle and self.region_transform.mapToPolygon(self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionTopLeftHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_top_left':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+                 if self.mouse_dragging and not self.region_mode:
+                     self.region_mode = 'scale_top_left'
+-            elif self.region_transform and self.regionBottomRightHandle and self.region_transform.mapToPolygon(self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionBottomRightHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_bottom_right':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+@@ -824,13 +980,25 @@ def mouseMoveEvent(self, event):
+             # Initialize new region coordinates at current event.pos()
+             if self.mouse_dragging and not self.region_mode:
+                 self.region_mode = 'scale_bottom_right'
+-                self.regionTopLeftHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
+-                self.regionBottomRightHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
++                self.regionTopLeftHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
++                self.regionBottomRightHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
+ 
+             # Move existing region coordinates
+             if self.mouse_dragging:
+-                diff_x = self.region_transform_inverted.map(event.pos()).x() - self.region_transform_inverted.map(self.mouse_position).x()
+-                diff_y = self.region_transform_inverted.map(event.pos()).y() - self.region_transform_inverted.map(self.mouse_position).y()
++                diff_x = int(
++                    self.region_transform_inverted.map(event.pos()).x()
++                    - self.region_transform_inverted.map(self.mouse_position).x()
++                )
++                diff_y = int(
++                    self.region_transform_inverted.map(event.pos()).y()
++                    - self.region_transform_inverted.map(self.mouse_position).y()
++                )
+                 if self.region_mode == 'scale_top_left':
+                     self.regionTopLeftHandle.adjust(diff_x, diff_y, diff_x, diff_y)
+                 elif self.region_mode == 'scale_bottom_right':
+@@ -859,12 +1027,11 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-
+-
+             if self.transforming_effect_object.info.has_tracked_object:
+                 # Get properties of effect at current frame
+                 raw_properties = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+-                 # Get properties for the first object in dict. PropertiesJSON should return one object at the time
++                # Get properties for the first object in dict.
++                # PropertiesJSON should return one object at the time
+                 obj_id = list(raw_properties.get('objects').keys())[0]
+                 raw_properties = raw_properties.get('objects').get(obj_id)
+ 
+@@ -878,18 +1045,28 @@ def mouseMoveEvent(self, event):
+                 # Transform effect object
+                 if self.transform_mode:
+ 
++                    x_motion = event.pos().x() - self.mouse_position.x()
++                    y_motion = event.pos().y() - self.mouse_position.y()
++
+                     if self.transform_mode == 'location':
+                         # Get current keyframe value
+                         location_x = raw_properties.get('delta_x').get('value')
+                         location_y = raw_properties.get('delta_y').get('value')
+ 
+                         # Calculate new location coordinates
+-                        location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                        location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                        location_x += x_motion / viewport_rect.width()
++                        location_y += y_motion / viewport_rect.height()
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_x', location_x, refresh=False)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_y', location_y)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_x', location_x,
++                            refresh=False)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_y', location_y)
+ 
+                     elif self.transform_mode == 'rotation':
+                         # Get current rotation keyframe value
+@@ -898,63 +1075,70 @@ def mouseMoveEvent(self, event):
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                         # Calculate new location coordinates
+-                        is_on_left = event.pos().x() < self.originHandle.x()
++                        is_on_right = event.pos().x() > self.originHandle.x()
+                         is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                        if is_on_top:
+-                            rotation += (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
+-                        else:
+-                            rotation -= (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
++                        x_adjust = x_motion / (self.clipBounds.width() * scale_x / 90)
++                        rotation += (x_adjust if is_on_top else -x_adjust)
+ 
+-                        if is_on_left:
+-                            rotation -= (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
+-                        else:
+-                            rotation += (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
++                        y_adjust = y_motion / (self.clipBounds.height() * scale_y / 90)
++                        rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'rotation', rotation)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id,
++                            clip_frame_number, obj_id,
++                            'rotation', rotation)
+ 
+                     elif self.transform_mode.startswith('scale_'):
+                         # Get current scale keyframe value
+                         scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                        half_w = self.clipBounds.width() / 2.0
++                        half_h = self.clipBounds.height() / 2.0
++
+                         if self.transform_mode == 'scale_top_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top':
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom':
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x -= x_motion / half_w
+                         elif self.transform_mode == 'scale_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x += x_motion / half_w
+ 
+                         if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+-                            # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                            if scale_x and scale_y:
++                            # If CTRL key is pressed, fix the scale_y to the correct aspect ratio
++                            if scale_x:
+                                 scale_y = scale_x
+                             elif scale_y:
+                                 scale_x = scale_y
+-                            elif scale_x:
+-                                scale_y = scale_x
+ 
+                         # Update keyframe value (or create new one)
+                         both_scaled = scale_x != 0.001 and scale_y != 0.001
+                         if scale_x != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_x', scale_x, refresh=(not both_scaled))
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_x', scale_x,
++                                refresh=(not both_scaled))
+                         if scale_y != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_y', scale_y)
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -1012,8 +1196,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             # No clip found
+             return
+ 
+-        for point in c.data['objects'][obj_id][property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        try:
++            props = c.data['objects'][obj_id]
++            points_list = props[property_key]["Points"]
++        except (TypeError, KeyError):
++            log.error("Corrupted project data!", exc_info=1)
++            return
++
++        for point in points_list:
++            log.info("looping points: co.X = %s", point["co"]["X"])
+ 
+             if point["co"]["X"] == frame_number:
+                 found_point = True
+@@ -1023,12 +1214,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+ 
+         if not found_point and new_value != None:
+             effect_updated = True
+-            log.info("Created new point at X=%s" % frame_number)
+-            c.data['objects'][obj_id][property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER})
++            log.info("Created new point at X=%s", frame_number)
++            points_list.append({
++                'co': { 'X': frame_number, 'Y': new_value },
++                'interpolation': openshot.BEZIER,
++                })
+ 
+         # Reduce # of clip properties we are saving (performance boost)
+         #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects').get(obj_id)}}
++        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+ 
+         if effect_updated:
+             c.save()
+@@ -1040,10 +1234,10 @@ def refreshTriggered(self):
+         """Signal to refresh viewport (i.e. a property might have changed that effects the preview)"""
+ 
+         # Update reference to clip
+-        if self and self.transforming_clip:
++        if self.transforming_clip:
+             self.transforming_clip = Clip.get(id=self.transforming_clip.id)
+ 
+-        if self and self.transforming_effect:
++        if self.transforming_effect:
+             self.transforming_effect = Effect.get(id=self.transforming_effect.id)
+ 
+     def transformTriggered(self, clip_id):
+@@ -1053,7 +1247,7 @@ def transformTriggered(self, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_clip and not clip_id:
++        if self.transforming_clip and not clip_id:
+             # Clear transform
+             self.transforming_clip = None
+             need_refresh = True
+@@ -1078,7 +1272,7 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_effect and not effect_id:
++        if self.transforming_effect and not effect_id:
+             # Clear transform
+             self.transforming_effect = None
+             self.transforming_clip = None
+@@ -1102,12 +1296,8 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+     def regionTriggered(self, clip_id):
+         """Handle the 'select region' signal when it's emitted"""
+-        if self and not clip_id:
+-            # Clear transform
+-            self.region_enabled = False
+-        else:
+-            self.region_enabled = True
+-
++        # Clear transform
++        self.region_enabled = bool(not clip_id)
+         get_app().window.refreshFrameSignal.emit()
+ 
+     def resizeEvent(self, event):
+@@ -1135,7 +1325,7 @@ def delayed_resize_callback(self):
+             ratio = float(project_size.width()) / float(project_size.height())
+             even_width = round(project_size.width() / 2.0) * 2
+             even_height = round(round(even_width / ratio) / 2.0) * 2
+-            project_size = QSize(even_width, even_height)
++            project_size = QSize(int(even_width), int(even_height))
+ 
+         # Emit signal that video widget changed size
+         self.win.MaxSizeChanged.emit(project_size)
+@@ -1199,7 +1389,6 @@ def __init__(self, watch_project=True, *args):
+         self.mouse_dragging = False
+         self.mouse_position = None
+         self.transform_mode = None
+-        self.gravity_point = None
+         self.original_clip_data = None
+         self.region_qimage = None
+         self.region_transform = None
+@@ -1208,8 +1397,8 @@ def __init__(self, watch_project=True, *args):
+         self.regionTopLeftHandle = None
+         self.regionBottomRightHandle = None
+         self.curr_frame_size = None # Frame size
+-        self.zoom = 1.0 # Zoom of widget (does not affect video, only workspace)
+-        self.cs = 14.0 # Corner size of Transform Handler rectangles
++        self.zoom = 1.0  # Zoom of widget (does not affect video, only workspace)
++        self.cs = 14.0  # Corner size of Transform Handler rectangles
+         self.resize_button = QPushButton(_('Reset Zoom'), self)
+         self.resize_button.hide()
+         self.resize_button.setStyleSheet('QPushButton { margin: 10px; padding: 2px; }')
+@@ -1251,7 +1440,6 @@ def __init__(self, watch_project=True, *args):
+ 
+         # Show Property timer
+         # Timer to use a delay before sending MaxSizeChanged signals (so we don't spam libopenshot)
+-        self.delayed_size = None
+         self.delayed_resize_timer = QTimer(self)
+         self.delayed_resize_timer.setInterval(200)
+         self.delayed_resize_timer.setSingleShot(True)
+diff --git a/src/windows/views/effects_listview.py b/src/windows/views/effects_listview.py
+index b7da28dc4..3ce3b4164 100644
+--- a/src/windows/views/effects_listview.py
++++ b/src/windows/views/effects_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsListView(QListView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -69,8 +70,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/effects_treeview.py b/src/windows/views/effects_treeview.py
+index 6a5ab79f4..910593524 100644
+--- a/src/windows/views/effects_treeview.py
++++ b/src/windows/views/effects_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/emojis_listview.py b/src/windows/views/emojis_listview.py
+index 6f09bc562..cb13c35a7 100644
+--- a/src/windows/views/emojis_listview.py
++++ b/src/windows/views/emojis_listview.py
+@@ -39,7 +39,8 @@
+ 
+ class EmojisListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def dragEnterEvent(self, event):
+         # If dragging urls onto widget, accept
+@@ -57,8 +58,8 @@ def startDrag(self, event):
+         drag = QDrag(self)
+         drag.setMimeData(self.model.mimeData(selected))
+         icon = self.model.data(selected[0], Qt.DecorationRole)
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+ 
+         # Create emoji file before drag starts
+         data = json.loads(drag.mimeData().text())
+diff --git a/src/windows/views/files_listview.py b/src/windows/views/files_listview.py
+index 83cdd0272..0e67306ae 100644
+--- a/src/windows/views/files_listview.py
++++ b/src/windows/views/files_listview.py
+@@ -38,7 +38,8 @@
+ 
+ class FilesListView(QListView):
+     """ A ListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -113,8 +114,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/files_treeview.py b/src/windows/views/files_treeview.py
+index d3fe74d88..776b71cc2 100644
+--- a/src/windows/views/files_treeview.py
++++ b/src/windows/views/files_treeview.py
+@@ -41,7 +41,8 @@
+ 
+ class FilesTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+ 
+@@ -114,8 +115,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/properties_tableview.py b/src/windows/views/properties_tableview.py
+index e79b13644..fdaa7ee24 100644
+--- a/src/windows/views/properties_tableview.py
++++ b/src/windows/views/properties_tableview.py
+@@ -53,8 +53,13 @@
+ 
+ 
+ class PropertyDelegate(QItemDelegate):
+-    def __init__(self, parent=None, *args):
+-        QItemDelegate.__init__(self, parent, *args)
++    def __init__(self, parent=None, *args, **kwargs):
++
++        self.model = kwargs.pop("model", None)
++        if not self.model:
++            log.error("Cannot create delegate without data model!")
++
++        super().__init__(parent, *args, **kwargs)
+ 
+         # pixmaps for curve icons
+         self.curve_pixmaps = {
+@@ -68,7 +73,7 @@ def paint(self, painter, option, index):
+         painter.setRenderHint(QPainter.Antialiasing)
+ 
+         # Get data model and selection
+-        model = get_app().window.propertyTableView.clip_properties_model.model
++        model = self.model
+         row = model.itemFromIndex(index).row()
+         selected_label = model.item(row, 0)
+         selected_value = model.item(row, 1)
+@@ -104,16 +109,16 @@ def paint(self, painter, option, index):
+         painter.setPen(QPen(Qt.NoPen))
+         if property_type == "color":
+             # Color keyframe
+-            red = cur_property[1]["red"]["value"]
+-            green = cur_property[1]["green"]["value"]
+-            blue = cur_property[1]["blue"]["value"]
+-            painter.setBrush(QBrush(QColor(QColor(red, green, blue))))
++            red = int(cur_property[1]["red"]["value"])
++            green = int(cur_property[1]["green"]["value"])
++            blue = int(cur_property[1]["blue"]["value"])
++            painter.setBrush(QColor(red, green, blue))
+         else:
+             # Normal Keyframe
+             if option.state & QStyle.State_Selected:
+-                painter.setBrush(QBrush(QColor("#575757")))
++                painter.setBrush(QColor("#575757"))
+             else:
+-                painter.setBrush(QBrush(QColor("#3e3e3e")))
++                painter.setBrush(QColor("#3e3e3e"))
+ 
+         if readonly:
+             # Set text color for read only fields
+@@ -146,7 +151,10 @@ def paint(self, painter, option, index):
+ 
+             if points > 1:
+                 # Draw interpolation icon on top
+-                painter.drawPixmap(option.rect.x() + option.rect.width() - 30.0, option.rect.y() + 4, self.curve_pixmaps[interpolation])
++                painter.drawPixmap(
++                    int(option.rect.x() + option.rect.width() - 30.0),
++                    int(option.rect.y() + 4),
++                    self.curve_pixmaps[interpolation])
+ 
+             # Set text color
+             painter.setPen(QPen(Qt.white))
+@@ -818,9 +826,9 @@ def Color_Picker_Triggered(self, cur_property):
+         _ = get_app()._tr
+ 
+         # Get current value of color
+-        red = cur_property[1]["red"]["value"]
+-        green = cur_property[1]["green"]["value"]
+-        blue = cur_property[1]["blue"]["value"]
++        red = int(cur_property[1]["red"]["value"])
++        green = int(cur_property[1]["green"]["value"])
++        blue = int(cur_property[1]["blue"]["value"])
+ 
+         # Show color dialog
+         currentColor = QColor(red, green, blue)
+@@ -865,7 +873,7 @@ def __init__(self, *args):
+         self.files_model = self.win.files_model.model
+ 
+         # Connect to update signals, so our menus stay current
+-        self.win.files_model.ModelRefreshed.connect(self.refresh_menu)
++        self.files_model.dataChanged.connect(self.refresh_menu)
+         self.win.transition_model.ModelRefreshed.connect(self.refresh_menu)
+         self.menu_reset = False
+ 
+@@ -890,7 +898,7 @@ def __init__(self, *args):
+         self.setWordWrap(True)
+ 
+         # Set delegate
+-        delegate = PropertyDelegate()
++        delegate = PropertyDelegate(model=self.clip_properties_model.model)
+         self.setItemDelegateForColumn(1, delegate)
+         self.previous_x = -1
+ 
+diff --git a/src/windows/views/transitions_listview.py b/src/windows/views/transitions_listview.py
+index 09af86b2b..3ba3346f5 100644
+--- a/src/windows/views/transitions_listview.py
++++ b/src/windows/views/transitions_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/transitions_treeview.py b/src/windows/views/transitions_treeview.py
+index 6f903e913..7aa0cd481 100644
+--- a/src/windows/views/transitions_treeview.py
++++ b/src/windows/views/transitions_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -68,8 +69,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/tutorial.py b/src/windows/views/tutorial.py
+index e2d7fb861..24124799f 100644
+--- a/src/windows/views/tutorial.py
++++ b/src/windows/views/tutorial.py
+@@ -53,7 +53,12 @@ def paintEvent(self, event, *args):
+ 
+         painter.setPen(QPen(frameColor, 2))
+         painter.setBrush(self.palette().color(QPalette.Window))
+-        painter.drawRoundedRect(QRectF(31, 0, self.width() - 31, self.height()), 10, 10)
++        painter.drawRoundedRect(
++            QRectF(31, 0,
++                   self.width() - 31,
++                   self.height()
++                   ),
++            10, 10)
+ 
+         # Paint blue triangle (if needed)
+         if self.arrow:
+@@ -61,7 +66,8 @@ def paintEvent(self, event, *args):
+             path = QPainterPath()
+             path.moveTo(0, 35)
+             path.lineTo(31, 35 - arrow_height)
+-            path.lineTo(31, (35 - arrow_height) + (arrow_height * 2))
++            path.lineTo(
++                31, int((35 - arrow_height) + (arrow_height * 2)))
+             path.lineTo(0, 35)
+             painter.fillPath(path, frameColor)
+ 
+@@ -199,7 +205,9 @@ def process(self, parent_name=None):
+ 
+             # Create tutorial
+             self.position_widget = tutorial_object
+-            self.offset = QPoint(tutorial_details["x"], tutorial_details["y"])
++            self.offset = QPoint(
++                int(tutorial_details["x"]),
++                int(tutorial_details["y"]))
+             tutorial_dialog = TutorialDialog(tutorial_id, tutorial_details["text"], tutorial_details["arrow"], self)
+ 
+             # Connect signals
+
+From 9fc55120f36c36b2b6b67e499128993c25e535cf Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:02:56 -0400
+Subject: [PATCH 3/6] VideoWidget: New checkTransformMode
+
+Replacement for getTransformMode with less repetitious code
+---
+ src/windows/video_widget.py | 160 ++++++++++++++----------------------
+ 1 file changed, 61 insertions(+), 99 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index f33696c5c..1b9c35b54 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -610,106 +610,68 @@ def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+             Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+-    def getTransformMode(self, rotation, shear_x, shear_y, event):
++    def checkTransformMode(self, rotation, shear_x, shear_y, event):
++        handle_uis = [
++            {"handle": self.centerHandle, "mode": 'origin', "cursor": 'hand'},
++            {"handle": self.topRightHandle, "mode": 'scale_top_right', "cursor": 'resize_bdiag'},
++            {"handle": self.topHandle, "mode": 'scale_top', "cursor": 'resize_y'},
++            {"handle": self.topLeftHandle, "mode": 'scale_top_left', "cursor": 'resize_fdiag'},
++            {"handle": self.leftHandle, "mode": 'scale_left', "cursor": 'resize_x'},
++            {"handle": self.rightHandle, "mode": 'scale_right', "cursor": 'resize_x'},
++            {"handle": self.bottomLeftHandle, "mode": 'scale_bottom_left', "cursor": 'resize_bdiag'},
++            {"handle": self.bottomHandle, "mode": 'scale_bottom', "cursor": 'resize_y'},
++            {"handle": self.bottomRightHandle, "mode": 'scale_bottom_right', "cursor": 'resize_fdiag'},
++            {"handle": self.topShearHandle, "mode": 'shear_top', "cursor": 'shear_x'},
++            {"handle": self.leftShearHandle, "mode": 'shear_left', "cursor": 'shear_y'},
++            {"handle": self.rightShearHandle, "mode": 'shear_right', "cursor": 'shear_y'},
++            {"handle": self.bottomShearHandle, "mode": 'shear_bottom', "cursor": 'shear_x'},
++        ]
++        non_handle_uis = {
++            "region": self.clipBounds,
++            "inside": {"mode": 'location', "cursor": 'move'},
++            "outside": {"mode": 'rotation', "cursor": "rotate"}
++            }
++
+         # Mouse over resize button (and not currently dragging)
+-        if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++        if (not self.mouse_dragging
++            and self.resize_button.isVisible()
++            and self.resize_button.rect().contains(event.pos()
++        ):
+             self.setCursor(Qt.ArrowCursor)
+-        # Determine if cursor is over a handle
+-        elif self.transform.mapToPolygon(self.centerHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'origin':
+-                self.setCursor(self.rotateCursor(self.cursors.get('hand'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'origin'
+-        elif self.transform.mapToPolygon(self.topRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_right'
+-        elif self.transform.mapToPolygon(self.topHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top'
+-        elif self.transform.mapToPolygon(self.topLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_left'
+-        elif self.transform.mapToPolygon(self.leftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_left'
+-        elif self.transform.mapToPolygon(self.rightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_right'
+-        elif self.transform.mapToPolygon(self.bottomLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_left'
+-        elif self.transform.mapToPolygon(self.bottomHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom'
+-        elif self.transform.mapToPolygon(self.bottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_right'
+-        elif self.transform.mapToPolygon(self.topShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_top'
+-        elif self.transform.mapToPolygon(self.leftShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_left'
+-        elif self.transform.mapToPolygon(self.rightShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_right'
+-        elif self.transform.mapToPolygon(self.bottomShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_bottom'
+-        elif self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'location':
+-                self.setCursor(self.rotateCursor(self.cursors.get('move'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'location'
+-        elif not self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'rotation':
+-                self.setCursor(self.rotateCursor(self.cursors.get('rotate'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'rotation'
+-        elif not self.transform_mode:
+-            # Reset cursor when not over a handle
+-            self.setCursor(QCursor(Qt.ArrowCursor))
++            self.transform_mode = None
++            return
++
++        # If mouse is over a handle, set corresponding pointer/mode
++        for h in handle_uis:
++            if self.transform.mapToPolygon(
++                    h["handle"].toRect()
++                    ).containsPoint(event.pos(), Qt.OddEvenFill):
++                # Handle contains cursor
++                if self.transform_mode and self.transform_mode != h["mode"]:
++                    # We're in different xform mode, skip
++                    continue
++                if self.mouse_dragging:
++                    self.transform_mode = h["mode"]
++                self.setCursor(self.rotateCursor(
++                    self.cursors.get(h["cursor"]), rotation, shear_x, shear_y))
++                return
++
++        # If not over any handles, determne inside/outside clip rectangle
++        r = non_handle_uis.get("region")
++        if self.transform.mapToPolygon(r.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            nh = non_handle_uis.get("inside", {})
++        else:
++            nh = non_handle_uis.get("outside", {})
++        if self.mouse_dragging and not self.transform_mode:
++            self.transform_mode = nh.get("mode")
++        if not self.transform_mode or self.transform_mode == nh.get("mode"):
++            self.setCursor(self.rotateCursor(
++                self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-        return True
++
++        # If we got this far and we don't have a transform mode, reset the cursor
++        if not self.transform_mode:
++            self.setCursor(QCursor(Qt.ArrowCursor))
+ 
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+@@ -746,7 +708,7 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-            _ = self.getTransformMode(rotation, shear_x, shear_y, event)
++            self.checkTransformMode(rotation, shear_x, shear_y, event)
+ 
+             # Transform clip object
+             if self.transform_mode:
+@@ -1040,7 +1002,7 @@ def mouseMoveEvent(self, event):
+                     self.mutex.unlock()
+                     return
+ 
+-                _ = self.getTransformMode(0, 0, 0, event)
++                self.checkTransformMode(0, 0, 0, event)
+ 
+                 # Transform effect object
+                 if self.transform_mode:
+
+From 1f058f730bb36068bce5c645ecea45b98279b614 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:42:48 -0400
+Subject: [PATCH 4/6] VideoWidget: Protect property accesses
+
+---
+ src/windows/video_widget.py | 61 +++++++++++++++++++++----------------
+ 1 file changed, 34 insertions(+), 27 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 1b9c35b54..a4d42969f 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -635,7 +635,7 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+         # Mouse over resize button (and not currently dragging)
+         if (not self.mouse_dragging
+             and self.resize_button.isVisible()
+-            and self.resize_button.rect().contains(event.pos()
++            and self.resize_button.rect().contains(event.pos())
+         ):
+             self.setCursor(Qt.ArrowCursor)
+             self.transform_mode = None
+@@ -668,11 +668,6 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+             self.setCursor(self.rotateCursor(
+                 self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-
+-        # If we got this far and we don't have a transform mode, reset the cursor
+-        if not self.transform_mode:
+-            self.setCursor(QCursor(Qt.ArrowCursor))
+-
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+         self.mutex.lock()
+@@ -1121,27 +1116,37 @@ def updateClipProperty(self, clip_id, frame_number, property_key, new_value, ref
+             # No clip found
+             return
+ 
+-        for point in c.data[property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        # Property missing? Create it!
++        if property_key not in c.data:
++            c.data[property_key] = {"Points": []}
++            log.warning(
++                "%s: Added missing '%s' to property data",
++                clip_id, property_key)
+ 
+-            if point["co"]["X"] == frame_number:
++        points = c.data.get(property_key).get("Points")
++        for point in points:
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s" % co.get("X"))
++
++            if co.get("X") == frame_number:
+                 found_point = True
+                 clip_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+         if not found_point and new_value is not None:
+             clip_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             c.data[property_key]["Points"].append({
+-                'co': {'X': frame_number, 'Y': new_value},
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        c.data = {property_key: c.data.get(property_key)}
+-
+         if clip_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            c.data = {property_key: c.data.get(property_key)}
+             c.save()
+             # Update the preview
+             if refresh:
+@@ -1166,27 +1171,29 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             return
+ 
+         for point in points_list:
+-            log.info("looping points: co.X = %s", point["co"]["X"])
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s", co.get("X"))
+ 
+-            if point["co"]["X"] == frame_number:
++            if co.get("X") == frame_number:
+                 found_point = True
+                 effect_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+-        if not found_point and new_value != None:
++        if not found_point and new_value is not None:
+             effect_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             points_list.append({
+-                'co': { 'X': frame_number, 'Y': new_value },
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER,
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+-
+         if effect_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            #TODO: This is too slow when draging transform handlers
++            c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+             c.save()
+             # Update the preview
+             if refresh:
+
+From 7df87ddc189c5e5031fbb19df13378428fab951e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:52:19 -0400
+Subject: [PATCH 5/6] classes/thumbnail: Fix dangling filehandles
+
+---
+ src/classes/thumbnail.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/classes/thumbnail.py b/src/classes/thumbnail.py
+index cca47d68f..dac7422a1 100644
+--- a/src/classes/thumbnail.py
++++ b/src/classes/thumbnail.py
+@@ -209,13 +209,13 @@ def do_GET(self):
+ 
+         # Send message back to client
+         if os.path.exists(thumb_path):
+-            if not only_path:
+-                self.wfile.write(open(thumb_path, 'rb').read())
+-            else:
++            if only_path:
+                 self.wfile.write(bytes(thumb_path, "utf-8"))
++            else:
++                with open(thumb_path, 'rb') as f:
++                    self.wfile.write(f.read())
+ 
+         # Pause processing of request (since we don't currently use thread pooling, this allows
+         # the threads to be processed without choking the CPU as much
+         # TODO: Make HTTPServer work with a limited thread pool and remove this sleep() hack.
+         time.sleep(0.01)
+-
+
+From 33cf68ca0b1ea57edd5dec3dbb8ba06d6a3f8fa4 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 25 Nov 2021 02:18:32 -0500
+Subject: [PATCH 6/6] properties_model: Fix bad logging call, Codacy flags
+
+---
+ src/windows/models/properties_model.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index 40897f642..695e9f39c 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -444,7 +444,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                 log.debug("%s: update property %s. %s", log_id, property_key, clip_data.get(property_key))
+ 
+                 # Check the type of property (some are keyframe, and some are not)
+-                if property_type != "reader" and type(clip_data[property_key]) == dict:
++                if property_type != "reader" and isinstance(clip_data[property_key], dict):
+                     # Keyframe
+                     # Loop through points, find a matching points on this frame
+                     found_point = False
+@@ -517,21 +517,21 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = int(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+@@ -558,7 +558,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_object.Close()
+                         clip_object = None
+                     except Exception:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
++                        log.warn('Invalid Reader value passed to property: %s', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
diff --git a/srcpkgs/openshot/template b/srcpkgs/openshot/template
index d16ea925d223..4248baa81e39 100644
--- a/srcpkgs/openshot/template
+++ b/srcpkgs/openshot/template
@@ -1,12 +1,11 @@
 # Template file for 'openshot'
 pkgname=openshot
-version=2.5.1
-revision=3
-archs="i686 x86_64 ppc64le"
+version=2.6.1
+revision=1
 wrksrc="${pkgname}-qt-${version}"
 build_style=python3-module
-hostmakedepends="python3"
-makedepends="ffmpeg-devel python3-PyQt5 python3-setuptools"
+hostmakedepends="python3 python3-setuptools"
+makedepends="ffmpeg-devel python3-PyQt5"
 depends="ImageMagick libopenshot mlt python3-PyQt5-svg
  python3-PyQt5-webkit python3-httplib2 python3-pyzmq python3-requests"
 short_desc="Open-source, non-linear video editor for Linux"
@@ -14,4 +13,5 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://www.openshot.org"
 distfiles="https://github.com/OpenShot/openshot-qt/archive/v${version}.tar.gz"
-checksum=4c25eb9a5de42e749de4c6ca2f7a313c60e1283fe52d70c121629dbb8bb2df7b
+checksum=11651d5e7287da3c766ce6b447aef8da5cdadaf5626a2816e9025c831d0e1a66
+make_check=no # tests are broken

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PR PATCH] [Updated] openshot: update to 2.6.1.
  2022-02-23  0:01 [PR PATCH] openshot: update to 2.6.1 tibequadorian
  2022-02-23  0:12 ` [PR PATCH] [Updated] " tibequadorian
@ 2022-02-24 14:47 ` tibequadorian
  2022-02-25 23:52 ` [PR PATCH] [Merged]: " Piraty
  2 siblings, 0 replies; 4+ messages in thread
From: tibequadorian @ 2022-02-24 14:47 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1481 bytes --]

There is an updated pull request by tibequadorian against master on the void-packages repository

https://github.com/tibequadorian/void-packages openshot
https://github.com/void-linux/void-packages/pull/35798

openshot: update to 2.6.1.
Closes #33897

Patches for openshot are already upstream so maybe we should wait for 2.6.2.

<!-- Uncomment relevant sections and delete options which are not applicable -->

#### Testing the changes
- I tested the changes in this PR: **briefly**

Removed the restriction on arch or libc so help for testing on musl and ARM is appreciated.
- [x] aarch64 glibc with wayland is confirmed to work.

<!--
#### New package
- This new package conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements): **YES**|**NO**
-->

<!-- Note: If the build is likely to take more than 2 hours, please [skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration)
and test at least one native build and, if supported, at least one cross build.
Ignore this section if this PR is not skipping CI.
-->
<!-- 
#### Local build testing
- I built this PR locally for my native architecture, (ARCH-LIBC)
- I built this PR locally for these architectures (if supported. mark crossbuilds):
  - aarch64-musl
  - armv7l
  - armv6l-musl
-->


A patch file from https://github.com/void-linux/void-packages/pull/35798.patch is attached

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: github-pr-openshot-35798.patch --]
[-- Type: text/x-diff, Size: 122522 bytes --]

From 7e91c46c332e83f0b89e2c97701890765f2fafb5 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:41:38 +0100
Subject: [PATCH 1/3] libopenshot-audio: update to 0.2.2.

---
 common/shlibs                                 |  2 +-
 .../libopenshot-audio/patches/fix-musl.patch  | 86 ++++++++++---------
 srcpkgs/libopenshot-audio/template            |  7 +-
 3 files changed, 51 insertions(+), 44 deletions(-)

diff --git a/common/shlibs b/common/shlibs
index 019618205973..8e2feccbee06 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2597,7 +2597,7 @@ libax25io.so.0 libax25-0.0.12rc4_1
 libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
-libopenshot-audio.so.7 libopenshot-audio-0.2.0_1
+libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
 libopenshot.so.19 libopenshot-0.2.5_3
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
diff --git a/srcpkgs/libopenshot-audio/patches/fix-musl.patch b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
index b5f178d92d8a..532248c86217 100644
--- a/srcpkgs/libopenshot-audio/patches/fix-musl.patch
+++ b/srcpkgs/libopenshot-audio/patches/fix-musl.patch
@@ -1,40 +1,46 @@
---- a/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp	2016-12-12 14:53:23.532613378 +0100
-@@ -97,7 +97,7 @@
-  #include <net/if.h>
-  #include <sys/ioctl.h>
- 
-- #if ! JUCE_ANDROID
-+ #if ! JUCE_ANDROID && defined(__GLIBC__)
-   #include <execinfo.h>
-  #endif
- #endif
---- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp	2016-12-12 14:58:35.988986030 +0100
-@@ -134,6 +134,8 @@
-         }
-     }
- 
-+   #elif !defined(__GLIBC__)
-+    jassertfalse; // sorry, not implemented yet!
-    #else
-     void* stack[128];
-     int frames = backtrace (stack, numElementsInArray (stack));
---- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-08-30 06:24:27.000000000 +0200
-+++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp	2016-12-12 15:07:35.046607788 +0100
-@@ -142,8 +142,15 @@
-     return result;
- }
- 
-+#if defined(__GLIBC__)
- String SystemStats::getUserLanguage()    { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
- String SystemStats::getUserRegion()      { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
-+#else
-+// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
-+// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
-+String SystemStats::getUserLanguage()    { return String("en"); }
-+String SystemStats::getUserRegion()      { return String("US"); }
-+#endif
- String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); }
- 
- //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/juce_core.cpp b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+index 8bac812..e23b422 100644
+--- a/JuceLibraryCode/modules/juce_core/juce_core.cpp
++++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp
+@@ -92,7 +92,7 @@
+  #include <net/if.h>
+  #include <sys/ioctl.h>
+ 
+- #if ! JUCE_ANDROID
++ #if ! JUCE_ANDROID && defined(__GLIBC__)
+   #include <execinfo.h>
+  #endif
+ #endif
+diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+index 2d7faa3..f132405 100644
+--- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp
+@@ -139,8 +139,15 @@ static String getLocaleValue (nl_item key)
+     return result;
+ }
+ 
++#if defined(__GLIBC__)
+ String SystemStats::getUserLanguage()     { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); }
+ String SystemStats::getUserRegion()       { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); }
++#else
++// The identifiers _NL_INDENTIFICATION_LANGUAGE and _TERRIRTORY are not defined in musl libc.
++// TODO: Find a better fix than just returning nonsense. Inspect env("LANG") perhaps?
++String SystemStats::getUserLanguage()     { return String("en"); }
++String SystemStats::getUserRegion()       { return String("US"); }
++#endif
+ String SystemStats::getDisplayLanguage()  { return getUserLanguage() + "-" + getUserRegion(); }
+ 
+ //==============================================================================
+diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+index 757ea24..6b61e16 100644
+--- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
++++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp
+@@ -138,7 +138,7 @@ String SystemStats::getStackBacktrace()
+ {
+     String result;
+ 
+-   #if JUCE_ANDROID || JUCE_MINGW
++   #if JUCE_ANDROID || JUCE_MINGW || !defined(__GLIBC__)
+     jassertfalse; // sorry, not implemented yet!
+ 
+    #elif JUCE_WINDOWS
diff --git a/srcpkgs/libopenshot-audio/template b/srcpkgs/libopenshot-audio/template
index 56c330eafcf1..254f8b6283bd 100644
--- a/srcpkgs/libopenshot-audio/template
+++ b/srcpkgs/libopenshot-audio/template
@@ -1,6 +1,6 @@
 # Template file for 'libopenshot-audio'
 pkgname=libopenshot-audio
-version=0.2.0
+version=0.2.2
 revision=1
 build_style=cmake
 hostmakedepends="doxygen"
@@ -11,7 +11,7 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot-audio"
 distfiles="https://github.com/OpenShot/libopenshot-audio/archive/v${version}.tar.gz"
-checksum=937ff4f1c2dfb8ab5d56ad85beacaa29dfd5a79af0d9cf647386034fe9882309
+checksum=66bedfda0d8d430598b21bc2dde6c0016a758a6c83467d0273a9d692de10baaf
 
 if [ "$XBPS_TARGET_NO_ATOMIC8" ]; then
 	makedepends+=" libatomic-devel"
@@ -20,9 +20,10 @@ fi
 
 libopenshot-audio-devel_package() {
 	short_desc+=" - development files"
-	depends+=" ${sourcepkg}>=${version}_${revision}"
+	depends+=" ${sourcepkg}>=${version}_${revision} alsa-lib-devel zlib-devel"
 	pkg_install() {
 		vmove usr/include
+		vmove usr/lib/cmake
 		vmove "usr/lib/*.so"
 	}
 }

From 2dd2236bc8f6c2d36a6e180359be5655e86418d0 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:37 +0100
Subject: [PATCH 2/3] libopenshot: update to 0.2.7.

enable for all archs and libc
---
 common/shlibs                                 |  2 +-
 .../patches/AV_GET_CODEC_CONTEXT-macro.patch  | 34 -------------------
 srcpkgs/libopenshot/template                  | 19 ++++++-----
 3 files changed, 12 insertions(+), 43 deletions(-)
 delete mode 100644 srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch

diff --git a/common/shlibs b/common/shlibs
index 8e2feccbee06..db11c39550d4 100644
--- a/common/shlibs
+++ b/common/shlibs
@@ -2598,7 +2598,7 @@ libmill.so.18 libmill-1.14_1
 libges-1.0.so.0 gst1-editing-services-1.6.2_1
 libykneomgr.so.0 libykneomgr-0.1.8_1
 libopenshot-audio.so.8 libopenshot-audio-0.2.2_1
-libopenshot.so.19 libopenshot-0.2.5_3
+libopenshot.so.21 libopenshot-0.2.7_1
 libpqxx-6.3.so libpqxx-6.3.3_1
 libndpi.so.3 ndpi-3.4_1
 liblog.so android-studio-3.0.1_1
diff --git a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch b/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
deleted file mode 100644
index e6c640a8e11e..000000000000
--- a/srcpkgs/libopenshot/patches/AV_GET_CODEC_CONTEXT-macro.patch
+++ /dev/null
@@ -1,34 +0,0 @@
---- a/include/FFmpegUtilities.h	2020-03-03 09:00:06.000000000 +0100
-+++ b/include/FFmpegUtilities.h	2020-08-19 17:04:58.535806744 +0200
-@@ -163,11 +163,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
-@@ -199,11 +198,10 @@
- 		#define AV_FREE_CONTEXT(av_context) avcodec_free_context(&av_context)
- 		#define AV_GET_CODEC_TYPE(av_stream) av_stream->codecpar->codec_type
- 		#define AV_FIND_DECODER_CODEC_ID(av_stream) av_stream->codecpar->codec_id
--		auto AV_GET_CODEC_CONTEXT = [](AVStream* av_stream, AVCodec* av_codec) { \
--			AVCodecContext *context = avcodec_alloc_context3(av_codec); \
--			avcodec_parameters_to_context(context, av_stream->codecpar); \
--			return context; \
--		};
-+		#define AV_GET_CODEC_CONTEXT(av_stream, av_codec) \
-+			({ AVCodecContext *context = avcodec_alloc_context3(av_codec); \
-+			   avcodec_parameters_to_context(context, av_stream->codecpar); \
-+			   context; })
- 		#define AV_GET_CODEC_PAR_CONTEXT(av_stream, av_codec) av_codec;
- 		#define AV_GET_CODEC_FROM_STREAM(av_stream,codec_in)
- 		#define AV_GET_CODEC_ATTRIBUTES(av_stream, av_context) av_stream->codecpar
diff --git a/srcpkgs/libopenshot/template b/srcpkgs/libopenshot/template
index b59472105b35..71dff12b5d61 100644
--- a/srcpkgs/libopenshot/template
+++ b/srcpkgs/libopenshot/template
@@ -1,11 +1,11 @@
 # Template file for 'libopenshot'
 pkgname=libopenshot
-version=0.2.5
-revision=6
-archs="i686 x86_64 ppc64le"
+version=0.2.7
+revision=1
 build_style=cmake
-configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON" # Builds fail with Ruby-2.4.1
-hostmakedepends="swig doxygen ruby python3 pkg-config"
+# Builds fail with Ruby-2.4.1
+configure_args="-DENABLE_RUBY=OFF -DUSE_SYSTEM_JSONCPP=ON"
+hostmakedepends="swig doxygen ruby python3 pkg-config qt5-qmake qt5-host-tools"
 makedepends="python3-devel ffmpeg-devel libmagick-devel qt5-devel libgomp-devel
  libopenshot-audio-devel qt5-multimedia-devel unittest-cpp zeromq-devel cppzmq
  jsoncpp-devel"
@@ -15,9 +15,12 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="LGPL-3.0-or-later"
 homepage="https://github.com/OpenShot/libopenshot"
 distfiles="https://github.com/OpenShot/libopenshot/archive/v${version}.tar.gz"
-checksum=8ae7d226fbd2efbc84da4f7d9d8c7f3cc9616e4de46e1233e3b0a84ac0a429bc
-# FIXME: tests segfault
-make_check=extended
+checksum=568eab6d69d469c5f745f0e25387ca5e000f7c28be48417b0d7770577ac74a28
+
+if [ "$XBPS_TARGET_LIBC" = musl ]; then
+	makedepends+=" libexecinfo-devel"
+	LIBS="-lexecinfo"
+fi
 
 libopenshot-devel_package() {
 	short_desc+=" - development files"

From 2566006abf2c58257977cd128dfbad75ae0c0a78 Mon Sep 17 00:00:00 2001
From: tibequadorian <tibequadorian@posteo.de>
Date: Wed, 23 Feb 2022 00:42:51 +0100
Subject: [PATCH 3/3] openshot: update to 2.6.1.

enable for all archs and libc
patches are upstream
---
 .../openshot/patches/0001-video_widget.patch  |   74 +
 .../patches/0002-python3.10_int.patch         | 1993 +++++++++++++++++
 srcpkgs/openshot/template                     |   12 +-
 3 files changed, 2073 insertions(+), 6 deletions(-)
 create mode 100644 srcpkgs/openshot/patches/0001-video_widget.patch
 create mode 100644 srcpkgs/openshot/patches/0002-python3.10_int.patch

diff --git a/srcpkgs/openshot/patches/0001-video_widget.patch b/srcpkgs/openshot/patches/0001-video_widget.patch
new file mode 100644
index 000000000000..5e93d764c4bf
--- /dev/null
+++ b/srcpkgs/openshot/patches/0001-video_widget.patch
@@ -0,0 +1,74 @@
+From 9748a13268d66a5949aebc970637b5903756d018 Mon Sep 17 00:00:00 2001
+From: Jonathan Thomas <jonathan@openshot.org>
+Date: Thu, 7 Oct 2021 13:53:09 -0500
+Subject: [PATCH] Support for previewing anamorphic video profiles, including a
+ few code clean-ups.
+
+---
+ src/windows/video_widget.py | 22 +++++++---------------
+ 1 file changed, 7 insertions(+), 15 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 7326598d3..842deb3ba 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -77,28 +77,20 @@ def changed(self, action):
+             if action.type == "load" and action.values.get("pixel_ratio"):
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("pixel_ratio").get("num", 16),
+-                    action.values.get("pixel_ratio").get("den", 9))
++                    action.values.get("pixel_ratio").get("num", 1),
++                    action.values.get("pixel_ratio").get("den", 1))
+                 log.info(
+                     "Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+             elif action.key and action.key[0] == "pixel_ratio":
+                 pixel_ratio_changed = True
+                 self.pixel_ratio = openshot.Fraction(
+-                    action.values.get("num", 16),
+-                    action.values.get("den", 9))
++                    action.values.get("num", 1),
++                    action.values.get("den", 1))
+                 log.info(
+                     "Update: Set video widget pixel aspect ratio to: %s",
+                     self.pixel_ratio.ToFloat())
+ 
+-            # Update max size (to size of video preview viewport)
+-            if display_ratio_changed or pixel_ratio_changed:
+-                timeline = get_app().window.timeline_sync.timeline
+-                timeline.SetMaxSize(
+-                    round(self.width() * self.pixel_ratio.ToFloat()),
+-                    round(self.height() * self.pixel_ratio.ToFloat())
+-                    )
+-
+ 
+     def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+      x1=None, y1=None, x2=None, y2=None, rotation = None):
+@@ -236,7 +228,7 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height)
++            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+@@ -405,7 +397,7 @@ def paintEvent(self, event, *args):
+         self.mutex.unlock()
+ 
+     def centeredViewport(self, width, height):
+-        """ Calculate size of viewport to maintain apsect ratio """
++        """ Calculate size of viewport to maintain aspect ratio """
+ 
+         # Calculate padding
+         top_padding = (height - (height * self.zoom)) / 2.0
+@@ -416,7 +408,7 @@ def centeredViewport(self, width, height):
+         height = height * self.zoom
+ 
+         # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        aspectRatio = self.aspect_ratio.ToFloat()
+         heightFromWidth = width / aspectRatio
+         widthFromHeight = height * aspectRatio
+ 
diff --git a/srcpkgs/openshot/patches/0002-python3.10_int.patch b/srcpkgs/openshot/patches/0002-python3.10_int.patch
new file mode 100644
index 000000000000..8f6dc53dbf40
--- /dev/null
+++ b/srcpkgs/openshot/patches/0002-python3.10_int.patch
@@ -0,0 +1,1993 @@
+From a3088503500e79877ce985e4784f75478d9b792e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Mon, 20 Sep 2021 05:36:19 -0400
+Subject: [PATCH 1/6] Preferences: Fix logging calls
+
+---
+ src/launch.py              |  2 +-
+ src/windows/preferences.py | 18 +++++++++++-------
+ 2 files changed, 12 insertions(+), 8 deletions(-)
+
+diff --git a/src/launch.py b/src/launch.py
+index 9fc600b23..b614e9bc7 100755
+--- a/src/launch.py
++++ b/src/launch.py
+@@ -41,7 +41,7 @@
+  """
+ 
+ import sys
+-import os.path
++import os
+ import argparse
+ 
+ from PyQt5.QtCore import Qt
+diff --git a/src/windows/preferences.py b/src/windows/preferences.py
+index bf7d67b0c..325f496b3 100644
+--- a/src/windows/preferences.py
++++ b/src/windows/preferences.py
+@@ -102,7 +102,7 @@ def __init__(self):
+ 
+     def txtSearch_changed(self):
+         """textChanged event handler for search box"""
+-        log.info("Search for %s" % self.txtSearch.text())
++        log.info("Search for %s", self.txtSearch.text())
+ 
+         # Populate preferences
+         self.Populate(filter=self.txtSearch.text())
+@@ -317,7 +317,7 @@ def Populate(self, filter=""):
+                                 value_list.remove(value_item)
+ 
+                         # Remove hardware mode items which cannot decode the example video
+-                        log.debug("Preparing to test hardware decoding: %s" % (value_list))
++                        log.debug("Preparing to test hardware decoding: %s", value_list)
+                         for value_item in list(value_list):
+                             v = value_item["value"]
+                             if (not self.testHardwareDecode(value_list, v, 0)
+@@ -470,7 +470,7 @@ def bool_value_changed(self, widget, param, state):
+         # Trigger specific actions
+         if param["setting"] == "debug-mode":
+             # Update debug setting of timeline
+-            log.info("Setting debug-mode to %s" % (state == Qt.Checked))
++            log.info("Setting debug-mode to %s", state == Qt.Checked)
+             debug_enabled = (state == Qt.Checked)
+ 
+             # Enable / Disable logger
+@@ -528,7 +528,9 @@ def text_value_changed(self, widget, param, value=None):
+         if param.get("category") == "Keyboard":
+             previous_value = value
+             value = QKeySequence(value).toString()
+-            log.info("Parsing keyboard mapping via QKeySequence from %s to %s" % (previous_value, value))
++            log.info(
++                "Parsing keyboard mapping via QKeySequence from %s to %s",
++                previous_value, value)
+ 
+         # Save setting
+         self.s.set(param["setting"], value)
+@@ -604,11 +606,13 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+             if reader.GetFrame(0).CheckPixel(0, 0, 2, 133, 255, 255, 5):
+                 is_supported = True
+                 self.hardware_tests_cards[decoder_card].append(int(decoder))
+-                log.debug("Successful hardware decoder! %s (%s-%s)" % (decoder_name, decoder, decoder_card))
++                log.debug(
++                    "Successful hardware decoder! %s (%s-%s)",
++                    decoder_name, decoder, decoder_card)
+             else:
+                 log.debug(
+                     "CheckPixel failed testing hardware decoding (i.e. wrong color found): %s (%s-%s)",
+-                    (decoder_name, decoder, decoder_card))
++                    decoder_name, decoder, decoder_card)
+ 
+             reader.Close()
+             clip.Close()
+@@ -616,7 +620,7 @@ def testHardwareDecode(self, all_decoders, decoder, decoder_card="0"):
+         except Exception:
+             log.debug(
+                 "Exception trying to test hardware decoding (this is expected): %s (%s-%s)",
+-                (decoder_name, decoder, decoder_card))
++                decoder_name, decoder, decoder_card)
+ 
+         # Resume current settings
+         openshot.Settings.Instance().HARDWARE_DECODER = current_decoder
+
+From 1b14896d2057df80b0b20ba22e1380ba9e9bd6e6 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:01:39 -0400
+Subject: [PATCH 2/6] Enforce integer function arguments
+
+---
+ src/windows/export.py                     |   8 +-
+ src/windows/models/properties_model.py    |  34 +-
+ src/windows/process_effect.py             |   2 +-
+ src/windows/video_widget.py               | 596 ++++++++++++++--------
+ src/windows/views/effects_listview.py     |   7 +-
+ src/windows/views/effects_treeview.py     |   7 +-
+ src/windows/views/emojis_listview.py      |   7 +-
+ src/windows/views/files_listview.py       |   7 +-
+ src/windows/views/files_treeview.py       |   7 +-
+ src/windows/views/properties_tableview.py |  38 +-
+ src/windows/views/transitions_listview.py |   7 +-
+ src/windows/views/transitions_treeview.py |   7 +-
+ src/windows/views/tutorial.py             |  14 +-
+ 13 files changed, 476 insertions(+), 265 deletions(-)
+
+diff --git a/src/windows/export.py b/src/windows/export.py
+index a624eb2e2..6461afb25 100644
+--- a/src/windows/export.py
++++ b/src/windows/export.py
+@@ -290,7 +290,7 @@ def updateProgressBar(self, title_message, start_frame, end_frame, current_frame
+             percentage_string = format_of_progress_string % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
+         else:
+             percentage_string = "100%"
+-        self.progressExportVideo.setValue(current_frame)
++        self.progressExportVideo.setValue(int(current_frame))
+         self.progressExportVideo.setFormat(percentage_string)
+         self.setWindowTitle("%s %s" % (percentage_string, title_message))
+ 
+@@ -690,9 +690,9 @@ def titlestring(sec, fps, mess):
+         fps_encode = 0
+ 
+         # Init progress bar
+-        self.progressExportVideo.setMinimum(self.txtStartFrame.value())
+-        self.progressExportVideo.setMaximum(self.txtEndFrame.value())
+-        self.progressExportVideo.setValue(self.txtStartFrame.value())
++        self.progressExportVideo.setMinimum(int(self.txtStartFrame.value()))
++        self.progressExportVideo.setMaximum(int(self.txtEndFrame.value()))
++        self.progressExportVideo.setValue(int(self.txtStartFrame.value()))
+ 
+         # Prompt error message
+         if self.txtStartFrame.value() == self.txtEndFrame.value():
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index c3236ed84..40897f642 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -414,8 +414,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+             new_value = None
+ 
+         log.info(
+-            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s"
+-            % (property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x))
++            "%s for %s changed to %s at frame %s with interpolation: %s at closest x: %s",
++            property_key, clip_id, new_value, self.frame_number, interpolation, closest_point_x)
+ 
+         # Find this clip
+         c = None
+@@ -518,35 +518,35 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     try:
+                         clip_data[property_key] = int(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Integer value passed to property: %s' % ex)
++                        log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Float value passed to property: %s' % ex)
++                        log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+                     except Exception as ex:
+-                        log.warn('Invalid Boolean value passed to property: %s' % ex)
++                        log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid String value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid String value passed to property', exc_info=1)
+ 
+                 elif property_type in ["font", "caption"]:
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = str(new_value)
+-                    except Exception as ex:
+-                        log.warn('Invalid Font/Caption value passed to property: %s' % ex)
++                    except Exception:
++                        log.warn('Invalid Font/Caption value passed to property', exc_info=1)
+ 
+                 elif property_type == "reader":
+                     # Transition
+@@ -557,8 +557,8 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_data[property_key] = json.loads(clip_object.Reader().Json())
+                         clip_object.Close()
+                         clip_object = None
+-                    except Exception as ex:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)' % (value, ex))
++                    except Exception:
++                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
+@@ -688,9 +688,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Color needs to be handled special
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             if readonly or type in ["color", "font", "caption"] or choices or label == "Track":
+@@ -789,9 +789,9 @@ def set_property(self, property, filter, c, item_type, object_id=None):
+ 
+             if type == "color":
+                 # Update the color based on the color curves
+-                red = property[1]["red"]["value"]
+-                green = property[1]["green"]["value"]
+-                blue = property[1]["blue"]["value"]
++                red = int(property[1]["red"]["value"])
++                green = int(property[1]["green"]["value"])
++                blue = int(property[1]["blue"]["value"])
+                 col.setBackground(QColor(red, green, blue))
+ 
+             # Update helper dictionary
+diff --git a/src/windows/process_effect.py b/src/windows/process_effect.py
+index e4f3120c0..ea0c2946e 100644
+--- a/src/windows/process_effect.py
++++ b/src/windows/process_effect.py
+@@ -352,7 +352,7 @@ def accept(self):
+         while(not processing.IsDone() ):
+             # update progressbar
+             progressionStatus = processing.GetProgress()
+-            self.progressBar.setValue(progressionStatus)
++            self.progressBar.setValue(int(progressionStatus))
+             time.sleep(0.01)
+ 
+             # Process any queued events
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index d5c89a204..f33696c5c 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -25,8 +25,11 @@
+  along with OpenShot Library.  If not, see <http://www.gnu.org/licenses/>.
+  """
+ 
++import json
++
+ from PyQt5.QtCore import (
+-    Qt, QCoreApplication, QPointF, QPoint, QRect, QRectF, QSize, QMutex, QTimer
++    Qt, QCoreApplication, QMutex, QTimer,
++    QPoint, QPointF, QSize, QSizeF, QRect, QRectF,
+ )
+ from PyQt5.QtGui import (
+     QTransform, QPainter, QIcon, QColor, QPen, QBrush, QCursor, QImage, QRegion
+@@ -41,8 +44,6 @@
+ from classes.app import get_app
+ from classes.query import Clip, Effect
+ 
+-import json
+-
+ 
+ class VideoWidget(QWidget, updates.UpdateInterface):
+     """ A QWidget used on the video display widget """
+@@ -86,37 +87,68 @@ def changed(self, action):
+                     self.pixel_ratio.ToFloat())
+ 
+ 
+-    def drawTransformHandler(self, painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-     x1=None, y1=None, x2=None, y2=None, rotation = None):
++    def drawTransformHandler(
++        self, painter, sx, sy, source_width, source_height,
++        origin_x, origin_y,
++        x1=None, y1=None, x2=None, y2=None, rotation = None
++    ):
+         # Draw transform corners and center origin circle
+         # Corner size
+         cs = self.cs
+         os = 12.0
+ 
++        csx = cs / sx
++        csy = cs / sy
++
+         # Rotate the transform handler
+         if rotation:
+-            bbox_center_x = (((x1*source_width + x2*source_width) / 2.0) ) - ( (os/2) /sx)
+-            bbox_center_y = (((y1*source_height + y2*source_height) / 2.0) ) - ( (os/2) /sy)
++            bbox_center_x = ((x1*source_width + x2*source_width) / 2.0) - ((os / 2) / sx)
++            bbox_center_y = ((y1*source_height + y2*source_height) / 2.0) - ((os / 2) / sy)
+             painter.translate(bbox_center_x, bbox_center_y)
+             painter.rotate(rotation)
+             painter.translate(-bbox_center_x, -bbox_center_y)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(x1*source_width, y1*source_height), QPointF(x2*source_width, y2*source_height))
++            self.clipBounds = QRectF(
++                QPointF(x1 * source_width, y1 * source_height),
++                QPointF(x2 * source_width, y2 * source_height)
++            )
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y1*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(x1*source_width -(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(x2*source_width-(cs/sx/2.0), y2*source_height-(cs/sy/2.0), cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.topRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y1 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomLeftHandle = QRectF(
++                x1 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.bottomRightHandle = QRectF(
++                x2 * source_width - (csx / 2.0),
++                y2 * source_height - (csy / 2.0),
++                csx,
++                csy)
+         else:
+             # Calculate bounds of clip
+-            self.clipBounds = QRectF(QPointF(0.0, 0.0), QPointF(source_width, source_height))
++            self.clipBounds = QRectF(
++                QPointF(0.0, 0.0),
++                QPointF(source_width, source_height))
+             # Calculate 4 corners coordinates
+-            self.topLeftHandle = QRectF(-cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.topRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomLeftHandle = QRectF(-cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomRightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
++            self.topLeftHandle = QRectF(
++                -csx / 2.0, -csy / 2.0, csx, csy)
++            self.topRightHandle = QRectF(
++                source_width - csx / 2.0, -csy / 2.0, csx, csy)
++            self.bottomLeftHandle = QRectF(
++                -csx / 2.0, source_height - csy / 2.0, csx, csy)
++            self.bottomRightHandle = QRectF(
++                source_width - csx / 2.0, source_height - csy / 2.0, csx, csy)
+ 
+         # Draw 4 corners
+         pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+@@ -127,47 +159,110 @@ def drawTransformHandler(self, painter, sx, sy, source_width, source_height, ori
+         painter.drawRect(self.bottomLeftHandle)
+         painter.drawRect(self.bottomRightHandle)
+ 
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y1*source_height)-cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF(((x1*source_width+x2*source_width) / 2.0) - (cs/sx/2.0), (y2*source_height)-( cs/sy/2.0), cs/sx, cs/sy)
+-            self.leftHandle = QRectF((x1*source_width)-(cs/sx/2.0), ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF((x2*source_width) - (cs/sx) + cs/sx/2.0, ((y1*source_height+y2*source_height) / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y1 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                ((x1 + x2) * source_width - csx) / 2.0,
++                (y2 * source_height) - csy / 2.0,
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                (x1 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                (x2 * source_width) - csx / 2.0,
++                ((y1 + y2) * source_height - csy) / 2.0,
++                csx, csy)
+ 
+         else:
+             # Calculate 4 side coordinates
+-            self.topHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), -cs/sy/2.0, cs/sx, cs/sy)
+-            self.bottomHandle = QRectF((source_width / 2.0) - (cs/sx/2.0), source_height - (cs/sy) + cs/sy/2.0, cs/sx, cs/sy)
+-            self.leftHandle = QRectF(-cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
+-            self.rightHandle = QRectF(source_width - (cs/sx) + cs/sx/2.0, (source_height / 2.0) - (cs/sy/2.0), cs/sx, cs/sy)
++            self.topHandle = QRectF(
++                (source_width - csx) / 2.0,
++                -csy / 2.0,
++                csx,
++                csy)
++            self.bottomHandle = QRectF(
++                (source_width - csx) / 2.0,
++                source_height - (csy / 2.0),
++                csx,
++                csy)
++            self.leftHandle = QRectF(
++                -csx / 2.0,
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
++            self.rightHandle = QRectF(
++                source_width - (csx / 2.0),
++                (source_height - csy) / 2.0,
++                csx,
++                csy)
+ 
+         # Calculate shear handles
+-        self.topShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
+-        self.leftShearHandle = QRectF(self.topLeftHandle.x(), self.topLeftHandle.y(), self.topLeftHandle.width(), self.clipBounds.height())
+-        self.rightShearHandle = QRectF(self.topRightHandle.x(), self.topRightHandle.y(), self.topRightHandle.width(), self.clipBounds.height())
+-        self.bottomShearHandle = QRectF(self.bottomLeftHandle.x(), self.bottomLeftHandle.y(), self.clipBounds.width(), self.topLeftHandle.height())
++        self.topShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
++        self.leftShearHandle = QRectF(
++            self.topLeftHandle.x(),
++            self.topLeftHandle.y(),
++            self.topLeftHandle.width(),
++            self.clipBounds.height())
++        self.rightShearHandle = QRectF(
++            self.topRightHandle.x(),
++            self.topRightHandle.y(),
++            self.topRightHandle.width(),
++            self.clipBounds.height())
++        self.bottomShearHandle = QRectF(
++            self.bottomLeftHandle.x(),
++            self.bottomLeftHandle.y(),
++            self.clipBounds.width(),
++            self.topLeftHandle.height())
+ 
+         # Draw 4 sides (centered)
+-        painter.drawRect(self.topHandle)
+-        painter.drawRect(self.bottomHandle)
+-        painter.drawRect(self.leftHandle)
+-        painter.drawRect(self.rightHandle)
+-        painter.drawRect(self.clipBounds)
++        painter.drawRects([
++            self.topHandle,
++            self.bottomHandle,
++            self.leftHandle,
++            self.rightHandle,
++            self.clipBounds,
++            ])
+ 
+         # Calculate center coordinate
+-        if(x1 and y1 and x2 and y2):
++        if all([x1, y1, x2, y2]):
+             cs = 5.0
+             os = 7.0
+-            self.centerHandle = QRectF( (((x1*source_width+x2*source_width) / 2.0) ) - (os/sx), (((y1*source_height+y2*source_height) / 2.0) ) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                ((x1 + x2) * source_width / 2.0) - (os / sx),
++                ((y1 + y2) * source_height / 2.0) - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0
++            )
+         else:
+-            self.centerHandle = QRectF((source_width * origin_x) - (os/sx), (source_height * origin_y) - (os/sy), os/sx*2.0, os/sy*2.0)
++            self.centerHandle = QRectF(
++                source_width * origin_x - (os / sx),
++                source_height * origin_y - (os / sy),
++                os / sx * 2.0,
++                os / sy * 2.0)
+ 
+         # Draw origin
+         painter.drawEllipse(self.centerHandle)
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) - self.centerHandle.height(),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0), self.centerHandle.y() + (self.centerHandle.height()/2.0) + self.centerHandle.height())
+-        painter.drawLine(self.centerHandle.x() + (self.centerHandle.width()/2.0) - self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0),
+-                            self.centerHandle.x() + (self.centerHandle.width()/2.0) + self.centerHandle.width(), self.centerHandle.y() + (self.centerHandle.height()/2.0))
++
++        # Draw cross at origin center, extending beyond ellipse by 25%
++        center = self.centerHandle.center()
++        halfW = QPointF(self.centerHandle.width() * 0.75, 0)
++        halfH = QPointF(0, self.centerHandle.height() * 0.75)
++        painter.drawLines(
++            center - halfW, center + halfW,
++            center - halfH, center + halfH,
++        )
+ 
+         # Remove transform
+         painter.resetTransform()
+@@ -179,7 +274,11 @@ def paintEvent(self, event, *args):
+ 
+         # Paint custom frame image on QWidget
+         painter = QPainter(self)
+-        painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++        painter.setRenderHints(
++            QPainter.Antialiasing
++            | QPainter.SmoothPixmapTransform
++            | QPainter.TextAntialiasing,
++            True)
+ 
+         # Fill the whole widget with the solid color
+         painter.fillRect(event.rect(), QColor("#191919"))
+@@ -191,7 +290,7 @@ def paintEvent(self, event, *args):
+             # DRAW FRAME
+             # Calculate new frame image size, maintaining aspect ratio
+             pixSize = self.current_image.size()
+-            pixSize.scale(event.rect().width(), event.rect().height(), Qt.KeepAspectRatio)
++            pixSize.scale(event.rect().size(), Qt.KeepAspectRatio)
+             self.curr_frame_size = pixSize
+ 
+             # Scale image (take into account display scaling for High DPI monitors)
+@@ -223,13 +322,19 @@ def paintEvent(self, event, *args):
+             # Determine original size of clip's reader
+             source_width = self.transforming_clip.data['reader']['width']
+             source_height = self.transforming_clip.data['reader']['height']
+-            source_size = QSize(source_width, source_height * self.pixel_ratio.Reciprocal().ToDouble())
++            pixel_adjust = self.pixel_ratio.Reciprocal().ToDouble()
++            source_size = QSize(
++                int(source_width),
++                int(source_height * pixel_adjust))
+ 
+             # Determine scale of clip
+             scale = self.transforming_clip.data['scale']
+ 
+             # Set scale as STRETCH if the clip is attached to an object
+-            if (raw_properties.get('parentObjectId').get('memo') != 'None' and len(raw_properties.get('parentObjectId').get('memo')) > 0 ):
++            if (
++                    raw_properties.get('parentObjectId').get('memo') != 'None'
++                    and len(raw_properties.get('parentObjectId').get('memo')) > 0
++            ):
+                 scale = openshot.SCALE_STRETCH
+ 
+             if scale == openshot.SCALE_FIT:
+@@ -239,12 +344,7 @@ def paintEvent(self, event, *args):
+                 source_size.scale(player_width, player_height, Qt.IgnoreAspectRatio)
+ 
+             elif scale == openshot.SCALE_CROP:
+-                width_size = QSize(player_width, round(player_width / (float(source_width) / float(source_height))))
+-                height_size = QSize(round(player_height / (float(source_height) / float(source_width))), player_height)
+-                if width_size.width() >= player_width and width_size.height() >= player_height:
+-                    source_size.scale(width_size.width(), width_size.height(), Qt.KeepAspectRatio)
+-                else:
+-                    source_size.scale(height_size.width(), height_size.height(), Qt.KeepAspectRatio)
++                source_size.scale(player_width, player_height, Qt.KeepAspectRatioByExpanding)
+ 
+             # Get new source width / height (after scaling mode applied)
+             source_width = source_size.width()
+@@ -285,9 +385,6 @@ def paintEvent(self, event, *args):
+                 x += player_width - scaled_source_width  # right
+                 y += (player_height - scaled_source_height)  # bottom
+ 
+-            # Track gravity starting coordinate
+-            self.gravity_point = QPointF(x, y)
+-
+             # Adjust x,y for location
+             x_offset = raw_properties.get('location_x').get('value')
+             y_offset = raw_properties.get('location_y').get('value')
+@@ -329,7 +426,6 @@ def paintEvent(self, event, *args):
+                     raw_properties_effect = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+                     # Get properties for the first object in dict. PropertiesJSON should return one object at the time
+                     tmp = raw_properties_effect.get('objects')
+-                    tmp2 = tmp.keys()
+                     obj_id = list(tmp.keys())[0]
+                     raw_properties_effect = raw_properties_effect.get('objects').get(obj_id)
+ 
+@@ -342,10 +438,19 @@ def paintEvent(self, event, *args):
+                             y1 = raw_properties_effect['y1']['value']
+                             x2 = raw_properties_effect['x2']['value']
+                             y2 = raw_properties_effect['y2']['value']
+-                            self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y,
+-                                x1, y1, x2, y2, rotation)
++                            self.drawTransformHandler(
++                                painter,
++                                sx, sy,
++                                source_width, source_height,
++                                origin_x, origin_y,
++                                x1, y1, x2, y2,
++                                rotation)
+             else:
+-                self.drawTransformHandler(painter, sx, sy, source_width, source_height, origin_x, origin_y)
++                self.drawTransformHandler(
++                    painter,
++                    sx, sy,
++                    source_width, source_height,
++                    origin_x, origin_y)
+ 
+         if self.region_enabled:
+             # Paint region selector onto video preview
+@@ -376,11 +481,21 @@ def paintEvent(self, event, *args):
+                 pen = QPen(QBrush(QColor("#53a0ed")), 1.5)
+                 pen.setCosmetic(True)
+                 painter.setPen(pen)
+-                painter.drawRect(self.regionTopLeftHandle.x() - (cs/2.0/self.zoom), self.regionTopLeftHandle.y() - (cs/2.0/self.zoom), self.regionTopLeftHandle.width() / self.zoom, self.regionTopLeftHandle.height() / self.zoom)
+-                painter.drawRect(self.regionBottomRightHandle.x() - (cs/2.0/self.zoom), self.regionBottomRightHandle.y() - (cs/2.0/self.zoom), self.regionBottomRightHandle.width() / self.zoom, self.regionBottomRightHandle.height() / self.zoom)
+-                region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                        self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                        self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
++                painter.drawRect(
++                    self.regionTopLeftHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionTopLeftHandle.width() / self.zoom,
++                    self.regionTopLeftHandle.height() / self.zoom)
++                painter.drawRect(
++                    self.regionBottomRightHandle.x() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.y() - (cs / 2.0 / self.zoom),
++                    self.regionBottomRightHandle.width() / self.zoom,
++                    self.regionBottomRightHandle.height() / self.zoom)
++                region_rect = QRectF(
++                    self.regionTopLeftHandle.x(),
++                    self.regionTopLeftHandle.y(),
++                    self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                    self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y())
+                 painter.drawRect(region_rect)
+ 
+             # Remove transform
+@@ -394,23 +509,15 @@ def paintEvent(self, event, *args):
+     def centeredViewport(self, width, height):
+         """ Calculate size of viewport to maintain aspect ratio """
+ 
+-        # Calculate padding
+-        top_padding = (height - (height * self.zoom)) / 2.0
+-        left_padding = (width - (width * self.zoom)) / 2.0
++        window_size = QSizeF(width, height)
++        window_rect = QRectF(QPointF(0, 0), window_size)
+ 
+-        # Adjust parameters to zoom
+-        width = width * self.zoom
+-        height = height * self.zoom
++        aspectRatio = self.aspect_ratio.ToFloat() * self.pixel_ratio.ToFloat()
++        viewport_size = QSizeF(aspectRatio, 1).scaled(window_size, Qt.KeepAspectRatio)
++        viewport_rect = QRectF(QPointF(0, 0), viewport_size)
++        viewport_rect.moveCenter(window_rect.center())
+ 
+-        # Calculate which direction to scale (for perfect centering)
+-        aspectRatio = self.aspect_ratio.ToFloat()
+-        heightFromWidth = width / aspectRatio
+-        widthFromHeight = height * aspectRatio
+-
+-        if heightFromWidth <= height:
+-            return QRect(left_padding, ((height - heightFromWidth) / 2) + top_padding, width, heightFromWidth)
+-        else:
+-            return QRect(((width - widthFromHeight) / 2.0) + left_padding, top_padding, widthFromHeight, height)
++        return viewport_rect.toRect()
+ 
+     def present(self, image, *args):
+         """ Present the current frame """
+@@ -448,12 +555,16 @@ def mouseReleaseEvent(self, event):
+         # This can be used other widgets to display the selected region
+         if self.region_enabled:
+             # Get region coordinates
+-            region_rect = QRectF(self.regionTopLeftHandle.x(), self.regionTopLeftHandle.y(),
+-                                 self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
+-                                 self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()).normalized()
++            region_rect = QRectF(
++                self.regionTopLeftHandle.x(),
++                self.regionTopLeftHandle.y(),
++                self.regionBottomRightHandle.x() - self.regionTopLeftHandle.x(),
++                self.regionBottomRightHandle.y() - self.regionTopLeftHandle.y()
++            ).normalized()
+ 
+             # Map region (due to zooming)
+-            mapped_region_rect = self.region_transform.mapToPolygon(region_rect.toRect()).boundingRect()
++            mapped_region_rect = self.region_transform.mapToPolygon(
++                region_rect.toRect()).boundingRect()
+ 
+             # Render a scaled version of the region (as a QImage)
+             # TODO: Grab higher quality pixmap from the QWidget, as this method seems to be 1/2 resolution
+@@ -461,14 +572,25 @@ def mouseReleaseEvent(self, event):
+             scale = 3.0
+ 
+             # Map rect to transform (for scaling video elements)
+-            mapped_region_rect = QRect(mapped_region_rect.x(), mapped_region_rect.y(), mapped_region_rect.width() * scale, mapped_region_rect.height() * scale)
++            mapped_region_rect = QRect(
++                mapped_region_rect.x(),
++                mapped_region_rect.y(),
++                int(mapped_region_rect.width() * scale),
++                int(mapped_region_rect.height() * scale))
+ 
+             # Render QWidget onto scaled QImage
+-            self.region_qimage = QImage(mapped_region_rect.width(), mapped_region_rect.height(), QImage.Format_RGBA8888)
++            self.region_qimage = QImage(
++                mapped_region_rect.size(), QImage.Format_RGBA8888)
+             region_painter = QPainter(self.region_qimage)
+-            region_painter.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing, True)
++            region_painter.setRenderHints(
++                QPainter.Antialiasing
++                | QPainter.SmoothPixmapTransform
++                | QPainter.TextAntialiasing,
++                True)
+             region_painter.scale(scale, scale)
+-            self.render(region_painter, QPoint(0,0), QRegion(mapped_region_rect, QRegion.Rectangle))
++            self.render(
++                region_painter, QPoint(0, 0),
++                QRegion(mapped_region_rect, QRegion.Rectangle))
+             region_painter.end()
+ 
+         # Inform UpdateManager to accept updates, and only store our final update
+@@ -484,7 +606,8 @@ def mouseReleaseEvent(self, event):
+     def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+         """Rotate cursor based on the current transform"""
+         rotated_pixmap = pixmap.transformed(
+-            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8), Qt.SmoothTransformation)
++            QTransform().rotate(rotation).shear(shear_x, shear_y).scale(0.8, 0.8),
++            Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+     def getTransformMode(self, rotation, shear_x, shear_y, event):
+@@ -627,6 +750,10 @@ def mouseMoveEvent(self, event):
+ 
+             # Transform clip object
+             if self.transform_mode:
++
++                x_motion = event.pos().x() - self.mouse_position.x()
++                y_motion = event.pos().y() - self.mouse_position.y()
++
+                 if self.transform_mode == 'origin':
+                     # Get current keyframe value
+                     origin_x = raw_properties.get('origin_x').get('value')
+@@ -635,8 +762,8 @@ def mouseMoveEvent(self, event):
+                     scale_y = raw_properties.get('scale_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    origin_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() * scale_x)
+-                    origin_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() * scale_y)
++                    origin_x += x_motion / (self.clipBounds.width() * scale_x)
++                    origin_y += y_motion / (self.clipBounds.height() * scale_y)
+ 
+                     # Constrain to clip
+                     if origin_x < 0.0:
+@@ -648,8 +775,13 @@ def mouseMoveEvent(self, event):
+                     if origin_y > 1.0:
+                         origin_y = 1.0
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_x', origin_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'origin_y', origin_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_x', origin_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'origin_y', origin_y)
+ 
+                 elif self.transform_mode == 'location':
+                     # Get current keyframe value
+@@ -657,12 +789,17 @@ def mouseMoveEvent(self, event):
+                     location_y = raw_properties.get('location_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                    location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                    location_x += x_motion / viewport_rect.width()
++                    location_y += y_motion / viewport_rect.height()
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_x', location_x, refresh=False)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'location_y', location_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_x', location_x,
++                        refresh=False)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'location_y', location_y)
+ 
+                 elif self.transform_mode == 'shear_top':
+                     # Get current keyframe shear value
+@@ -672,11 +809,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x -= (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_bottom':
+                     # Get current keyframe shear value
+@@ -686,11 +825,13 @@ def mouseMoveEvent(self, event):
+                     # Calculate new location coordinates
+                     aspect_ratio = (self.clipBounds.width() / self.clipBounds.height()) * 2.0
+                     shear_x += (
+-                        event.pos().x() - self.mouse_position.x()) / (
++                        x_motion) / (
+                         (self.clipBounds.width() * scale_x) / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_x', shear_x)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_x', shear_x)
+ 
+                 elif self.transform_mode == 'shear_left':
+                     # Get current keyframe shear value
+@@ -701,11 +842,13 @@ def mouseMoveEvent(self, event):
+                     aspect_ratio = (
+                         self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y -= (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'shear_right':
+                     # Get current keyframe shear value
+@@ -713,13 +856,16 @@ def mouseMoveEvent(self, event):
+                     shear_y = raw_properties.get('shear_y').get('value')
+ 
+                     # Calculate new location coordinates
+-                    aspect_ratio = (self.clipBounds.height() / self.clipBounds.width()) * 2.0
++                    aspect_ratio = (
++                        self.clipBounds.height() / self.clipBounds.width()) * 2.0
+                     shear_y += (
+-                        event.pos().y() - self.mouse_position.y()) / (
++                        y_motion) / (
+                         self.clipBounds.height() * scale_y / aspect_ratio)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'shear_y', shear_y)
++                    self.updateClipProperty(
++                        self.transforming_clip.id, clip_frame_number,
++                        'shear_y', shear_y)
+ 
+                 elif self.transform_mode == 'rotation':
+                     # Get current rotation keyframe value
+@@ -728,71 +874,68 @@ def mouseMoveEvent(self, event):
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                     # Calculate new location coordinates
+-                    is_on_left = event.pos().x() < self.originHandle.x()
++                    is_on_right = event.pos().x() > self.originHandle.x()
+                     is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                    if is_on_top:
+-                        rotation += (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-                    else:
+-                        rotation -= (
+-                            event.pos().x() - self.mouse_position.x()) / (
+-                            (self.clipBounds.width() * scale_x) / 90)
+-
+-                    if is_on_left:
+-                        rotation -= (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
+-                    else:
+-                        rotation += (
+-                            event.pos().y() - self.mouse_position.y()) / (
+-                            (self.clipBounds.height() * scale_y) / 90)
++                    x_adjust = x_motion / ((self.clipBounds.width() * scale_x) / 90)
++                    rotation += (x_adjust if is_on_top else -x_adjust)
++
++                    y_adjust = y_motion / ((self.clipBounds.height() * scale_y) / 90)
++                    rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                     # Update keyframe value (or create new one)
+-                    self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'rotation', rotation)
++                    self.updateClipProperty(
++                        self.transforming_clip.id,
++                        clip_frame_number,
++                        'rotation', rotation)
+ 
+                 elif self.transform_mode.startswith('scale_'):
+                     # Get current scale keyframe value
+                     scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                     scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                    half_w = self.clipBounds.width() / 2.0
++                    half_h = self.clipBounds.height() / 2.0
++
+                     if self.transform_mode == 'scale_top_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x += x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_x -= x_motion / half_w
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_top':
+-                        scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y -= y_motion / half_h
+                     elif self.transform_mode == 'scale_bottom':
+-                        scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                        scale_y += y_motion / half_h
+                     elif self.transform_mode == 'scale_left':
+-                        scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x -= x_motion / half_w
+                     elif self.transform_mode == 'scale_right':
+-                        scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                        scale_x += x_motion / half_w
+ 
+                     if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+                         # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                        if scale_x and scale_y:
++                        if scale_x:
+                             scale_y = scale_x
+                         elif scale_y:
+                             scale_x = scale_y
+-                        elif scale_x:
+-                            scale_y = scale_x
+ 
+                     # Update keyframe value (or create new one)
+                     both_scaled = scale_x != 0.001 and scale_y != 0.001
+                     if scale_x != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_x', scale_x, refresh=(not both_scaled))
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_x', scale_x,
++                            refresh=(not both_scaled))
+                     if scale_y != 0.001:
+-                        self.updateClipProperty(self.transforming_clip.id, clip_frame_number, 'scale_y', scale_y)
++                        self.updateClipProperty(
++                            self.transforming_clip.id, clip_frame_number,
++                            'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -803,16 +946,29 @@ def mouseMoveEvent(self, event):
+             cs = self.cs
+ 
+             # Adjust existing region coordinates (if any)
+-            if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++            if (not self.mouse_dragging
++                and self.resize_button.isVisible()
++                and self.resize_button.rect().contains(event.pos())
++            ):
+                 # Mouse over resize button (and not currently dragging)
+                 self.setCursor(Qt.ArrowCursor)
+-            elif self.region_transform and self.regionTopLeftHandle and self.region_transform.mapToPolygon(self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionTopLeftHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionTopLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_top_left':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+                 if self.mouse_dragging and not self.region_mode:
+                     self.region_mode = 'scale_top_left'
+-            elif self.region_transform and self.regionBottomRightHandle and self.region_transform.mapToPolygon(self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            elif (
++                    self.region_transform
++                    and self.regionBottomRightHandle
++                    and self.region_transform.mapToPolygon(
++                        self.regionBottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill)
++                    ):
+                 if not self.region_mode or self.region_mode == 'scale_bottom_right':
+                     self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), 0, 0, 0))
+                 # Set the region mode
+@@ -824,13 +980,25 @@ def mouseMoveEvent(self, event):
+             # Initialize new region coordinates at current event.pos()
+             if self.mouse_dragging and not self.region_mode:
+                 self.region_mode = 'scale_bottom_right'
+-                self.regionTopLeftHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
+-                self.regionBottomRightHandle = QRectF(self.region_transform_inverted.map(event.pos()).x(), self.region_transform_inverted.map(event.pos()).y(), cs, cs)
++                self.regionTopLeftHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
++                self.regionBottomRightHandle = QRectF(
++                    self.region_transform_inverted.map(event.pos()).x(),
++                    self.region_transform_inverted.map(event.pos()).y(),
++                    cs, cs)
+ 
+             # Move existing region coordinates
+             if self.mouse_dragging:
+-                diff_x = self.region_transform_inverted.map(event.pos()).x() - self.region_transform_inverted.map(self.mouse_position).x()
+-                diff_y = self.region_transform_inverted.map(event.pos()).y() - self.region_transform_inverted.map(self.mouse_position).y()
++                diff_x = int(
++                    self.region_transform_inverted.map(event.pos()).x()
++                    - self.region_transform_inverted.map(self.mouse_position).x()
++                )
++                diff_y = int(
++                    self.region_transform_inverted.map(event.pos()).y()
++                    - self.region_transform_inverted.map(self.mouse_position).y()
++                )
+                 if self.region_mode == 'scale_top_left':
+                     self.regionTopLeftHandle.adjust(diff_x, diff_y, diff_x, diff_y)
+                 elif self.region_mode == 'scale_bottom_right':
+@@ -859,12 +1027,11 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-
+-
+             if self.transforming_effect_object.info.has_tracked_object:
+                 # Get properties of effect at current frame
+                 raw_properties = json.loads(self.transforming_effect_object.PropertiesJSON(clip_frame_number))
+-                 # Get properties for the first object in dict. PropertiesJSON should return one object at the time
++                # Get properties for the first object in dict.
++                # PropertiesJSON should return one object at the time
+                 obj_id = list(raw_properties.get('objects').keys())[0]
+                 raw_properties = raw_properties.get('objects').get(obj_id)
+ 
+@@ -878,18 +1045,28 @@ def mouseMoveEvent(self, event):
+                 # Transform effect object
+                 if self.transform_mode:
+ 
++                    x_motion = event.pos().x() - self.mouse_position.x()
++                    y_motion = event.pos().y() - self.mouse_position.y()
++
+                     if self.transform_mode == 'location':
+                         # Get current keyframe value
+                         location_x = raw_properties.get('delta_x').get('value')
+                         location_y = raw_properties.get('delta_y').get('value')
+ 
+                         # Calculate new location coordinates
+-                        location_x += (event.pos().x() - self.mouse_position.x()) / viewport_rect.width()
+-                        location_y += (event.pos().y() - self.mouse_position.y()) / viewport_rect.height()
++                        location_x += x_motion / viewport_rect.width()
++                        location_y += y_motion / viewport_rect.height()
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_x', location_x, refresh=False)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'delta_y', location_y)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_x', location_x,
++                            refresh=False)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id, clip_frame_number,
++                            obj_id,
++                            'delta_y', location_y)
+ 
+                     elif self.transform_mode == 'rotation':
+                         # Get current rotation keyframe value
+@@ -898,63 +1075,70 @@ def mouseMoveEvent(self, event):
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
+                         # Calculate new location coordinates
+-                        is_on_left = event.pos().x() < self.originHandle.x()
++                        is_on_right = event.pos().x() > self.originHandle.x()
+                         is_on_top = event.pos().y() < self.originHandle.y()
+ 
+-                        if is_on_top:
+-                            rotation += (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
+-                        else:
+-                            rotation -= (event.pos().x() - self.mouse_position.x()) / ((self.clipBounds.width() * scale_x) / 90)
++                        x_adjust = x_motion / (self.clipBounds.width() * scale_x / 90)
++                        rotation += (x_adjust if is_on_top else -x_adjust)
+ 
+-                        if is_on_left:
+-                            rotation -= (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
+-                        else:
+-                            rotation += (event.pos().y() - self.mouse_position.y()) / ((self.clipBounds.height() * scale_y) / 90)
++                        y_adjust = y_motion / (self.clipBounds.height() * scale_y / 90)
++                        rotation += (y_adjust if is_on_right else -y_adjust)
+ 
+                         # Update keyframe value (or create new one)
+-                        self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'rotation', rotation)
++                        self.updateEffectProperty(
++                            self.transforming_effect.id,
++                            clip_frame_number, obj_id,
++                            'rotation', rotation)
+ 
+                     elif self.transform_mode.startswith('scale_'):
+                         # Get current scale keyframe value
+                         scale_x = max(float(raw_properties.get('scale_x').get('value')), 0.001)
+                         scale_y = max(float(raw_properties.get('scale_y').get('value')), 0.001)
+ 
++                        half_w = self.clipBounds.width() / 2.0
++                        half_h = self.clipBounds.height() / 2.0
++
+                         if self.transform_mode == 'scale_top_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x += x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_x -= x_motion / half_w
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_top':
+-                            scale_y -= (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y -= y_motion / half_h
+                         elif self.transform_mode == 'scale_bottom':
+-                            scale_y += (event.pos().y() - self.mouse_position.y()) / (self.clipBounds.height() / 2.0)
++                            scale_y += y_motion / half_h
+                         elif self.transform_mode == 'scale_left':
+-                            scale_x -= (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x -= x_motion / half_w
+                         elif self.transform_mode == 'scale_right':
+-                            scale_x += (event.pos().x() - self.mouse_position.x()) / (self.clipBounds.width() / 2.0)
++                            scale_x += x_motion / half_w
+ 
+                         if int(QCoreApplication.instance().keyboardModifiers() & Qt.ControlModifier) > 0:
+-                            # If CTRL key is pressed, fix the scale_y to the correct aspect ration
+-                            if scale_x and scale_y:
++                            # If CTRL key is pressed, fix the scale_y to the correct aspect ratio
++                            if scale_x:
+                                 scale_y = scale_x
+                             elif scale_y:
+                                 scale_x = scale_y
+-                            elif scale_x:
+-                                scale_y = scale_x
+ 
+                         # Update keyframe value (or create new one)
+                         both_scaled = scale_x != 0.001 and scale_y != 0.001
+                         if scale_x != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_x', scale_x, refresh=(not both_scaled))
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_x', scale_x,
++                                refresh=(not both_scaled))
+                         if scale_y != 0.001:
+-                            self.updateEffectProperty(self.transforming_effect.id, clip_frame_number, obj_id, 'scale_y', scale_y)
++                            self.updateEffectProperty(
++                                self.transforming_effect.id,
++                                clip_frame_number, obj_id,
++                                'scale_y', scale_y)
+ 
+             # Force re-paint
+             self.update()
+@@ -1012,8 +1196,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             # No clip found
+             return
+ 
+-        for point in c.data['objects'][obj_id][property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        try:
++            props = c.data['objects'][obj_id]
++            points_list = props[property_key]["Points"]
++        except (TypeError, KeyError):
++            log.error("Corrupted project data!", exc_info=1)
++            return
++
++        for point in points_list:
++            log.info("looping points: co.X = %s", point["co"]["X"])
+ 
+             if point["co"]["X"] == frame_number:
+                 found_point = True
+@@ -1023,12 +1214,15 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+ 
+         if not found_point and new_value != None:
+             effect_updated = True
+-            log.info("Created new point at X=%s" % frame_number)
+-            c.data['objects'][obj_id][property_key]["Points"].append({'co': {'X': frame_number, 'Y': new_value}, 'interpolation': openshot.BEZIER})
++            log.info("Created new point at X=%s", frame_number)
++            points_list.append({
++                'co': { 'X': frame_number, 'Y': new_value },
++                'interpolation': openshot.BEZIER,
++                })
+ 
+         # Reduce # of clip properties we are saving (performance boost)
+         #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects').get(obj_id)}}
++        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+ 
+         if effect_updated:
+             c.save()
+@@ -1040,10 +1234,10 @@ def refreshTriggered(self):
+         """Signal to refresh viewport (i.e. a property might have changed that effects the preview)"""
+ 
+         # Update reference to clip
+-        if self and self.transforming_clip:
++        if self.transforming_clip:
+             self.transforming_clip = Clip.get(id=self.transforming_clip.id)
+ 
+-        if self and self.transforming_effect:
++        if self.transforming_effect:
+             self.transforming_effect = Effect.get(id=self.transforming_effect.id)
+ 
+     def transformTriggered(self, clip_id):
+@@ -1053,7 +1247,7 @@ def transformTriggered(self, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_clip and not clip_id:
++        if self.transforming_clip and not clip_id:
+             # Clear transform
+             self.transforming_clip = None
+             need_refresh = True
+@@ -1078,7 +1272,7 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+         # Disable Transform UI
+         # Is this the same clip_id already being transformed?
+-        if self and self.transforming_effect and not effect_id:
++        if self.transforming_effect and not effect_id:
+             # Clear transform
+             self.transforming_effect = None
+             self.transforming_clip = None
+@@ -1102,12 +1296,8 @@ def keyFrameTransformTriggered(self, effect_id, clip_id):
+ 
+     def regionTriggered(self, clip_id):
+         """Handle the 'select region' signal when it's emitted"""
+-        if self and not clip_id:
+-            # Clear transform
+-            self.region_enabled = False
+-        else:
+-            self.region_enabled = True
+-
++        # Clear transform
++        self.region_enabled = bool(not clip_id)
+         get_app().window.refreshFrameSignal.emit()
+ 
+     def resizeEvent(self, event):
+@@ -1135,7 +1325,7 @@ def delayed_resize_callback(self):
+             ratio = float(project_size.width()) / float(project_size.height())
+             even_width = round(project_size.width() / 2.0) * 2
+             even_height = round(round(even_width / ratio) / 2.0) * 2
+-            project_size = QSize(even_width, even_height)
++            project_size = QSize(int(even_width), int(even_height))
+ 
+         # Emit signal that video widget changed size
+         self.win.MaxSizeChanged.emit(project_size)
+@@ -1199,7 +1389,6 @@ def __init__(self, watch_project=True, *args):
+         self.mouse_dragging = False
+         self.mouse_position = None
+         self.transform_mode = None
+-        self.gravity_point = None
+         self.original_clip_data = None
+         self.region_qimage = None
+         self.region_transform = None
+@@ -1208,8 +1397,8 @@ def __init__(self, watch_project=True, *args):
+         self.regionTopLeftHandle = None
+         self.regionBottomRightHandle = None
+         self.curr_frame_size = None # Frame size
+-        self.zoom = 1.0 # Zoom of widget (does not affect video, only workspace)
+-        self.cs = 14.0 # Corner size of Transform Handler rectangles
++        self.zoom = 1.0  # Zoom of widget (does not affect video, only workspace)
++        self.cs = 14.0  # Corner size of Transform Handler rectangles
+         self.resize_button = QPushButton(_('Reset Zoom'), self)
+         self.resize_button.hide()
+         self.resize_button.setStyleSheet('QPushButton { margin: 10px; padding: 2px; }')
+@@ -1251,7 +1440,6 @@ def __init__(self, watch_project=True, *args):
+ 
+         # Show Property timer
+         # Timer to use a delay before sending MaxSizeChanged signals (so we don't spam libopenshot)
+-        self.delayed_size = None
+         self.delayed_resize_timer = QTimer(self)
+         self.delayed_resize_timer.setInterval(200)
+         self.delayed_resize_timer.setSingleShot(True)
+diff --git a/src/windows/views/effects_listview.py b/src/windows/views/effects_listview.py
+index b7da28dc4..3ce3b4164 100644
+--- a/src/windows/views/effects_listview.py
++++ b/src/windows/views/effects_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsListView(QListView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -69,8 +70,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/effects_treeview.py b/src/windows/views/effects_treeview.py
+index 6a5ab79f4..910593524 100644
+--- a/src/windows/views/effects_treeview.py
++++ b/src/windows/views/effects_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class EffectsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/emojis_listview.py b/src/windows/views/emojis_listview.py
+index 6f09bc562..cb13c35a7 100644
+--- a/src/windows/views/emojis_listview.py
++++ b/src/windows/views/emojis_listview.py
+@@ -39,7 +39,8 @@
+ 
+ class EmojisListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def dragEnterEvent(self, event):
+         # If dragging urls onto widget, accept
+@@ -57,8 +58,8 @@ def startDrag(self, event):
+         drag = QDrag(self)
+         drag.setMimeData(self.model.mimeData(selected))
+         icon = self.model.data(selected[0], Qt.DecorationRole)
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+ 
+         # Create emoji file before drag starts
+         data = json.loads(drag.mimeData().text())
+diff --git a/src/windows/views/files_listview.py b/src/windows/views/files_listview.py
+index 83cdd0272..0e67306ae 100644
+--- a/src/windows/views/files_listview.py
++++ b/src/windows/views/files_listview.py
+@@ -38,7 +38,8 @@
+ 
+ class FilesListView(QListView):
+     """ A ListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -113,8 +114,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/files_treeview.py b/src/windows/views/files_treeview.py
+index d3fe74d88..776b71cc2 100644
+--- a/src/windows/views/files_treeview.py
++++ b/src/windows/views/files_treeview.py
+@@ -41,7 +41,8 @@
+ 
+ class FilesTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+ 
+@@ -114,8 +115,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     # Without defining this method, the 'copy' action doesn't show with cursor
+diff --git a/src/windows/views/properties_tableview.py b/src/windows/views/properties_tableview.py
+index e79b13644..fdaa7ee24 100644
+--- a/src/windows/views/properties_tableview.py
++++ b/src/windows/views/properties_tableview.py
+@@ -53,8 +53,13 @@
+ 
+ 
+ class PropertyDelegate(QItemDelegate):
+-    def __init__(self, parent=None, *args):
+-        QItemDelegate.__init__(self, parent, *args)
++    def __init__(self, parent=None, *args, **kwargs):
++
++        self.model = kwargs.pop("model", None)
++        if not self.model:
++            log.error("Cannot create delegate without data model!")
++
++        super().__init__(parent, *args, **kwargs)
+ 
+         # pixmaps for curve icons
+         self.curve_pixmaps = {
+@@ -68,7 +73,7 @@ def paint(self, painter, option, index):
+         painter.setRenderHint(QPainter.Antialiasing)
+ 
+         # Get data model and selection
+-        model = get_app().window.propertyTableView.clip_properties_model.model
++        model = self.model
+         row = model.itemFromIndex(index).row()
+         selected_label = model.item(row, 0)
+         selected_value = model.item(row, 1)
+@@ -104,16 +109,16 @@ def paint(self, painter, option, index):
+         painter.setPen(QPen(Qt.NoPen))
+         if property_type == "color":
+             # Color keyframe
+-            red = cur_property[1]["red"]["value"]
+-            green = cur_property[1]["green"]["value"]
+-            blue = cur_property[1]["blue"]["value"]
+-            painter.setBrush(QBrush(QColor(QColor(red, green, blue))))
++            red = int(cur_property[1]["red"]["value"])
++            green = int(cur_property[1]["green"]["value"])
++            blue = int(cur_property[1]["blue"]["value"])
++            painter.setBrush(QColor(red, green, blue))
+         else:
+             # Normal Keyframe
+             if option.state & QStyle.State_Selected:
+-                painter.setBrush(QBrush(QColor("#575757")))
++                painter.setBrush(QColor("#575757"))
+             else:
+-                painter.setBrush(QBrush(QColor("#3e3e3e")))
++                painter.setBrush(QColor("#3e3e3e"))
+ 
+         if readonly:
+             # Set text color for read only fields
+@@ -146,7 +151,10 @@ def paint(self, painter, option, index):
+ 
+             if points > 1:
+                 # Draw interpolation icon on top
+-                painter.drawPixmap(option.rect.x() + option.rect.width() - 30.0, option.rect.y() + 4, self.curve_pixmaps[interpolation])
++                painter.drawPixmap(
++                    int(option.rect.x() + option.rect.width() - 30.0),
++                    int(option.rect.y() + 4),
++                    self.curve_pixmaps[interpolation])
+ 
+             # Set text color
+             painter.setPen(QPen(Qt.white))
+@@ -818,9 +826,9 @@ def Color_Picker_Triggered(self, cur_property):
+         _ = get_app()._tr
+ 
+         # Get current value of color
+-        red = cur_property[1]["red"]["value"]
+-        green = cur_property[1]["green"]["value"]
+-        blue = cur_property[1]["blue"]["value"]
++        red = int(cur_property[1]["red"]["value"])
++        green = int(cur_property[1]["green"]["value"])
++        blue = int(cur_property[1]["blue"]["value"])
+ 
+         # Show color dialog
+         currentColor = QColor(red, green, blue)
+@@ -865,7 +873,7 @@ def __init__(self, *args):
+         self.files_model = self.win.files_model.model
+ 
+         # Connect to update signals, so our menus stay current
+-        self.win.files_model.ModelRefreshed.connect(self.refresh_menu)
++        self.files_model.dataChanged.connect(self.refresh_menu)
+         self.win.transition_model.ModelRefreshed.connect(self.refresh_menu)
+         self.menu_reset = False
+ 
+@@ -890,7 +898,7 @@ def __init__(self, *args):
+         self.setWordWrap(True)
+ 
+         # Set delegate
+-        delegate = PropertyDelegate()
++        delegate = PropertyDelegate(model=self.clip_properties_model.model)
+         self.setItemDelegateForColumn(1, delegate)
+         self.previous_x = -1
+ 
+diff --git a/src/windows/views/transitions_listview.py b/src/windows/views/transitions_listview.py
+index 09af86b2b..3ba3346f5 100644
+--- a/src/windows/views/transitions_listview.py
++++ b/src/windows/views/transitions_listview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsListView(QListView):
+     """ A QListView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         event.accept()
+@@ -70,8 +71,8 @@ def startDrag(self, supportedActions):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def filter_changed(self):
+diff --git a/src/windows/views/transitions_treeview.py b/src/windows/views/transitions_treeview.py
+index 6f903e913..7aa0cd481 100644
+--- a/src/windows/views/transitions_treeview.py
++++ b/src/windows/views/transitions_treeview.py
+@@ -36,7 +36,8 @@
+ 
+ class TransitionsTreeView(QTreeView):
+     """ A TreeView QWidget used on the main window """
+-    drag_item_size = 48
++    drag_item_size = QSize(48, 48)
++    drag_item_center = QPoint(24, 24)
+ 
+     def contextMenuEvent(self, event):
+         # Set context menu mode
+@@ -68,8 +69,8 @@ def startDrag(self, event):
+         # Start drag operation
+         drag = QDrag(self)
+         drag.setMimeData(self.model().mimeData(selected))
+-        drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
+-        drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
++        drag.setPixmap(icon.pixmap(self.drag_item_size))
++        drag.setHotSpot(self.drag_item_center)
+         drag.exec_()
+ 
+     def refresh_columns(self):
+diff --git a/src/windows/views/tutorial.py b/src/windows/views/tutorial.py
+index e2d7fb861..24124799f 100644
+--- a/src/windows/views/tutorial.py
++++ b/src/windows/views/tutorial.py
+@@ -53,7 +53,12 @@ def paintEvent(self, event, *args):
+ 
+         painter.setPen(QPen(frameColor, 2))
+         painter.setBrush(self.palette().color(QPalette.Window))
+-        painter.drawRoundedRect(QRectF(31, 0, self.width() - 31, self.height()), 10, 10)
++        painter.drawRoundedRect(
++            QRectF(31, 0,
++                   self.width() - 31,
++                   self.height()
++                   ),
++            10, 10)
+ 
+         # Paint blue triangle (if needed)
+         if self.arrow:
+@@ -61,7 +66,8 @@ def paintEvent(self, event, *args):
+             path = QPainterPath()
+             path.moveTo(0, 35)
+             path.lineTo(31, 35 - arrow_height)
+-            path.lineTo(31, (35 - arrow_height) + (arrow_height * 2))
++            path.lineTo(
++                31, int((35 - arrow_height) + (arrow_height * 2)))
+             path.lineTo(0, 35)
+             painter.fillPath(path, frameColor)
+ 
+@@ -199,7 +205,9 @@ def process(self, parent_name=None):
+ 
+             # Create tutorial
+             self.position_widget = tutorial_object
+-            self.offset = QPoint(tutorial_details["x"], tutorial_details["y"])
++            self.offset = QPoint(
++                int(tutorial_details["x"]),
++                int(tutorial_details["y"]))
+             tutorial_dialog = TutorialDialog(tutorial_id, tutorial_details["text"], tutorial_details["arrow"], self)
+ 
+             # Connect signals
+
+From 9fc55120f36c36b2b6b67e499128993c25e535cf Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:02:56 -0400
+Subject: [PATCH 3/6] VideoWidget: New checkTransformMode
+
+Replacement for getTransformMode with less repetitious code
+---
+ src/windows/video_widget.py | 160 ++++++++++++++----------------------
+ 1 file changed, 61 insertions(+), 99 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index f33696c5c..1b9c35b54 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -610,106 +610,68 @@ def rotateCursor(self, pixmap, rotation, shear_x, shear_y):
+             Qt.SmoothTransformation)
+         return QCursor(rotated_pixmap)
+ 
+-    def getTransformMode(self, rotation, shear_x, shear_y, event):
++    def checkTransformMode(self, rotation, shear_x, shear_y, event):
++        handle_uis = [
++            {"handle": self.centerHandle, "mode": 'origin', "cursor": 'hand'},
++            {"handle": self.topRightHandle, "mode": 'scale_top_right', "cursor": 'resize_bdiag'},
++            {"handle": self.topHandle, "mode": 'scale_top', "cursor": 'resize_y'},
++            {"handle": self.topLeftHandle, "mode": 'scale_top_left', "cursor": 'resize_fdiag'},
++            {"handle": self.leftHandle, "mode": 'scale_left', "cursor": 'resize_x'},
++            {"handle": self.rightHandle, "mode": 'scale_right', "cursor": 'resize_x'},
++            {"handle": self.bottomLeftHandle, "mode": 'scale_bottom_left', "cursor": 'resize_bdiag'},
++            {"handle": self.bottomHandle, "mode": 'scale_bottom', "cursor": 'resize_y'},
++            {"handle": self.bottomRightHandle, "mode": 'scale_bottom_right', "cursor": 'resize_fdiag'},
++            {"handle": self.topShearHandle, "mode": 'shear_top', "cursor": 'shear_x'},
++            {"handle": self.leftShearHandle, "mode": 'shear_left', "cursor": 'shear_y'},
++            {"handle": self.rightShearHandle, "mode": 'shear_right', "cursor": 'shear_y'},
++            {"handle": self.bottomShearHandle, "mode": 'shear_bottom', "cursor": 'shear_x'},
++        ]
++        non_handle_uis = {
++            "region": self.clipBounds,
++            "inside": {"mode": 'location', "cursor": 'move'},
++            "outside": {"mode": 'rotation', "cursor": "rotate"}
++            }
++
+         # Mouse over resize button (and not currently dragging)
+-        if not self.mouse_dragging and self.resize_button.isVisible() and self.resize_button.rect().contains(event.pos()):
++        if (not self.mouse_dragging
++            and self.resize_button.isVisible()
++            and self.resize_button.rect().contains(event.pos()
++        ):
+             self.setCursor(Qt.ArrowCursor)
+-        # Determine if cursor is over a handle
+-        elif self.transform.mapToPolygon(self.centerHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'origin':
+-                self.setCursor(self.rotateCursor(self.cursors.get('hand'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'origin'
+-        elif self.transform.mapToPolygon(self.topRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_right'
+-        elif self.transform.mapToPolygon(self.topHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top'
+-        elif self.transform.mapToPolygon(self.topLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_top_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_top_left'
+-        elif self.transform.mapToPolygon(self.leftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_left'
+-        elif self.transform.mapToPolygon(self.rightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_right'
+-        elif self.transform.mapToPolygon(self.bottomLeftHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_bdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_left'
+-        elif self.transform.mapToPolygon(self.bottomHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom'
+-        elif self.transform.mapToPolygon(self.bottomRightHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'scale_bottom_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('resize_fdiag'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'scale_bottom_right'
+-        elif self.transform.mapToPolygon(self.topShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_top':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_top'
+-        elif self.transform.mapToPolygon(self.leftShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_left':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_left'
+-        elif self.transform.mapToPolygon(self.rightShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_right':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_y'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_right'
+-        elif self.transform.mapToPolygon(self.bottomShearHandle.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'shear_bottom':
+-                self.setCursor(self.rotateCursor(self.cursors.get('shear_x'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'shear_bottom'
+-        elif self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'location':
+-                self.setCursor(self.rotateCursor(self.cursors.get('move'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'location'
+-        elif not self.transform.mapToPolygon(self.clipBounds.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
+-            if not self.transform_mode or self.transform_mode == 'rotation':
+-                self.setCursor(self.rotateCursor(self.cursors.get('rotate'), rotation, shear_x, shear_y))
+-            # Set the transform mode
+-            if self.mouse_dragging and not self.transform_mode:
+-                self.transform_mode = 'rotation'
+-        elif not self.transform_mode:
+-            # Reset cursor when not over a handle
+-            self.setCursor(QCursor(Qt.ArrowCursor))
++            self.transform_mode = None
++            return
++
++        # If mouse is over a handle, set corresponding pointer/mode
++        for h in handle_uis:
++            if self.transform.mapToPolygon(
++                    h["handle"].toRect()
++                    ).containsPoint(event.pos(), Qt.OddEvenFill):
++                # Handle contains cursor
++                if self.transform_mode and self.transform_mode != h["mode"]:
++                    # We're in different xform mode, skip
++                    continue
++                if self.mouse_dragging:
++                    self.transform_mode = h["mode"]
++                self.setCursor(self.rotateCursor(
++                    self.cursors.get(h["cursor"]), rotation, shear_x, shear_y))
++                return
++
++        # If not over any handles, determne inside/outside clip rectangle
++        r = non_handle_uis.get("region")
++        if self.transform.mapToPolygon(r.toRect()).containsPoint(event.pos(), Qt.OddEvenFill):
++            nh = non_handle_uis.get("inside", {})
++        else:
++            nh = non_handle_uis.get("outside", {})
++        if self.mouse_dragging and not self.transform_mode:
++            self.transform_mode = nh.get("mode")
++        if not self.transform_mode or self.transform_mode == nh.get("mode"):
++            self.setCursor(self.rotateCursor(
++                self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-        return True
++
++        # If we got this far and we don't have a transform mode, reset the cursor
++        if not self.transform_mode:
++            self.setCursor(QCursor(Qt.ArrowCursor))
+ 
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+@@ -746,7 +708,7 @@ def mouseMoveEvent(self, event):
+             if self.mouse_dragging and not self.transform_mode:
+                 self.original_clip_data = self.transforming_clip.data
+ 
+-            _ = self.getTransformMode(rotation, shear_x, shear_y, event)
++            self.checkTransformMode(rotation, shear_x, shear_y, event)
+ 
+             # Transform clip object
+             if self.transform_mode:
+@@ -1040,7 +1002,7 @@ def mouseMoveEvent(self, event):
+                     self.mutex.unlock()
+                     return
+ 
+-                _ = self.getTransformMode(0, 0, 0, event)
++                self.checkTransformMode(0, 0, 0, event)
+ 
+                 # Transform effect object
+                 if self.transform_mode:
+
+From 1f058f730bb36068bce5c645ecea45b98279b614 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:42:48 -0400
+Subject: [PATCH 4/6] VideoWidget: Protect property accesses
+
+---
+ src/windows/video_widget.py | 61 +++++++++++++++++++++----------------
+ 1 file changed, 34 insertions(+), 27 deletions(-)
+
+diff --git a/src/windows/video_widget.py b/src/windows/video_widget.py
+index 1b9c35b54..a4d42969f 100644
+--- a/src/windows/video_widget.py
++++ b/src/windows/video_widget.py
+@@ -635,7 +635,7 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+         # Mouse over resize button (and not currently dragging)
+         if (not self.mouse_dragging
+             and self.resize_button.isVisible()
+-            and self.resize_button.rect().contains(event.pos()
++            and self.resize_button.rect().contains(event.pos())
+         ):
+             self.setCursor(Qt.ArrowCursor)
+             self.transform_mode = None
+@@ -668,11 +668,6 @@ def checkTransformMode(self, rotation, shear_x, shear_y, event):
+             self.setCursor(self.rotateCursor(
+                 self.cursors.get(nh.get("cursor")), rotation, shear_x, shear_y))
+ 
+-
+-        # If we got this far and we don't have a transform mode, reset the cursor
+-        if not self.transform_mode:
+-            self.setCursor(QCursor(Qt.ArrowCursor))
+-
+     def mouseMoveEvent(self, event):
+         """Capture mouse events on video preview window """
+         self.mutex.lock()
+@@ -1121,27 +1116,37 @@ def updateClipProperty(self, clip_id, frame_number, property_key, new_value, ref
+             # No clip found
+             return
+ 
+-        for point in c.data[property_key]["Points"]:
+-            log.info("looping points: co.X = %s" % point["co"]["X"])
++        # Property missing? Create it!
++        if property_key not in c.data:
++            c.data[property_key] = {"Points": []}
++            log.warning(
++                "%s: Added missing '%s' to property data",
++                clip_id, property_key)
+ 
+-            if point["co"]["X"] == frame_number:
++        points = c.data.get(property_key).get("Points")
++        for point in points:
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s" % co.get("X"))
++
++            if co.get("X") == frame_number:
+                 found_point = True
+                 clip_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+         if not found_point and new_value is not None:
+             clip_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             c.data[property_key]["Points"].append({
+-                'co': {'X': frame_number, 'Y': new_value},
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        c.data = {property_key: c.data.get(property_key)}
+-
+         if clip_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            c.data = {property_key: c.data.get(property_key)}
+             c.save()
+             # Update the preview
+             if refresh:
+@@ -1166,27 +1171,29 @@ def updateEffectProperty(self, effect_id, frame_number, obj_id, property_key, ne
+             return
+ 
+         for point in points_list:
+-            log.info("looping points: co.X = %s", point["co"]["X"])
++            co = point.get("co", {})
++            log.info("looping points: co.X = %s", co.get("X"))
+ 
+-            if point["co"]["X"] == frame_number:
++            if co.get("X") == frame_number:
+                 found_point = True
+                 effect_updated = True
+-                point["interpolation"] = openshot.BEZIER
+-                point["co"]["Y"] = float(new_value)
++                point.update({
++                    "co": {"X": frame_number, "Y": float(new_value)},
++                    "interpolation": openshot.BEZIER,
++                })
+ 
+-        if not found_point and new_value != None:
++        if not found_point and new_value is not None:
+             effect_updated = True
+-            log.info("Created new point at X=%s", frame_number)
++            log.info("Creating new point at X=%s", frame_number)
+             points_list.append({
+-                'co': { 'X': frame_number, 'Y': new_value },
++                'co': {'X': frame_number, 'Y': float(new_value)},
+                 'interpolation': openshot.BEZIER,
+                 })
+ 
+-        # Reduce # of clip properties we are saving (performance boost)
+-        #TODO: This is too slow when draging transform handlers
+-        c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+-
+         if effect_updated:
++            # Reduce # of clip properties we are saving (performance boost)
++            #TODO: This is too slow when draging transform handlers
++            c.data = {'objects': {obj_id: c.data.get('objects', {}).get(obj_id)}}
+             c.save()
+             # Update the preview
+             if refresh:
+
+From 7df87ddc189c5e5031fbb19df13378428fab951e Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 4 Nov 2021 21:52:19 -0400
+Subject: [PATCH 5/6] classes/thumbnail: Fix dangling filehandles
+
+---
+ src/classes/thumbnail.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/classes/thumbnail.py b/src/classes/thumbnail.py
+index cca47d68f..dac7422a1 100644
+--- a/src/classes/thumbnail.py
++++ b/src/classes/thumbnail.py
+@@ -209,13 +209,13 @@ def do_GET(self):
+ 
+         # Send message back to client
+         if os.path.exists(thumb_path):
+-            if not only_path:
+-                self.wfile.write(open(thumb_path, 'rb').read())
+-            else:
++            if only_path:
+                 self.wfile.write(bytes(thumb_path, "utf-8"))
++            else:
++                with open(thumb_path, 'rb') as f:
++                    self.wfile.write(f.read())
+ 
+         # Pause processing of request (since we don't currently use thread pooling, this allows
+         # the threads to be processed without choking the CPU as much
+         # TODO: Make HTTPServer work with a limited thread pool and remove this sleep() hack.
+         time.sleep(0.01)
+-
+
+From 33cf68ca0b1ea57edd5dec3dbb8ba06d6a3f8fa4 Mon Sep 17 00:00:00 2001
+From: "FeRD (Frank Dana)" <ferdnyc@gmail.com>
+Date: Thu, 25 Nov 2021 02:18:32 -0500
+Subject: [PATCH 6/6] properties_model: Fix bad logging call, Codacy flags
+
+---
+ src/windows/models/properties_model.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/windows/models/properties_model.py b/src/windows/models/properties_model.py
+index 40897f642..695e9f39c 100644
+--- a/src/windows/models/properties_model.py
++++ b/src/windows/models/properties_model.py
+@@ -444,7 +444,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                 log.debug("%s: update property %s. %s", log_id, property_key, clip_data.get(property_key))
+ 
+                 # Check the type of property (some are keyframe, and some are not)
+-                if property_type != "reader" and type(clip_data[property_key]) == dict:
++                if property_type != "reader" and isinstance(clip_data[property_key], dict):
+                     # Keyframe
+                     # Loop through points, find a matching points on this frame
+                     found_point = False
+@@ -517,21 +517,21 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = int(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Integer value passed to property', exc_info=1)
+ 
+                 elif property_type == "float":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = float(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Float value passed to property', exc_info=1)
+ 
+                 elif property_type == "bool":
+                     clip_updated = True
+                     try:
+                         clip_data[property_key] = bool(new_value)
+-                    except Exception as ex:
++                    except Exception:
+                         log.warn('Invalid Boolean value passed to property', exc_info=1)
+ 
+                 elif property_type == "string":
+@@ -558,7 +558,7 @@ def value_updated(self, item, interpolation=-1, value=None, interpolation_detail
+                         clip_object.Close()
+                         clip_object = None
+                     except Exception:
+-                        log.warn('Invalid Reader value passed to property: %s (%s)', value, exc_info=1)
++                        log.warn('Invalid Reader value passed to property: %s', value, exc_info=1)
+ 
+             # Reduce # of clip properties we are saving (performance boost)
+             clip_data = {property_key: clip_data.get(property_key)}
diff --git a/srcpkgs/openshot/template b/srcpkgs/openshot/template
index d16ea925d223..4248baa81e39 100644
--- a/srcpkgs/openshot/template
+++ b/srcpkgs/openshot/template
@@ -1,12 +1,11 @@
 # Template file for 'openshot'
 pkgname=openshot
-version=2.5.1
-revision=3
-archs="i686 x86_64 ppc64le"
+version=2.6.1
+revision=1
 wrksrc="${pkgname}-qt-${version}"
 build_style=python3-module
-hostmakedepends="python3"
-makedepends="ffmpeg-devel python3-PyQt5 python3-setuptools"
+hostmakedepends="python3 python3-setuptools"
+makedepends="ffmpeg-devel python3-PyQt5"
 depends="ImageMagick libopenshot mlt python3-PyQt5-svg
  python3-PyQt5-webkit python3-httplib2 python3-pyzmq python3-requests"
 short_desc="Open-source, non-linear video editor for Linux"
@@ -14,4 +13,5 @@ maintainer="Spencer Hill <spencernh77@gmail.com>"
 license="GPL-3.0-or-later"
 homepage="https://www.openshot.org"
 distfiles="https://github.com/OpenShot/openshot-qt/archive/v${version}.tar.gz"
-checksum=4c25eb9a5de42e749de4c6ca2f7a313c60e1283fe52d70c121629dbb8bb2df7b
+checksum=11651d5e7287da3c766ce6b447aef8da5cdadaf5626a2816e9025c831d0e1a66
+make_check=no # tests are broken

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PR PATCH] [Merged]: openshot: update to 2.6.1.
  2022-02-23  0:01 [PR PATCH] openshot: update to 2.6.1 tibequadorian
  2022-02-23  0:12 ` [PR PATCH] [Updated] " tibequadorian
  2022-02-24 14:47 ` tibequadorian
@ 2022-02-25 23:52 ` Piraty
  2 siblings, 0 replies; 4+ messages in thread
From: Piraty @ 2022-02-25 23:52 UTC (permalink / raw)
  To: ml

[-- Attachment #1: Type: text/plain, Size: 1312 bytes --]

There's a merged pull request on the void-packages repository

openshot: update to 2.6.1.
https://github.com/void-linux/void-packages/pull/35798

Description:
Closes #33897

Patches for openshot are already upstream so maybe we should wait for 2.6.2.

<!-- Uncomment relevant sections and delete options which are not applicable -->

#### Testing the changes
- I tested the changes in this PR: **briefly**

Removed the restriction on arch or libc so help for testing on musl and ARM is appreciated.
- [x] aarch64 glibc with wayland is confirmed to work.

<!--
#### New package
- This new package conforms to the [quality requirements](https://github.com/void-linux/void-packages/blob/master/Manual.md#quality-requirements): **YES**|**NO**
-->

<!-- Note: If the build is likely to take more than 2 hours, please [skip CI](https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md#continuous-integration)
and test at least one native build and, if supported, at least one cross build.
Ignore this section if this PR is not skipping CI.
-->
<!-- 
#### Local build testing
- I built this PR locally for my native architecture, (ARCH-LIBC)
- I built this PR locally for these architectures (if supported. mark crossbuilds):
  - aarch64-musl
  - armv7l
  - armv6l-musl
-->


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2022-02-25 23:52 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-23  0:01 [PR PATCH] openshot: update to 2.6.1 tibequadorian
2022-02-23  0:12 ` [PR PATCH] [Updated] " tibequadorian
2022-02-24 14:47 ` tibequadorian
2022-02-25 23:52 ` [PR PATCH] [Merged]: " Piraty

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).