Skip to content

Commit 544fda3

Browse files
authored
Merge pull request #3781 from ferdnyc/new-scaler
project_data: New keyframe scaler implementation
2 parents 8cdb471 + 3ad93c7 commit 544fda3

File tree

2 files changed

+94
-56
lines changed

2 files changed

+94
-56
lines changed

src/classes/keyframe_scaler.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
@file
3+
@brief Process project data, scaling keyframe X coordinates by the given factor
4+
@author Jonathan Thomas <[email protected]>
5+
@author FeRD (Frank Dana) <[email protected]>
6+
7+
@section LICENSE
8+
9+
Copyright (c) 2008-2020 OpenShot Studios, LLC
10+
(http://www.openshotstudios.com). This file is part of
11+
OpenShot Video Editor (http://www.openshot.org), an open-source project
12+
dedicated to delivering high quality video editing and animation solutions
13+
to the world.
14+
15+
OpenShot Video Editor is free software: you can redistribute it and/or modify
16+
it under the terms of the GNU General Public License as published by
17+
the Free Software Foundation, either version 3 of the License, or
18+
(at your option) any later version.
19+
20+
OpenShot Video Editor is distributed in the hope that it will be useful,
21+
but WITHOUT ANY WARRANTY; without even the implied warranty of
22+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+
GNU General Public License for more details.
24+
25+
You should have received a copy of the GNU General Public License
26+
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
27+
"""
28+
29+
30+
class KeyframeScaler:
31+
"""This factory class produces scaler objects which, when called,
32+
will apply the assigned scaling factor to the keyframe points
33+
in a project data dictionary. Keyframe X coordinate values are
34+
multiplied by the scaling factor, except X=1 (because the first
35+
frame never changes)"""
36+
37+
def _scale_x_value(self, value: float) -> int:
38+
"""Scale value by some factor, except for 1 (leave that alone)"""
39+
if value == 1.0:
40+
return value
41+
# Round to nearest INT
42+
return round(value * self._scale_factor)
43+
44+
def _update_prop(self, prop: dict):
45+
"""Find the keyframe points in a property and scale"""
46+
# Create a list of lists of keyframe points for this prop
47+
if "red" in prop:
48+
# It's a color, one list of points for each channel
49+
keyframes = [prop[color].get("Points", []) for color in prop]
50+
else:
51+
# Not a color, just a single list of points
52+
keyframes = [prop.get("Points", [])]
53+
for k in keyframes:
54+
# Scale the X coordinate (frame #) by the stored factor
55+
[point["co"].update({
56+
"X": self._scale_x_value(point["co"].get("X", 0.0))
57+
})
58+
for point in k if "co" in point]
59+
60+
def _process_item(self, item: dict):
61+
"""Process all the dict sub-members of the current dict"""
62+
dict_props = [
63+
item[prop] for prop in item
64+
if isinstance(item[prop], dict)
65+
]
66+
for prop in dict_props:
67+
self._update_prop(prop)
68+
69+
def __call__(self, data: dict) -> dict:
70+
"""Apply the stored scaling factor to a project data dict"""
71+
# Look for keyframe objects in clips
72+
for clip in data.get('clips', []):
73+
self._process_item(clip)
74+
# Also update any effects applied to the clip
75+
for effect in clip.get("effects", []):
76+
self._process_item(effect)
77+
# Look for keyframe objects in project effects (transitions)
78+
for effect in data.get('effects', []):
79+
self._process_item(effect)
80+
# return the scaled project data
81+
return data
82+
83+
def __init__(self, factor: float):
84+
"""Store the scale factor assigned to this instance"""
85+
self._scale_factor = factor

src/classes/project_data.py

+9-56
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from classes.assets import get_assets_path
4242
from windows.views.find_file import find_missing_file
4343

44+
from .keyframe_scaler import KeyframeScaler
45+
4446

4547
class ProjectDataStore(JsonDataStore, UpdateInterface):
4648
""" This class allows advanced searching of data structure, implements changes interface """
@@ -394,65 +396,16 @@ def load(self, file_path, clear_thumbnails=True):
394396
from classes.app import get_app
395397
get_app().updates.load(self._data)
396398

397-
def scale_keyframe_value(self, original_value, scale_factor):
398-
"""Scale keyframe X coordinate by some factor, except for 1 (leave that alone)"""
399-
if original_value == 1.0:
400-
# This represents the first frame of a clip (so we want to maintain that)
401-
return original_value
402-
else:
403-
# Round to nearest INT
404-
return round(original_value * scale_factor)
405-
406399
def rescale_keyframes(self, scale_factor):
407400
"""Adjust all keyframe coordinates from previous FPS to new FPS (using a scale factor)
408401
and return scaled project data without modifing the current project."""
409-
log.info('Scale all keyframes by a factor of %s' % scale_factor)
410-
411-
# Create copy of active project data
412-
data = copy.deepcopy(self._data)
413-
414-
# Rescale the the copied project data
415-
# Loop through all clips (and look for Keyframe objects)
416-
# Scale the X coordinate by factor (which represents the frame #)
417-
for clip in data.get('clips', []):
418-
for attribute in clip:
419-
if type(clip.get(attribute)) == dict and "Points" in clip.get(attribute):
420-
for point in clip.get(attribute).get("Points"):
421-
if "co" in point:
422-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
423-
if type(clip.get(attribute)) == dict and "red" in clip.get(attribute):
424-
for color in clip.get(attribute):
425-
for point in clip.get(attribute).get(color).get("Points"):
426-
if "co" in point:
427-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
428-
for effect in clip.get("effects", []):
429-
for attribute in effect:
430-
if type(effect.get(attribute)) == dict and "Points" in effect.get(attribute):
431-
for point in effect.get(attribute).get("Points"):
432-
if "co" in point:
433-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
434-
if type(effect.get(attribute)) == dict and "red" in effect.get(attribute):
435-
for color in effect.get(attribute):
436-
for point in effect.get(attribute).get(color).get("Points"):
437-
if "co" in point:
438-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
439-
440-
# Loop through all effects/transitions (and look for Keyframe objects)
441-
# Scale the X coordinate by factor (which represents the frame #)
442-
for effect in data.get('effects', []):
443-
for attribute in effect:
444-
if type(effect.get(attribute)) == dict and "Points" in effect.get(attribute):
445-
for point in effect.get(attribute).get("Points"):
446-
if "co" in point:
447-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
448-
if type(effect.get(attribute)) == dict and "red" in effect.get(attribute):
449-
for color in effect.get(attribute):
450-
for point in effect.get(attribute).get(color).get("Points"):
451-
if "co" in point:
452-
point["co"]["X"] = self.scale_keyframe_value(point["co"].get("X", 0.0), scale_factor)
453-
454-
# return the copied and scaled project data
455-
return data
402+
#
403+
log.info('Scale all keyframes by a factor of %s', scale_factor)
404+
# Create a scaler instance
405+
scaler = KeyframeScaler(factor=scale_factor)
406+
# Create copy of active project data and scale
407+
scaled = scaler(copy.deepcopy(self._data))
408+
return scaled
456409

457410
def read_legacy_project_file(self, file_path):
458411
"""Attempt to read a legacy version 1.x openshot project file"""

0 commit comments

Comments
 (0)