37 from classes.json_data
import JsonDataStore
38 from classes.updates
import UpdateInterface
39 from classes
import info, settings
40 from classes.logger
import log
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),
139 return copy.deepcopy(obj)
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):
241 parent.append(values)
244 elif isinstance(values, dict):
251 self.
_data[my_key] = values
267 default_profile = s.get(
"default-profile")
270 for profile_folder
in [info.USER_PROFILES_PATH, info.PROFILES_PATH]:
271 for file
in os.listdir(profile_folder):
273 profile_path = os.path.join(profile_folder, file)
274 profile = openshot.Profile(profile_path)
276 if default_profile == profile.info.description:
277 log.info(
"Setting default profile to %s" % profile.info.description)
280 self.
_data[
"profile"] = profile.info.description
281 self.
_data[
"width"] = profile.info.width
282 self.
_data[
"height"] = profile.info.height
283 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
320 default_project = self.
_data 324 project_data = self.read_from_file(file_path)
326 except Exception
as ex:
331 except Exception
as ex:
336 self.
_data = self.merge_settings(default_project, project_data)
346 project_thumbnails_folder = os.path.join(loaded_project_folder,
"thumbnail")
347 if os.path.exists(project_thumbnails_folder):
349 shutil.rmtree(info.THUMBNAIL_PATH,
True)
352 shutil.copytree(project_thumbnails_folder, info.THUMBNAIL_PATH)
361 from classes.app
import get_app
371 from classes.query
import File, Track, Clip, Transition
372 from classes.app
import get_app
378 import simplejson
as json
384 v = openshot.GetVersion()
386 project_data[
"version"] = {
"openshot-qt" : info.VERSION,
387 "libopenshot" : v.ToString() }
390 from classes.app
import get_app
391 fps =
get_app().project.get([
"fps"])
392 fps_float = float(fps[
"num"]) / float(fps[
"den"])
395 from classes.legacy.openshot
import classes
as legacy_classes
396 from classes.legacy.openshot.classes
import project
as legacy_project
397 from classes.legacy.openshot.classes
import sequences
as legacy_sequences
398 from classes.legacy.openshot.classes
import track
as legacy_track
399 from classes.legacy.openshot.classes
import clip
as legacy_clip
400 from classes.legacy.openshot.classes
import keyframe
as legacy_keyframe
401 from classes.legacy.openshot.classes
import files
as legacy_files
402 from classes.legacy.openshot.classes
import transition
as legacy_transition
403 sys.modules[
'openshot.classes'] = legacy_classes
404 sys.modules[
'classes.project'] = legacy_project
405 sys.modules[
'classes.sequences'] = legacy_sequences
406 sys.modules[
'classes.track'] = legacy_track
407 sys.modules[
'classes.clip'] = legacy_clip
408 sys.modules[
'classes.keyframe'] = legacy_keyframe
409 sys.modules[
'classes.files'] = legacy_files
410 sys.modules[
'classes.transition'] = legacy_transition
415 with open(file_path.encode(
'UTF-8'),
'rb')
as f:
418 v1_data = pickle.load(f, fix_imports=
True)
422 for item
in v1_data.project_folder.items:
424 if isinstance(item, legacy_files.OpenShotFile):
427 clip = openshot.Clip(item.name)
428 reader = clip.Reader()
429 file_data = json.loads(reader.Json())
432 if file_data[
"has_video"]
and not self.
is_image(file_data):
433 file_data[
"media_type"] =
"video" 434 elif file_data[
"has_video"]
and self.
is_image(file_data):
435 file_data[
"media_type"] =
"image" 436 elif file_data[
"has_audio"]
and not file_data[
"has_video"]:
437 file_data[
"media_type"] =
"audio" 441 file.data = file_data
445 file_lookup[item.unique_id] = file
449 msg = (
"%s is not a valid video, audio, or image file." % item.name)
451 failed_files.append(item.name)
454 track_list = copy.deepcopy(Track.filter())
455 for track
in track_list:
460 for legacy_t
in reversed(v1_data.sequences[0].tracks):
462 t.data = {
"number": track_counter,
"y": 0,
"label": legacy_t.name}
469 for sequence
in v1_data.sequences:
470 for track
in reversed(sequence.tracks):
471 for clip
in track.clips:
473 if clip.file_object.unique_id
in file_lookup.keys():
474 file = file_lookup[clip.file_object.unique_id]
477 log.info(
"Skipping importing missing file: %s" % clip.file_object.unique_id)
481 if (file.data[
"media_type"] ==
"video" or file.data[
"media_type"] ==
"image"):
483 thumb_path = os.path.join(info.THUMBNAIL_PATH,
"%s.png" % file.data[
"id"])
486 thumb_path = os.path.join(info.PATH,
"images",
"AudioThumbnail.png")
489 path, filename = os.path.split(file.data[
"path"])
492 file_path = file.absolute_path()
495 c = openshot.Clip(file_path)
498 new_clip = json.loads(c.Json())
499 new_clip[
"file_id"] = file.id
500 new_clip[
"title"] = filename
501 new_clip[
"image"] = thumb_path
504 new_clip[
"start"] = clip.start_time
505 new_clip[
"end"] = clip.end_time
506 new_clip[
"position"] = clip.position_on_track
507 new_clip[
"layer"] = track_counter
510 if clip.video_fade_in
or clip.video_fade_out:
511 new_clip[
"alpha"][
"Points"] = []
514 if clip.video_fade_in:
516 start = openshot.Point(clip.start_time * fps_float, 0.0, openshot.BEZIER)
517 start_object = json.loads(start.Json())
518 end = openshot.Point((clip.start_time + clip.video_fade_in_amount) * fps_float, 1.0, openshot.BEZIER)
519 end_object = json.loads(end.Json())
520 new_clip[
"alpha"][
"Points"].append(start_object)
521 new_clip[
"alpha"][
"Points"].append(end_object)
524 if clip.video_fade_out:
526 start = openshot.Point((clip.end_time - clip.video_fade_out_amount) * fps_float, 1.0, openshot.BEZIER)
527 start_object = json.loads(start.Json())
528 end = openshot.Point(clip.end_time * fps_float, 0.0, openshot.BEZIER)
529 end_object = json.loads(end.Json())
530 new_clip[
"alpha"][
"Points"].append(start_object)
531 new_clip[
"alpha"][
"Points"].append(end_object)
534 if clip.audio_fade_in
or clip.audio_fade_out:
535 new_clip[
"volume"][
"Points"] = []
537 p = openshot.Point(1, clip.volume / 100.0, openshot.BEZIER)
538 p_object = json.loads(p.Json())
539 new_clip[
"volume"] = {
"Points" : [p_object]}
542 if clip.audio_fade_in:
544 start = openshot.Point(clip.start_time * fps_float, 0.0, openshot.BEZIER)
545 start_object = json.loads(start.Json())
546 end = openshot.Point((clip.start_time + clip.video_fade_in_amount) * fps_float, clip.volume / 100.0, openshot.BEZIER)
547 end_object = json.loads(end.Json())
548 new_clip[
"volume"][
"Points"].append(start_object)
549 new_clip[
"volume"][
"Points"].append(end_object)
552 if clip.audio_fade_out:
554 start = openshot.Point((clip.end_time - clip.video_fade_out_amount) * fps_float, clip.volume / 100.0, openshot.BEZIER)
555 start_object = json.loads(start.Json())
556 end = openshot.Point(clip.end_time * fps_float, 0.0, openshot.BEZIER)
557 end_object = json.loads(end.Json())
558 new_clip[
"volume"][
"Points"].append(start_object)
559 new_clip[
"volume"][
"Points"].append(end_object)
563 clip_object.data = new_clip
567 for trans
in track.transitions:
569 if not trans.resource
or not os.path.exists(trans.resource):
570 trans.resource = os.path.join(info.PATH,
"transitions",
"common",
"fade.svg")
573 transition_reader = openshot.QtImageReader(trans.resource)
575 trans_begin_value = 1.0
576 trans_end_value = -1.0
578 trans_begin_value = -1.0
579 trans_end_value = 1.0
581 brightness = openshot.Keyframe()
582 brightness.AddPoint(1, trans_begin_value, openshot.BEZIER)
583 brightness.AddPoint(trans.length * fps_float, trans_end_value, openshot.BEZIER)
584 contrast = openshot.Keyframe(trans.softness * 10.0)
588 "id":
get_app().project.generate_id(),
589 "layer": track_counter,
590 "title":
"Transition",
592 "position": trans.position_on_track,
595 "brightness": json.loads(brightness.Json()),
596 "contrast": json.loads(contrast.Json()),
597 "reader": json.loads(transition_reader.Json()),
598 "replace_image":
False 603 t.data = transitions_data
609 except Exception
as ex:
611 msg = _(
"Failed to load project file %s: %s" % (file_path, ex))
618 raise Exception(_(
"Failed to load the following files:\n%s" %
", ".join(failed_files)))
621 log.info(
"Successfully loaded legacy project file: %s" % file_path)
625 path = file[
"path"].lower()
627 if path.endswith((
".jpg",
".jpeg",
".png",
".bmp",
".svg",
".thm",
".gif",
".bmp",
".pgm",
".tif",
".tiff")):
635 openshot_version = self.
_data[
"version"][
"openshot-qt"]
636 libopenshot_version = self.
_data[
"version"][
"libopenshot"]
638 log.info(openshot_version)
639 log.info(libopenshot_version)
641 if openshot_version ==
"0.0.0":
644 for clip
in self.
_data[
"clips"]:
646 for point
in clip[
"alpha"][
"Points"]:
649 point[
"co"][
"Y"] = 1.0 - point[
"co"][
"Y"]
650 if "handle_left" in point:
651 point[
"handle_left"][
"Y"] = 1.0 - point[
"handle_left"][
"Y"]
652 if "handle_right" in point:
653 point[
"handle_right"][
"Y"] = 1.0 - point[
"handle_right"][
"Y"]
657 def save(self, file_path, move_temp_files=True, make_paths_relative=True):
665 if make_paths_relative:
669 v = openshot.GetVersion()
670 self.
_data[
"version"] = {
"openshot-qt" : info.VERSION,
671 "libopenshot" : v.ToString() }
674 self.write_to_file(file_path, self.
_data)
680 if make_paths_relative:
694 new_project_folder = os.path.dirname(file_path)
695 new_thumbnails_folder = os.path.join(new_project_folder,
"thumbnail")
698 if not os.path.exists(new_thumbnails_folder):
699 os.mkdir(new_thumbnails_folder)
702 for filename
in glob.glob(os.path.join(info.THUMBNAIL_PATH,
'*.*')):
703 shutil.copy(filename, new_thumbnails_folder)
706 for file
in self.
_data[
"files"]:
710 if info.BLENDER_PATH
in path:
711 log.info(
"Temp blender file path detected in file")
714 folder_path, file_name = os.path.split(path)
715 parent_path, folder_name = os.path.split(folder_path)
717 path = os.path.join(new_project_folder, folder_name)
719 shutil.copytree(folder_path, path)
722 file[
"path"] = os.path.join(path, file_name)
725 for clip
in self.
_data[
"clips"]:
726 path = clip[
"reader"][
"path"]
729 if info.BLENDER_PATH
in path:
730 log.info(
"Temp blender file path detected in clip")
733 folder_path, file_name = os.path.split(path)
734 parent_path, folder_name = os.path.split(folder_path)
736 path = os.path.join(new_project_folder, folder_name)
739 clip[
"reader"][
"path"] = os.path.join(path, file_name)
742 for clip
in self.
_data[
"clips"]:
746 if info.BLENDER_PATH
in path:
747 log.info(
"Temp blender file path detected in clip thumbnail")
750 folder_path, file_name = os.path.split(path)
751 parent_path, folder_name = os.path.split(folder_path)
753 path = os.path.join(new_project_folder, folder_name)
756 clip[
"image"] = os.path.join(path, file_name)
758 except Exception
as ex:
759 log.error(
"Error while moving temp files into project folder: %s" % str(ex))
766 recent_projects = s.get(
"recent_projects")
769 if file_path
in recent_projects:
770 recent_projects.remove(file_path)
773 if len(recent_projects) > 10:
774 del recent_projects[0]
777 recent_projects.append(file_path)
780 s.set(
"recent_projects", recent_projects)
787 existing_project_folder =
None 790 new_project_folder = os.path.dirname(file_path)
793 for file
in self.
_data[
"files"]:
796 if not os.path.isabs(path):
798 path = os.path.abspath(os.path.join(existing_project_folder, path))
801 file[
"path"] = os.path.relpath(path, new_project_folder)
804 for clip
in self.
_data[
"clips"]:
806 path = clip[
"reader"][
"path"]
808 if not os.path.isabs(path):
810 path = os.path.abspath(os.path.join(existing_project_folder, path))
812 clip[
"reader"][
"path"] = os.path.relpath(path, new_project_folder)
817 if not os.path.isabs(path):
819 path = os.path.abspath(os.path.join(existing_project_folder, path))
821 clip[
"image"] = os.path.relpath(path, new_project_folder)
824 for effect
in self.
_data[
"effects"]:
826 path = effect[
"reader"][
"path"]
829 folder_path, file_path = os.path.split(path)
830 if os.path.join(info.PATH,
"transitions")
in folder_path:
832 folder_path, category_path = os.path.split(folder_path)
835 effect[
"reader"][
"path"] = os.path.join(
"@transitions", category_path, file_path)
839 if not os.path.isabs(path):
841 path = os.path.abspath(os.path.join(existing_project_folder, path))
843 effect[
"reader"][
"path"] = os.path.relpath(path, new_project_folder)
845 except Exception
as ex:
846 log.error(
"Error while converting absolute paths to relative paths: %s" % str(ex))
854 existing_project_folder =
None 859 for file
in self.
_data[
"files"]:
862 if not os.path.isabs(path):
864 path = os.path.abspath(os.path.join(existing_project_folder, path))
870 for clip
in self.
_data[
"clips"]:
872 path = clip[
"reader"][
"path"]
874 if not os.path.isabs(path):
876 path = os.path.abspath(os.path.join(existing_project_folder, path))
878 clip[
"reader"][
"path"] = path
883 if not os.path.isabs(path):
885 path = os.path.abspath(os.path.join(existing_project_folder, path))
890 for effect
in self.
_data[
"effects"]:
892 path = effect[
"reader"][
"path"]
895 if "@transitions" in path:
896 path = path.replace(
"@transitions", os.path.join(info.PATH,
"transitions"))
899 if not os.path.isabs(path):
901 path = os.path.abspath(os.path.join(existing_project_folder, path))
903 effect[
"reader"][
"path"] = path
905 except Exception
as ex:
906 log.error(
"Error while converting relative paths to absolute paths: %s" % str(ex))
914 if action.type ==
"insert":
916 old_vals = self.
_set(action.key, action.values, add=
True)
917 action.set_old_values(old_vals)
919 elif action.type ==
"update":
921 old_vals = self.
_set(action.key, action.values, partial_update=action.partial_update)
922 action.set_old_values(old_vals)
924 elif action.type ==
"delete":
926 old_vals = self.
_set(action.key, remove=
True)
927 action.set_old_values(old_vals)
934 chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 936 for i
in range(digits):
937 c_index = random.randint(0, len(chars) - 1)
938 id += (chars[c_index])
def needs_save(self)
Returns if project data Has unsaved changes.
def get_app()
Returns the current QApplication instance of OpenShot.
def generate_id(self, digits=10)
Generate random alphanumeric ids.
def convert_paths_to_absolute(self)
Convert all paths to absolute.
def read_legacy_project_file(self, file_path)
Attempt to read a legacy version 1.x openshot project file.
def load(self, file_path)
Load project from file.
def convert_paths_to_relative(self, file_path)
Convert all paths relative to this filepath.
def _set(self, key, values=None, add=False, partial_update=False, remove=False)
Store setting, but adding isn't allowed.
def move_temp_paths_to_project_folder(self, file_path)
Move all temp files (such as Thumbnails, Titles, and Blender animations) to the project folder...
def set(self, key, value)
Prevent calling JsonDataStore set() method.
def get_settings()
Get the current QApplication's settings instance.
def add_to_recent_files(self, file_path)
Add this project to the recent files list.
This class allows advanced searching of data structure, implements changes interface.
def new(self)
Try to load default project settings file, will raise error on failure.
def save(self, file_path, move_temp_files=True, make_paths_relative=True)
Save project file to disk.
def changed(self, action)
This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface) ...
def get(self, key)
Get copied value of a given key in data store.
def upgrade_project_data_structures(self)
Fix any issues with old project files (if any)