OpenShot Video Editor  2.0.0
preview_thread.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file contains the preview thread, used for displaying previews of the timeline
5 # @author Jonathan Thomas <jonathan@openshot.org>
6 #
7 # @section LICENSE
8 #
9 # Copyright (c) 2008-2016 OpenShot Studios, LLC
10 # (http://www.openshotstudios.com). This file is part of
11 # OpenShot Video Editor (http://www.openshot.org), an open-source project
12 # dedicated to delivering high quality video editing and animation solutions
13 # to the world.
14 #
15 # OpenShot Video Editor is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # OpenShot Video Editor is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
27 #
28 
29 import os
30 import time
31 import sip
32 
33 from PyQt5.QtCore import QObject, QThread, QTimer, pyqtSlot, pyqtSignal, QCoreApplication
34 import openshot # Python module for libopenshot (required video editing module installed separately)
35 
36 from classes.logger import log
37 from classes import settings
38 
39 try:
40  import json
41 except ImportError:
42  import simplejson as json
43 
44 
45 ##
46 # Class which communicates with the PlayerWorker Class (running on a separate thread)
47 class PreviewParent(QObject):
48 
49  # Signal when the frame position changes in the preview player
50  def onPositionChanged(self, current_frame):
51  self.parent.movePlayhead(current_frame)
52 
53  # Check if we are at the end of the timeline
54  if self.worker.player.Mode() == openshot.PLAYBACK_PLAY and current_frame >= self.worker.timeline_length and self.worker.timeline_length != -1:
55  # Yes, pause the video
56  self.parent.actionPlay.trigger()
57  self.worker.timeline_length = -1
58 
59  # Signal when the playback mode changes in the preview player (i.e PLAY, PAUSE, STOP)
60  def onModeChanged(self, current_mode):
61  log.info('onModeChanged')
62 
63  @pyqtSlot(object, object)
64  def Init(self, parent, timeline, video_widget):
65  # Important vars
66  self.parent = parent
67  self.timeline = timeline
68 
69  # Background Worker Thread (for preview video process)
70  self.background = QThread(self)
71  self.worker = PlayerWorker() # no parent!
72 
73  # Init worker variables
74  self.worker.Init(parent, timeline, video_widget)
75 
76  # Hook up signals to Background Worker
77  self.worker.position_changed.connect(self.onPositionChanged)
78  self.worker.mode_changed.connect(self.onModeChanged)
79  self.background.started.connect(self.worker.Start)
80  self.worker.finished.connect(self.background.quit)
81 
82  # Connect preview thread to main UI signals
83  self.parent.previewFrameSignal.connect(self.worker.previewFrame)
84  self.parent.refreshFrameSignal.connect(self.worker.refreshFrame)
85  self.parent.LoadFileSignal.connect(self.worker.LoadFile)
86  self.parent.PlaySignal.connect(self.worker.Play)
87  self.parent.PauseSignal.connect(self.worker.Pause)
88  self.parent.SeekSignal.connect(self.worker.Seek)
89  self.parent.SpeedSignal.connect(self.worker.Speed)
90  self.parent.StopSignal.connect(self.worker.Stop)
91 
92  # Move Worker to new thread, and Start
93  self.worker.moveToThread(self.background)
94  self.background.start()
95 
96 
97 ##
98 # QT Player Worker Object (to preview video on a separate thread)
99 class PlayerWorker(QObject):
100 
101  position_changed = pyqtSignal(int)
102  mode_changed = pyqtSignal(object)
103  finished = pyqtSignal()
104 
105  @pyqtSlot(object, object)
106  def Init(self, parent, timeline, videoPreview):
107  self.parent = parent
108  self.timeline = timeline
109  self.videoPreview = videoPreview
110  self.clip_path = None
111  self.clip_reader = None
112  self.original_speed = 0
116  self.is_running = True
117  self.number = None
118  self.current_frame = None
119  self.current_mode = None
120  self.timeline_length = -1
121 
122  # Create QtPlayer class from libopenshot
123  self.player = openshot.QtPlayer()
124 
125  @pyqtSlot()
126  ##
127  # This method starts the video player
128  def Start(self):
129  log.info("QThread Start Method Invoked")
130 
131  # Init new player
132  self.initPlayer()
133 
134  # Connect player to timeline reader
135  self.player.Reader(self.timeline)
136  self.player.Play()
137  self.player.Pause()
138 
139  # Main loop, waiting for frames to process
140  while self.is_running:
141 
142  # Emit position changed signal (if needed)
143  if self.current_frame != self.player.Position():
144  self.current_frame = self.player.Position()
145 
146  if not self.clip_path:
147  # Emit position of overall timeline (don't emit this for clip previews)
148  self.position_changed.emit(self.current_frame)
149 
150  # TODO: Remove this hack and really determine what's blocking the main thread
151  # Try and keep things responsive
152  QCoreApplication.processEvents()
153 
154  # Emit mode changed signal (if needed)
155  if self.player.Mode() != self.current_mode:
156  self.current_mode = self.player.Mode()
157  self.mode_changed.emit(self.current_mode)
158 
159  # wait for a small delay
160  time.sleep(0.01)
161 
162  self.finished.emit()
163  log.info('exiting thread')
164 
165  @pyqtSlot()
166  def initPlayer(self):
167  log.info("initPlayer")
168 
169  # Get the address of the player's renderer (a QObject that emits signals when frames are ready)
170  self.renderer_address = self.player.GetRendererQObject()
171  self.player.SetQWidget(sip.unwrapinstance(self.videoPreview))
172  self.renderer = sip.wrapinstance(self.renderer_address, QObject)
173  self.videoPreview.connectSignals(self.renderer)
174 
175  ##
176  # Kill this thread
177  def kill(self):
178  self.is_running = False
179 
180  ##
181  # Preview a certain frame
182  def previewFrame(self, number):
183 
184  log.info("previewFrame: %s" % number)
185 
186  # Mark frame number for processing
187  self.player.Seek(number)
188 
189  log.info("self.player.Position(): %s" % self.player.Position())
190 
191  ##
192  # Refresh a certain frame
193  def refreshFrame(self):
194 
195  log.info("refreshFrame")
196 
197  # Always load back in the timeline reader
198  self.LoadFile(None)
199 
200  # Mark frame number for processing
201  self.player.Seek(self.player.Position())
202 
203  log.info("self.player.Position(): %s" % self.player.Position())
204 
205  ##
206  # Load a media file into the video player
207  def LoadFile(self, path=None):
209 
210  # Check to see if this path is already loaded
211  # TODO: Determine why path is passed in as an empty string instead of None
212  if path == self.clip_path or (not path and not self.clip_path):
213  return
214 
215  # Determine the current frame of the timeline (when switching to a clip)
216  seek_position = 1
217  if path and not self.clip_path:
218  # Track the current frame
219  self.original_position = self.player.Position()
220 
221  # Stop player (very important to prevent crashing)
222  self.original_speed = self.player.Speed()
223  self.player.Speed(0)
224 
225  # If blank path, switch back to self.timeline reader
226  if not path:
227  # Return to self.timeline reader
228  log.info("Set timeline reader again in player: %s" % self.timeline)
229  self.player.Reader(self.timeline)
230 
231  # Clear clip reader reference
232  self.clip_reader = None
233  self.clip_path = None
234 
235  # Switch back to last timeline position
236  seek_position = self.original_position
237  else:
238  # Get extension of media path
239  ext = os.path.splitext(path)
240 
241  # Load Reader based on extension
242  new_reader = None
243  if ext in ['.avi', 'mov', 'mkv', 'mpg', 'mpeg', 'mp3', 'mp4', 'mts', 'ogg', 'wav', 'wmv', 'webm', 'vob']:
244  try:
245  new_reader = openshot.FFmpegReader(path)
246  new_reader.Open()
247  except:
248  try:
249  new_reader = openshot.QtImageReader(path)
250  new_reader.Open()
251  except:
252  log.error('Failed to load media file into video player: %s' % path)
253  return
254  else:
255  try:
256  new_reader = openshot.QtImageReader(path)
257  new_reader.Open()
258  except:
259  try:
260  new_reader = openshot.FFmpegReader(path)
261  new_reader.Open()
262  except:
263  log.error('Failed to load media file into video player: %s' % path)
264  return
265 
266 
267 
268  # Wrap reader in FrameMapper (to match current settings of timeline)
269  new_mapper = openshot.FrameMapper(new_reader, self.timeline.info.fps, openshot.PULLDOWN_NONE, self.timeline.info.sample_rate,
270  self.timeline.info.channels, self.timeline.info.channel_layout)
271 
272  # Keep track of previous clip readers (so we can Close it later)
273  self.previous_clip_mappers.append(new_mapper)
274  self.previous_clip_readers.append(new_reader)
275 
276  # Assign new clip_reader
277  self.clip_reader = new_mapper
278  self.clip_path = path
279 
280  # Open reader
281  self.clip_reader.Open()
282 
283  log.info("Set new FrameMapper reader in player: %s" % self.clip_reader)
284  self.player.Reader(self.clip_reader)
285 
286  # Close and destroy old clip readers (leaving the 3 most recent)
287  while len(self.previous_clip_readers) > 3:
288  log.info('Removing old clip reader: %s' % self.previous_clip_readers[0])
289  self.previous_clip_mappers.pop(0)
290  self.previous_clip_readers.pop(0)
291 
292  # Seek to frame 1, and resume speed
293  self.player.Seek(seek_position)
294  self.player.Speed(self.original_speed)
295 
296  ##
297  # Start playing the video player
298  def Play(self, timeline_length):
299 
300  # Set length of timeline in frames
301  self.timeline_length = timeline_length
302 
303  # Start playback
304  self.player.Play()
305 
306  ##
307  # Pause the video player
308  def Pause(self):
309 
310  # Pause playback
311  self.player.Pause()
312 
313  ##
314  # Stop the video player and terminate the playback threads
315  def Stop(self):
316 
317  # Stop playback
318  self.player.Stop()
319 
320  ##
321  # Seek to a specific frame
322  def Seek(self, number):
323 
324  # Seek to frame
325  self.player.Seek(number)
326 
327  ##
328  # Set the speed of the video player
329  def Speed(self, new_speed):
330 
331  # Set speed
332  self.player.Speed(new_speed)
def onPositionChanged(self, current_frame)
def Play(self, timeline_length)
Start playing the video player.
def onModeChanged(self, current_mode)
def Seek(self, number)
Seek to a specific frame.
def Stop(self)
Stop the video player and terminate the playback threads.
QT Player Worker Object (to preview video on a separate thread)
def LoadFile(self, path=None)
Load a media file into the video player.
def Start(self)
This method starts the video player.
def refreshFrame(self)
Refresh a certain frame.
def Init(self, parent, timeline, video_widget)
def kill(self)
Kill this thread.
def get_settings()
Get the current QApplication&#39;s settings instance.
Definition: settings.py:43
def Init(self, parent, timeline, videoPreview)
def previewFrame(self, number)
Preview a certain frame.
Class which communicates with the PlayerWorker Class (running on a separate thread) ...
def Speed(self, new_speed)
Set the speed of the video player.
def Pause(self)
Pause the video player.