Skip to content

Commit 62e757f

Browse files
committed
Large assets refactor, plus adding recovery ability (~/.openshot_qt/recovery/). Introduced a new @assets path placeholder which is replaced by the project's dynamic asset path. This allows for external renaming of assets folder + project file. Also simplifies 'Save As', as we no longer have hard-coded absolute paths to assets folders. Also, once a project is saved/loaded, Titles and Thumbnails are saved inside the new assets folder (instead of inside our temp ~/.openshot_qt/ folders).
1 parent 7d7eb8d commit 62e757f

13 files changed

+244
-172
lines changed

src/classes/app.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,17 @@
2727
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
2828
"""
2929

30+
import atexit
3031
import os
31-
import sys
3232
import platform
33+
import sys
3334
import traceback
34-
import atexit
35-
from uuid import uuid4
36-
from PyQt5.QtWidgets import QApplication, QStyleFactory, QMessageBox
37-
from PyQt5.QtGui import QPalette, QColor, QFontDatabase, QFont
38-
from PyQt5.QtCore import Qt
39-
from PyQt5.QtCore import QT_VERSION_STR
35+
4036
from PyQt5.QtCore import PYQT_VERSION_STR
41-
from PyQt5.QtCore import pyqtSlot
37+
from PyQt5.QtCore import QT_VERSION_STR
38+
from PyQt5.QtCore import Qt
39+
from PyQt5.QtGui import QPalette, QColor, QFontDatabase, QFont
40+
from PyQt5.QtWidgets import QApplication, QStyleFactory, QMessageBox
4241

4342
try:
4443
# Enable High-DPI resolutions
@@ -102,7 +101,7 @@ def __init__(self, *args, mode=None):
102101
log.info("pyqt5 version: %s" % PYQT_VERSION_STR)
103102
except Exception:
104103
pass
105-
104+
106105
# Setup application
107106
self.setApplicationName('openshot')
108107
self.setApplicationVersion(info.SETUP['version'])
@@ -255,6 +254,7 @@ def run(self):
255254
# return exit result
256255
return res
257256

257+
258258
# Log the session's end
259259
@atexit.register
260260
def onLogTheEnd():
@@ -268,4 +268,3 @@ def onLogTheEnd():
268268
log.info("================================================")
269269
except Exception:
270270
pass
271-

src/classes/assets.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
@file
3+
@brief This file generates the path for a project's assets
4+
@author Jonathan Thomas <[email protected]>
5+
6+
@section LICENSE
7+
8+
Copyright (c) 2008-2018 OpenShot Studios, LLC
9+
(http://www.openshotstudios.com). This file is part of
10+
OpenShot Video Editor (http://www.openshot.org), an open-source project
11+
dedicated to delivering high quality video editing and animation solutions
12+
to the world.
13+
14+
OpenShot Video Editor is free software: you can redistribute it and/or modify
15+
it under the terms of the GNU General Public License as published by
16+
the Free Software Foundation, either version 3 of the License, or
17+
(at your option) any later version.
18+
19+
OpenShot Video Editor is distributed in the hope that it will be useful,
20+
but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
GNU General Public License for more details.
23+
24+
You should have received a copy of the GNU General Public License
25+
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
26+
"""
27+
28+
import os
29+
from classes import info, settings
30+
from classes.logger import log
31+
32+
33+
def get_assets_path(file_path=None, create_paths=True):
34+
"""Get and/or create the current assets path. This path is used for thumbnail and blender files,
35+
and is unique to each project. For example: `Project1.osp` would use `Project1_assets` folder."""
36+
if not file_path:
37+
return info.USER_PATH
38+
39+
try:
40+
# Generate asset folder name, max 30 chars of filename + "_assets"
41+
file_path = file_path
42+
asset_filename = os.path.splitext(os.path.basename(file_path))[0]
43+
asset_folder_name = asset_filename[:30] + "_assets"
44+
asset_path = os.path.join(os.path.dirname(file_path), asset_folder_name)
45+
46+
# Create asset folder, if necessary
47+
if create_paths:
48+
if not os.path.exists(asset_path):
49+
os.mkdir(asset_path)
50+
log.info("Asset dir created as {}".format(asset_path))
51+
else:
52+
log.info("Using existing asset folder {}".format(asset_path))
53+
54+
# Create asset thumbnails folder
55+
asset_thumbnails_folder = os.path.join(asset_path, "thumbnail")
56+
if not os.path.exists(asset_thumbnails_folder):
57+
os.mkdir(asset_thumbnails_folder)
58+
log.info("New thumbnails folder: {}".format(asset_thumbnails_folder))
59+
60+
# Create asset title folder
61+
asset_titles_folder = os.path.join(asset_path, "title")
62+
if not os.path.exists(asset_titles_folder):
63+
os.mkdir(asset_titles_folder)
64+
log.info("New titles folder: {}".format(asset_titles_folder))
65+
66+
# Create asset blender folder
67+
asset_blender_folder = os.path.join(asset_path, "blender")
68+
if not os.path.exists(asset_blender_folder):
69+
os.mkdir(asset_blender_folder)
70+
log.info("New blender folder: {}".format(asset_blender_folder))
71+
72+
return asset_path
73+
74+
except Exception as ex:
75+
log.error("Error while getting/creating asset folder {}: {}".format(asset_path, ex))

