From 7e91c46c332e83f0b89e2c97701890765f2fafb5 Mon Sep 17 00:00:00 2001 From: tibequadorian 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 - #include - -- #if ! JUCE_ANDROID -+ #if ! JUCE_ANDROID && defined(__GLIBC__) - #include - #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 + #include + +- #if ! JUCE_ANDROID ++ #if ! JUCE_ANDROID && defined(__GLIBC__) + #include + #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 " 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 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 " 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 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 +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)" +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)" +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 . + """ + ++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)" +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)" +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)" +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)" +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 " 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