37 from classes
import info, settings
38 from classes.json_data
import JsonDataStore
39 from classes.logger
import log
40 from classes.updates
import UpdateInterface
48 JsonDataStore.__init__(self)
71 if not isinstance(key, list):
72 log.warning(
"get() key must be a list. key: {}".format(key))
75 log.warning(
"Cannot get empty key.")
82 for key_index
in range(len(key)):
83 key_part = key[key_index]
86 if not isinstance(key_part, dict)
and not isinstance(key_part, str):
87 log.error(
"Unexpected key part type: {}".format(type(key_part).__name__))
92 if isinstance(key_part, dict)
and isinstance(obj, list):
96 for item_index
in range(len(obj)):
97 item = obj[item_index]
101 for subkey
in key_part.keys():
103 subkey = subkey.lower()
105 if not (subkey
in item
and item[subkey] == key_part[subkey]):
118 if isinstance(key_part, str):
119 key_part = key_part.lower()
122 if not isinstance(obj, dict):
124 "Invalid project data structure. Trying to use a key on a non-dictionary object. Key part: {} (\"{}\").\nKey: {}".format(
125 (key_index), key_part, key))
129 if not key_part
in obj:
130 log.warn(
"Key not found in project. Mismatch on key part {} (\"{}\").\nKey: {}".format((key_index),
143 def set(self, key, value):
144 raise Exception(
"ProjectDataStore.set() is not allowed. Changes must route through UpdateManager.")
148 def _set(self, key, values=None, add=False, partial_update=False, remove=False):
151 "_set key: {} values: {} add: {} partial: {} remove: {}".format(key, values, add, partial_update, remove))
152 parent, my_key =
None,
""
155 if not isinstance(key, list):
156 log.warning(
"_set() key must be a list. key: {}".format(key))
159 log.warning(
"Cannot set empty key.")
166 for key_index
in range(len(key)):
167 key_part = key[key_index]
170 if not isinstance(key_part, dict)
and not isinstance(key_part, str):
171 log.error(
"Unexpected key part type: {}".format(type(key_part).__name__))
176 if isinstance(key_part, dict)
and isinstance(obj, list):
180 for item_index
in range(len(obj)):
181 item = obj[item_index]
185 for subkey
in key_part.keys():
187 subkey = subkey.lower()
189 if not (subkey
in item
and item[subkey] == key_part[subkey]):
204 if isinstance(key_part, str):
205 key_part = key_part.lower()
208 if not isinstance(obj, dict):
212 if not key_part
in obj:
213 log.warn(
"Key not found in project. Mismatch on key part {} (\"{}\").\nKey: {}".format((key_index),
224 if key_index < (len(key) - 1)
or key_index == 0:
229 ret = copy.deepcopy(obj)
239 if add
and isinstance(parent, list):
240 parent.append(values)
243 elif isinstance(values, dict):
250 self.
_data[my_key] = values
266 default_profile = s.get(
"default-profile")
269 for profile_folder
in [info.USER_PROFILES_PATH, info.PROFILES_PATH]:
270 for file
in os.listdir(profile_folder):
272 profile_path = os.path.join(profile_folder, file)
273 profile = openshot.Profile(profile_path)
275 if default_profile == profile.info.description:
276 log.info(
"Setting default profile to %s" % profile.info.description)
279 self.
_data[
"profile"] = profile.info.description
280 self.
_data[
"width"] = profile.info.width
281 self.
_data[
"height"] = profile.info.height
282 self.
_data[
"fps"] = {
"num" : profile.info.fps.num,
"den" : profile.info.fps.den}
286 default_sample_rate = int(s.get(
"default-samplerate"))
287 default_channel_ayout = s.get(
"default-channellayout")
290 channel_layout = openshot.LAYOUT_STEREO
291 if default_channel_ayout ==
"LAYOUT_MONO":
293 channel_layout = openshot.LAYOUT_MONO
294 elif default_channel_ayout ==
"LAYOUT_STEREO":
296 channel_layout = openshot.LAYOUT_STEREO
297 elif default_channel_ayout ==
"LAYOUT_SURROUND":
299 channel_layout = openshot.LAYOUT_SURROUND
300 elif default_channel_ayout ==
"LAYOUT_5POINT1":
302 channel_layout = openshot.LAYOUT_5POINT1
303 elif default_channel_ayout ==
"LAYOUT_7POINT1":
305 channel_layout = openshot.LAYOUT_7POINT1
308 self.
_data[
"sample_rate"] = default_sample_rate
309 self.
_data[
"channels"] = channels
310 self.
_data[
"channel_layout"] = channel_layout
319 log.info(
"Loading project file: {}".format(file_path))
322 default_project = self.
_data
326 project_data = self.read_from_file(file_path, path_mode=
"absolute")
328 except Exception
as ex:
333 except Exception
as ex:
338 self.
_data = self.merge_settings(default_project, project_data)
348 project_thumbnails_folder = os.path.join(loaded_project_folder,
"thumbnail")
349 if os.path.exists(project_thumbnails_folder):
351 shutil.rmtree(info.THUMBNAIL_PATH,
True)
354 shutil.copytree(project_thumbnails_folder, info.THUMBNAIL_PATH)
363 from classes.app
import get_app
372 if original_value == 1.0:
374 return original_value
377 return round(original_value * scale_factor)
382 log.info(
'Scale all keyframes by a factor of %s' % scale_factor)
386 for clip
in self._data.get(
'clips', []):
387 for attribute
in clip:
388 if type(clip.get(attribute)) == dict
and "Points" in clip.get(attribute):
389 for point
in clip.get(attribute).
get(
"Points"):
392 if type(clip.get(attribute)) == dict
and "red" in clip.get(attribute):
393 for color
in clip.get(attribute):
394 for point
in clip.get(attribute).
get(color).
get(
"Points"):
397 for effect
in clip.get(
"effects", []):
398 for attribute
in effect:
399 if type(effect.get(attribute)) == dict
and "Points" in effect.get(attribute):
400 for point
in effect.get(attribute).
get(
"Points"):
403 if type(effect.get(attribute)) == dict
and "red" in effect.get(attribute):
404 for color
in effect.get(attribute):
405 for point
in effect.get(attribute).
get(color).
get(
"Points"):
411 for effect
in self._data.get(
'effects',[]):
412 for attribute
in effect:
413 if type(effect.get(attribute)) == dict
and "Points" in effect.get(attribute):
414 for point
in effect.get(attribute).
get(
"Points"):
417 if type(effect.get(attribute)) == dict
and "red" in effect.get(attribute):
418 for color
in effect.get(attribute):
419 for point
in effect.get(attribute).
get(color).
get(
"Points"):
424 from classes.app
import get_app
431 from classes.query
import File, Track, Clip, Transition
432 from classes.app
import get_app
438 import simplejson
as json
444 v = openshot.GetVersion()
446 project_data[
"version"] = {
"openshot-qt" : info.VERSION,
447 "libopenshot" : v.ToString()}
450 from classes.app
import get_app
451 fps =
get_app().project.get([
"fps"])
452 fps_float = float(fps[
"num"]) / float(fps[
"den"])
455 from classes.legacy.openshot
import classes
as legacy_classes
456 from classes.legacy.openshot.classes
import project
as legacy_project
457 from classes.legacy.openshot.classes
import sequences
as legacy_sequences
458 from classes.legacy.openshot.classes
import track
as legacy_track
459 from classes.legacy.openshot.classes
import clip
as legacy_clip
460 from classes.legacy.openshot.classes
import keyframe
as legacy_keyframe
461 from classes.legacy.openshot.classes
import files
as legacy_files
462 from classes.legacy.openshot.classes
import transition
as legacy_transition
463 from classes.legacy.openshot.classes
import effect
as legacy_effect
464 from classes.legacy.openshot.classes
import marker
as legacy_marker
465 sys.modules[
'openshot.classes'] = legacy_classes
466 sys.modules[
'classes.project'] = legacy_project
467 sys.modules[
'classes.sequences'] = legacy_sequences
468 sys.modules[
'classes.track'] = legacy_track
469 sys.modules[
'classes.clip'] = legacy_clip
470 sys.modules[
'classes.keyframe'] = legacy_keyframe
471 sys.modules[
'classes.files'] = legacy_files
472 sys.modules[
'classes.transition'] = legacy_transition
473 sys.modules[
'classes.effect'] = legacy_effect
474 sys.modules[
'classes.marker'] = legacy_marker
479 with open(file_path.encode(
'UTF-8'),
'rb')
as f:
482 v1_data = pickle.load(f, fix_imports=
True, encoding=
"UTF-8")
486 for item
in v1_data.project_folder.items:
488 if isinstance(item, legacy_files.OpenShotFile):
491 clip = openshot.Clip(item.name)
492 reader = clip.Reader()
493 file_data = json.loads(reader.Json(), strict=
False)
496 if file_data[
"has_video"]
and not self.
is_image(file_data):
497 file_data[
"media_type"] =
"video"
498 elif file_data[
"has_video"]
and self.
is_image(file_data):
499 file_data[
"media_type"] =
"image"
500 elif file_data[
"has_audio"]
and not file_data[
"has_video"]:
501 file_data[
"media_type"] =
"audio"
505 file.data = file_data
509 file_lookup[item.unique_id] = file
513 msg = (
"%s is not a valid video, audio, or image file." % item.name)
515 failed_files.append(item.name)
518 track_list = copy.deepcopy(Track.filter())
519 for track
in track_list:
524 for legacy_t
in reversed(v1_data.sequences[0].tracks):
526 t.data = {
"number": track_counter,
"y": 0,
"label": legacy_t.name}
533 for sequence
in v1_data.sequences:
534 for track
in reversed(sequence.tracks):
535 for clip
in track.clips:
537 if clip.file_object.unique_id
in file_lookup.keys():
538 file = file_lookup[clip.file_object.unique_id]
541 log.info(
"Skipping importing missing file: %s" % clip.file_object.unique_id)
545 if (file.data[
"media_type"] ==
"video" or file.data[
"media_type"] ==
"image"):
547 thumb_path = os.path.join(info.THUMBNAIL_PATH,
"%s.png" % file.data[
"id"])
550 thumb_path = os.path.join(info.PATH,
"images",
"AudioThumbnail.png")
553 path, filename = os.path.split(file.data[
"path"])
556 file_path = file.absolute_path()
559 c = openshot.Clip(file_path)
562 new_clip = json.loads(c.Json(), strict=
False)
563 new_clip[
"file_id"] = file.id
564 new_clip[
"title"] = filename
565 new_clip[
"image"] = thumb_path
568 new_clip[
"start"] = clip.start_time
569 new_clip[
"end"] = clip.end_time
570 new_clip[
"position"] = clip.position_on_track
571 new_clip[
"layer"] = track_counter
574 if clip.video_fade_in
or clip.video_fade_out:
575 new_clip[
"alpha"][
"Points"] = []
578 if clip.video_fade_in:
580 start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER)
581 start_object = json.loads(start.Json(), strict=
False)
582 end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, 1.0, openshot.BEZIER)
583 end_object = json.loads(end.Json(), strict=
False)
584 new_clip[
"alpha"][
"Points"].append(start_object)
585 new_clip[
"alpha"][
"Points"].append(end_object)
588 if clip.video_fade_out:
590 start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, 1.0, openshot.BEZIER)
591 start_object = json.loads(start.Json(), strict=
False)
592 end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER)
593 end_object = json.loads(end.Json(), strict=
False)
594 new_clip[
"alpha"][
"Points"].append(start_object)
595 new_clip[
"alpha"][
"Points"].append(end_object)
598 if clip.audio_fade_in
or clip.audio_fade_out:
599 new_clip[
"volume"][
"Points"] = []
601 p = openshot.Point(1, clip.volume / 100.0, openshot.BEZIER)
602 p_object = json.loads(p.Json(), strict=
False)
603 new_clip[
"volume"] = {
"Points" : [p_object]}
606 if clip.audio_fade_in:
608 start = openshot.Point(round(clip.start_time * fps_float) + 1, 0.0, openshot.BEZIER)
609 start_object = json.loads(start.Json(), strict=
False)
610 end = openshot.Point(round((clip.start_time + clip.video_fade_in_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER)
611 end_object = json.loads(end.Json(), strict=
False)
612 new_clip[
"volume"][
"Points"].append(start_object)
613 new_clip[
"volume"][
"Points"].append(end_object)
616 if clip.audio_fade_out:
618 start = openshot.Point(round((clip.end_time - clip.video_fade_out_amount) * fps_float) + 1, clip.volume / 100.0, openshot.BEZIER)
619 start_object = json.loads(start.Json(), strict=
False)
620 end = openshot.Point(round(clip.end_time * fps_float) + 1, 0.0, openshot.BEZIER)
621 end_object = json.loads(end.Json(), strict=
False)
622 new_clip[
"volume"][
"Points"].append(start_object)
623 new_clip[
"volume"][
"Points"].append(end_object)
627 clip_object.data = new_clip
631 for trans
in track.transitions:
633 if not trans.resource
or not os.path.exists(trans.resource):
634 trans.resource = os.path.join(info.PATH,
"transitions",
"common",
"fade.svg")
637 transition_reader = openshot.QtImageReader(trans.resource)
639 trans_begin_value = 1.0
640 trans_end_value = -1.0
642 trans_begin_value = -1.0
643 trans_end_value = 1.0
645 brightness = openshot.Keyframe()
646 brightness.AddPoint(1, trans_begin_value, openshot.BEZIER)
647 brightness.AddPoint(round(trans.length * fps_float) + 1, trans_end_value, openshot.BEZIER)
648 contrast = openshot.Keyframe(trans.softness * 10.0)
652 "id":
get_app().project.generate_id(),
653 "layer": track_counter,
654 "title":
"Transition",
656 "position": trans.position_on_track,
659 "brightness": json.loads(brightness.Json(), strict=
False),
660 "contrast": json.loads(contrast.Json(), strict=
False),
661 "reader": json.loads(transition_reader.Json(), strict=
False),
662 "replace_image":
False
667 t.data = transitions_data
673 except Exception
as ex:
675 msg = _(
"Failed to load project file %(path)s: %(error)s" % {
"path": file_path,
"error": ex})
682 raise Exception(_(
"Failed to load the following files:\n%s" %
", ".join(failed_files)))
685 log.info(
"Successfully loaded legacy project file: %s" % file_path)
689 path = file[
"path"].lower()
691 if path.endswith((
".jpg",
".jpeg",
".png",
".bmp",
".svg",
".thm",
".gif",
".bmp",
".pgm",
".tif",
".tiff")):
699 openshot_version = self.
_data[
"version"][
"openshot-qt"]
700 libopenshot_version = self.
_data[
"version"][
"libopenshot"]
702 log.info(openshot_version)
703 log.info(libopenshot_version)
705 if openshot_version ==
"0.0.0":
708 for clip
in self.
_data[
"clips"]:
710 for point
in clip[
"alpha"][
"Points"]:
713 point[
"co"][
"Y"] = 1.0 - point[
"co"][
"Y"]
714 if "handle_left" in point:
715 point[
"handle_left"][
"Y"] = 1.0 - point[
"handle_left"][
"Y"]
716 if "handle_right" in point:
717 point[
"handle_right"][
"Y"] = 1.0 - point[
"handle_right"][
"Y"]
719 elif openshot_version <=
"2.1.0-dev":
722 for clip_type
in [
"clips",
"effects"]:
723 for clip
in self.
_data[clip_type]:
724 for object
in [clip] + clip.get(
'effects',[]):
725 for item_key, item_data
in object.items():
727 if type(item_data) == dict
and "Points" in item_data:
728 for point
in item_data.get(
"Points"):
730 if "handle_left" in point:
732 point.get(
"handle_left")[
"X"] = 0.5
733 point.get(
"handle_left")[
"Y"] = 1.0
734 if "handle_right" in point:
736 point.get(
"handle_right")[
"X"] = 0.5
737 point.get(
"handle_right")[
"Y"] = 0.0
739 elif type(item_data) == dict
and "red" in item_data:
740 for color
in [
"red",
"blue",
"green",
"alpha"]:
741 for point
in item_data.get(color).
get(
"Points"):
743 if "handle_left" in point:
745 point.get(
"handle_left")[
"X"] = 0.5
746 point.get(
"handle_left")[
"Y"] = 1.0
747 if "handle_right" in point:
749 point.get(
"handle_right")[
"X"] = 0.5
750 point.get(
"handle_right")[
"Y"] = 0.0
754 def save(self, file_path, move_temp_files=True, make_paths_relative=True):
757 log.info(
"Saving project file: {}".format(file_path))
764 v = openshot.GetVersion()
765 self.
_data[
"version"] = {
"openshot-qt" : info.VERSION,
766 "libopenshot" : v.ToString() }
785 new_project_folder = os.path.dirname(file_path)
786 new_thumbnails_folder = os.path.join(new_project_folder,
"thumbnail")
789 if not os.path.exists(new_thumbnails_folder):
790 os.mkdir(new_thumbnails_folder)
793 for filename
in glob.glob(os.path.join(info.THUMBNAIL_PATH,
'*.*')):
794 shutil.copy(filename, new_thumbnails_folder)
797 for file
in self.
_data[
"files"]:
801 if info.BLENDER_PATH
in path
or info.ASSETS_PATH
in path:
802 log.info(
"Temp blender file path detected in file")
805 folder_path, file_name = os.path.split(path)
806 parent_path, folder_name = os.path.split(folder_path)
807 new_parent_path = new_project_folder
809 if os.path.isdir(path)
or "%" in path:
811 new_parent_path = os.path.join(new_project_folder, folder_name)
814 shutil.copytree(folder_path, new_parent_path)
817 new_parent_path = os.path.join(new_project_folder,
"assets")
820 if not os.path.exists(new_parent_path):
821 os.mkdir(new_parent_path)
824 shutil.copy2(path, os.path.join(new_parent_path, file_name))
827 file[
"path"] = os.path.join(new_parent_path, file_name)
830 for clip
in self.
_data[
"clips"]:
831 path = clip[
"reader"][
"path"]
834 if info.BLENDER_PATH
in path
or info.ASSETS_PATH
in path:
835 log.info(
"Temp blender file path detected in clip")
838 folder_path, file_name = os.path.split(path)
839 parent_path, folder_name = os.path.split(folder_path)
841 path = os.path.join(new_project_folder, folder_name)
844 clip[
"reader"][
"path"] = os.path.join(path, file_name)
847 for clip
in self.
_data[
"clips"]:
851 if info.BLENDER_PATH
in path
or info.ASSETS_PATH
in path:
852 log.info(
"Temp blender file path detected in clip thumbnail")
855 folder_path, file_name = os.path.split(path)
856 parent_path, folder_name = os.path.split(folder_path)
858 path = os.path.join(new_project_folder, folder_name)
861 clip[
"image"] = os.path.join(path, file_name)
863 except Exception
as ex:
864 log.error(
"Error while moving temp files into project folder: %s" % str(ex))
869 if "backup.osp" in file_path:
874 recent_projects = s.get(
"recent_projects")
877 if file_path
in recent_projects:
878 recent_projects.remove(file_path)
881 if len(recent_projects) > 10:
882 del recent_projects[0]
885 recent_projects.append(file_path)
888 s.set(
"recent_projects", recent_projects)
895 starting_folder =
None
896 if self.
_data[
"import_path"]:
897 starting_folder = os.path.join(self.
_data[
"import_path"])
902 from classes.app
import get_app
907 log.info(
"checking project files...")
910 for file
in reversed(self.
_data[
"files"]):
912 parent_path, file_name_with_ext = os.path.split(path)
914 log.info(
"checking file %s" % path)
915 while not os.path.exists(path)
and "%" not in path:
918 if starting_folder
and os.path.exists(os.path.join(starting_folder, file_name_with_ext)):
920 path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
922 get_app().updates.update([
"import_path"], os.path.dirname(path))
923 log.info(
"Auto-updated missing file: %s" % path)
927 QMessageBox.warning(
None, _(
"Missing File (%s)") % file[
"id"], _(
"%s cannot be found.") % file_name_with_ext)
928 starting_folder = QFileDialog.getExistingDirectory(
None, _(
"Find directory that contains: %s" % file_name_with_ext), starting_folder)
929 log.info(
"Missing folder chosen by user: %s" % starting_folder)
932 path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
934 get_app().updates.update([
"import_path"], os.path.dirname(path))
936 log.info(
'Removed missing file: %s' % file_name_with_ext)
937 self.
_data[
"files"].remove(file)
941 for clip
in reversed(self.
_data[
"clips"]):
942 path = clip[
"reader"][
"path"]
943 parent_path, file_name_with_ext = os.path.split(path)
945 log.info(
"checking file %s" % path)
946 while not os.path.exists(path)
and "%" not in path:
949 if starting_folder
and os.path.exists(os.path.join(starting_folder, file_name_with_ext)):
951 path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
952 clip[
"reader"][
"path"] = path
953 log.info(
"Auto-updated missing file: %s" % clip[
"reader"][
"path"])
956 QMessageBox.warning(
None, _(
"Missing File in Clip (%s)") % clip[
"id"], _(
"%s cannot be found.") % file_name_with_ext)
957 starting_folder = QFileDialog.getExistingDirectory(
None, _(
"Find directory that contains: %s" % file_name_with_ext), starting_folder)
958 log.info(
"Missing folder chosen by user: %s" % starting_folder)
961 path = os.path.abspath(os.path.join(starting_folder, file_name_with_ext))
962 clip[
"reader"][
"path"] = path
964 log.info(
'Removed missing clip: %s' % file_name_with_ext)
965 self.
_data[
"clips"].remove(clip)
974 if action.type ==
"insert":
976 old_vals = self.
_set(action.key, action.values, add=
True)
977 action.set_old_values(old_vals)
979 elif action.type ==
"update":
981 old_vals = self.
_set(action.key, action.values, partial_update=action.partial_update)
982 action.set_old_values(old_vals)
984 elif action.type ==
"delete":
986 old_vals = self.
_set(action.key, remove=
True)
987 action.set_old_values(old_vals)
994 chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
996 for i
in range(digits):
997 c_index = random.randint(0, len(chars) - 1)
998 id += (chars[c_index])
def _set
Store setting, but adding isn't allowed.
def add_to_recent_files
Add this project to the recent files list.
def scale_keyframe_value
Scale keyframe X coordinate by some factor, except for 1 (leave that alone)
def rescale_keyframes
Adjust all keyframe coordinates from previous FPS to new FPS (using a scale factor) ...
def get_app
Returns the current QApplication instance of OpenShot.
def load
Load project from file.
def new
Try to load default project settings file, will raise error on failure.
def read_legacy_project_file
Attempt to read a legacy version 1.x openshot project file.
def generate_id
Generate random alphanumeric ids.
def set
Prevent calling JsonDataStore set() method.
def check_if_paths_are_valid
Check if all paths are valid, and prompt to update them if needed.
def get
Get copied value of a given key in data store.
This class allows advanced searching of data structure, implements changes interface.
def move_temp_paths_to_project_folder
Move all temp files (such as Thumbnails, Titles, and Blender animations) to the project folder...
def save
Save project file to disk.
def needs_save
Returns if project data Has unsaved changes.
def upgrade_project_data_structures
Fix any issues with old project files (if any)
def changed
This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) ...
def get_settings
Get the current QApplication's settings instance.