src/classes/importers/edl.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ def create_clip(context, track):
111111
clip.data["file_id"] = file.id
112112
clip.data["title"] = context.get("clip_path", "")
113113
clip.data["layer"] = track.data.get("number", 1000000)
114-
clip.data["image"] = thumb_path
115114
if video_ctx and not audio_ctx:
116115
# Only video
117116
clip.data["position"] = timecodeToSeconds(video_ctx.get("timeline_position", "00:00:00:00"), fps_num, fps_den)
@@ -280,4 +279,4 @@ def import_edl():
280279

281280
# Update the preview and reselect current frame in properties
282281
get_app().window.refreshFrameSignal.emit()
283-
get_app().window.propertyTableView.select_frame(get_app().window.preview_thread.player.Position())
282+
get_app().window.propertyTableView.select_frame(get_app().window.preview_thread.player.Position())

src/classes/importers/final_cut_pro.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ def import_xml():
153153
clip.data["file_id"] = file.id
154154
clip.data["title"] = clip_element.getElementsByTagName("name")[0].childNodes[0].nodeValue
155155
clip.data["layer"] = track.data.get("number", 1000000)
156-
clip.data["image"] = thumb_path
157156
clip.data["position"] = float(clip_element.getElementsByTagName("start")[0].childNodes[0].nodeValue) / fps_float
158157
clip.data["start"] = float(clip_element.getElementsByTagName("in")[0].childNodes[0].nodeValue) / fps_float
159158
clip.data["end"] = float(clip_element.getElementsByTagName("out")[0].childNodes[0].nodeValue) / fps_float
@@ -196,4 +195,4 @@ def import_xml():
196195

197196
# Update the preview and reselect current frame in properties
198197
get_app().window.refreshFrameSignal.emit()
199-
get_app().window.propertyTableView.select_frame(get_app().window.preview_thread.player.Position())
198+
get_app().window.propertyTableView.select_frame(get_app().window.preview_thread.player.Position())

src/classes/info.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
HOME_PATH = os.path.join(os.path.expanduser("~"))
4444
USER_PATH = os.path.join(HOME_PATH, ".openshot_qt")
4545
BACKUP_PATH = os.path.join(USER_PATH)
46+
RECOVERY_PATH = os.path.join(USER_PATH, "recovery")
4647
BLENDER_PATH = os.path.join(USER_PATH, "blender")
47-
ASSETS_PATH = os.path.join(USER_PATH, "assets")
4848
RESOURCES_PATH = os.path.join(PATH, "resources")
4949
THUMBNAIL_PATH = os.path.join(USER_PATH, "thumbnail")
5050
CACHE_PATH = os.path.join(USER_PATH, "cache")
@@ -60,8 +60,9 @@
6060
USER_DEFAULT_PROJECT = os.path.join(USER_PATH, "default.project")
6161

