OpenShot Video Editor  2.0.0
files_model.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file contains the project file model, used by the project tree
5 # @author Noah Figg <eggmunkee@hotmail.com>
6 # @author Jonathan Thomas <jonathan@openshot.org>
7 #
8 # @section LICENSE
9 #
10 # Copyright (c) 2008-2016 OpenShot Studios, LLC
11 # (http://www.openshotstudios.com). This file is part of
12 # OpenShot Video Editor (http://www.openshot.org), an open-source project
13 # dedicated to delivering high quality video editing and animation solutions
14 # to the world.
15 #
16 # OpenShot Video Editor is free software: you can redistribute it and/or modify
17 # it under the terms of the GNU General Public License as published by
18 # the Free Software Foundation, either version 3 of the License, or
19 # (at your option) any later version.
20 #
21 # OpenShot Video Editor is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License for more details.
25 #
26 # You should have received a copy of the GNU General Public License
27 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
28 #
29 
30 import os
31 
32 from PyQt5.QtCore import QMimeData, Qt, pyqtSignal
33 from PyQt5.QtGui import *
34 from PyQt5.QtWidgets import QMessageBox
35 import openshot # Python module for libopenshot (required video editing module installed separately)
36 
37 from classes import updates
38 from classes import info
39 from classes.query import File
40 from classes.logger import log
41 from classes.app import get_app
42 
43 try:
44  import json
45 except ImportError:
46  import simplejson as json
47 
48 
49 class FileStandardItemModel(QStandardItemModel):
50  ModelRefreshed = pyqtSignal()
51 
52  def __init__(self, parent=None):
53  QStandardItemModel.__init__(self)
54 
55  def mimeData(self, indexes):
56  # Create MimeData for drag operation
57  data = QMimeData()
58 
59  # Get list of all selected file ids
60  files = []
61  for item in indexes:
62  selected_row = self.itemFromIndex(item).row()
63  files.append(self.item(selected_row, 5).text())
64  data.setText(json.dumps(files))
65  data.setHtml("clip")
66 
67  # Return Mimedata
68  return data
69 
70 
72  # This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface)
73  def changed(self, action):
74 
75  # Something was changed in the 'files' list
76  if len(action.key) >= 1 and action.key[0].lower() == "files":
77  # Refresh project files model
78  if action.type == "insert":
79  # Don't clear the existing items if only inserting new things
80  self.update_model(clear=False)
81  else:
82  # Clear existing items
83  self.update_model(clear=True)
84 
85  def update_model(self, clear=True):
86  log.info("updating files model.")
87  app = get_app()
88 
89  # Get window to check filters
90  win = app.window
91  _ = app._tr
92 
93  # Skip updates (if needed)
94  if self.ignore_update_signal:
95  return
96 
97  # Clear all items
98  if clear:
99  self.model_ids = {}
100  self.model.clear()
101 
102  # Add Headers
103  self.model.setHorizontalHeaderLabels([_("Thumb"), _("Name"), _("Tags"), "", "", ""])
104 
105  # Get list of files in project
106  files = File.filter() # get all files
107 
108  # add item for each file
109  for file in files:
110  path, filename = os.path.split(file.data["path"])
111  tags = ""
112  if "tags" in file.data.keys():
113  tags = file.data["tags"]
114  name = filename
115  if "name" in file.data.keys():
116  name = file.data["name"]
117 
118  if not win.actionFilesShowAll.isChecked():
119  if win.actionFilesShowVideo.isChecked():
120  if not file.data["media_type"] == "video":
121  continue # to next file, didn't match filter
122  elif win.actionFilesShowAudio.isChecked():
123  if not file.data["media_type"] == "audio":
124  continue # to next file, didn't match filter
125  elif win.actionFilesShowImage.isChecked():
126  if not file.data["media_type"] == "image":
127  continue # to next file, didn't match filter
128 
129 
130  if win.filesFilter.text() != "":
131  if not win.filesFilter.text().lower() in filename.lower() \
132  and not win.filesFilter.text().lower() in tags.lower() \
133  and not win.filesFilter.text().lower() in name.lower():
134  continue
135 
136  # Generate thumbnail for file (if needed)
137  if (file.data["media_type"] == "video" or file.data["media_type"] == "image"):
138  # Determine thumb path
139  thumb_path = os.path.join(info.THUMBNAIL_PATH, "{}.png".format(file.id))
140 
141  # Check if thumb exists
142  if not os.path.exists(thumb_path):
143 
144  try:
145  # Convert path to the correct relative path (based on this folder)
146  file_path = file.absolute_path()
147 
148  # Reload this reader
149  clip = openshot.Clip(file_path)
150  reader = clip.Reader()
151 
152  # Open reader
153  reader.Open()
154 
155  # Determine if video overlay should be applied to thumbnail
156  overlay_path = ""
157  if file.data["media_type"] == "video":
158  overlay_path = os.path.join(info.IMAGES_PATH, "overlay.png")
159 
160  # Check for start and end attributes (optional)
161  thumbnail_frame = 1
162  if 'start' in file.data.keys():
163  fps = file.data["fps"]
164  fps_float = float(fps["num"]) / float(fps["den"])
165  thumbnail_frame = round(float(file.data['start']) * fps_float) + 1
166 
167  # Save thumbnail
168  reader.GetFrame(thumbnail_frame).Thumbnail(thumb_path, 98, 64, os.path.join(info.IMAGES_PATH, "mask.png"),
169  overlay_path, "#000", False)
170  reader.Close()
171  clip.Close()
172 
173  except:
174  # Handle exception
175  msg = QMessageBox()
176  msg.setText(_("{} is not a valid video, audio, or image file.".format(filename)))
177  msg.exec_()
178  continue
179 
180 
181  else:
182  # Audio file
183  thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png")
184 
185  row = []
186 
187  # Append thumbnail
188  col = QStandardItem()
189  col.setIcon(QIcon(thumb_path))
190  col.setText((name[:9] + '...') if len(name) > 10 else name)
191  col.setToolTip(filename)
192  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
193  row.append(col)
194 
195  # Append Filename
196  col = QStandardItem("Name")
197  col.setData(filename, Qt.DisplayRole)
198  col.setText((name[:20] + '...') if len(name) > 15 else name)
199  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable)
200  row.append(col)
201 
202  # Append Tags
203  col = QStandardItem("Tags")
204  col.setData(tags, Qt.DisplayRole)
205  col.setText(tags)
206  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable)
207  row.append(col)
208 
209  # Append Media Type
210  col = QStandardItem("Type")
211  col.setData(file.data["media_type"], Qt.DisplayRole)
212  col.setText(file.data["media_type"])
213  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsEditable)
214  row.append(col)
215 
216  # Append Path
217  col = QStandardItem("Path")
218  col.setData(path, Qt.DisplayRole)
219  col.setText(path)
220  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled)
221  row.append(col)
222 
223  # Append ID
224  col = QStandardItem("ID")
225  col.setData(file.data["id"], Qt.DisplayRole)
226  col.setText(file.data["id"])
227  col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled)
228  row.append(col)
229 
230  # Append ROW to MODEL (if does not already exist in model)
231  if not file.data["id"] in self.model_ids:
232  self.model.appendRow(row)
233  self.model_ids[file.data["id"]] = file.data["id"]
234 
235  # Process events in QT (to keep the interface responsive)
236  app.processEvents()
237 
238  # Refresh view and filters (to hide or show this new item)
239  get_app().window.resize_contents()
240 
241  # Emit signal
242  self.model.ModelRefreshed.emit()
243 
244  def __init__(self, *args):
245 
246  # Add self as listener to project data updates (undo/redo, as well as normal actions handled within this class all update the tree model)
247  app = get_app()
248  app.updates.add_listener(self)
249 
250  # Create standard model
252  self.model.setColumnCount(6)
253  self.model_ids = {}
254  self.ignore_update_signal = False
def get_app()
Returns the current QApplication instance of OpenShot.
Definition: app.py:54
def __init__(self, parent=None)
Definition: files_model.py:52
def changed(self, action)
Definition: files_model.py:73
def update_model(self, clear=True)
Definition: files_model.py:85
def __init__(self, args)
Definition: files_model.py:244
Interface for classes that listen for changes (insert, update, and delete).
Definition: updates.py:51