Coverage for lib/lottie/gui/import_export.py: 0%

200 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-20 16:17 +0100

1import threading 

2from functools import reduce 

3from PyQt5 import QtCore, QtWidgets, QtGui 

4from PyQt5.QtCore import Qt 

5 

6from ..exporters.base import exporters 

7from ..importers.base import importers 

8from ..parsers.baseporter import IoProgressReporter, ExtraOption 

9 

10 

11class GuiProgressReporter(IoProgressReporter): 

12 def __init__(self): 

13 self.dialogs = {} 

14 self.threads = {} 

15 self.lock = threading.Lock() 

16 self.id = 0 

17 

18 @classmethod 

19 def set_global(cls): 

20 IoProgressReporter.instance = cls() 

21 

22 def gen_id(self, parent): 

23 self.id += 1 

24 with self.lock: 

25 dialog = QtWidgets.QProgressDialog(parent) 

26 dialog.setCancelButton(None) 

27 dialog.setWindowModality(Qt.ApplicationModal) 

28 self.dialogs[self.id] = dialog 

29 return self.id 

30 

31 def get_thread_dialog(self): 

32 with self.lock: 

33 id = threading.current_thread().ident 

34 return self.dialogs[self.threads[id]] 

35 

36 def setup(self, title, id): 

37 with self.lock: 

38 tid = threading.current_thread().ident 

39 self.threads[tid] = id 

40 dialog = self.dialogs[id] 

41 

42 dialog.setWindowTitle(title) 

43 dialog.hide() 

44 

45 def report_progress(self, title, value, total): 

46 dialog = self.get_thread_dialog() 

47 dialog.show() 

48 dialog.setValue(value) 

49 dialog.setMaximum(total) 

50 dialog.setLabelText(title) 

51 

52 def report_message(self, message): 

53 dialog = self.get_thread_dialog() 

54 dialog.show() 

55 dialog.setRange(0, 0) 

56 dialog.setLabelText(message) 

57 

58 def cleanup(self): 

59 with self.lock: 

60 id = threading.current_thread().ident 

61 dialog = self.dialogs.pop(self.threads.pop(id)) 

62 dialog.hide() 

63 dialog.deleteLater() 

64 

65 

66class GuiBasePorter: 

67 def __init__(self, porter): 

68 self.porter = porter 

69 self.file_filter = "%s (%s)" % (porter.name, " ".join("*.%s" % e for e in porter.extensions)) 

70 self._dialog = None 

71 self.widgets = {} 

72 

73 def _build_dialog(self): 

74 self._dialog = QtWidgets.QDialog() 

75 self._dialog.setWindowTitle("Export to %s" % self.porter.name) 

76 layout = QtWidgets.QFormLayout() 

77 self._dialog.setLayout(layout) 

78 

79 for go in self.porter.generic_options: 

80 self._generic_option(layout, go) 

81 

82 for option in self.porter.extra_options: 

83 self._add_option(layout, option) 

84 

85 button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 

86 button_box.accepted.connect(self._dialog.accept) 

87 button_box.rejected.connect(self._dialog.reject) 

88 layout.addRow(button_box) 

89 

90 def _add_option(self, layout, option: ExtraOption): 

91 if option.dest in self.widgets: 

92 return 

93 label = QtWidgets.QLabel(option.name.replace("_", " ").title()) 

94 

95 default = option.kwargs.get("default", None) 

96 option_type = option.kwargs.get("type", str) 

97 

98 if option.kwargs.get("action") == "store_true": 

99 widget = QtWidgets.QCheckBox() 

100 widget.setChecked(False) 

101 getter = widget.isChecked 

102 elif option.kwargs.get("action") == "store_false": 

103 widget = QtWidgets.QCheckBox() 

104 labtext = label.text() 

105 if labtext.startswith("No "): 

106 label.setText(labtext[3:]) 

107 widget.setChecked(True) 

108 getter = widget.isChecked 

109 elif "choices" in option.kwargs: 

110 widget = QtWidgets.QComboBox() 

111 for choice in option.kwargs["choices"]: 

112 widget.addItem(str(choice)) 

113 if default: 

114 widget.setCurrentText(str(default)) 

115 getter = lambda: option_type(widget.currentText()) 

116 elif option_type is int: 

117 widget = QtWidgets.QSpinBox() 

118 widget.setMinimum(-1000) 

119 widget.setMaximum(1000) 

120 if default is not None: 

121 widget.setValue(int(default)) 

122 getter = widget.value 

123 else: 

124 widget = QtWidgets.QLineEdit() 

125 if default is not None: 

126 widget.setText(str(default)) 

127 getter = widget.text 

