diff --git a/src/classes/json_data.py b/src/classes/json_data.py
index 3ef4b56a6c..0698e86a5d 100644
--- a/src/classes/json_data.py
+++ b/src/classes/json_data.py
@@ -40,7 +40,8 @@
from classes import info
# Compiled path regex
-path_regex = re.compile(r'\"(?:image|path)\":.*?\"(.*?)\"')
+path_regex = re.compile(r'\"(image|path)\":.*?\"(.*?)\"', re.UNICODE)
+path_context = {}
class JsonDataStore:
@@ -134,7 +135,7 @@ def read_from_file(self, file_path, path_mode="ignore"):
if path_mode == "absolute":
# Convert any paths to absolute
contents = self.convert_paths_to_absolute(file_path, contents)
- return json.loads(contents)
+ return json.loads(contents, strict=False)
except Exception as ex:
msg = ("Couldn't load {} file: {}".format(self.data_type, ex))
log.error(msg)
@@ -146,7 +147,7 @@ def read_from_file(self, file_path, path_mode="ignore"):
def write_to_file(self, file_path, data, path_mode="ignore", previous_path=None):
""" Save JSON settings to a file """
try:
- contents = json.dumps(data)
+ contents = json.dumps(data, indent=4, sort_keys=True)
if path_mode == "relative":
# Convert any paths to relative
contents = self.convert_paths_to_relative(file_path, previous_path, contents)
@@ -157,60 +158,89 @@ def write_to_file(self, file_path, data, path_mode="ignore", previous_path=None)
log.error(msg)
raise Exception(msg)
+ def replace_string_to_absolute(self, match):
+ """Replace matched string for converting paths to relative paths"""
+ key = match.groups(0)[0]
+ path = match.groups(0)[1]
+
+ # Find absolute path of file (if needed)
+ utf_path = json.loads('"%s"' % path, encoding="utf-8") # parse bytestring into unicode string
+ if "@transitions" not in utf_path and not os.path.isabs(utf_path):
+ # Convert path to the correct relative path (based on the existing folder)
+ new_path = os.path.abspath(os.path.join(path_context.get("existing_project_folder", ""), utf_path))
+ new_path = json.dumps(new_path) # Escape backslashes
+ return '"%s": %s' % (key, new_path)
+
+ # Determine if @transitions path is found
+ elif "@transitions" in path:
+ new_path = path.replace("@transitions", os.path.join(info.PATH, "transitions"))
+ new_path = json.dumps(new_path) # Escape backslashes
+ return '"%s": %s' % (key, new_path)
+
def convert_paths_to_absolute(self, file_path, data):
""" Convert all paths to absolute using regex """
try:
# Get project folder
- existing_project_folder = os.path.dirname(file_path)
-
- # Find all "path" attributes in the JSON string
- for path in path_regex.findall(data):
- # Find absolute path of file (if needed)
- if "@transitions" not in path and not os.path.isabs(path):
- # Convert path to the correct relative path (based on the existing folder)
- new_path = os.path.abspath(os.path.join(existing_project_folder, path))
- data = data.replace('"%s"' % path, '"%s"' % new_path)
+ path_context["new_project_folder"] = os.path.dirname(file_path)
+ path_context["existing_project_folder"] = os.path.dirname(file_path)
- # Determine if @transitions path is found
- elif "@transitions" in path:
- new_path = path.replace("@transitions", os.path.join(info.PATH, "transitions"))
- data = data.replace('"%s"' % path, '"%s"' % new_path)
+ # Optimized regex replacement
+ data = re.sub(path_regex, self.replace_string_to_absolute, data)
except Exception as ex:
log.error("Error while converting relative paths to absolute paths: %s" % str(ex))
return data
+ def replace_string_to_relative(self, match):
+ """Replace matched string for converting paths to relative paths"""
+ key = match.groups(0)[0]
+ path = match.groups(0)[1]
+ utf_path = json.loads('"%s"' % path, encoding="utf-8") # parse bytestring into unicode string
+ folder_path, file_path = os.path.split(os.path.abspath(utf_path))
+
+ # Determine if thumbnail path is found
+ if info.THUMBNAIL_PATH in folder_path:
+ # Convert path to relative thumbnail path
+ new_path = os.path.join("thumbnail", file_path).replace("\\", "/")
+ new_path = json.dumps(new_path) # Escape backslashes
+ return '"%s": %s' % (key, new_path)
+
+ # Determine if @transitions path is found
+ elif os.path.join(info.PATH, "transitions") in folder_path:
+ # Yes, this is an OpenShot transitions
+ folder_path, category_path = os.path.split(folder_path)
+
+ # Convert path to @transitions/ path
+ new_path = os.path.join("@transitions", category_path, file_path).replace("\\", "/")
+ new_path = json.dumps(new_path) # Escape backslashes
+ return '"%s": %s' % (key, new_path)
+
+ # Find absolute path of file (if needed)
+ else:
+ # Convert path to the correct relative path (based on the existing folder)
+ orig_abs_path = os.path.abspath(utf_path)
+
+ # Remove file from abs path
+ orig_abs_folder = os.path.split(orig_abs_path)[0]
+
+ # Calculate new relateive path
+ new_rel_path_folder = os.path.relpath(orig_abs_folder, path_context.get("new_project_folder", ""))
+ new_rel_path = os.path.join(new_rel_path_folder, file_path).replace("\\", "/")
+ new_rel_path = json.dumps(new_rel_path) # Escape backslashes
+ return '"%s": %s' % (key, new_rel_path)
+
def convert_paths_to_relative(self, file_path, previous_path, data):
""" Convert all paths relative to this filepath """
try:
# Get project folder
- new_project_folder = os.path.dirname(file_path)
- existing_project_folder = os.path.dirname(file_path)
+ path_context["new_project_folder"] = os.path.dirname(file_path)
+ path_context["existing_project_folder"] = os.path.dirname(file_path)
if previous_path:
- existing_project_folder = os.path.dirname(previous_path)
-
- # Find all "path" attributes in the JSON string
- for path in path_regex.findall(data):
- folder_path, file_path = os.path.split(path)
-
- # Find absolute path of file (if needed)
- if not os.path.join(info.PATH, "transitions") in folder_path:
- # Convert path to the correct relative path (based on the existing folder)
- orig_abs_path = path
- if not os.path.isabs(path):
- orig_abs_path = os.path.abspath(os.path.join(existing_project_folder, path))
- new_rel_path = os.path.relpath(orig_abs_path, new_project_folder)
- data = data.replace('"%s"' % path, '"%s"' % new_rel_path)
-
- # Determine if @transitions path is found
- else:
- # Yes, this is an OpenShot transitions
- folder_path, category_path = os.path.split(folder_path)
-
- # Convert path to @transitions/ path
- new_path = os.path.join("@transitions", category_path, file_path)
- data = data.replace('"%s"' % path, '"%s"' % new_path)
+ path_context["existing_project_folder"] = os.path.dirname(previous_path)
+
+ # Optimized regex replacement
+ data = re.sub(path_regex, self.replace_string_to_relative, data)
except Exception as ex:
log.error("Error while converting absolute paths to relative paths: %s" % str(ex))
diff --git a/src/classes/project_data.py b/src/classes/project_data.py
index 575e867534..4800b73dfb 100644
--- a/src/classes/project_data.py
+++ b/src/classes/project_data.py
@@ -423,7 +423,7 @@ def read_legacy_project_file(self, file_path):
try:
clip = openshot.Clip(item.name)
reader = clip.Reader()
- file_data = json.loads(reader.Json())
+ file_data = json.loads(reader.Json(), strict=False)
# Determine media type
if file_data["has_video"] and not self.is_image(file_data):
@@ -492,7 +492,7 @@ def read_legacy_project_file(self, file_path):
c = openshot.Clip(file_path)
# Append missing attributes to Clip JSON
- new_clip = json.loads(c.Json())
+ new_clip = json.loads(c.Json(), strict=False)
new_clip["file_id"] = file.id
new_clip["title"] = filename
new_clip["image"] = thumb_path
@@ -511,9 +511,9 @@ def read_legacy_project_file(self, file_path):
if clip.video_fade_in:
# Add keyframes
start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER)
- start_object = json.loads(start.Json())
+ start_object = json.loads(start.Json(), strict=False)
end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, 1.0, openshot.BEZIER)
- end_object = json.loads(end.Json())
+ end_object = json.loads(end.Json(), strict=False)
new_clip["alpha"]["Points"].append(start_object)
new_clip["alpha"]["Points"].append(end_object)
@@ -521,9 +521,9 @@ def read_legacy_project_file(self, file_path):
if clip.video_fade_out:
# Add keyframes
start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, 1.0, openshot.BEZIER)
- start_object = json.loads(start.Json())
+ start_object = json.loads(start.Json(), strict=False)
end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER)
- end_object = json.loads(end.Json())
+ end_object = json.loads(end.Json(), strict=False)
new_clip["alpha"]["Points"].append(start_object)
new_clip["alpha"]["Points"].append(end_object)
@@ -532,16 +532,16 @@ def read_legacy_project_file(self, file_path):
new_clip["volume"]["Points"] = []
else:
p = openshot.Point(1, clip.volume / 100.0, openshot.BEZIER)
- p_object = json.loads(p.Json())
+ p_object = json.loads(p.Json(), strict=False)
new_clip["volume"] = { "Points" : [p_object]}
# Audio Fade IN
if clip.audio_fade_in:
# Add keyframes
start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER)
- start_object = json.loads(start.Json())
+ start_object = json.loads(start.Json(), strict=False)
end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER)
- end_object = json.loads(end.Json())
+ end_object = json.loads(end.Json(), strict=False)
new_clip["volume"]["Points"].append(start_object)
new_clip["volume"]["Points"].append(end_object)
@@ -549,9 +549,9 @@ def read_legacy_project_file(self, file_path):
if clip.audio_fade_out:
# Add keyframes
start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER)
- start_object = json.loads(start.Json())
+ start_object = json.loads(start.Json(), strict=False)
end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER)
- end_object = json.loads(end.Json())
+ end_object = json.loads(end.Json(), strict=False)
new_clip["volume"]["Points"].append(start_object)
new_clip["volume"]["Points"].append(end_object)
@@ -589,9 +589,9 @@ def read_legacy_project_file(self, file_path):
"position": trans.position_on_track,
"start": 0,
"end": trans.length,
- "brightness": json.loads(brightness.Json()),
- "contrast": json.loads(contrast.Json()),
- "reader": json.loads(transition_reader.Json()),
+ "brightness": json.loads(brightness.Json(), strict=False),
+ "contrast": json.loads(contrast.Json(), strict=False),
+ "reader": json.loads(transition_reader.Json(), strict=False),
"replace_image": False
}
@@ -845,7 +845,7 @@ def check_if_paths_are_valid(self):
# try to find file with previous starting folder:
if starting_folder and os.path.exists(os.path.join(starting_folder, file_name_with_ext)):
# Update file path
- path = os.path.join(starting_folder, file_name_with_ext)
+ path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
file["path"] = path
get_app().updates.update(["import_path"], os.path.dirname(path))
log.info("Auto-updated missing file: %s" % path)
@@ -857,7 +857,7 @@ def check_if_paths_are_valid(self):
log.info("Missing folder chosen by user: %s" % starting_folder)
if starting_folder:
# Update file path and import_path
- path = os.path.join(starting_folder, file_name_with_ext)
+ path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
file["path"] = path
get_app().updates.update(["import_path"], os.path.dirname(path))
else:
@@ -876,7 +876,7 @@ def check_if_paths_are_valid(self):
# try to find clip with previous starting folder:
if starting_folder and os.path.exists(os.path.join(starting_folder, file_name_with_ext)):
# Update clip path
- path = os.path.join(starting_folder, file_name_with_ext)
+ path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
clip["reader"]["path"] = path
log.info("Auto-updated missing file: %s" % clip["reader"]["path"])
break
@@ -886,7 +886,7 @@ def check_if_paths_are_valid(self):
log.info("Missing folder chosen by user: %s" % starting_folder)
if starting_folder:
# Update clip path
- path = os.path.join(starting_folder, file_name_with_ext)
+ path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
clip["reader"]["path"] = path
else:
log.info('Removed missing clip: %s' % file_name_with_ext)
diff --git a/src/classes/updates.py b/src/classes/updates.py
index 805d24bf9f..012a26f195 100644
--- a/src/classes/updates.py
+++ b/src/classes/updates.py
@@ -105,7 +105,7 @@ def load_json(self, value):
""" Load this UpdateAction from a JSON string """
# Load JSON string
- update_action_dict = json.loads(value)
+ update_action_dict = json.loads(value, strict=False)
# Set the Update Action properties
self.type = update_action_dict.get("type")
@@ -176,13 +176,13 @@ def save_history(self, project, history_length):
history_length_int = int(history_length)
for action in self.redoHistory[-history_length_int:]:
if action.type != "load" and action.key[0] != "history":
- actionDict = json.loads(action.json())
+ actionDict = json.loads(action.json(), strict=False)
redo_list.append(actionDict)
else:
log.info("Saving redo history, skipped key: %s" % str(action.key))
for action in self.actionHistory[-history_length_int:]:
if action.type != "load" and action.key[0] != "history":
- actionDict = json.loads(action.json())
+ actionDict = json.loads(action.json(), strict=False)
undo_list.append(actionDict)
else:
log.info("Saving undo, skipped key: %s" % str(action.key))
diff --git a/src/windows/ui/main-window.ui b/src/windows/ui/main-window.ui
index 5e107dad38..22a3c0ae91 100644
--- a/src/windows/ui/main-window.ui
+++ b/src/windows/ui/main-window.ui
@@ -379,7 +379,7 @@
Qt::NoContextMenu
- true
+ false
false
@@ -416,7 +416,7 @@
Qt::NoContextMenu
- true
+ false
false