1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
6
# Copyright (c) 2010, Robert Swain <rob@opendot.cl>
8
# This program is free software; you can redistribute it and/or
9
# modify it under the terms of the GNU Lesser General Public
10
# License as published by the Free Software Foundation; either
11
# version 2.1 of the License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
# Lesser General Public License for more details.
18
# You should have received a copy of the GNU Lesser General Public
19
# License along with this program; if not, write to the
20
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21
# Boston, MA 02110-1301, USA.
30
from pitivi.action import RenderAction, ViewAction
31
from pitivi.encode import RenderFactory, RenderSinkFactory
32
from pitivi.factories.base import SourceFactory
33
from pitivi.factories.file import URISinkFactory
34
from pitivi.factories.timeline import TimelineSourceFactory
35
from pitivi.log.loggable import Loggable
36
from pitivi.settings import export_settings_to_render_settings
37
from pitivi.signalinterface import Signallable
38
from pitivi.stream import AudioStream, VideoStream
39
from pitivi.utils import beautify_ETA
42
class Actioner(Loggable, Signallable):
43
""" Previewer/Renderer helper methods """
50
def __init__(self, project, pipeline=None, settings=None):
51
Loggable.__init__(self)
52
# grab the Pipeline and settings
53
self.project = project
55
self.pipeline = pipeline
57
self.pipeline = self.project.pipeline
61
self.settings = settings
63
self.settings = project.getSettings()
66
def _eosCb(self, unused_pipeline):
75
def updateUIOnEOS(self):
78
def _errorCb(self, pipeline, error, detail):
81
self.updateUIOnError()
85
def updateUIOnError(self):
88
def _changeSourceSettings(self, settings):
89
videocaps = settings.getVideoCaps()
90
for source in self.project.sources.getSources():
91
source.setFilterCaps(videocaps)
94
self.debug("action %r", self.action)
97
self._connectFunctions()
98
self.debug("Setting pipeline to STOP")
100
self.debug("Creating action")
101
sources = self._getSources()
102
self.action = self._createAction(sources)
104
self.debug("Setting action on pipeline")
105
self.pipeline.addAction(self.action)
106
self.debug("Activating action")
107
self._activateAction()
108
self.debug("Updating all sources to render settings")
109
self._changeSourceSettings(self.settings)
110
self.debug("Setting pipeline to PAUSE")
111
self.pipeline.pause()
114
def _getSources(self):
115
if not self.pipeline.factories:
116
return [self.project.factory]
118
for factory in self.pipeline.factories
119
if isinstance(factory, SourceFactory)]
121
def _connectFunctions(self):
122
self.pipeline.connect('eos', self._eosCb)
123
self.pipeline.connect('error', self._errorCb)
125
def _disconnectFunctions(self):
126
self.pipeline.disconnect_by_function(self._eosCb)
127
self.pipeline.disconnect_by_function(self._errorCb)
129
def _activateAction(self):
130
self.action.activate()
132
def removeAction(self):
133
self.debug("action %r", self.action)
137
self.action.deactivate()
138
self.pipeline.removeAction(self.action)
139
self.debug("putting all active ViewActions back to sync=True")
140
for ac in self.pipeline.actions:
141
if isinstance(ac, ViewAction) and ac.isActive():
143
self._changeSourceSettings(self.project.getSettings())
144
self.pipeline.pause()
145
self._disconnectFunctions()
148
def startAction(self):
149
if not self._isReady():
153
self.timestarted = time.time()
157
""" Whether the @action can be started """
158
raise NotImplementedError()
160
def _createAction(self, sources):
161
""" Create the @action for this Actioner
163
@param sources: The source factories
164
@type sources: L{SourceFactory}
166
raise NotImplementedError()
169
class Renderer(Actioner):
170
""" Rendering helper methods """
172
def __init__(self, project, pipeline=None, settings=None, outfile=None):
174
@param settings: The export settings to be used, or None to use
175
the default export settings of the project.
176
@type settings: ExportSettings
177
@param outfile: The destination URI
178
@type outfile: C{URI}
180
Actioner.__init__(self, project, pipeline, settings)
181
self.detectStreamTypes()
182
self.outfile = outfile
184
def detectStreamTypes(self):
185
self.have_video = False
186
self.have_audio = False
188
# we can only render TimelineSourceFactory
189
if len(self.pipeline.factories) == 0:
190
timeline_source = self.project.factory
192
sources = [factory for factory in self.pipeline.factories.keys()
193
if isinstance(factory, SourceFactory)]
194
timeline_source = sources[0]
195
assert isinstance(timeline_source, TimelineSourceFactory)
197
for track in timeline_source.timeline.tracks:
198
if isinstance(track.stream, AudioStream) and track.duration > 0:
199
self.have_audio = True
200
elif isinstance(track.stream, VideoStream) and \
202
self.have_video = True
204
def _positionCb(self, unused_pipeline, position):
205
self.debug("%r %r", unused_pipeline, position)
207
timediff = time.time() - self.timestarted
208
length = self.project.timeline.duration
209
fraction = float(min(position, length)) / float(length)
210
if timediff > 5.0 and position:
211
# only display ETA after 5s in order to have enough averaging and
212
# if the position is non-null
213
totaltime = (timediff * float(length) / float(position)) - timediff
214
text = beautify_ETA(int(totaltime * gst.SECOND))
215
self.updatePosition(fraction, text)
217
def updatePosition(self, fraction, text):
221
return bool(not self.acting and self.outfile)
223
def _eosCb(self, unused_pipeline):
225
Actioner._eosCb(self, unused_pipeline)
227
def _createAction(self, sources):
228
"""Creates a L{RenderAction}."""
229
settings = export_settings_to_render_settings(self.settings,
230
self.have_video, self.have_audio)
231
sf = RenderSinkFactory(RenderFactory(settings=settings),
232
URISinkFactory(uri=self.outfile))
234
a.addProducers(*sources)
239
def _connectFunctions(self):
240
self.pipeline.connect('position', self._positionCb)
241
Actioner._connectFunctions(self)
243
def _disconnectFunctions(self):
244
self.pipeline.disconnect_by_function(self._positionCb)
245
Actioner._disconnectFunctions(self)
247
def _activateAction(self):
248
Actioner._activateAction(self)
249
self.debug("Setting all active ViewAction to sync=False")
250
for action in self.pipeline.actions:
251
if isinstance(action, ViewAction) and action.isActive():
252
action.setSync(False)
255
class Previewer(Actioner):
256
""" Previewing helper methods """
258
def __init__(self, project, pipeline=None, ui=None):
259
Actioner.__init__(self, project, pipeline=pipeline)
263
return bool(not self.acting and self.ui)
265
def _createAction(self, sources):
266
action = ViewAction()
267
action.addProducers(*sources)
268
self.ui.setAction(action)
269
self.ui.setPipeline(self.pipeline)