128 

129 help = option.kwargs.get("help", "") 

130 widget.setWhatsThis(help) 

131 widget.setToolTip(help) 

132 

133 widget.setObjectName(option.dest) 

134 self.widgets[option.dest] = getter 

135 layout.addRow(label, widget) 

136 

137 @property 

138 def needs_dialog(self): 

139 return self.porter.generic_options or self.porter.extra_options 

140 

141 @property 

142 def dialog(self): 

143 if not self._dialog: 

144 self._build_dialog() 

145 return self._dialog 

146 

147 def get_options(self): 

148 return { 

149 name: getter() 

150 for name, getter in self.widgets.items() 

151 } 

152 

153 def prompt_options(self, parent): 

154 if self.needs_dialog: 

155 self.dialog.setParent(parent) 

156 self.dialog.setWindowFlags(Qt.Dialog) 

157 if self.dialog.exec_() != QtWidgets.QDialog.DialogCode.Accepted: 

158 return None 

159 

160 return self.get_options() 

161 

162 def _generic_option(self, layout, go): 

163 pass 

164 

165 

166class GuiExporter(GuiBasePorter): 

167 def _generic_option(self, layout, go): 

168 if go == "pretty": 

169 self._add_option(layout, ExtraOption("pretty", action="store_true", help="Pretty print")) 

170 if go == "frame": 

171 self._add_option(layout, ExtraOption("frame", type=int, help="Frame to extract")) 

172 

173 @property 

174 def exporter(self): 

175 return self.porter 

176 

177 

178class GuiImporter(GuiBasePorter): 

179 @property 

180 def importer(self): 

181 return self.porter 

182 

183 

184class ExportThread(QtCore.QThread): 

185 def __init__(self, parent, exporter, animation, file_name, options): 

186 super().__init__() 

187 self.id = IoProgressReporter.instance.gen_id(parent) 

188 self.animation = animation 

189 self.exporter = exporter 

190 self.file_name = file_name 

191 self.options = options 

192 

193 def run(self): 

194 IoProgressReporter.instance.setup("Export to %s" % self.exporter.name, self.id) 

195 try: 

196 self.exporter.process(self.animation, self.file_name, **self.options) 

197 except Exception: 

198 IoProgressReporter.instance.report_message("Error on export") 

199 IoProgressReporter.instance.cleanup() 

200 

201 

202gui_exporters = list(map(GuiExporter, exporters)) 

203gui_importers = list(map(GuiImporter, importers)) 

204 

205 

206def get_open_filename(parent, title, dirname): 

207 extensions = reduce(lambda a, b: a | b, (set(gi.importer.extensions) for gi in gui_importers)) 

208 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions) 

209 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_importers) 

210 file_name, filter = QtWidgets.QFileDialog.getOpenFileName( 

211 parent, title, dirname, filters 

212 ) 

213 if file_name: 

214 if filter == all_files: 

215 importer = gui_importer_from_filename(file_name) 

216 if importer: 

217 return file_name, importer 

218 else: 

219 for importer in gui_importers: 

220 if importer.file_filter == filter: 

221 return file_name, importer 

222 

223 return None, None 

224 

225 

226def get_save_filename(parent, title, dirname): 

227 extensions = reduce(lambda a, b: a | b, (set(gi.exporter.extensions) for gi in gui_exporters)) 

228 all_files = "All Supported Files (%s)" % " ".join("*.%s" % ex for ex in extensions) 

229 filters = all_files + ";;" + ";;".join(ge.file_filter for ge in gui_exporters) 

230 file_name, filter = QtWidgets.QFileDialog.getSaveFileName( 

231 parent, title, dirname, filters 

232 ) 

233 

234 if file_name: 

235 if filter == all_files: 

236 exporter = gui_exporter_from_filename(file_name) 

237 if exporter: 

238 return file_name, exporter 

239 else: 

240 for exporter in gui_exporters: 

241 if exporter.file_filter == filter: 

242 return file_name, exporter 

243 

244 return None, None 

245 

246 

247def start_export(parent, exporter, animation, file_name, options): 

248 thread = ExportThread(parent, exporter, animation, file_name, options) 

249 thread.start() 

250 return thread 

251 

252 

253def gui_importer_from_filename(filename): 

254 importer = importers.get_from_filename(filename) 

255 for gip in gui_importers: 

256 if gip.importer is importer: 

257 return gip 

258 return None 

259 

260 

261def gui_exporter_from_filename(filename): 

262 exporter = exporters.get_from_filename(filename) 

263 for gip in gui_exporters: 

264 if gip.exporter is exporter: 

265 return gip 

266 return None