6262
# Create PATHS if they do not exist (this is where temp files are stored... such as cached thumbnails)
63-
for folder in [USER_PATH, THUMBNAIL_PATH, CACHE_PATH, BLENDER_PATH, ASSETS_PATH, TITLE_PATH, PROFILES_PATH, IMAGES_PATH,
64-
TRANSITIONS_PATH, EXPORT_TESTS, BACKUP_PATH, USER_PROFILES_PATH, USER_PRESETS_PATH, PREVIEW_CACHE_PATH]:
63+
for folder in [USER_PATH, THUMBNAIL_PATH, CACHE_PATH, BLENDER_PATH, TITLE_PATH, PROFILES_PATH, IMAGES_PATH,
64+
TRANSITIONS_PATH, EXPORT_TESTS, BACKUP_PATH, USER_PROFILES_PATH, USER_PRESETS_PATH, PREVIEW_CACHE_PATH,
65+
RECOVERY_PATH]:
6566
if not os.path.exists(folder.encode("UTF-8")):
6667
os.makedirs(folder, exist_ok=True)
6768

src/classes/json_data.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import os
3434
import re
3535

36+
from classes.assets import get_assets_path
3637
from classes.logger import log
3738
from classes import info
3839

@@ -162,15 +163,19 @@ def replace_string_to_absolute(self, match):
162163

163164
# Find absolute path of file (if needed)
164165
utf_path = json.loads('"%s"' % path, encoding="utf-8") # parse bytestring into unicode string
165-
if "@transitions" not in utf_path:
166-
# Convert path to the correct relative path (based on the existing folder)
167-
new_path = os.path.abspath(os.path.join(path_context.get("existing_project_folder", ""), utf_path))
166+
if "@transitions" in utf_path:
167+
new_path = path.replace("@transitions", os.path.join(info.PATH, "transitions"))
168+
new_path = json.dumps(new_path) # Escape backslashes
169+
return '"%s": %s' % (key, new_path)
170+
171+
elif "@assets" in utf_path:
172+
new_path = path.replace("@assets", path_context["new_project_assets"])
168173
new_path = json.dumps(new_path) # Escape backslashes
169174
return '"%s": %s' % (key, new_path)
170175

171-
# Determine if @transitions path is found
172176
else:
173-
new_path = path.replace("@transitions", os.path.join(info.PATH, "transitions"))
177+
# Convert path to the correct relative path
178+
new_path = os.path.abspath(os.path.join(path_context.get("new_project_folder", ""), utf_path))
174179
new_path = json.dumps(new_path) # Escape backslashes
175180
return '"%s": %s' % (key, new_path)
176181

@@ -180,6 +185,8 @@ def convert_paths_to_absolute(self, file_path, data):
180185
# Get project folder
181186
path_context["new_project_folder"] = os.path.dirname(file_path)
182187
path_context["existing_project_folder"] = os.path.dirname(file_path)
188+
path_context["new_project_assets"] = get_assets_path(file_path, create_paths=False)
189+
path_context["existing_project_assets"] = get_assets_path(file_path, create_paths=False)
183190

184191
# Optimized regex replacement
185192
data = re.sub(path_regex, self.replace_string_to_absolute, data)
@@ -213,6 +220,16 @@ def replace_string_to_relative(self, match):
213220
new_path = json.dumps(new_path) # Escape backslashes
214221
return '"%s": %s' % (key, new_path)
215222

223+
# Determine if @assets path is found
224+
elif path_context["new_project_assets"] in folder_path:
225+
# Yes, this is an OpenShot transitions
226+
folder_path = folder_path.replace(path_context["new_project_assets"], "@assets")
227+
228+
# Convert path to @transitions/ path
229+
new_path = os.path.join(folder_path, file_path).replace("\\", "/")
230+
new_path = json.dumps(new_path) # Escape backslashes
231+
return '"%s": %s' % (key, new_path)
232+
216233
# Find absolute path of file (if needed)
217234
else:
218235
# Convert path to the correct relative path (based on the existing folder)
@@ -232,9 +249,12 @@ def convert_paths_to_relative(self, file_path, previous_path, data):
232249
try:
233250
# Get project folder
234251
path_context["new_project_folder"] = os.path.dirname(file_path)
252+
path_context["new_project_assets"] = get_assets_path(file_path, create_paths=False)
235253
path_context["existing_project_folder"] = os.path.dirname(file_path)
236-
if previous_path:
254+
path_context["existing_project_assets"] = get_assets_path(file_path, create_paths=False)
255+
if previous_path and file_path != previous_path:
237256
path_context["existing_project_folder"] = os.path.dirname(previous_path)
257+
path_context["existing_project_assets"] = get_assets_path(previous_path, create_paths=False)
238258

239259
# Optimized regex replacement
240260
data = re.sub(path_regex, self.replace_string_to_relative, data)

0 commit comments

Comments
 (0)