Merge lp:~nico-inattendu/luciole/luciole-with-sound into lp:luciole

Proposed by NicoInattendu
Status: Merged
Approved by: NicoInattendu
Approved revision: 177
Merged at revision: 125
Proposed branch: lp:~nico-inattendu/luciole/luciole-with-sound
Merge into: lp:luciole
Diff against target: 63657 lines (+57987/-2079)
218 files modified
Examples_launch.txt (+5/-0)
WebcamProps.py (+390/-0)
luciole/base/options.py (+2/-3)
luciole/conf.py (+21/-11)
luciole/constants.py (+44/-4)
luciole/ctrl/base.py (+105/-0)
luciole/ctrl/constants.py (+2/-1)
luciole/ctrl/ctrl.py (+78/-71)
luciole/ctrl/ctrl_acq.py (+634/-0)
luciole/ctrl/ctrl_app.py (+1050/-0)
luciole/ctrl/ctrl_img_vw.py (+82/-0)
luciole/ctrl/ctrl_no_project.py (+60/-0)
luciole/ctrl/ctrl_project.py (+505/-293)
luciole/ctrl/ctrl_timeline.py (+953/-0)
luciole/ctrl/import_image.py (+24/-8)
luciole/ctrl/load_rush.py (+12/-7)
luciole/ctrl/viewer_ctrl.py (+8/-1)
luciole/data/templates/project_template.xml (+4/-2)
luciole/data/themes/Tropical.rc (+10/-0)
luciole/gui/actions.py (+787/-0)
luciole/gui/base/builder.py (+23/-17)
luciole/gui/base/signals.py (+33/-4)
luciole/gui/constants.py (+88/-1)
luciole/gui/fpi_widget.py (+103/-0)
luciole/gui/gui_ctrl.py (+52/-12)
luciole/gui/main_window.py (+178/-0)
luciole/gui/project_modes.py (+443/-0)
luciole/gui/treeviews/luciole_tree.py (+145/-91)
luciole/gui/treeviews/treeviews.py (+188/-8)
luciole/gui/viewer_widget.py (+543/-128)
luciole/gui/webcam_properties_widget.py (+236/-0)
luciole/gui/windows/assistant_new_project.py (+215/-30)
luciole/gui/windows/dialog.py (+42/-0)
luciole/gui/windows/dialog_project_properties.py (+280/-112)
luciole/gui/windows/export_video_window.py (+13/-3)
luciole/gui/windows/webcam_detection_widget.py (+374/-117)
luciole/gui/windows/windows.py (+6/-0)
luciole/main.py (+3/-3)
luciole/media/acquisition.py (+33/-4)
luciole/media/image.py (+9/-8)
luciole/media/lgst/acq.py (+33/-4)
luciole/media/lgst/common.py (+150/-0)
luciole/media/lgst/gst_base.py (+75/-25)
luciole/media/lgst/image_save_bin.py (+295/-0)
luciole/media/lgst/image_src.py (+176/-0)
luciole/media/lgst/mix_stream_img_bin.py (+284/-0)
luciole/media/lgst/play.py (+70/-19)
luciole/media/lgst/play_sound.py (+31/-5)
luciole/media/lgst/scale_bin.py (+157/-0)
luciole/media/lgst/videotest_src.py (+138/-0)
luciole/media/lgst/webcam_bin.py (+165/-0)
luciole/media/lgst/webcam_caps.py (+245/-0)
luciole/media/lgst/webcam_factory.py (+357/-0)
luciole/media/player.py (+8/-0)
luciole/media/webcam_detect/webcam_detection.py (+444/-412)
luciole/project/export/export_tool_base.py (+1/-1)
luciole/project/export/export_video.py (+34/-16)
luciole/project/project_etree.py (+218/-69)
luciole/ui/luciole.glade (+319/-589)
luciole/ui/no_project_mode.glade (+88/-0)
luciole/ui/viewer.ui (+394/-0)
luciole/ui/webcam_properties.glade (+22/-0)
pitivi/.gitignore (+4/-0)
pitivi/Makefile (+686/-0)
pitivi/Makefile.am (+46/-0)
pitivi/Makefile.in (+686/-0)
pitivi/__init__.py (+3/-0)
pitivi/action.py (+770/-0)
pitivi/actioner.py (+224/-0)
pitivi/application.py (+428/-0)
pitivi/check.py (+154/-0)
pitivi/configure.py (+65/-0)
pitivi/configure.py.in (+65/-0)
pitivi/device.py (+331/-0)
pitivi/discoverer.py (+732/-0)
pitivi/effects.py (+364/-0)
pitivi/elements/.gitignore (+3/-0)
pitivi/elements/Makefile (+445/-0)
pitivi/elements/Makefile.am (+12/-0)
pitivi/elements/Makefile.in (+445/-0)
pitivi/elements/__init__.py (+3/-0)
pitivi/elements/arraysink.py (+78/-0)
pitivi/elements/mixer.py (+292/-0)
pitivi/elements/singledecodebin.py (+411/-0)
pitivi/elements/thumbnailsink.py (+91/-0)
pitivi/elements/videofade.py (+100/-0)
pitivi/encode.py (+405/-0)
pitivi/factories/Makefile (+445/-0)
pitivi/factories/Makefile.am (+12/-0)
pitivi/factories/Makefile.in (+445/-0)
pitivi/factories/base.py (+789/-0)
pitivi/factories/file.py (+80/-0)
pitivi/factories/operation.py (+212/-0)
pitivi/factories/test.py (+117/-0)
pitivi/factories/timeline.py (+183/-0)
pitivi/formatters/Makefile (+444/-0)
pitivi/formatters/Makefile.am (+11/-0)
pitivi/formatters/Makefile.in (+444/-0)
pitivi/formatters/__init__.py (+25/-0)
pitivi/formatters/base.py (+400/-0)
pitivi/formatters/etree.py (+828/-0)
pitivi/formatters/format.py (+78/-0)
pitivi/formatters/playlist.py (+96/-0)
pitivi/instance.py (+33/-0)
pitivi/log/Makefile (+443/-0)
pitivi/log/Makefile.am (+11/-0)
pitivi/log/Makefile.in (+443/-0)
pitivi/log/log.py (+976/-0)
pitivi/log/loggable.py (+40/-0)
pitivi/log/termcolor.py (+213/-0)
pitivi/log/test_log.py (+284/-0)
pitivi/pipeline.py (+948/-0)
pitivi/pitivigstutils.py (+72/-0)
pitivi/pixmaps/.gitignore (+2/-0)
pitivi/pixmaps/Makefile (+509/-0)
pitivi/pixmaps/Makefile.am (+95/-0)
pitivi/pixmaps/Makefile.in (+509/-0)
pitivi/pixmaps/pitivi-group-24.svg (+221/-0)
pitivi/pixmaps/pitivi-group.svg (+225/-0)
pitivi/pixmaps/pitivi-keyframe-24.svg (+485/-0)
pitivi/pixmaps/pitivi-keyframe.svg (+419/-0)
pitivi/pixmaps/pitivi-relink-24.svg (+666/-0)
pitivi/pixmaps/pitivi-relink.svg (+668/-0)
pitivi/pixmaps/pitivi-split-24.svg (+296/-0)
pitivi/pixmaps/pitivi-split.svg (+324/-0)
pitivi/pixmaps/pitivi-ungroup-24.svg (+184/-0)
pitivi/pixmaps/pitivi-ungroup.svg (+188/-0)
pitivi/pixmaps/pitivi-unlink-24.svg (+766/-0)
pitivi/pixmaps/pitivi-unlink.svg (+264/-0)
pitivi/pixmaps/processing-clip.svg (+271/-0)
pitivi/plugincore.py (+77/-0)
pitivi/pluginmanager.py (+453/-0)
pitivi/plumber.py (+178/-0)
pitivi/project.py (+208/-0)
pitivi/projectmanager.py (+301/-0)
pitivi/receiver.py (+64/-0)
pitivi/reflect.py (+151/-0)
pitivi/settings.py (+625/-0)
pitivi/signalgroup.py (+85/-0)
pitivi/signalinterface.py (+191/-0)
pitivi/sourcelist.py (+176/-0)
pitivi/sourcelist_undo.py (+79/-0)
pitivi/stream.py (+564/-0)
pitivi/threads.py (+113/-0)
pitivi/thumbnailcache.py (+64/-0)
pitivi/timeline/Makefile (+444/-0)
pitivi/timeline/Makefile.am (+11/-0)
pitivi/timeline/Makefile.in (+444/-0)
pitivi/timeline/__init__.py (+3/-0)
pitivi/timeline/gap.py (+145/-0)
pitivi/timeline/timeline.py (+2102/-0)
pitivi/timeline/timeline_undo.py (+471/-0)
pitivi/timeline/track.py (+1314/-0)
pitivi/ui/.gitignore (+3/-0)
pitivi/ui/Makefile (+513/-0)
pitivi/ui/Makefile.am (+60/-0)
pitivi/ui/Makefile.in (+513/-0)
pitivi/ui/__init__.py (+3/-0)
pitivi/ui/audiofxlist.py (+70/-0)
pitivi/ui/basetabs.py (+102/-0)
pitivi/ui/cam_capture.glade (+134/-0)
pitivi/ui/clipproperties.py (+423/-0)
pitivi/ui/common.py (+175/-0)
pitivi/ui/controller.py (+237/-0)
pitivi/ui/curve.py (+339/-0)
pitivi/ui/defaultpropertyeditor.py (+131/-0)
pitivi/ui/dnd.py (+46/-0)
pitivi/ui/dynamic.py (+677/-0)
pitivi/ui/effectlist.py (+353/-0)
pitivi/ui/effectsconfiguration.py (+110/-0)
pitivi/ui/elementsettingsdialog.glade (+325/-0)
pitivi/ui/encodingdialog.glade (+220/-0)
pitivi/ui/encodingdialog.py (+156/-0)
pitivi/ui/exportsettingswidget.glade (+452/-0)
pitivi/ui/exportsettingswidget.py (+463/-0)
pitivi/ui/filelisterrordialog.glade (+102/-0)
pitivi/ui/filelisterrordialog.py (+116/-0)
pitivi/ui/glade.py (+187/-0)
pitivi/ui/gstwidget.py (+206/-0)
pitivi/ui/mainwindow.py (+1185/-0)
pitivi/ui/mainwindow.xml (+69/-0)
pitivi/ui/net_capture.glade (+1073/-0)
pitivi/ui/netstream_managerdialog.py (+185/-0)
pitivi/ui/pathwalker.py (+62/-0)
pitivi/ui/pluginmanagerdialog.glade (+205/-0)
pitivi/ui/pluginmanagerdialog.py (+360/-0)
pitivi/ui/point.py (+45/-0)
pitivi/ui/prefs.py (+497/-0)
pitivi/ui/preview.py (+113/-0)
pitivi/ui/previewer.py (+563/-0)
pitivi/ui/projectsettings.glade (+175/-0)
pitivi/ui/projectsettings.py (+64/-0)
pitivi/ui/propertyeditor.py (+116/-0)
pitivi/ui/ruler.py (+350/-0)
pitivi/ui/screencast_manager.glade (+343/-0)
pitivi/ui/screencast_managerdialog.py (+101/-0)
pitivi/ui/sourcelist.py (+1074/-0)
pitivi/ui/timeline.py (+808/-0)
pitivi/ui/timelinecanvas.py (+335/-0)
pitivi/ui/timelinecontrols.py (+70/-0)
pitivi/ui/track.py (+132/-0)
pitivi/ui/trackobject.py (+426/-0)
pitivi/ui/videofxlist.py (+74/-0)
pitivi/ui/view.py (+24/-0)
pitivi/ui/viewer.py (+532/-0)
pitivi/ui/webcam_managerdialog.py (+261/-0)
pitivi/ui/zoominterface.py (+150/-0)
pitivi/undo.py (+255/-0)
pitivi/utils.py (+499/-0)
test/common.py (+45/-0)
test/luciole_project/luciole_project.xml (+40/-0)
test/test_assistant_new_project.py (+87/-0)
test/test_image.py (+100/-0)
test/test_import_image.py (+135/-0)
test/test_project_etree.py (+135/-0)
test/test_project_properties.py (+97/-0)
test/test_scale_bin.py (+93/-0)
test/test_webcam_viewer.py (+236/-0)
To merge this branch: bzr merge lp:~nico-inattendu/luciole/luciole-with-sound
Reviewer Review Type Date Requested Status
NicoInattendu Pending
Review via email: mp+51532@code.launchpad.net

Description of the change

Finally a merge of luciole with sound !
New features :
- Sound track can be added. Only wav files accepted
- Timeline management with Pitivi. implies huge changes of luciole
- Webcam detection improved. No more use of HAL replaced by udev
- Webcam definition / framerates / videotypes can now be selected
- Acquisition can now be made in full webcam definition or PAL definition

Know problems :
- Mixer features does not work
- Acqusisition from DV cam should be implemented ( urgent ! ). try with udev ?
- Only export to file is implemented with sound
- assistant page webcam when back/ rewind action the cam detection appears twice ...
- i18n with gettext to improve
- code cleaning needed (remove unused files and classes)
- GUI tuning needed : in particular the aspect ratio of viewer window
- Problems of display when no acquisition is set ( digicam mode)

For packaging :
- need to include all Pitivi files
- review files to include
- review files for i18n
- add-dependency to python-udev , and check gstreamer package depdendencies ( bad-plugins, etc ...)

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'Examples_launch.txt'
--- Examples_launch.txt 1970-01-01 00:00:00 +0000
+++ Examples_launch.txt 2011-02-28 13:21:06 +0000
@@ -0,0 +1,5 @@
1PITIVI_DEBUG=2,*action*:4,*ctrlp*:4 bin/luciole
2
3PITIVI_DEBUG=2,*webcam*:4,*tester:4* PYTHONPATH=".:$PYTHONPATH" python test/test_project_etree.py
4
5
06
=== added file 'WebcamProps.py'
--- WebcamProps.py 1970-01-01 00:00:00 +0000
+++ WebcamProps.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,390 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# -*- Mode: Python -*-
4# vim:si:ai:et:sw=4:sts=4:ts=4
5
6import gst
7
8from pitivi.log.loggable import Loggable
9from pitivi.signalinterface import Signallable
10
11from luciole.media.lgst.webcam_caps import WebcamCaps
12
13class WebcamProps(Loggable, Signallable):
14
15 __signals__ = {
16 'caps-selected' : ['caps'],
17 }
18
19 BEST_RATIO = (720.0/576.0)
20 BEST_VIDEOTYPES = ['video/x-raw-yuv', 'video/x-raw-rgb']
21 BEST_YUV_FORMATS = [gst.Fourcc('I420'), gst.Fourcc('YUY2')]
22
23 VIDEOTYPE_PROPS = [
24 'red_mask',
25 'blue_mask',
26 'green_mask',
27 'bpp',
28 'depth',
29 'format'
30 ]
31
32
33 FIELDS_TYPE = {
34 'width' : 'int' ,
35 'height' : 'int' ,
36 'framerate' : 'fraction' ,
37 'definition' : 'definition',
38 'videotype' : 'videotype',
39 'red_mask' : 'int' ,
40 'blue_mask' : 'int' ,
41 'green_mask' : 'int' ,
42 'bpp' :'int',
43 'depth' :'int',
44 'format': 'fourcc',
45 }
46
47
48
49 def __init__(self, widget) :
50 Loggable.__init__(self)
51 Signallable.__init__(self)
52
53 self.widget = widget
54 self._filter_caps = gst.caps_new_any()
55 self._webcam_caps = gst.caps_new_any()
56 self._selected_props = {}
57 self._selected_videotype = ""
58
59 self.PROPS_WITH_BEST_VALUE = {
60 'format' : self.get_best_yuv_format,
61 'definition' : self.get_best_definition,
62 'videotype' : self.get_best_videotype,
63 }
64
65 self._active_videotype_props = []
66 self._active_common_props = []
67
68
69 def prepare(self, caps) :
70 # store caps for webcam
71 self._webcam_caps = WebcamCaps(caps)
72
73
74 self.widget.connect('prop-changed', self._prop_changed_cb)
75
76 # first show definition
77 self.manage_prop('definition', None)
78
79
80
81 def get_best_value(self, prop, prop_values) :
82 _best = None
83 if prop in self.PROPS_WITH_BEST_VALUE :
84 _best = self.PROPS_WITH_BEST_VALUE[prop](prop_values)
85 else :
86 _best = prop_values[0]
87 self.info('No best value algorithm for %s. Fist value taken %s',
88 prop, _best)
89 return _best
90
91 def get_best_definition(self, definitions) :
92 """ algorithms who search first for best ratios,
93 then for higher width
94 """
95 _def_dict = {}
96 # Write algotithm
97 _def_dict['width'] = definitions[-1]['width']
98 _def_dict['height'] = definitions[-1]['height']
99 return _def_dict
100
101 def get_best_videotype(self, videotypes) :
102 """ parse BEST_VIDEOTYPES list in preference order.
103 if videotype not in BEST_VIDEOTYPES return the first
104 videotype in input list
105 """
106 _best_vtype = None
107 for _vtype in self.BEST_VIDEOTYPES :
108 if _vtype in videotypes :
109 _best_vtype = _vtype
110 break
111 if _best_vtype == None :
112 _best_vtype = videotypes[0]
113 self.warning( ("No best video type founds. selelcted %s."
114 "First in list") ) \
115 % _best_vtype
116 return _best_vtype
117
118 def get_best_yuv_format(self, yuv_formats) :
119 """ parse BEST_YUV_FORMAT list in preference order.
120 if videotype not in BEST_YUV_FORMAT return the first
121 videotype in input list
122 """
123 _best = None
124 self.debug('avlb values : "%s', yuv_formats)
125 for _format in self.BEST_YUV_FORMATS :
126 if _format in yuv_formats :
127 _best = _format
128 break
129 if _best == None :
130 _best = yuv_formats[0]
131 print _best
132 self.warning("No best yuv format found. selelcted %s. First in list", _best)
133 self.debug("Best yuv fomat is %s",_best )
134 return _best
135
136 def get_best_framerate(self, framerates) :
137 """ take a framerate on the middle.
138 Not too high, not too low
139 """
140 _len = len(framerates)
141 _best = _len/2
142 return _best
143
144
145
146 def _prop2str_single(self, name, value) :
147 _prop_str = ""
148 if self.FIELDS_TYPE[name] == 'fourcc' :
149 _prop_str = "%s" % value.fourcc
150 elif self.FIELDS_TYPE[name] == 'fraction' :
151 _prop_str = "%s/%s" % (value.num, value.denom)
152 elif self.FIELDS_TYPE[name] == 'definition' :
153 _prop_str = "%sx%s" % (value['width'], value['height'])
154 else :
155 _prop_str = "%s" % (value)
156 return _prop_str
157
158 def _prop2str(self, prop_name, values) :
159 _prop_out = None
160 if isinstance(values, list) :
161 _prop_out = [ self._prop2str_single(prop_name, _val) \
162 for _val in values ]
163 else :
164 _prop_out = self._prop2str_single(prop_name, values)
165 return _prop_out
166
167 return _prop_out
168
169
170 def _str2def(self, string) :
171 if string is not None :
172 _list = string.split('x')
173 _def_dict = {
174 'width' : _list[0],
175 'height' : _list[1]
176 }
177 return _def_dict
178 else :
179 self.error('Impossible to understand/interpret %s'%string)
180 return None
181
182
183 def set_prop_widget(self, prop, prop_values , active_value, wdg_type = 'combo' ) :
184 self.widget.set_prop(prop, prop_values , wdg_type )
185
186 # TODO : manage signal connect
187
188 self.debug("Active value for %s is %s", prop, active_value)
189 self.widget.set_active_prop(prop, active_value)
190
191 def set_framerates_widget(self, framerates, best_framerate ) :
192 pass
193
194
195 def _prop_changed_cb(self, widget, prop, value) :
196 if prop == 'definition' :
197 self._definition_changed_cb(value)
198 elif prop == 'videotype' :
199 self._videotype_changed_cb(value)
200 elif prop == 'framerate' :
201 self._framerate_changed_cb(value)
202 elif prop in self.VIDEOTYPE_PROPS :
203 self._videotype_prop_changed_cb(prop, value)
204
205
206
207
208 def _definition_changed_cb(self, str_definition) :
209 """ definition changed cb :
210 _ store definition
211 _ look videotype
212
213 """
214 _selected_def = self._str2def(str_definition)
215 self._selected_props.update(_selected_def)
216
217 self._filter_caps = \
218 self._webcam_caps.get_caps_from_definition( _selected_def)
219
220 # when defintion changed clear videotype ( if exists)
221 self._clear_videotype()
222 self._clear_framerate()
223
224 #now look for videotypes
225 self.manage_prop('videotype', self._filter_caps)
226
227 def _videotype_changed_cb(self, videotype) :
228 self._selected_videotype = videotype
229 self.debug("RX videotype-changed : %s", self._selected_videotype)
230
231 _caps = self._make_caps(self._selected_videotype, self._selected_props)
232 self._filter_caps = self._webcam_caps.intersect(_caps)
233 _size = self._filter_caps.get_size()
234 self.debug(" %s selected caps :%s",
235 _size,
236 self._filter_caps ,
237 )
238 if _size == 0 :
239 self.error("No caps found for tpl %s ", _caps)
240 return
241 self._clear_videotype_props()
242 self._clear_framerate()
243 if _size == 1 :
244 # no more selection of props needed
245 # request for available framerates according definition
246 _is_single = self.manage_prop('framerate', self._filter_caps)
247 if _is_single == True :
248 self._check_single_caps(self._filter_caps)
249
250
251 else :
252 # more than one struct in caps
253 if self._selected_videotype == 'video/x-raw-yuv' :
254 self.manage_prop('format', self._filter_caps)
255
256 if self._selected_videotype == 'video/x-raw-rgb' :
257 self.manage_prop('red_mask', self._filter_caps)
258
259 def _videotype_prop_changed_cb(self, prop, value) :
260
261 self.debug("RX prop %s changed = %s", prop, value)
262 self._selected_props[prop] = value
263
264 self._clear_framerate()
265
266 _caps = self._make_caps(self._selected_videotype, self._selected_props)
267 self._filter_caps = self._webcam_caps.intersect(_caps)
268
269 _size = self._filter_caps.get_size()
270 self.debug(" %s selected caps :%s",
271 _size,
272 self._filter_caps ,
273 )
274
275
276 if _size == 0 :
277 self.error("No caps found for tpl %s ", _caps)
278 return
279 if _size > 1 :
280 self.warning(" Now what we do ???. What prop to select ? take the fitst_srtuct")
281 _single_caps = gst.Caps(self._filter_caps[0])
282 self._filter_caps = _single_caps
283
284 # no more selection of props needed
285 # request for available framerates according definition
286 _is_single = self.manage_prop('framerate', self._filter_caps)
287 if _is_single == True :
288 self._check_single_caps(self._filter_caps)
289
290
291 def _framerate_changed_cb(self, value) :
292 self.debug("RX framerate changed = %s (%s)", value, type(value))
293 if isinstance(value, float) :
294 value = gst.Fraction(value)
295 value = "%s/%s"%(int(value.num), int(value.denom))
296 print value
297 self._selected_props['framerate'] = value
298
299 _caps = self._make_caps(self._selected_videotype, self._selected_props)
300 self._filter_caps = self._webcam_caps.intersect(_caps)
301
302 self._filter_caps.get_size()
303 self.debug(" %s selected caps :%s",
304 self._filter_caps.get_size(),
305 self._filter_caps ,
306 )
307 # check if a single caps is set
308 self._check_single_caps(self._filter_caps)
309
310
311 def _check_single_caps(self, caps) :
312 if caps.get_size() == 1 \
313 and\
314 caps.is_fixed() == True :
315 self.debug("Hourra ! . caps %s can be applied to webcam", caps )
316 self.emit('caps-selected', caps)
317
318
319
320 def manage_prop(self, prop_name, caps) :
321 """ generic manage of a prop """
322 _is_single_value = False
323 is_range, _prop_values = \
324 self._webcam_caps.get_prop_values(prop_name , caps)
325 self.debug("%s = %s",prop_name, _prop_values)
326 if len(_prop_values) > 1 or is_range == True :
327 if is_range == False :
328 _best = self.get_best_value(prop_name, _prop_values)
329 _prop_values = self._prop2str(prop_name, _prop_values)
330 _best = self._prop2str(prop_name, _best)
331 self.set_prop_widget(prop_name, _prop_values , _best)
332 else :
333 _best = _prop_values[0]
334 self.set_prop_widget(prop_name, _prop_values , _best, 'range')
335
336 else :
337 self.info("Only one value %s for %s",_prop_values,prop_name )
338 _is_single_value = True
339 if prop_name in self.VIDEOTYPE_PROPS and _is_single_value == False :
340 self._active_videotype_props.append(prop_name)
341
342 return _is_single_value
343
344
345 def _clear_videotype(self) :
346 self._clear_videotype_props()
347 _prop = 'videotype'
348 if self._selected_videotype != "" :
349 self.widget.remove_prop(_prop)
350 self._selected_videotype = ""
351
352 def _clear_videotype_props(self) :
353 while self._active_videotype_props != [] :
354 _prop = self._active_videotype_props.pop()
355 self.widget.remove_prop(_prop)
356 del self._selected_props[_prop]
357
358 def _clear_framerate(self) :
359 _prop = 'framerate'
360 if _prop in self._selected_props :
361 self.widget.remove_prop(_prop)
362 del self._selected_props[_prop]
363
364 def clear_all(self) :
365 self.widget.remove_all_props()
366 self._selected_props.clear()
367 self._active_videotype_props = []
368 self._selected_videotype = ""
369
370 try :
371 self.widget.disconnect_by_function(self._prop_changed_cb)
372 except :
373 self.warning('Impossible to disconnect')
374 pass
375
376 def _make_caps(self, mediatype, fields) :
377 _caps_str = ""
378 for _key, _value in fields.iteritems() :
379 _caps_str = "%s, %s=(%s)%s" % ( \
380 _caps_str,
381 _key,
382 self.FIELDS_TYPE[_key],
383 _value
384 )
385
386 _caps_str = "%s %s" % (mediatype, _caps_str)
387 _caps = gst.Caps(_caps_str)
388 self.debug(" Caps created : %s", _caps.to_string())
389 return _caps
390
0391
=== modified file 'luciole/base/options.py'
--- luciole/base/options.py 2010-09-01 09:29:58 +0000
+++ luciole/base/options.py 2011-02-28 13:21:06 +0000
@@ -34,7 +34,7 @@
3434
35# local application/library specific imports35# local application/library specific imports
3636
37def options_parser() :37def options_parser(args) :
38 """ parse application options """38 """ parse application options """
39 option_list = [39 option_list = [
40 make_option("-f", "--file",40 make_option("-f", "--file",
@@ -50,6 +50,5 @@
5050
51 usage = "usage: %prog [options] "51 usage = "usage: %prog [options] "
52 parser = OptionParser(option_list=option_list, usage=usage)52 parser = OptionParser(option_list=option_list, usage=usage)
53 (options, args2) = parser.parse_args()53 return parser.parse_args(args)
54 return options
5554
5655
=== modified file 'luciole/conf.py'
--- luciole/conf.py 2010-09-06 05:04:33 +0000
+++ luciole/conf.py 2011-02-28 13:21:06 +0000
@@ -37,13 +37,14 @@
37# related third party imports37# related third party imports
38import gtk38import gtk
3939
40from pitivi.log.loggable import Loggable
4041
41# local application/library specific imports42# local application/library specific imports
42import luciole.base.exceptions as LEXCEP43import luciole.base.exceptions as LEXCEP
43import luciole.base.tools as LT44import luciole.base.tools as LT
44import luciole.constants as LCONST45import luciole.constants as LCONST
45import luciole.base.lcl_et as LE46import luciole.base.lcl_et as LE
4647import luciole.gui.constants as GUI_CONST
4748
48class RecentPjtMngr(object):49class RecentPjtMngr(object):
49 """ Manage recent projects, display50 """ Manage recent projects, display
@@ -114,7 +115,9 @@
114 """ Callback on menu activation. open selected project """115 """ Callback on menu activation. open selected project """
115 try :116 try :
116 # respect strucuture or emit signals 117 # respect strucuture or emit signals
117 self.__cbs['open-project'](self, {'path':project})118 # TODO : emit open-project signal
119 #self.__cbs['open-project'](self, {'path':project})
120 pass
118 except LEXCEP.LucioException, _err :121 except LEXCEP.LucioException, _err :
119 _msg = _("Project %s no more exist"%project)122 _msg = _("Project %s no more exist"%project)
120 self.__gui_window.error_message(_msg)123 self.__gui_window.error_message(_msg)
@@ -122,12 +125,11 @@
122125
123126
124127
125class LucioleConf(object):128class LucioleConf(Loggable):
126 """ Manage the configuration file of luciole """ 129 """ Manage the configuration file of luciole """
127 130
128 __USER_LUCIOLE_DIR = ".luciole"131 __USER_LUCIOLE_DIR = ".luciole"
129 __CONF_FILE_NAME = "lucioleConf.xml"132 __CONF_FILE_NAME = "lucioleConf.xml"
130 __ORIGINAL_DIR = "templates"
131 __THEME_DIR = LCONST.THEMES_DIR133 __THEME_DIR = LCONST.THEMES_DIR
132134
133 def __get_conf_options(self):135 def __get_conf_options(self):
@@ -148,8 +150,7 @@
148 - if conf file does not exist in user dir create it150 - if conf file does not exist in user dir create it
149 - parse xml conf file151 - parse xml conf file
150 """152 """
151 153 Loggable.__init__(self)
152 self.logger = logging.getLogger('luciole')
153 self._home_dir = os.path.expandvars('$HOME')154 self._home_dir = os.path.expandvars('$HOME')
154 self._option_dict = dict()155 self._option_dict = dict()
155 self._option_dict["LastProjects"] = list()156 self._option_dict["LastProjects"] = list()
@@ -165,7 +166,7 @@
165 else :166 else :
166 try :167 try :
167 # copy file to local dir168 # copy file to local dir
168 self._copy_conf_file(os.path.join(self.__ORIGINAL_DIR,169 self._copy_conf_file(os.path.join(LCONST.TEMPLATE_DIR,
169 self.__CONF_FILE_NAME))170 self.__CONF_FILE_NAME))
170 # and parse it 171 # and parse it
171 self._parse_conf_file()172 self._parse_conf_file()
@@ -232,18 +233,27 @@
232 233
233 def load_theme(self) :234 def load_theme(self) :
234 """ Load a gtk theme """235 """ Load a gtk theme """
235 self.logger.debug('Entering theme load')236 self.debug('Entering theme load')
236 if self._option_dict.has_key('Theme') :237 if self._option_dict.has_key('Theme') :
237 l_path = os.path.join(self.__THEME_DIR, self._option_dict['Theme'])238 l_path = os.path.join(self.__THEME_DIR, self._option_dict['Theme'])
238 if os.path.exists(l_path) :239 if os.path.exists(l_path) :
239 gtk.rc_parse(l_path)240 gtk.rc_parse(l_path)
241 self.debug('Theme %s loaded', l_path)
242 # load specidic icons
243 factory = gtk.IconFactory()
244 for _stock in GUI_CONST.STOCKS :
245 gtk.stock_add([GUI_CONST.STOCK_ACQUISITION_ITEM])
246 iconset = gtk.IconSet()
247 factory.add(_stock, iconset)
248 factory.add_default()
249
240 else :250 else :
241 msg = _('Theme %s does not exist'%l_path)251 msg = _('Theme %s does not exist'%l_path)
242 self.logger.info(msg) 252 self.info(msg)
243 else : 253 else :
244 msg = _('Impossible to load theme')254 msg = _('Impossible to load theme')
245 self.logger.info(msg) 255 self.info(msg)
246 self.logger.debug('Exiting theme load')256 self.debug('Exiting theme load')
247257
248 #258 #
249 # Private methods259 # Private methods
250260
=== modified file 'luciole/constants.py'
--- luciole/constants.py 2010-09-01 06:11:33 +0000
+++ luciole/constants.py 2011-02-28 13:21:06 +0000
@@ -31,7 +31,9 @@
31import os.path31import os.path
3232
33# related third party imports33# related third party imports
34# N/A34import pygst
35pygst.require('0.10')
36import gst
3537
36# local application/library specific imports38# local application/library specific imports
37# N/A39# N/A
@@ -93,8 +95,9 @@
93########################################95########################################
94# IMAGE FORMATS96# IMAGE FORMATS
95########################################97########################################
96THUMB_RATIO = 4 # ration normal/thumbnail. 98# Thumb width displayed in treeviews, the height is calcuated in app
97 # To set size ot thumbnail images in treeview.99# according image ratio
100THUMB_WIDTH = 120
98# clor in rgb format muliply by 255 each level to go in gtk.gdk.Color format101# clor in rgb format muliply by 255 each level to go in gtk.gdk.Color format
99THUMB_COLOR_RATIO = 256102THUMB_COLOR_RATIO = 256
100THUMB_TEXT_COLOR = (103THUMB_TEXT_COLOR = (
@@ -106,7 +109,6 @@
106THUMB_TEXT_FAMILY = 'sans' # font family109THUMB_TEXT_FAMILY = 'sans' # font family
107110
108111
109ALPHA_DEFAULT = 0.4 # default alpha value used for mixer
110112
111############################################113############################################
112# PROGRAM_PATH 114# PROGRAM_PATH
@@ -117,9 +119,47 @@
117TEMPLATE_DIR = os.path.join(BASE_DIR, 'data/templates')119TEMPLATE_DIR = os.path.join(BASE_DIR, 'data/templates')
118IMAGE_DIR = os.path.join(BASE_DIR, 'data', 'images')120IMAGE_DIR = os.path.join(BASE_DIR, 'data', 'images')
119LOCALE_DIR = os.path.join(BASE_DIR, 'po')121LOCALE_DIR = os.path.join(BASE_DIR, 'po')
122SOUNDS_DIR = os.path.join(BASE_DIR, 'data', 'sounds')
123
120###############################################124###############################################
121# For GLADE125# For GLADE
122###############################################126###############################################
123MAIN_GLADE_FILE = "luciole.glade"127MAIN_GLADE_FILE = "luciole.glade"
124MAIN_GLADE_FILE_PATH = os.path.join(UI_DIR, MAIN_GLADE_FILE)128MAIN_GLADE_FILE_PATH = os.path.join(UI_DIR, MAIN_GLADE_FILE)
129MAIN_WINDOW_NAME = 'window1'
130
131
132
133ACQ_MODE_GLADE_FILE = "acquisition_mode.glade"
134ACQ_MODE_GLADE_PATH = os.path.join(UI_DIR, ACQ_MODE_GLADE_FILE)
135
136NO_PROJECT_MODE_GLADE_FILE = "no_project_mode.glade"
137NO_PROJECT_MODE_GLADE_PATH = os.path.join(UI_DIR, NO_PROJECT_MODE_GLADE_FILE)
138
139VIEWER_GLADE_FILE = "viewer.ui"
140VIEWER_GLADE_PATH = os.path.join(UI_DIR, VIEWER_GLADE_FILE)
141
142
143MODE_MAIN_WINDOW = 'window_main'
144
145
146###############################################
147# GST constants
148###############################################
149CLOCK_TIME_NONE = gst.CLOCK_TIME_NONE
150
151###############################################
152# SOUNDS_FILE
153###############################################
154SOUND_SNAPSHOT_FILE = 'camera.ogg'
155SOUND_SNAPSHOT_PATH = os.path.join(SOUNDS_DIR, SOUND_SNAPSHOT_FILE)
156
157SOUND_TRACK_NAME = 'sound_track.wav'
158#################################################
159# For mixer
160################################################
161IMAGE2MIX_NAME = 'ToMix.jpeg'
162DEFAULT_IMAGE2MIX = os.path.join(IMAGE_DIR,'white.jpg')
163DEFAULT_ALPHA_IMAGE = 0.5
164DEFAULT_ALPHA_STREAM = 1.0
125165
126166
=== added file 'luciole/ctrl/base.py'
--- luciole/ctrl/base.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/base.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,105 @@
1# -*- Mode: Python -*-
2# vim:si:ai:et:sw=4:sts=4:ts=4
3#
4#
5# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
6#
7# This file is part of Luciole.
8#
9# Luciole is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# Luciole is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
21#
22#
23"""
24ctrl_base.py :
25 Base class for ctrl class
26"""
27# standard library imports
28
29# related third party imports
30from pitivi.signalinterface import Signallable
31from pitivi.log.loggable import Loggable
32# local application/library specific imports
33
34
35class ProjectMode(Signallable, Loggable) :
36 """ Base class """
37 __signals__ = {}
38
39 def __init__(self, ctrl_project, mode_type = None) :
40 Signallable.__init__(self)
41 Loggable.__init__(self)
42 self.mode_type = mode_type
43 self.ctrl_project = ctrl_project
44 self.gui = ctrl_project.gui
45 self._viewer = None
46
47 self.project = None
48 self._pipeline = None
49
50 self.connect_to_project()
51
52 def set_viewer(self, viewer) :
53 """ set the gui viewer """
54 self._viewer = viewer
55
56 def connect_to_project(self) :
57 """ conecto project controller """
58 self.debug('Connect to project')
59 self.ctrl_project.connect('project-loaded', self.project_loaded_cb)
60 self.ctrl_project.connect('project-closed', self.project_closed_cb)
61
62 def project_loaded_cb(self, project_ctrl, project) :
63 """ project-loaded callback """
64 self.debug('RX project-loaded')
65 self.prepare(project)
66
67 def project_closed_cb(self, project_ctrl) :
68 """ project-closed callback """
69 self.debug('RX project-closed')
70 self.release()
71
72 def connect_to_gui(self, gui) :
73 """ Meta function.
74 connect to gui signals """
75 self.debug('Connect to gui')
76
77
78 def active_viewer(self) :
79 """ Meta function.
80 active displays associted to mode """
81 pass
82
83 def prepare(self, project) :
84 """ Meta function.
85 prepare the mode """
86
87 self.debug("project : %s", project)
88 self.project = project
89 if project == None :
90 self.error('No project to prepare')
91 else :
92 self.project = project
93
94
95 def release(self) :
96 """ Meta function
97 release """
98 self.debug('Release')
99 self._viewer.setPipeline(None)
100 pass
101
102
103
104
105
0106
=== modified file 'luciole/ctrl/constants.py'
--- luciole/ctrl/constants.py 2010-04-06 13:57:15 +0000
+++ luciole/ctrl/constants.py 2011-02-28 13:21:06 +0000
@@ -43,7 +43,8 @@
43 "move-down-chrono",43 "move-down-chrono",
44 "move-to-chrono",44 "move-to-chrono",
45 "move-up-chrono",45 "move-up-chrono",
46 "open-project",46# "open-project",
47 "play-query-position",
47 "play-video",48 "play-video",
48 "quit-app",49 "quit-app",
49 "save-as-project",50 "save-as-project",
5051
=== modified file 'luciole/ctrl/ctrl.py'
--- luciole/ctrl/ctrl.py 2010-09-03 12:44:41 +0000
+++ luciole/ctrl/ctrl.py 2011-02-28 13:21:06 +0000
@@ -28,13 +28,15 @@
28"""28"""
29# standard library imports29# standard library imports
30import sys30import sys
31import os
32import os.path
31import locale33import locale
32import gettext34import gettext
33_ = gettext.gettext35_ = gettext.gettext
3436
35# related third party imports37# related third party imports
36import gobject38import gobject
37import os.path39from pitivi.log import log
3840
39# local application/library specific imports41# local application/library specific imports
40import luciole.base.app_logging as LOGGING42import luciole.base.app_logging as LOGGING
@@ -52,6 +54,8 @@
52#####54#####
5355
5456
57
58
55class LucioleController(object):59class LucioleController(object):
56 """ 60 """
57 Main luciole controller61 Main luciole controller
@@ -59,7 +63,12 @@
5963
60 def __init__(self, args) :64 def __init__(self, args) :
61 """ Controller initialisation.65 """ Controller initialisation.
66
62 Is the main appication intialisation """67 Is the main appication intialisation """
68 # init logging as early as possible so we can log startup code
69 enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
70 log.init('PITIVI_DEBUG', enable_color)
71
63 #72 #
64 # init attributes73 # init attributes
65 #74 #
@@ -91,7 +100,7 @@
91100
92101
93 # load recent projects102 # load recent projects
94 _cbs = {'open-project' : self.on_open_project}103 _cbs = None
95 _recent_mnger = self.__configurer.init_recent_manager(104 _recent_mnger = self.__configurer.init_recent_manager(
96 self.__gui.file_recent_menu,105 self.__gui.file_recent_menu,
97 self.__gui.windows,106 self.__gui.windows,
@@ -103,7 +112,7 @@
103112
104 113
105 # start project controller114 # start project controller
106 self.__project = \115 self._project = \
107 CTRL_PROJECT.ProjectController(self.__gui,_recent_mnger ,_cbs)116 CTRL_PROJECT.ProjectController(self.__gui,_recent_mnger ,_cbs)
108 117
109 _cbs = {118 _cbs = {
@@ -120,7 +129,12 @@
120129
121 # conncet gui signals after the controller initialisation130 # conncet gui signals after the controller initialisation
122 self.__gui.connect_gui_signals()131 self.__gui.connect_gui_signals()
123 132 # Connect GUI with app
133 self.__gui.connect_to_app_ctrl(self)
134
135 # TODO : load timeline
136 #TMLN.Timeline()
137
124 def __connect_signals(self):138 def __connect_signals(self):
125 """ connect controller signals """139 """ connect controller signals """
126 self.ctrl_signals = gobject.GObject()140 self.ctrl_signals = gobject.GObject()
@@ -152,42 +166,42 @@
152 def on_change_framerate(self, *args) :166 def on_change_framerate(self, *args) :
153 """ Request of framerate(fpi) change """167 """ Request of framerate(fpi) change """
154 _args = self.__extract_signal_args(args) 168 _args = self.__extract_signal_args(args)
155 self.__project.change_framerate(_args['framerate']) 169 self._project.change_framerate(_args['framerate'])
156 170
157 def on_change_project(self, *args) :171 def on_change_project(self, *args) :
158 """ Request for prohect change """172 """ Request for prohect change """
159 _args = self.__extract_signal_args(args)173 _args = self.__extract_signal_args(args)
160 for _key, _value in _args.iteritems() :174 for _key, _value in _args.iteritems() :
161 self.__project.change_project(_key, _value)175 self._project.change_project(_key, _value)
162 176
163 def on_close_project(self, *args ) :177 def on_close_project(self, *args ) :
164 """ Request for project close """178 """ Request for project close """
165 _args = self.__extract_signal_args(args) 179 _args = self.__extract_signal_args(args)
166 # test if project exists180 # test if project exists
167 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :181 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
168 # check if project is modified 182 # check if project is modified
169 if self.__project.data['is_modified'] :183 if self._project.data['is_modified'] :
170 # ask for save it before close184 # ask for save it before close
171 _res = self.__gui.windows.\185 _res = self.__gui.windows.\
172 question_message(_('Save Project before closing'))186 question_message(_('Save Project before closing'))
173 if _res : 187 if _res :
174 self.__project.save()188 self._project.save()
175 189
176 # set viewer in default mode 190 # set viewer in default mode
177 self._viewer.mode = self._viewer.DEFAULT191 self._viewer.mode = self._viewer.DEFAULT
178 192
179 # clear project controller 193 # clear project controller
180 self.__project.mode = CTRL_CONST.NO_PROJECT194 self._project.mode = CTRL_CONST.NO_PROJECT
181 195
182 # display close message 196 # display close message
183 _msg = _('Project %s is closed' % self.__project.data['project_name'])197 _msg = _('Project %s is closed' % self._project.data['project_name'])
184 self.__gui.status_bar.display_message(_msg)198 self.__gui.status_bar.display_message(_msg)
185199
186200
187 def on_create_project(self, *args) :201 def on_create_project(self, *args) :
188 """ Request new project """202 """ Request new project """
189 _args = self.__extract_signal_args(args) 203 _args = self.__extract_signal_args(args)
190 self.__project.new()204 self._project.new()
191205
192206
193 def on_delete_capture(self, *args) :207 def on_delete_capture(self, *args) :
@@ -248,7 +262,7 @@
248 """ Request display project properties """262 """ Request display project properties """
249 _args = self.__extract_signal_args(args) 263 _args = self.__extract_signal_args(args)
250 # test if project exists264 # test if project exists
251 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :265 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
252 # go in default mode before dispalying window266 # go in default mode before dispalying window
253 # due to webcam detection and avoid confilcts with acquisition267 # due to webcam detection and avoid confilcts with acquisition
254 self._viewer.mode = self._viewer.DEFAULT268 self._viewer.mode = self._viewer.DEFAULT
@@ -262,30 +276,30 @@
262 # launch project poperties window276 # launch project poperties window
263 _cbs = \277 _cbs = \
264 {'change-project-properties' : self.on_change_project_properties} 278 {'change-project-properties' : self.on_change_project_properties}
265 self.__gui.windows.project_properties(self.__project.data, _cbs) 279 self.__gui.windows.project_properties(self._project.data, _cbs)
266280
267281
268 def on_export_to_tool(self, *args) :282 def on_export_to_tool(self, *args) :
269 """ Request export to tool """283 """ Request export to tool """
270 _args = self.__extract_signal_args(args) 284 _args = self.__extract_signal_args(args)
271 # test if project exists285 # test if project exists
272 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :286 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
273 self.__gui.windows.export_tool(self.__project.data)287 self.__gui.windows.export_tool(self._project.data)
274288
275289
276 def on_export_to_video(self, *args) :290 def on_export_to_video(self, *args) :
277 """ Request export to video """291 """ Request export to video """
278 _args = self.__extract_signal_args(args) 292 _args = self.__extract_signal_args(args)
279 # test if project exists293 # test if project exists
280 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :294 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
281 self.__gui.windows.export_video(self.__project.data)295 self.__gui.windows.export_video(self._project.data)
282296
283297
284 def on_import_image(self, *args) :298 def on_import_image(self, *args) :
285 """ Request image import """299 """ Request image import """
286 _args = self.__extract_signal_args(args) 300 _args = self.__extract_signal_args(args)
287 # check if project exists301 # check if project exists
288 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :302 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
289 # open filename chooser dialog 303 # open filename chooser dialog
290 _filenames = self.__gui.windows.import_dialog()304 _filenames = self.__gui.windows.import_dialog()
291 305
@@ -293,7 +307,7 @@
293 _cbs = { 'done-import' : self.on_done_import}307 _cbs = { 'done-import' : self.on_done_import}
294 # start import controller308 # start import controller
295 CTRL_IMPORT.ImportController(_filenames, 309 CTRL_IMPORT.ImportController(_filenames,
296 self.__project.data, 310 self._project.data,
297 self.__rusher,311 self.__rusher,
298 self.__gui.status_bar,312 self.__gui.status_bar,
299 _cbs)313 _cbs)
@@ -307,7 +321,7 @@
307 def on_move_down_chrono(self, *args) :321 def on_move_down_chrono(self, *args) :
308 """ request to move down an image """322 """ request to move down an image """
309 _args = self.__extract_signal_args(args) 323 _args = self.__extract_signal_args(args)
310 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :324 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
311 self.__gui.treeviews[LCONST.CHRONO].move_down()325 self.__gui.treeviews[LCONST.CHRONO].move_down()
312326
313327
@@ -326,40 +340,35 @@
326 def on_move_up_chrono(self, *args) :340 def on_move_up_chrono(self, *args) :
327 """ request to move up an image """341 """ request to move up an image """
328 _args = self.__extract_signal_args(args) 342 _args = self.__extract_signal_args(args)
329 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :343 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
330 self.__gui.treeviews[LCONST.CHRONO].move_up()344 self.__gui.treeviews[LCONST.CHRONO].move_up()
331345
332346
333 def on_open_project(self, *args ) :347
334 """ Request open project """348
349
350 def on_play_query_position(self, *args) :
351 """ Query for player position """
335 _args = self.__extract_signal_args(args) 352 _args = self.__extract_signal_args(args)
336 if 'path' in _args :353 (_position, _duration) = self._viewer.play_query_position()
337 if _args['path'] :354 if _args['cb'] :
338 # path is givan as param355 # callback call to gui
339 _path = _args['path']356 _args['cb'](_position, _duration)
340 else :
341 # launch open project window
342 _path = self.__gui.windows.open_project()
343 try :
344 self.__project.open(_path)
345 except LEXCEP.LucioException, _err_msg :
346 _msg = _("Project load impossible\n%s" % _err_msg)
347 self.__gui.windows.error_message( _msg)
348357
349358
350 def on_play_video(self, *args) :359 def on_play_video(self, *args) :
351 """ Play video request """360 """ Play video request """
352 _args = self.__extract_signal_args(args) 361 _args = self.__extract_signal_args(args)
353 # test if project exists362 # test if project exists
354 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :363 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
355 try :364 try :
356 self._viewer.init_video_player(self.__project.data)365 self._viewer.init_video_player(self._project.data)
357 except LEXCEP.LucioException, _err_msg :366 except LEXCEP.LucioException, _err_msg :
358 raise LEXCEP.LucioException, _err_msg367 raise LEXCEP.LucioException, _err_msg
359 else :368 else :
360 self._viewer.mode = self._viewer.PLAYER369 self._viewer.mode = self._viewer.PLAYER
361 try :370 try :
362 self._viewer.play_video(self.__project.data)371 self._viewer.play_video(self._project.data)
363 except LEXCEP.LucioException, _err_msg :372 except LEXCEP.LucioException, _err_msg :
364 raise LEXCEP.LucioException, _err_msg373 raise LEXCEP.LucioException, _err_msg
365 else :374 else :
@@ -371,15 +380,15 @@
371 def on_quit_app(self, *args) :380 def on_quit_app(self, *args) :
372 """ Request quit application """381 """ Request quit application """
373 _args = self.__extract_signal_args(args) 382 _args = self.__extract_signal_args(args)
374 if self.__project.mode == CTRL_CONST.PROJECT_LOADED and \383 if self._project.mode == CTRL_CONST.PROJECT_LOADED and \
375 self.__project.data['is_modified'] :384 self._project.data['is_modified'] :
376 _msg = _('Project modified. Save project before exit ?')385 _msg = _('Project modified. Save project before exit ?')
377 _res = self.__gui.windows.question_message(_msg)386 _res = self.__gui.windows.question_message(_msg)
378 # if response is not a bool : cancel clicked387 # if response is not a bool : cancel clicked
379 if isinstance(_res, bool) :388 if isinstance(_res, bool) :
380 if _res :389 if _res :
381 # if True save project before exit390 # if True save project before exit
382 self.__project.save()391 self._project.save()
383 GUI_CTRL.GuiController.quit()392 GUI_CTRL.GuiController.quit()
384 else :393 else :
385 GUI_CTRL.GuiController.quit()394 GUI_CTRL.GuiController.quit()
@@ -387,45 +396,45 @@
387 def on_save_as_project(self, *args) :396 def on_save_as_project(self, *args) :
388 """ save as project request """397 """ save as project request """
389 _args = self.__extract_signal_args(args) 398 _args = self.__extract_signal_args(args)
390 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :399 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
391 _dir = self.__gui.windows.dir_chooser()400 _dir = self.__gui.windows.dir_chooser()
392 if _dir != None :401 if _dir != None :
393 # set viewer in default mode due to project reload402 # set viewer in default mode due to project reload
394 self._viewer.mode = self._viewer.DEFAULT403 self._viewer.mode = self._viewer.DEFAULT
395 try : 404 try :
396 self.__project.save_as(_dir) 405 self._project.save_as(_dir)
397 except LEXCEP.LucioException, _err_msg :406 except LEXCEP.LucioException, _err_msg :
398 _msg = _('Impossible to save as project : %s' % _err_msg)407 _msg = _('Impossible to save as project : %s' % _err_msg)
399 self.__gui.windows.error_message(_msg)408 self.__gui.windows.error_message(_msg)
400 else :409 else :
401 _msg = _('Project saved as %s' % \410 _msg = _('Project saved as %s' % \
402 self.__project.data['project_name'])411 self._project.data['project_name'])
403 self.__gui.status_bar.display_message(_msg)412 self.__gui.status_bar.display_message(_msg)
404413
405414
406 def on_save_project(self, *args) :415 def on_save_project(self, *args) :
407 """ save project request """416 """ save project request """
408 _args = self.__extract_signal_args(args) 417 _args = self.__extract_signal_args(args)
409 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :418 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
410 try : 419 try :
411 self.__project.save() 420 self._project.save()
412 except LEXCEP.LucioException, _err_msg :421 except LEXCEP.LucioException, _err_msg :
413 _msg = _('Impossible to save project : %s' % _err_msg)422 _msg = _('Impossible to save project : %s' % _err_msg)
414 self.__gui.windows.error_message(_msg)423 self.__gui.windows.error_message(_msg)
415 else :424 else :
416 _msg = _('Project %s saved' % \425 _msg = _('Project %s saved' % \
417 self.__project.data['project_name'])426 self._project.data['project_name'])
418 self.__gui.status_bar.display_message(_msg)427 self.__gui.status_bar.display_message(_msg)
419 # update project name on Main bar428 # update project name on Main bar
420 self.__gui.set_program_bar(self.__project.data['project_name'],429 self.__gui.set_program_bar(self._project.data['project_name'],
421 self.__project.data['is_modified'])430 self._project.data['is_modified'])
422431
423432
424 def on_start_acquisition(self, *args ) :433 def on_start_acquisition(self, *args ) :
425 """Request for starting acqusisition """434 """Request for starting acqusisition """
426 _args = self.__extract_signal_args(args) 435 _args = self.__extract_signal_args(args)
427 # check if a project is loaded436 # check if a project is loaded
428 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :437 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
429 self._viewer.mode = self._viewer.ACQUIRER438 self._viewer.mode = self._viewer.ACQUIRER
430 439
431 # change control widget in acquisition mode440 # change control widget in acquisition mode
@@ -441,7 +450,7 @@
441 """ Request mixer start """450 """ Request mixer start """
442 _args = self.__extract_signal_args(args) 451 _args = self.__extract_signal_args(args)
443 # check if a project is loaded452 # check if a project is loaded
444 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :453 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
445 # switch to viewer with acqusisition and mixer454 # switch to viewer with acqusisition and mixer
446 self._viewer.mode = self._viewer.ACQUIRER_WITH_MIXER455 self._viewer.mode = self._viewer.ACQUIRER_WITH_MIXER
447 # change control widget in no acquisition mode456 # change control widget in no acquisition mode
@@ -462,7 +471,7 @@
462 """ Request to stop acquistion """471 """ Request to stop acquistion """
463 _args = self.__extract_signal_args(args) 472 _args = self.__extract_signal_args(args)
464 # check if a project is loaded473 # check if a project is loaded
465 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :474 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
466 # switch to viewer default475 # switch to viewer default
467 self._viewer.mode = self._viewer.DEFAULT 476 self._viewer.mode = self._viewer.DEFAULT
468 # change control widget in no acquisition mode477 # change control widget in no acquisition mode
@@ -478,7 +487,7 @@
478 """ Request mixer stop """487 """ Request mixer stop """
479 _args = self.__extract_signal_args(args) 488 _args = self.__extract_signal_args(args)
480 # check if a project is loaded489 # check if a project is loaded
481 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :490 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
482 # switch to viewer with acqusisition and mixer491 # switch to viewer with acqusisition and mixer
483 self._viewer.mode = self._viewer.ACQUIRER492 self._viewer.mode = self._viewer.ACQUIRER
484 # change control widget in no acquisition mode493 # change control widget in no acquisition mode
@@ -507,7 +516,7 @@
507 def on_take_snapshot(self, *args) :516 def on_take_snapshot(self, *args) :
508 """ take image snapshot request """517 """ take image snapshot request """
509 _args = self.__extract_signal_args(args) 518 _args = self.__extract_signal_args(args)
510 if self.__project.mode == CTRL_CONST.PROJECT_LOADED :519 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
511 self._viewer.take_snapshot()520 self._viewer.take_snapshot()
512 else :521 else :
513 # robustness522 # robustness
@@ -558,10 +567,8 @@
558 if self.__options.filename \567 if self.__options.filename \
559 and os.path.exists(self.__options.filename):568 and os.path.exists(self.__options.filename):
560 # emit open project signal569 # emit open project signal
561 self.ctrl_signals.emit(570 self.__gui.emit(
562 "open-project",571 "open-project", self.__options.filename)
563 {'path' : self.__options.filename}
564 )
565572
566 GUI_CTRL.GuiController.main_loop()573 GUI_CTRL.GuiController.main_loop()
567 GUI_CTRL.GuiController.threads_leave()574 GUI_CTRL.GuiController.threads_leave()
@@ -572,32 +579,32 @@
572 def on_done_rush(self, rusher) :579 def on_done_rush(self, rusher) :
573 """ callback : when tush images are loaded """580 """ callback : when tush images are loaded """
574 if rusher != None \581 if rusher != None \
575 and self.__project.mode == CTRL_CONST.PROJECT_LOADED :582 and self._project.mode == CTRL_CONST.PROJECT_LOADED :
576 # load treeviews583 # load treeviews
577 # prepare data584 # prepare data
578 _app_data = {}585 _app_data = {}
579 _app_data['capture_images'] = self.__project.data['capture_images']586 _app_data['capture_images'] = self._project.data['capture_images']
580 _app_data['chrono_images'] = self.__project.data['chrono_images']587 _app_data['chrono_images'] = self._project.data['chrono_images']
581 _app_data['rush'] = rusher588 _app_data['rush'] = rusher
582589
583 self.__gui.load_treeviews(_app_data) 590 self.__gui.load_treeviews(_app_data)
584 591
585 # init acquisition 592 # init acquisition
586 self._viewer.init_acquisition(self.__project.data) 593 self._viewer.init_acquisition(self._project.data)
587 594
588 595
589 # update fpi on gui and show it596 # update fpi on gui and show it
590 self.__gui.update_framerate(int(self.__project.data['fpi']))597 self.__gui.update_framerate(int(self._project.data['fpi']))
591 598
592 # show project acquisition widgets599 # show project acquisition widgets
593 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE 600 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
594601
595 # update project name on Main bar602 # update project name on Main bar
596 self.__gui.set_program_bar(self.__project.data['project_name'],603 self.__gui.set_program_bar(self._project.data['project_name'],
597 self.__project.data['is_modified'])604 self._project.data['is_modified'])
598 605
599 # finish project load (project aspect)606 # finish project load (project aspect)
600 self.__project.finish_project_load()607 self._project.finish_project_load()
601608
602 # store rusher609 # store rusher
603 self.__rusher = rusher610 self.__rusher = rusher
@@ -609,7 +616,7 @@
609 callback : image snapshot is done.616 callback : image snapshot is done.
610 Now image can be processed into project617 Now image can be processed into project
611 """618 """
612 self.__project.append_snapshot(self.__rusher, self._viewer.acquirer)619 self._project.append_snapshot(self.__rusher, self._viewer.acquirer)
613 620
614 def on_done_preferences(self , modified_options) :621 def on_done_preferences(self , modified_options) :
615 """ callback : preferences modified fome diaog """622 """ callback : preferences modified fome diaog """
@@ -632,17 +639,17 @@
632 # Test if webcam data change key639 # Test if webcam data change key
633 if key == 'webcam_data':640 if key == 'webcam_data':
634 # make a local copy of webcam_data641 # make a local copy of webcam_data
635 webcam_dict = self.__project.data['webcam_data']642 webcam_dict = self._project.data['webcam_data']
636 #test if webcam dict key exists643 #test if webcam dict key exists
637 if webcam_dict.has_key(key_webcam) :644 if webcam_dict.has_key(key_webcam) :
638 # update webcam key and call project change645 # update webcam key and call project change
639 webcam_dict[key_webcam] = data646 webcam_dict[key_webcam] = data
640 self.__project.change_project('webcam_data', webcam_dict)647 self._project.change_project('webcam_data', webcam_dict)
641648
642 def on_done_import(self, image_objs = [] ) :649 def on_done_import(self, image_objs = [] ) :
643 """ callback : image import (rush generation) is done """650 """ callback : image import (rush generation) is done """
644 if image_objs != [] :651 if image_objs != [] :
645 self.__project.change_project( 'rush_images',652 self._project.change_project( 'rush_images',
646 self.__rusher.dump_image_name())653 self.__rusher.dump_image_name())
647 # Not to be done in thread : interacts with gui654 # Not to be done in thread : interacts with gui
648 for _image_obj in image_objs :655 for _image_obj in image_objs :
649656
=== added file 'luciole/ctrl/ctrl_acq.py'
--- luciole/ctrl/ctrl_acq.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/ctrl_acq.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,634 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# -*- Mode: Python -*-
4# vim:si:ai:et:sw=4:sts=4:ts=4
5#
6#
7# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
8#
9# This file is part of Luciole.
10#
11# Luciole is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# Luciole is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
23#
24#
25"""
26ctrl_acq.py :
27 manages acquisition
28"""
29# standard library imports
30import os.path
31
32# related third party imports
33import gst
34from pitivi.signalinterface import Signallable
35from pitivi.log.loggable import Loggable
36from pitivi.signalgroup import SignalGroup
37from pitivi.action import ViewAction
38from pitivi.pipeline import Pipeline
39
40# local application/library specific imports
41from luciole.media.lgst.webcam_factory import WebcamSourceFactory
42import luciole.constants as LCONST
43import luciole.base.exceptions as LEXCEP
44import luciole.base.tools as LT
45import luciole.media.image as LI
46
47from luciole.ctrl.base import ProjectMode
48
49class ProjectModeAcquisition(ProjectMode) :
50 MODES = ( "INVALID",
51 "NO_ACQUISITION",
52 "ACQUISITION",
53 "ACQUISITION_WITH_MIXER",
54 )
55
56 __signals__ = {
57 'acquirer-ready' : [],
58 'acquirer-error' : ['msg'],
59 'acquisition-started' : ['with_mixer'],
60 'acquisition-stopped' : [],
61 'snapshot-on-progress' : [],
62 'snapshot-taken' : ['rush_image'],
63 }
64
65 def __init__(self, ctrl_project) :
66 self._mode ="INVALID"
67 ProjectMode.__init__(self, ctrl_project, "ACQUISITION")
68 self.gui_signals = SignalGroup()
69
70 def prepare(self, project) :
71 """ prepare acquistion """
72 ProjectMode.prepare(self, project)
73 # prepare acqusisition
74 _project = project.props
75 if _project != None :
76 if _project['hardtype'] == LCONST.WEBCAM :
77 # create webcam source
78 _capture_path = os.path.join(
79 _project['project_dir'],
80 LCONST.ACQUIRED_IMAGE_NAME)
81 self.debug ("Image capture path : %s", _capture_path)
82 self._source_factory = \
83 WebcamSourceFactory(capture_path = _capture_path,
84 with_mixer = False)
85
86 self.debug(' webcam filter : %s',self.project.props['webcam_data'])
87 self._set_webcam_params(
88 self.project.props['webcam_data'],
89 self._source_factory)
90
91 # set video resize/scale mode (or not)
92 if _project['resize'] == 'yes' :
93 self._source_factory.with_scale = True
94 else :
95 self._source_factory.with_scale = False
96
97 self._connect_to_factory(self._source_factory, _capture_path )
98
99 # create pipeline and resource
100 self._pipeline = Pipeline()
101 self._view_action = ViewAction()
102 self._view_action.addProducers(self._source_factory)
103
104 self._mode = "NO_ACQUISITION"
105 self.debug('emit acquirer-ready, Webcam factory : %s, pipeline : %s',
106 self._source_factory, self._pipeline )
107 self.emit('acquirer-ready')
108
109
110 def release(self) :
111 ProjectMode.release(self)
112 self.debug('releasing acquisition')
113 self._disconnect_from_factory(self._source_factory)
114 self._mode = "INVALID"
115 self._pipeline.release()
116 self._pipeline = None
117 self._source_factory = None
118 self._view_action = None
119
120 def active_viewer(self) :
121 if self._viewer :
122 self._viewer.setPipeline(None)
123 self._viewer.hideSlider()
124 self.debug('active viewer - Pipeline : %s, action %s',
125 self._pipeline,
126 self._view_action)
127 self._viewer.setAction(self._view_action)
128 self._viewer.setPipeline(self._pipeline, "ACQUISITION")
129 else :
130 self.error("No viewer set")
131
132
133 def start(self, with_mixer = False) :
134 """ start acquisition """
135 # check if image view mode
136 # in that case restart acquisition mode
137 if self.ctrl_project.mode == 'IMAGE_VIEW' :
138 self.ctrl_project.acquistion_mode()
139 return
140
141 if self._mode == "NO_ACQUISITION" :
142 if with_mixer == True :
143 self._source_factory.active_mixer(True)
144 self._mode = "ACQUISITION_WITH_MIXER"
145 else :
146 #self._source_factory.active_mixer(False)
147 self._mode = "ACQUISITION"
148
149 self._pipeline.play()
150 self.debug('send acquisition-started - mode : %s',self._mode )
151 self.emit('acquisition-started', with_mixer)
152
153
154 else :
155 self.error("Cannot start acquisition. Controller in incorrect mode (%s)",
156 self._mode)
157
158 def stop(self) :
159 """ stop acquisition """
160 self.debug('stop acquisition - mode %s', self._mode)
161 if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") :
162 if self._mode == "ACQUISITION_WITH_MIXER" :
163 pass
164 #self.acquirer.deactive_onion_skin()
165 self._pipeline.stop()
166 self._mode = "NO_ACQUISITION"
167 self.debug('emit acquisition-stopped')
168 self.emit('acquisition-stopped')
169 else :
170 self.error(
171 "Cannot stop acquistion. Controller in incorrect mode (%s)",
172 self._mode)
173
174
175 def do_snapshot(self) :
176 if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") :
177 self._source_factory.capture()
178
179 else :
180 self.error(
181 "Cannot take snapshot. Controller in incorrect mode (%s)",
182 self._mode)
183
184 def active_mixer(self, is_active) :
185 # check with current mixer status
186 self.debug('active_mixer : %s', is_active)
187 if self.project.props['is-mixer-active'] == is_active :
188 self.info('No mixer status to change')
189 return
190
191
192
193 # stop pipeline, deactivate producer and remove old webcam factory
194 self._pipeline.stop()
195 self._view_action.deactivate()
196 self._view_action.removeProducers(self._source_factory)
197
198 # create new webcam factory
199 _capture_path = os.path.join(
200 self.project.props['project_dir'],
201 LCONST.ACQUIRED_IMAGE_NAME)
202 self.debug ("Image capture path : %s", _capture_path)
203
204 self._source_factory = WebcamSourceFactory(
205 capture_path = _capture_path,
206 with_mixer = is_active,
207 image2mix = self.project.props['image2mix'] )
208 # set video resize/scale mode (or not)
209 if self.project.props['resize'] == 'yes' :
210 self._source_factory.with_scale = True
211 else :
212 self._source_factory.with_scale = False
213
214
215 # add producer
216 self._view_action.addProducers(self._source_factory)
217 # activate it --> regeneration of pipeline bins
218 self._view_action.activate()
219 # set image alpha if webcam active
220 if is_active == True :
221 self._source_factory.alpha_value = self.project.props['alpha']
222
223 self.debug(' image to mix is %s with alpha = %s',
224 self._source_factory.image2mix,
225 self._source_factory.alpha_value)
226
227 # start acquisition
228 self._pipeline.play()
229
230 self.project.props['is-mixer-active'] = is_active
231
232 def _connect_to_factory(self, factory, _capture_path) :
233 factory.connect("image-capture-done", self._image_capture_done_cb, _capture_path )
234
235 def _disconnect_from_factory(self, factory) :
236 factory.disconnect_by_function(self._image_capture_done_cb)
237
238 def _image_capture_done_cb( self, factory, _capture_path) :
239 self.debug('RX image-capture-done')
240 _rush_image = self._process_snapshot(_capture_path)
241 self.debug('emit snapshot-taken : %s', _rush_image.name)
242 self.emit('snapshot-taken', _rush_image)
243
244 def _process_snapshot(self, capture_path) :
245 # get acquired image name
246 _acq_image = capture_path
247
248 # build temp impage path
249 _temp_dir = os.path.join(self.project.props['project_dir'], 'tmp')
250 # copy name
251 _ac_image_temp = os.path.join(_temp_dir, _acq_image)
252 # resized copy name
253 _ac_image_temp_rz = \
254 os.path.join(_temp_dir, LCONST.ACQUIRED_IMAGE_NAME_RZ)
255
256 # build rush image name
257 _basename = LCONST.RUSH_FILENAME_TPL % self.project.rush.rush_index
258 _rush_image = \
259 os.path.join(self.project.props['project_dir'],
260 self.project.props['rush_dir'],
261 _basename)
262
263 try :
264 # 1. move image acquired image to tmp dir
265 LT.movef(_acq_image, _ac_image_temp)
266 # 2. resize image result is in _ac_image_temp_rz
267 if self.project.props['resize'] == 'yes' :
268 _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz )
269 _rz_obj.convert()
270 else :
271 _ac_image_temp_rz = _ac_image_temp
272
273 # 3. move resized image to rush dire
274 LT.movef(_ac_image_temp_rz, _rush_image)
275
276 except LEXCEP.LucioException, _err_msg :
277 self.error(_err_msg)
278 else :
279 self.debug("create rush image : %s", _basename)
280 # 4. append image to rush list
281 _rush_image = self.project.rush.append(_basename)
282
283 # always update the image 2 mix, even if mixer is not active
284 # used to memorize the last capture
285 #self.acquirer.Image2Mix = _rush_image.path
286
287 # project modified
288 self.project.props['rush_images'].append(_basename)
289 self.project.props['capture_images'].append(_basename)
290
291 self.ctrl_project.is_modified = True
292
293 self.debug("capture post-treatemnt done ")
294 return _rush_image
295
296 def _set_webcam_params(self, webcam_props, factory) :
297 """
298 set webcam parameters according project propperties
299 """
300 #retrieve params value
301 _wcam_params = {
302 'wcam_driver': {
303 'element': webcam_props['v4ldriver'] ,
304 'properties' : {
305 'device':webcam_props['device-file'],
306 },
307 },
308
309 'wcam_filter' : {
310 'media_type' :gst.Caps(webcam_props['caps']),
311 },
312 }
313
314 # set params on factory
315 factory.webcam_params = _wcam_params
316 self.debug(" webcam params set : %s", factory.webcam_params)
317
318
319
320
321
322
323# TODO : suppress CtrlAcq
324
325class CtrlAcq(Signallable, Loggable) :
326 MODES = ( "INVALID",
327 "NO_ACQUISITION",
328 "ACQUISITION",
329 "ACQUISITION_WITH_MIXER",
330 )
331
332
333 __signals__ = {
334 'acquirer-ready' : ['acquirer'],
335 'acquirer-error' : ['msg'],
336 'acquisition-started' : ['with_mixer'],
337 'acquisition-stopped' : [],
338 'snapshot-on-progress' : [],
339 'snapshot-taken' : ['rush_image'],
340 }
341
342
343 def __init__(self, ctrl_project) :
344 """ initialize the acquirer """
345 Signallable.__init__(self)
346 Loggable.__init__(self)
347 self.ctrl_project = ctrl_project
348
349 self.acquirer = None
350 self.project = None
351 self._mode = "INVALID"
352
353 # signal connections
354 self._connect_to_project(self.ctrl_project)
355
356 self.gui_signals = SignalGroup()
357
358 self.debug('CtrlAcq intialized : %s %s',self.ctrl_project, ctrl_project )
359
360
361 def init_acquirer(self, project) :
362 self.debug("Init acquirer")
363 _project = project.props
364 if _project != None :
365 # Create acquisition object
366 _acq_obj = None
367 # remark; acquisition object for webcam and dv cam
368 if _project['hardtype'] == LCONST.WEBCAM :
369 # init webcam factory
370 self.factory = WebcamSourceFactory()
371 self.debug('Webcam factory : %s', self.factory )
372 self.pipeline = PTV_PPLN.Pipeline()
373 self.view_action = ViewAction()
374 self.view_action.addProducers(self.factory)
375
376 self.viewer = self.ctrl_project.gui._get_drawarea()
377 self.viewer.hideSlider()
378 self.viewer.setPipeline(None)
379 self.viewer.setAction(self.view_action)
380 self.viewer.setPipeline(self.pipeline, "ACQUISITION")
381 #self.viewer.play()
382 # just for debug
383 _acq_obj =self.pipeline
384
385
386
387 elif _project['hardtype'] == LCONST.DVCAM :
388 # default acquisition load i.e. DVCAM
389 _acq_obj = LACQ.Acquisition(
390 self._get_drawarea(self.ctrl_project.gui),
391 False,
392 _project['hardtype'],
393 project_dir = _project['project_dir'],
394 cb_error = self._on_error_acqusition,
395 cb_capture_done = self._on_done_snaphot)
396 else :
397 # Nothing to do
398 self.emit('acquirer-error', 'No valid acquirer found' )
399 if _acq_obj != None :
400 self._mode = "NO_ACQUISITION"
401 # for mixer initialisation set image to mix with the last image
402 # of the capture view. only if capture image is not empty
403 if len(_project['capture_images'] ) > 0 :
404 _last_image_path = \
405 os.path.join( _project['project_dir'],
406 _project['rush_dir'],
407 _project['capture_images'][-1])
408 if os.path.exists(_last_image_path) :
409 _acq_obj.Image2Mix = _last_image_path
410
411 self.emit('acquirer-ready', _acq_obj )
412 self.acquirer = _acq_obj
413
414
415 def start_acquisition(self) :
416 if self._mode == "NO_ACQUISITION" :
417 self.pipeline.play()
418 self._mode = "ACQUISITION"
419 self.emit('acquisition-started', False)
420 else :
421 self.error(
422 "Cannot start acquisition. Controller in incorrect mode (%s)",
423 self._mode)
424
425 def stop_acquisition(self) :
426 if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") :
427 if self._mode == "ACQUISITION_WITH_MIXER" :
428 pass
429 #self.acquirer.deactive_onion_skin()
430 self.pipeline.stop()
431
432 self._mode = "NO_ACQUISITION"
433 self.debug('emit acquisition-stopped')
434 self.emit('acquisition-stopped')
435 else :
436 self.error(
437 "Cannot stop acquistion. Controller in incorrect mode (%s)",
438 self._mode)
439
440 def active_mixer(self) :
441 if self._mode == "ACQUISITION" :
442 # the active onion skin stop the acquistion
443 self.acquirer.active_onion_skin()
444 # so start acqusition
445 self.acquirer.start_acqusition()
446
447 self._mode = "ACQUISITION_WITH_MIXER"
448 self.emit('acquisition-started', True)
449 else :
450 self.error(
451 "Cannot active mixer. Controller in incorrect mode (%s)",
452 self._mode)
453
454
455 def deactive_mixer(self) :
456 if self._mode == "ACQUISITION_WITH_MIXER" :
457 # deactivate onion skin stop the acquistion
458 self.acquirer.deactive_onion_skin()
459 # so start acqusition
460 self.acquirer.start_acqusition()
461
462 self._mode = "ACQUISITION"
463 self.emit('acquisition-started', False)
464 else :
465 self.error(
466 "Cannot deactive mixer. Controller in incorrect mode (%s)",
467 self._mode)
468
469 def take_snapshot(self) :
470 if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") :
471 if self.acquirer.is_streaming_active == True :
472
473 self.debug('emit snapshot-on-progress')
474 self.emit('snapshot-on-progress')
475 self.acquirer.capture_image()
476
477
478
479 def release(self) :
480 # ensure acquisition stop
481 self.debug('Enterin release in mode=%s', self._mode)
482 if self._mode in ("ACQUISITION","ACQUISITION_WITH_MIXER") :
483 self.debug('Force acquistion stop : %s', self._mode)
484 self.stop_acquisition()
485 else :
486 self.debug('Bad state : %s', self._mode)
487 self.acquirer.release()
488
489 #self._disconnect_from_project(self.ctrl_project)
490 #self._disconnect_from_gui(self.ctrl_project.gui)
491 #self.gui_signals.disconnectAll()
492
493 def _connect_to_project(self, ctrl_project) :
494 ctrl_project.connect('project-loaded', self._project_loaded_cb)
495 ctrl_project.connect('project-closed', self._project_closed_cb)
496 """
497 def _disconnect_from_project(self, ctrl_project) :
498 ctrl_project.disconnect_by_function(self._project_loaded_cb)
499 ctrl_project.disconnect_by_function(self._project_closed_cb)
500 """
501 def _project_closed_cb(self, project_ctrl) :
502 self.debug('RX project-closed')
503 self.release()
504
505
506 def _project_loaded_cb(self, project_ctrl, project) :
507 self.debug('RX project-loaded')
508 self.init_acquirer(project)
509 self._connect_to_gui(self.ctrl_project.gui)
510 self.pipeline.play()
511 self.project = project
512
513 """"
514 def _connect_to_gui(self, gui) :
515 # connection GUI to ctrl_acq
516 gui.connect('start-acqusition', self._start_acq_cb)
517 gui.connect('stop-acqusition', self._stop_acq_cb)
518 gui.connect('take-snapshot', self._take_snapshot_cb)
519 gui.connect('active-mixer', self._active_mixer_cb)
520 gui.connect('deactive-mixer', self._deactive_mixer_cb)
521
522 # connection ctrl_acq to gui
523 gui._project_mode.connect_to_ctrl('ACQUISITION', self)
524 """
525
526 def _connect_to_gui(self, gui) :
527 self.gui_signals.connect(
528 gui, "start-acqusition", None, self._start_acq_cb)
529 self.gui_signals.connect(
530 gui, "stop-acqusition", None, self._stop_acq_cb)
531 self.gui_signals.connect(
532 gui, "take-snapshot", None, self._take_snapshot_cb)
533 self.gui_signals.connect(
534 gui, "active-mixer", None, self._active_mixer_cb)
535 self.gui_signals.connect(
536 gui, "deactive-mixer", None, self._deactive_mixer_cb)
537
538 # connection ctrl_acq to gui
539 gui._project_mode.connect_to_ctrl('ACQUISITION', self)
540
541
542 def _disconnect_from_gui(self,gui) :
543 # dicconnection GUI to ctrl_acq
544 gui.disconnect_by_function(self._start_acq_cb)
545 gui.disconnect_by_function(self._stop_acq_cb)
546 gui.disconnect_by_function(self._take_snapshot_cb)
547 gui.disconnect_by_function(self._active_mixer_cb)
548 gui.disconnect_by_function(self._deactive_mixer_cb)
549
550
551 def _start_acq_cb(self, gui) :
552 if self.ctrl_project.mode == 'ACQUISITION' :
553 self.start_acquisition()
554
555 def _stop_acq_cb(self, gui) :
556 if self.ctrl_project.mode == 'ACQUISITION' :
557 self.stop_acquisition()
558
559 def _take_snapshot_cb(self, gui) :
560 if self.ctrl_project.mode == 'ACQUISITION' :
561 self.take_snapshot()
562
563 def _active_mixer_cb(self, gui) :
564 self.active_mixer()
565
566 def _deactive_mixer_cb(self, gui) :
567 self.deactive_mixer()
568
569 def _on_error_acqusition(self, message) :
570 """
571 callback error
572 propagate it to main controller.
573 """
574 print " acqusistion error\n ", message
575 self.emit('acquirer-error', message)
576
577 def _on_done_snaphot(self, *unsued) :
578 """
579 callback when snaphot done.
580 """
581 _rush_image = self._process_snapshot()
582 self.debug('emit snapshot-taken : %s', _rush_image.name)
583 self.emit('snapshot-taken', _rush_image)
584
585 def _process_snapshot(self) :
586 # get acquired image name
587 _acq_image = self.acquirer.image2save
588
589 # build temp impage path
590 _temp_dir = os.path.join(self.project.props['project_dir'], 'tmp')
591 # copy name
592 _ac_image_temp = os.path.join(_temp_dir, _acq_image)
593 # resized copy name
594 _ac_image_temp_rz = \
595 os.path.join(_temp_dir, LCONST.ACQUIRED_IMAGE_NAME_RZ)
596
597 # build rush image name
598 _basename = LCONST.RUSH_FILENAME_TPL % self.project.rush.rush_index
599 _rush_image = \
600 os.path.join(self.project.props['project_dir'],
601 self.project.props['rush_dir'],
602 _basename)
603
604 try :
605 # 1. move image acquired image to tmp dir
606 LT.movef(_acq_image, _ac_image_temp)
607
608 # 2. resize image result is in _ac_image_temp_rz
609 _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz )
610 _rz_obj.convert()
611
612 # 3. move resized image to rush dire
613 LT.movef(_ac_image_temp_rz, _rush_image)
614
615 except LEXCEP.LucioException, _err_msg :
616 self.error(_err_msg)
617 else :
618 # 4. append image to rush list
619 _rush_image = self.project.rush.append(_basename)
620
621 # always update the image 2 mix, even if mixer is not active
622 # used to memorize the last capture
623 self.acquirer.Image2Mix = _rush_image.path
624
625 # project modified
626 self.ctrl_project.is_modified = True
627
628 return _rush_image
629
630 def _get_drawarea(self, gui) :
631 _da = gui._get_drawarea()
632 self.debug(" Drawarea used : %s", _da)
633 return _da
634
0635
=== added file 'luciole/ctrl/ctrl_app.py'
--- luciole/ctrl/ctrl_app.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/ctrl_app.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,1050 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# -*- Mode: Python -*-
4# vim:si:ai:et:sw=4:sts=4:ts=4
5#
6#
7# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
8#
9# This file is part of Luciole.
10#
11# Luciole is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# Luciole is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
23#
24#
25"""
26ctrl.py :
27 Main application controller
28"""
29# standard library imports
30import sys
31import os
32import os.path
33import locale
34import gettext
35_ = gettext.gettext
36
37# related third party imports
38import gobject
39from pitivi.log import log
40
41from pitivi.signalinterface import Signallable
42from pitivi.log.loggable import Loggable
43from pitivi.log import log
44
45# local application/library specific imports
46import luciole.base.app_logging as LOGGING
47import luciole.base.exceptions as LEXCEP
48import luciole.base.options as OPTIONS
49import luciole.conf as LCONF
50import luciole.constants as LCONST
51import luciole.ctrl.constants as CTRL_CONST
52import luciole.ctrl.import_image as CTRL_IMPORT
53import luciole.ctrl.ctrl_project as CTRL_PROJECT
54import luciole.gui.constants as GUI_CONST
55import luciole.gui.main_window as GUI_MAIN
56import luciole.gui.gui_ctrl as GUI_CTRL
57import luciole.info as INFO
58import luciole.media.lgst.play_sound as LSOUND
59#####
60
61
62
63class CtrlAppGui(Signallable, Loggable) :
64 """ Interface between gui and App"""
65
66 def __init__(self, app) :
67 Signallable.__init__(self)
68 Loggable.__init__(self)
69 self.app = app
70 self.project_ctrl = self.app.project_ctrl
71 self.gui = self.app.gui
72 self._connect_gui_and_project()
73
74 def _connect_gui_and_project(self) :
75
76 # application high level actions
77 self.gui.gui_actions.connect('open-project', self._open_project_cb)
78 self.gui.gui_actions.connect('new-project', self._new_project_cb)
79 self.gui.gui_actions.connect('quit-app', self._quit_cb)
80
81 # Project level actions
82 self.gui.gui_actions.connect('save-project', self._save_project_cb)
83 self.gui.gui_actions.connect('save-as-project', self._save_as_project_cb)
84 self.gui.gui_actions.connect('close-project', self._close_project_cb)
85 self.gui.gui_actions.connect('acquisition-mode', self._acq_mode_cb)
86 self.gui.gui_actions.connect('timeline-mode', self._timeline_mode_cb)
87 self.gui.gui_actions.connect('import-sound', self._import_sound_cb)
88 self.gui.gui_actions.connect('image-view', self._image_view_cb)
89 self.gui.gui_actions.connect('import-images', self._import_images_cb)
90 self.gui.gui_actions.connect('show-project-properties', self._show_project_properties_cb)
91
92
93 # Acqusition actions
94 self.gui.gui_actions.connect('start-acquistion', self._start_acq_cb)
95 self.gui.gui_actions.connect('stop-acquistion', self._stop_acq_cb)
96 self.gui.gui_actions.connect('take-snapshot', self._take_snapshot_cb)
97 self.gui.gui_actions.connect('active-mixer', self._active_mixer_cb)
98
99 # Timeline actions
100 self.gui.gui_actions.connect('change-timeline-order', self._change_timeline_order_cb)
101 self.gui.gui_actions.connect('insert-image-on-timeline', self._insert_image_on_timeline_cb)
102 self.gui.gui_actions.connect('remove-image-on-timeline', self._remove_image_on_timeline_cb)
103 self.gui.gui_actions.connect('active-sound', self._active_sound_cb)
104 self.gui.gui_actions.connect('deactive-sound', self._deactive_sound_cb)
105 self.gui.gui_actions.connect('sound-stop-with-video', self._sound_stop_with_video_cb)
106 self.gui.gui_actions.connect('change-framerate', self._change_framerate_cb)
107
108
109
110
111 #
112 # application high level callbacks
113 #
114
115 def _open_project_cb(self, gui, path) :
116 self.debug('RX close-project signal')
117 self.project_ctrl.open(path)
118
119 def _new_project_cb(self, gui ) :
120 self.debug('RX new-project signal' )
121 self.project_ctrl.new_project()
122
123
124 def _quit_cb(self, gui):
125 self.app.quit()
126 return True
127 #
128 # Project level callbacks
129 #
130 def _save_project_cb(self, gui) :
131 self.debug('RX save-project signal')
132 self.project_ctrl.save()
133
134 def _save_as_project_cb(self, gui, path) :
135 self.debug('RX save-as-project signal')
136 self.project_ctrl.save(path)
137
138
139 def _close_project_cb(self, gui) :
140 self.debug('RX close-project signal')
141 self.project_ctrl.close()
142
143
144 def _acq_mode_cb(self, gui) :
145 self.debug('RX acquisition-mode signal')
146 self.project_ctrl.acquisition_mode()
147
148 def _timeline_mode_cb(self, gui) :
149 self.debug('RX timeline-mode signal')
150 self.project_ctrl.timleline_mode()
151
152 def _import_sound_cb(self, gui, path) :
153 self.debug('RX import-sound signal : %s', path)
154 self.project_ctrl.ctrl_tmln.add_sound_track(path)
155
156 def _image_view_cb(self, gui, origin, image_name, position = 0) :
157 self.debug('RX image-view signal : origin %s image %s', origin, image_name)
158 self.project_ctrl.img_vw_mode(origin, image_name, position)
159
160 def _import_images_cb(self, gui, paths) :
161 self.debug('RX import-images signal : %s',paths )
162 self.project_ctrl.import_images(paths)
163
164 def _show_project_properties_cb(self, gui) :
165 """ show project properties callback """
166 self.debug('RX show-project-properties' )
167 self.project_ctrl.show_project_properties()
168
169
170
171 #
172 # Acqusition callbacks
173 #
174
175 def _start_acq_cb(self, gui) :
176 self.debug(' RX start-acquistion signal')
177 self.project_ctrl.ctrl_acq.start()
178
179 def _stop_acq_cb(self, gui) :
180 self.debug(' RX stop-acquistion signal')
181 self.project_ctrl.ctrl_acq.stop()
182
183 def _take_snapshot_cb(self, gui) :
184 self.debug('RX take-snapshot signal')
185 self.project_ctrl.ctrl_acq.do_snapshot()
186
187 def _active_mixer_cb(self, gui, is_active) :
188 self.debug('RX active-mixer signal : %s', is_active)
189 self.project_ctrl.ctrl_acq.active_mixer(is_active)
190
191 #
192 # Timeline callbacks
193 #
194 def _change_timeline_order_cb(self, gui, objs, target):
195 self.debug('RX change-timeline-order signal - objs %s target %s', objs, target)
196 self.project_ctrl.ctrl_tmln.change_order(objs, target)
197
198 def _insert_image_on_timeline_cb(self, gui, objs, target):
199 self.debug('RX insert-image-on-timeline signal - objs %s target %s', objs, target)
200 self.project_ctrl.ctrl_tmln.insert(objs, target)
201
202 def _remove_image_on_timeline_cb(self, gui, objs):
203 self.debug('RX remove-image-on-timeline signal - objs %s', objs)
204 self.project_ctrl.ctrl_tmln.remove(objs)
205
206 def _active_sound_cb(self, gui) :
207 self.debug('RX active-sound signal')
208 self.project_ctrl.ctrl_tmln.add_sound_track_to_timeline()
209
210 def _deactive_sound_cb(self, gui) :
211 self.debug('RX deactive-sound signal')
212 self.project_ctrl.ctrl_tmln.remove_sound_track_from_timeline()
213
214 def _sound_stop_with_video_cb(self, gui, is_stop) :
215 self.debug('RX sound-stop-with-video, is_stop %s', is_stop)
216 self.project_ctrl.ctrl_tmln.stop_sound_with_video(is_stop)
217
218 def _change_framerate_cb(self, gui, fpi) :
219 self.debug('RXchange-framerate, fpi %s', fpi)
220 self.project_ctrl.set_framerate(fpi)
221
222class CtrlApp(Signallable, Loggable) :
223 # CONSTANTS
224 MODES = ("INIT", "RUN", "QUIT")
225
226 def __init__(self) :
227 """
228 Controller initialisation.
229 Is the main appication intialisation
230 """
231 # initialize threads
232 gobject.threads_init()
233
234 Loggable.__init__(self)
235
236 self.mode = "INIT"
237 # init logging as early as possible so we can log startup code
238 enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
239 log.init('PITIVI_DEBUG', enable_color)
240
241 self.mainloop = gobject.MainLoop()
242
243 # initializes i18n
244 self._init_i18n()
245
246 # load configuration : ie. saved user options
247 self.configuration = LCONF.LucioleConf()
248 # load GUI theme i.e parse rc file
249 self.configuration.load_theme()
250
251
252
253
254 def run(self, args) :
255
256 # TODO : seems no more needed : check
257 import gtk.gdk
258 gtk.gdk.threads_enter()
259
260
261 # intializes options :
262 (_options, _args) = OPTIONS.options_parser(args)
263
264 # initialize sound player i.e from snapshot sound
265 self.soundplayer = LSOUND.SoundPlayer(LCONST.SOUND_SNAPSHOT_PATH)
266
267 # start Project controller
268 self.project_ctrl = CTRL_PROJECT.CtrlProject()
269
270 # start GUI
271 self.gui = GUI_MAIN.LucioleMainWindow(self)
272
273
274 # connect project controller with GUI
275 self.project_ctrl.connect_to_gui(self.gui)
276
277 self.connect_to_project(self.project_ctrl)
278
279 self.gui_interface = CtrlAppGui(self)
280
281 # load a project ?
282 if _options.filename \
283 and os.path.exists(_options.filename):
284 self.project_ctrl.open(_options.filename)
285 else :
286 self.project_ctrl.no_project_mode()
287 self.mode = "RUN"
288
289
290
291 self.mainloop.run()
292
293 # TODO : seems no more needed : check
294 import gtk.gdk
295 gtk.gdk.threads_leave()
296
297 def quit(self) :
298 self.mainloop.quit()
299
300 def _init_i18n(self) :
301 """ intialize locales """
302 _locale_path = LCONST.LOCALE_DIR
303
304 # Init the list of languages to support
305 _langs = []
306 #Check the default locale
307 _lc, _encoding = locale.getdefaultlocale()
308 if (_lc):
309 #If we have a default, it's the first in the list
310 _langs = [_lc]
311 # Now lets get all of the supported languages on the system
312 _language = os.environ.get('LANGUAGE', None)
313 _msg = "language : " , _language
314 self.debug(_msg)
315 if (_language):
316 # langage comes back something like en_CA:en_US:en_GB:en
317 # on linuxy systems, on Win32 it's nothing, so we need to
318 # split it up into a list
319 _langs += _language.split(":")
320 # Now add on to the back of the list the translations that we
321 # know that we have, our defaults"""
322 _langs += ["en_US"]
323
324 # Now langs is a list of all of the languages that we are going
325 # to try to use. First we check the default, then what the system
326 # told us, and finally the 'known' list
327 _msg = "locale path", _locale_path
328 self.debug(_msg)
329
330 GUI_CTRL.GuiController.init_18n(_locale_path)
331 #import gtk.glade
332 #gtk.glade.bindtextdomain(LCONST.APP_NAME, _locale_path)
333 #gtk.glade.textdomain(LCONST.APP_NAME)
334
335 gettext.bindtextdomain(LCONST.APP_NAME, _locale_path)
336 gettext.textdomain(LCONST.APP_NAME)
337
338 # Get the language to use
339 _lang = gettext.translation(LCONST.APP_NAME, _locale_path
340 , languages=_langs, fallback = True)
341 # Install the language, map _() (which we marked our
342 # strings to translate with) to self.lang.gettext() which will
343 # translate them."""
344 global _
345 _ = _lang.gettext
346
347 def connect_to_project(self, project_ctrl) :
348 project_ctrl.connect('project-loaded', self._project_loaded_cb)
349 project_ctrl.connect('project-closed', self._project_closed_cb)
350 project_ctrl.connect('quit', self._quit_cb)
351
352 def disconnect_from_project(self, project_ctrl) :
353 project_ctrl.disconnect_by_function(self._project_loaded_cb)
354 project_ctrl.disconnect_by_function(self._project_closed_cb)
355
356 def _project_loaded_cb(self, project_ctrl, project) :
357 self.soundplayer.connect_to_app(project_ctrl)
358
359 def _project_closed_cb(self, project_ctrl) :
360 pass
361 #self.soundplayer.disconnect_from_app(project_ctrl)
362
363 def _quit_cb(self, project) :
364 self.quit()
365
366class LucioleController(object):
367 """
368 Main luciole controller
369 """
370
371 def __init__(self, args) :
372 """ Controller initialisation.
373
374 Is the main appication intialisation """
375 # init logging as early as possible so we can log startup code
376 enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0')
377 log.init('PITIVI_DEBUG', enable_color)
378
379 #
380 # init attributes
381 #
382 self.ctrl_signals = None
383 self.__rusher = None
384
385 # parse program options
386 self.__options = OPTIONS.options_parser()
387
388 # init logging
389 self.logger = LOGGING.init_logging(
390 self.__options.is_verbose,
391 log_to_file = self.__options.is_logfile
392 )
393 self.logger.info("Starting luciole")
394
395 # Init i18n
396 self.__init_i18n()
397
398 # load configuration : ie. saved user options
399 self.__configurer = LCONF.LucioleConf()
400 # load GUI theme
401 self.__configurer.load_theme()
402 # create controller signals
403 self.__connect_signals()
404 # initialize gui and BTW threads
405 self.__gui = GUI_CTRL.GuiController(self.ctrl_signals)
406
407
408
409 # load recent projects
410 _cbs = None
411 _recent_mnger = self.__configurer.init_recent_manager(
412 self.__gui.file_recent_menu,
413 self.__gui.windows,
414 _cbs)
415
416 # prepare callbacks for Project controller
417 _cbs = { 'done-rush' : self.on_done_rush,
418 'close-project' : self.on_close_project }
419
420
421 # start project controller
422 self._project = \
423 CTRL_PROJECT.ProjectController(self.__gui,_recent_mnger ,_cbs)
424
425 _cbs = {
426 'on-done-snapshot' : self.on_done_snapshot,
427 'on-error':self.on_error
428 }
429 self._viewer = CTRL_VIEWER.Viewer(self.__gui, _cbs)
430
431 # display capture trash if option is set
432 if self.__configurer.conf_options['CaptureTrashDisplay'] == 'yes' :
433 self.__gui.capture_trash = True
434
435
436
437 # conncet gui signals after the controller initialisation
438 self.__gui.connect_gui_signals()
439 # Connect GUI with app
440 self.__gui.connect_to_app_ctrl(self)
441
442 # TODO : load timeline
443 #TMLN.Timeline()
444
445 def __connect_signals(self):
446 """ connect controller signals """
447 self.ctrl_signals = gobject.GObject()
448 for _signal in CTRL_CONST.CTRL_SIGNALS :
449
450 # declare signal
451 gobject.signal_new(_signal, self.ctrl_signals,
452 gobject.SIGNAL_RUN_LAST,
453 gobject.TYPE_NONE,
454 (gobject.TYPE_PYOBJECT,))
455
456 # Build signal callback function name from signal name
457 # example : signal "change-alpha-mixer"
458 # have callback self.on_change_alpha_mixer
459 _cb = 'self.on_' + _signal.replace('-', '_')
460 # connect signal
461 self.ctrl_signals.connect(_signal, eval(_cb))
462
463
464 def on_change_alpha_mixer(self, *args) :
465 """ Request change mixer alpha value """
466 _args = self.__extract_signal_args(args)
467 try :
468 self._viewer.change_mixer_alpha(_args['alpha'])
469 except LEXCEP.LucioException, _err_msg :
470 raise LEXCEP.LucioException, _err_msg
471
472
473 def on_change_framerate(self, *args) :
474 """ Request of framerate(fpi) change """
475 _args = self.__extract_signal_args(args)
476 self._project.change_framerate(_args['framerate'])
477
478 def on_change_project(self, *args) :
479 """ Request for prohect change """
480 _args = self.__extract_signal_args(args)
481 for _key, _value in _args.iteritems() :
482 self._project.change_project(_key, _value)
483
484 def on_close_project(self, *args ) :
485 """ Request for project close """
486 _args = self.__extract_signal_args(args)
487 # test if project exists
488 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
489 # check if project is modified
490 if self._project.data['is_modified'] :
491 # ask for save it before close
492 _res = self.__gui.windows.\
493 question_message(_('Save Project before closing'))
494 if _res :
495 self._project.save()
496
497 # set viewer in default mode
498 self._viewer.mode = self._viewer.DEFAULT
499
500 # clear project controller
501 self._project.mode = CTRL_CONST.NO_PROJECT
502
503 # display close message
504 _msg = _('Project %s is closed' % self._project.data['project_name'])
505 self.__gui.status_bar.display_message(_msg)
506
507
508 def on_create_project(self, *args) :
509 """ Request new project """
510 _args = self.__extract_signal_args(args)
511 self._project.new()
512
513
514 def on_delete_capture(self, *args) :
515 """ Request delete on capture """
516 _args = self.__extract_signal_args(args)
517 # remove on treeview
518 self.__gui.treeviews[LCONST.CAPTURE].remove()
519
520 # The image selected for delete is displayed before delete
521 # so after go in default mode to nom more display the deleted image
522 self._viewer.mode = self._viewer.DEFAULT
523
524 # change control widget in no acquisition mode
525 if self.__gui.control_widget.mode in\
526 ( GUI_CONST.ACQUISITION_ACTIVE,
527 GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) :
528 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
529
530
531 def on_delete_chrono(self, *args) :
532 """ Request delete on capture """
533 _args = self.__extract_signal_args(args)
534 # remove on treeview
535 self.__gui.treeviews[LCONST.CHRONO].remove()
536
537 # The image selected for delete is displayed before delete
538 # so after go in default mode to nom more display the deleted image
539 self._viewer.mode = self._viewer.DEFAULT
540
541 # change control widget in no acquisition mode
542 if self.__gui.control_widget.mode in \
543 ( GUI_CONST.ACQUISITION_ACTIVE,
544 GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) :
545 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
546
547
548 def on_display_about(self, *args) :
549 """ request display about """
550 _args = self.__extract_signal_args(args)
551 # compute version
552 __version = "\n %s (%s)" % (INFO.VERSION, INFO.REVNO)
553 # display window
554 self.__gui.windows.about(__version)
555
556
557 def on_display_luciole_preferences(self, *args) :
558 """ Request display of application preferences """
559 _args = self.__extract_signal_args(args)
560 _cbs = {'done-preferences' : self.on_done_preferences}
561 self.__gui.windows.preferences( self.__configurer.conf_options,
562 _cbs)
563
564
565
566
567
568 def on_display_project_properties(self, *args) :
569 """ Request display project properties """
570 _args = self.__extract_signal_args(args)
571 # test if project exists
572 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
573 # go in default mode before dispalying window
574 # due to webcam detection and avoid confilcts with acquisition
575 self._viewer.mode = self._viewer.DEFAULT
576
577 # change control widget in no acquisition mode
578 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
579
580 # display message
581 self.__gui.status_bar.display_message(_('No Acquistion'))
582
583 # launch project poperties window
584 _cbs = \
585 {'change-project-properties' : self.on_change_project_properties}
586 self.__gui.windows.project_properties(self._project.data, _cbs)
587
588
589 def on_export_to_tool(self, *args) :
590 """ Request export to tool """
591 _args = self.__extract_signal_args(args)
592 # test if project exists
593 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
594 self.__gui.windows.export_tool(self._project.data)
595
596
597 def on_export_to_video(self, *args) :
598 """ Request export to video """
599 _args = self.__extract_signal_args(args)
600 # test if project exists
601 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
602 self.__gui.windows.export_video(self._project.data)
603
604
605 def on_import_image(self, *args) :
606 """ Request image import """
607 _args = self.__extract_signal_args(args)
608 # check if project exists
609 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
610 # open filename chooser dialog
611 _filenames = self.__gui.windows.import_dialog()
612
613 if _filenames != [] :
614 _cbs = { 'done-import' : self.on_done_import}
615 # start import controller
616 CTRL_IMPORT.ImportController(_filenames,
617 self._project.data,
618 self.__rusher,
619 self.__gui.status_bar,
620 _cbs)
621 else :
622 _msg = _("No files or valid files choosen for image import.")
623 self.__gui.windows.error_message( _msg)
624 else :
625 _msg = _("Impossible to import images when no project are loaded.")
626 self.__gui.windows.error_message( _msg)
627
628 def on_move_down_chrono(self, *args) :
629 """ request to move down an image """
630 _args = self.__extract_signal_args(args)
631 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
632 self.__gui.treeviews[LCONST.CHRONO].move_down()
633
634
635 def on_move_to_chrono(self, *args) :
636 """ request for move imge from capture to chrono"""
637 _args = self.__extract_signal_args(args)
638 # get images to move from capture widget
639 _images_name = self.__gui.treeviews[LCONST.CAPTURE].images_to_move()
640 # append this images in chrono widget
641 _images_obj = \
642 [ self.__rusher.get_image(_image) for _image in _images_name ]
643 for _image in _images_obj :
644 self.__gui.treeviews[LCONST.CHRONO].append_image(_image)
645
646
647 def on_move_up_chrono(self, *args) :
648 """ request to move up an image """
649 _args = self.__extract_signal_args(args)
650 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
651 self.__gui.treeviews[LCONST.CHRONO].move_up()
652
653
654
655
656
657 def on_play_query_position(self, *args) :
658 """ Query for player position """
659 _args = self.__extract_signal_args(args)
660 (_position, _duration) = self._viewer.play_query_position()
661 if _args['cb'] :
662 # callback call to gui
663 _args['cb'](_position, _duration)
664
665
666 def on_play_video(self, *args) :
667 """ Play video request """
668 _args = self.__extract_signal_args(args)
669 # test if project exists
670 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
671 try :
672 self._viewer.init_video_player(self._project.data)
673 except LEXCEP.LucioException, _err_msg :
674 raise LEXCEP.LucioException, _err_msg
675 else :
676 self._viewer.mode = self._viewer.PLAYER
677 try :
678 self._viewer.play_video(self._project.data)
679 except LEXCEP.LucioException, _err_msg :
680 raise LEXCEP.LucioException, _err_msg
681 else :
682 # send error message
683 msg = _("Can not play animation : No project loaded")
684 self.__gui.windows.error_message( msg)
685
686
687 def on_quit_app(self, *args) :
688 """ Request quit application """
689 _args = self.__extract_signal_args(args)
690 if self._project.mode == CTRL_CONST.PROJECT_LOADED and \
691 self._project.data['is_modified'] :
692 _msg = _('Project modified. Save project before exit ?')
693 _res = self.__gui.windows.question_message(_msg)
694 # if response is not a bool : cancel clicked
695 if isinstance(_res, bool) :
696 if _res :
697 # if True save project before exit
698 self._project.save()
699 GUI_CTRL.GuiController.quit()
700 else :
701 GUI_CTRL.GuiController.quit()
702
703 def on_save_as_project(self, *args) :
704 """ save as project request """
705 _args = self.__extract_signal_args(args)
706 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
707 _dir = self.__gui.windows.dir_chooser()
708 if _dir != None :
709 # set viewer in default mode due to project reload
710 self._viewer.mode = self._viewer.DEFAULT
711 try :
712 self._project.save_as(_dir)
713 except LEXCEP.LucioException, _err_msg :
714 _msg = _('Impossible to save as project : %s' % _err_msg)
715 self.__gui.windows.error_message(_msg)
716 else :
717 _msg = _('Project saved as %s' % \
718 self._project.data['project_name'])
719 self.__gui.status_bar.display_message(_msg)
720
721
722 def on_save_project(self, *args) :
723 """ save project request """
724 _args = self.__extract_signal_args(args)
725 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
726 try :
727 self._project.save()
728 except LEXCEP.LucioException, _err_msg :
729 _msg = _('Impossible to save project : %s' % _err_msg)
730 self.__gui.windows.error_message(_msg)
731 else :
732 _msg = _('Project %s saved' % \
733 self._project.data['project_name'])
734 self.__gui.status_bar.display_message(_msg)
735 # update project name on Main bar
736 self.__gui.set_program_bar(self._project.data['project_name'],
737 self._project.data['is_modified'])
738
739
740 def on_start_acquisition(self, *args ) :
741 """Request for starting acqusisition """
742 _args = self.__extract_signal_args(args)
743 # check if a project is loaded
744 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
745 self._viewer.mode = self._viewer.ACQUIRER
746
747 # change control widget in acquisition mode
748 self.__gui.control_widget.mode = GUI_CONST.ACQUISITION_ACTIVE
749
750 else :
751 _msg = \
752 _(' Can not start acquisition when no project are loaded.')
753 self.__gui.windows.error_message(_msg)
754
755
756 def on_start_mixer(self, *args) :
757 """ Request mixer start """
758 _args = self.__extract_signal_args(args)
759 # check if a project is loaded
760 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
761 # switch to viewer with acqusisition and mixer
762 self._viewer.mode = self._viewer.ACQUIRER_WITH_MIXER
763 # change control widget in no acquisition mode
764 self.__gui.control_widget.mode = \
765 GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER
766
767 # display message
768 self.__gui.status_bar.display_message(_('Acquiring with mixer'))
769 else :
770 # robustness
771 _msg = \
772 _('No project are loaded.')
773 self.__gui.windows.error_message(_msg)
774
775
776
777 def on_stop_acquisition(self, *args) :
778 """ Request to stop acquistion """
779 _args = self.__extract_signal_args(args)
780 # check if a project is loaded
781 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
782 # switch to viewer default
783 self._viewer.mode = self._viewer.DEFAULT
784 # change control widget in no acquisition mode
785 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
786
787 else :
788 # robustness
789 _msg = \
790 _('No project are loaded.')
791 self.__gui.windows.error_message(_msg)
792
793 def on_stop_mixer(self, *args) :
794 """ Request mixer stop """
795 _args = self.__extract_signal_args(args)
796 # check if a project is loaded
797 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
798 # switch to viewer with acqusisition and mixer
799 self._viewer.mode = self._viewer.ACQUIRER
800 # change control widget in no acquisition mode
801 self.__gui.control_widget.mode = \
802 GUI_CONST.ACQUISITION_ACTIVE
803
804 # display message
805 self.__gui.status_bar.display_message(_('Acquiring'))
806 else :
807 # robustness
808 _msg = \
809 _('No project are loaded.')
810 self.__gui.windows.error_message(_msg)
811
812 def on_stop_video(self, *args) :
813 """ Stop video request """
814 _args = self.__extract_signal_args(args)
815 try :
816 self._viewer.stop_video()
817 except LEXCEP.LucioException, _err_msg :
818 raise LEXCEP.LucioException, _err_msg
819 else :
820 self._viewer.mode = self._viewer.DEFAULT
821
822
823 def on_take_snapshot(self, *args) :
824 """ take image snapshot request """
825 _args = self.__extract_signal_args(args)
826 if self._project.mode == CTRL_CONST.PROJECT_LOADED :
827 self._viewer.take_snapshot()
828 else :
829 # robustness
830 _msg = \
831 _('No project are loaded.')
832 self.__gui.windows.error_message(_msg)
833
834 _msg = \
835 _('No project are loaded.')
836 self.__gui.windows.error_message(_msg)
837
838
839 def on_view_image(self, *args) :
840 """
841 request image view/display :
842 Mix image with stream if viewer mode is acquirer with mixer
843 """
844 _args = self.__extract_signal_args(args)
845 #if mode is player does not allow image view
846 if self._viewer.mode == self._viewer.PLAYER :
847 pass
848
849 # if mode is acquirer with mixer and image is from capture treeview :
850 # mix image with acqusisition stream
851 elif self._viewer.mode == self._viewer.ACQUIRER_WITH_MIXER \
852 and _args['tv_type'] == LCONST.CAPTURE :
853 self._viewer.mix_image_in_stream(_args['image'])
854
855 # on other cases view image
856 else :
857 self._viewer.mode = self._viewer.IMAGER
858 self._viewer.view_image(_args['image'])
859
860 #change control widget in no acquisition mode
861 if self.__gui.control_widget.mode in \
862 ( GUI_CONST.ACQUISITION_ACTIVE,
863 GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) :
864 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
865
866
867 def run(self) :
868 """ main program ininite loop """
869 # TODO :
870 GUI_CTRL.GuiController.threads_enter()
871 # before starting main loop check for
872 # options given to application
873
874 # load a project ?
875 if self.__options.filename \
876 and os.path.exists(self.__options.filename):
877 # emit open project signal
878 self.__gui.emit(
879 "open-project", self.__options.filename)
880
881 GUI_CTRL.GuiController.main_loop()
882 GUI_CTRL.GuiController.threads_leave()
883 self.logger.info("Leaving luciole")
884 #
885 # Callbacks from controller
886 #
887 def on_done_rush(self, rusher) :
888 """ callback : when tush images are loaded """
889 if rusher != None \
890 and self._project.mode == CTRL_CONST.PROJECT_LOADED :
891 # load treeviews
892 # prepare data
893 _app_data = {}
894 _app_data['capture_images'] = self._project.data['capture_images']
895 _app_data['chrono_images'] = self._project.data['chrono_images']
896 _app_data['rush'] = rusher
897
898 self.__gui.load_treeviews(_app_data)
899
900 # init acquisition
901 self._viewer.init_acquisition(self._project.data)
902
903
904 # update fpi on gui and show it
905 self.__gui.update_framerate(int(self._project.data['fpi']))
906
907 # show project acquisition widgets
908 self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE
909
910 # update project name on Main bar
911 self.__gui.set_program_bar(self._project.data['project_name'],
912 self._project.data['is_modified'])
913
914 # finish project load (project aspect)
915 self._project.finish_project_load()
916
917 # store rusher
918 self.__rusher = rusher
919
920
921
922 def on_done_snapshot(self) :
923 """
924 callback : image snapshot is done.
925 Now image can be processed into project
926 """
927 self._project.append_snapshot(self.__rusher, self._viewer.acquirer)
928
929 def on_done_preferences(self , modified_options) :
930 """ callback : preferences modified fome diaog """
931 # update prefernces
932 (_capture_trash, _ask_restart) = \
933 self.__configurer.update_preferences(modified_options)
934
935 # check if gui update is needed
936 if isinstance(_capture_trash, bool) :
937 self.__gui.capture_trash = _capture_trash
938
939 if isinstance(_ask_restart, bool) and _ask_restart == True :
940 _msg = \
941 _('Please restart Luciole to take into account the new theme ')
942 self.__gui.status_bar.display_message(_msg)
943
944 def on_change_project_properties(self, key, key_webcam= None, data =None) :
945 """ callback project change from properties window """
946 # webcam_data field of project dictionaty is a dictionaty
947 # Test if webcam data change key
948 if key == 'webcam_data':
949 # make a local copy of webcam_data
950 webcam_dict = self._project.data['webcam_data']
951 #test if webcam dict key exists
952 if webcam_dict.has_key(key_webcam) :
953 # update webcam key and call project change
954 webcam_dict[key_webcam] = data
955 self._project.change_project('webcam_data', webcam_dict)
956
957 def on_done_import(self, image_objs = [] ) :
958 """ callback : image import (rush generation) is done """
959 if image_objs != [] :
960 self._project.change_project( 'rush_images',
961 self.__rusher.dump_image_name())
962 # Not to be done in thread : interacts with gui
963 for _image_obj in image_objs :
964 self.__gui.append_capture(_image_obj)
965
966
967
968 #
969 # Error calbacks
970 #
971 def on_error(self, err_type, message) :
972 """ Main error callback """
973 _msg = _("error type : %s. %s" % (err_type, message))
974 # Display error on log window
975 self.logger.info(_msg)
976 #
977 # private methods
978 #
979 def __init_i18n(self) :
980 """ intialize locales """
981 _locale_path = LCONST.LOCALE_DIR
982
983 # Init the list of languages to support
984 _langs = []
985 #Check the default locale
986 _lc, _encoding = locale.getdefaultlocale()
987 if (_lc):
988 #If we have a default, it's the first in the list
989 _langs = [_lc]
990 # Now lets get all of the supported languages on the system
991 _language = os.environ.get('LANGUAGE', None)
992 _msg = "language : " , _language
993 self.logger.debug(_msg)
994 if (_language):
995 # langage comes back something like en_CA:en_US:en_GB:en
996 # on linuxy systems, on Win32 it's nothing, so we need to
997 # split it up into a list
998 _langs += _language.split(":")
999 # Now add on to the back of the list the translations that we
1000 # know that we have, our defaults"""
1001 _langs += ["en_US"]
1002
1003 # Now langs is a list of all of the languages that we are going
1004 # to try to use. First we check the default, then what the system
1005 # told us, and finally the 'known' list
1006 _msg = "locale path", _locale_path
1007 self.logger.debug(_msg)
1008
1009 GUI_CTRL.GuiController.init_18n(_locale_path)
1010 #import gtk.glade
1011 #gtk.glade.bindtextdomain(LCONST.APP_NAME, _locale_path)
1012 #gtk.glade.textdomain(LCONST.APP_NAME)
1013
1014 gettext.bindtextdomain(LCONST.APP_NAME, _locale_path)
1015 gettext.textdomain(LCONST.APP_NAME)
1016
1017 # Get the language to use
1018 _lang = gettext.translation(LCONST.APP_NAME, _locale_path
1019 , languages=_langs, fallback = True)
1020 # Install the language, map _() (which we marked our
1021 # strings to translate with) to self.lang.gettext() which will
1022 # translate them."""
1023 global _
1024 _ = _lang.gettext
1025
1026
1027
1028 #
1029 # static_method
1030 #
1031 @staticmethod
1032 def __extract_signal_args(args) :
1033 """
1034 extract the signals arguments :
1035 arfs is a tuple
1036 The first argument is the emitter object ( gobject)
1037 the second argument is a dict with all the parameter given
1038 to signal.
1039
1040 The function returns the paramter dictionary
1041 """
1042 return args[1]
1043
1044
1045if __name__ == '__main__' :
1046 X = LucioleController(sys.argv)
1047 X.run()
1048
1049
1050
01051
=== added file 'luciole/ctrl/ctrl_img_vw.py'
--- luciole/ctrl/ctrl_img_vw.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/ctrl_img_vw.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,82 @@
1# -*- Mode: Python -*-
2# vim:si:ai:et:sw=4:sts=4:ts=4
3#
4#
5# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
6#
7# This file is part of Luciole.
8#
9# Luciole is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# Luciole is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
21#
22#
23"""
24ctrl_img_vw.py :
25 manage image viewer
26"""
27# standard library imports
28
29# related third party imports
30
31from pitivi.action import ViewAction
32from pitivi.pipeline import Pipeline
33
34# local application/library specific imports
35from luciole.ctrl.base import ProjectMode
36
37
38class ProjectModeImgVw(ProjectMode) :
39 """ Base class """
40 __signals__ = {}
41
42 def __init__(self, ctrl_project, mode_type = None) :
43 ProjectMode.__init__(self, ctrl_project, "IMAGE_VIEW")
44
45
46
47
48
49
50
51 def prepare(self, project) :
52 """ prepare image viewer """
53 ProjectMode.prepare(self, project)
54
55
56 def release(self) :
57 """ Meta function
58 release """
59 ProjectMode.release(self)
60
61
62 def active_viewer(self, factory) :
63 self._pipeline = Pipeline()
64 self._view_action = ViewAction()
65
66 self.debug(" factory to play :%s", factory)
67 self._view_action.addProducers(factory)
68 if self._viewer :
69 self._viewer.setPipeline(None, 'IMAGE_VIEW')
70 self._viewer.hideSlider()
71
72 self._viewer.setAction(self._view_action)
73 self._viewer.setPipeline(self._pipeline, 'IMAGE_VIEW')
74 self._pipeline.play()
75
76 def stop(self) :
77
78 self._pipeline.stop()
79
80
81
82
083
=== added file 'luciole/ctrl/ctrl_no_project.py'
--- luciole/ctrl/ctrl_no_project.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/ctrl_no_project.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,60 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# -*- Mode: Python -*-
4# vim:si:ai:et:sw=4:sts=4:ts=4
5#
6#
7# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
8#
9# This file is part of Luciole.
10#
11# Luciole is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# Luciole is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
23#
24#
25"""
26ctrl_no_project.py :
27 manages no project loaded mode
28"""
29# standard library imports
30# N/A
31
32# related third party imports
33from pitivi.signalgroup import SignalGroup
34
35# local application/library specific imports
36from luciole.ctrl.base import ProjectMode
37
38class ProjectModeNoProject(ProjectMode) :
39 __signals__ = {
40 }
41
42 def __init__(self, ctrl_project) :
43 ProjectMode.__init__(self, ctrl_project, "NO_PROJECT")
44 self.gui_signals = SignalGroup()
45
46 def prepare(self, project) :
47 """ Nothing to prepare """
48 pass
49
50 def release(self) :
51 ProjectMode.release(self)
52
53 def active_viewer(self) :
54 if self._viewer :
55 self._viewer.setPipeline(None, "NO_PROJECT")
56 else :
57 self.error("No viewer set")
58
59
60
061
=== renamed file 'luciole/ctrl/project_ctrl.py' => 'luciole/ctrl/ctrl_project.py'
--- luciole/ctrl/project_ctrl.py 2010-09-07 06:27:09 +0000
+++ luciole/ctrl/ctrl_project.py 2011-02-28 13:21:06 +0000
@@ -22,7 +22,7 @@
22#22#
23#23#
24"""24"""
25project_ctrl.py :25ctrl_project.py :
26 Manage interactions with a luciole Project 26 Manage interactions with a luciole Project
27"""27"""
2828
@@ -31,325 +31,537 @@
31from gettext import gettext as _31from gettext import gettext as _
3232
33# related third party imports33# related third party imports
34# N/A34from pitivi.signalinterface import Signallable
35from pitivi.log.loggable import Loggable
36
3537
36# local application/library specific imports38# local application/library specific imports
37import luciole.base.exceptions as LEXCEP39import luciole.base.exceptions as LEXCEP
38import luciole.base.tools as LT40import luciole.base.tools as LT
39import luciole.constants as LCONST41import luciole.constants as LCONST
40import luciole.ctrl.constants as CTRL_CONST42import luciole.ctrl.constants as CTRL_CONST
43import luciole.ctrl.ctrl_acq as CTRL_ACQ
41import luciole.ctrl.ctrl_base as CTRL_BASE44import luciole.ctrl.ctrl_base as CTRL_BASE
45import luciole.ctrl.ctrl_timeline as CTRL_TMLN
46import luciole.ctrl.ctrl_no_project as CTRL_NO_PROJECT
47import luciole.ctrl.ctrl_img_vw as CTRL_IMG_VW
42import luciole.ctrl.load_rush as LRUSH48import luciole.ctrl.load_rush as LRUSH
43import luciole.gui.constants as GUI_CONST49import luciole.gui.constants as GUI_CONST
44import luciole.media.image as LI50import luciole.media.image as LI
45import luciole.project.project as LPF51import luciole.project.project_etree as LPF
52
53from luciole.gui.windows.assistant_new_project import AssistantNewProject
54from luciole.gui.windows.dialog_project_properties import ProjectProperties
55from luciole.ctrl.import_image import ImportController
4656
47# NULL tuple constant defined for compat with gobject signals on ctrl.py file57# NULL tuple constant defined for compat with gobject signals on ctrl.py file
48_NULL_TUPLE = None, None58_NULL_TUPLE = None, None
4959
50class ProjectController(CTRL_BASE.CtrlBase) :60class Project(object) :
51 """ 61
52 Class to manage luciole project 62
53 SubClass of controller63 def __init__(self) :
54 """64 self.parser = LPF.ProjectToFile()
55 #65 self.props = LPF.ProjectDico()
56 # CONSTANTS 66 self.timeline = None
57 #67 self.rush = None
58 __MODES = (CTRL_CONST.NO_PROJECT, CTRL_CONST.PROJECT_LOADED)68 self.acquirer = None
5969
60 def __get_data(self):70
61 """ getter for is_project"""71 def save(self, path) :
62 return self.__data72 _project_path = None
63 data = property( __get_data,73 if path is not None :
64 None,74 try :
65 None,75 _project_path = self.parser.save_as(path , self.props)
66 """Retrieve project data """76 except LEXCEP.LucioException, _err_msg :
67 ) 77 raise LEXCEP.LucioException, _err_msg
6878 else :
69 def __get_mode(self):79 try :
70 """ getter for project controller mode """80 _project_path = self.parser.save(self.props)
71 return self.__mode81 except LEXCEP.LucioException, _err_msg :
72 def __set_mode(self, mode) :82 raise LEXCEP.LucioException, _err_msg
73 """ setter for project controller mode """83 return _project_path
74 # change mode only on transition84
75 if mode in self.__MODES and mode != self.__mode :85 def set_non_pjt_dpdt_props(self) :
76 self.__mode_call[mode]()86 # init props not managed by porject in file
77 mode = property( __get_mode, __set_mode, None,87 self.props['is_modified'] = False
78 """ Project controller mode """)88 self.props['is-mixer-active'] = False
7989 self.props['image2mix'] = None
80 def __init__(self, gui, recent_mnger, cbs):90 self.props['alpha'] = LCONST.DEFAULT_ALPHA_IMAGE
81 """ 91
82 ctrl_cbs: dictionary of main main controller callbacks92class CtrlProject(Signallable, Loggable) :
83 """93
84 CTRL_BASE.CtrlBase.__init__(self) 94 # CONSTANTS
85 self.__data = None95 MODES = ("NO_PROJECT", "ACQUISITION", "TIMELINE", "IMAGE_VIEW", "EXPORT")
86 self.__mode = CTRL_CONST.NO_PROJECT96 PROJECT_LOADED_MODES = ("ACQUISITION", "TIMELINE", "IMAGE_VIEW", "EXPORT")
87 # init mode 97
88 self.__mode_call = {98
89 CTRL_CONST.NO_PROJECT : \99
90 self.__set_mode_no_project,100 __signals__ = {
91 CTRL_CONST.PROJECT_LOADED : \101 "project-loaded": ["project"],
92 self.__set_mode_project_loaded,102 "project-closing" : ["project"],
93 }103 "project-closed" : [],
94104 'project-modified' : ["is_modified"],
95 self.__gui = gui105 'project-mode-changed' : ["mode"],
96 self.__gui_windows = gui.windows106 'quit' : [],
97 self.__cbs = cbs107 'import-image-done' : ['images'],
98 self.__recent_mngr = recent_mnger108 }
99 109
100 self.__pjt_file = LPF.ProjectToFile()110 def _get_mode(self) :
101111 """ mode getter """
102 def new(self) :112 return self._mode
103 """ create a new project """113 mode = property(_get_mode,
104 # if project exist : close it114 None,
105 if self.__data != None :115 None,
106 # (None, None) params for compat with gobject signal116 """ project mode """)
107 self.__cbs['close-project'](None, None)117
108 # launch New project assitant window118
109 self.__gui_windows.new_project_assistant(self._cb_new_project) 119 def _get_is_modified(self):
110120 """ is_modified getter """
121 return self.project.props['is_modified']
122
123 def _set_is_modified(self, is_modified = True) :
124 """ is_modified getter """
125 if isinstance(is_modified, bool)\
126 and\
127 is_modified != self.project.props['is_modified'] :
128 self.project.props['is_modified'] = is_modified
129 self.debug('emit signal project-modified : is_modified = %s',
130 is_modified)
131 self.emit('project-modified', is_modified)
132
133 is_modified = property(_get_is_modified,
134 _set_is_modified,
135 None,
136 """ is_modified project property """)
137
138
139 def __init__(self) :
140 Signallable.__init__(self)
141 Loggable.__init__(self)
142
143 self._mode = None
144 self.gui = None
145 self.project = Project()
146
147 self.ctrl_acq = CTRL_ACQ.ProjectModeAcquisition(self)
148 self.ctrl_tmln = CTRL_TMLN.ProjectModeTimeline(self)
149 self.ctrl_no_project = CTRL_NO_PROJECT.ProjectModeNoProject(self)
150 self.ctrl_img_vw = CTRL_IMG_VW.ProjectModeImgVw(self)
151
152 self._connect_to_acq(self.ctrl_acq)
153 self._connect_to_timeline(self.ctrl_tmln)
154
155
156 def connect_to_gui(self, gui) :
157 self.gui = gui
158
159 self.ctrl_acq.set_viewer(gui.drawarea)
160 self.ctrl_tmln.set_viewer(gui.drawarea)
161 self.ctrl_no_project.set_viewer(gui.drawarea)
162 self.ctrl_img_vw.set_viewer(gui.drawarea)
163
164 def no_project_mode(self) :
165 self.debug('No project mode')
166
167 if self._mode == 'ACQUISITION' :
168 self.ctrl_acq.stop()
169
170 if self._mode != 'NO_PROJECT' :
171 self._mode = 'NO_PROJECT'
172 self.ctrl_no_project.active_viewer()
173 else :
174 self.warning("impossible to switch to No Project mode (%s)",
175 self._mode )
176
177
178
179
180 def acquisition_mode(self, force = False) :
181 """ activate acquisition mode """
182 self.debug('Acquisition mode')
183 if self._mode in self.PROJECT_LOADED_MODES \
184 and self._mode != 'ACQUISITION' :
185 self._mode = 'ACQUISITION'
186 #force = True
187 if force == True :
188 self.debug('switch to acquisition mode forced')
189 # force prepare for pipelline configuration
190 # the _acquirer_ready_cb will call acquisition_mode
191 # when aquirer(pipeline) is ready
192 self.ctrl_acq.release()
193 self.ctrl_acq.prepare(self.project)
194 return
195
196 self.ctrl_acq.active_viewer()
197 self.ctrl_acq.start()
198 self.debug('Emit project-mode-changed : %s'%self._mode)
199 self.emit('project-mode-changed',self._mode)
200
201 else :
202 self.warning("impossible to switch to acquisition mode (%s)",
203 self._mode )
204
205 def timleline_mode(self) :
206 """ activate timeline mode """
207 if self._mode == 'ACQUISITION' :
208 self.ctrl_acq.stop()
209
210 if self._mode in self.PROJECT_LOADED_MODES \
211 and self._mode != 'TIMELINE' :
212
213 self._mode = 'TIMELINE'
214 self.ctrl_tmln.active_viewer()
215 self.debug('Emit project-mode-changed : %s'%self._mode)
216 self.emit('project-mode-changed',self._mode)
217 else :
218 self.warning("impossible to switch to timeline mode")
219
220
221 def img_vw_mode(self, origin, image_name, position = 0) :
222 if self._mode in self.PROJECT_LOADED_MODES :
223 if self._mode == 'ACQUISITION' :
224 self.ctrl_acq.stop()
225
226 if self._mode == 'TIMELINE' and origin == LCONST.CHRONO :
227 self.ctrl_tmln.go_to_position(position)
228 # TODO : implemnt better then return here
229 return
230
231 if self._mode == 'IMAGE_VIEW' :
232 self.ctrl_img_vw.stop()
233
234
235 _factory = self.ctrl_tmln._get_factory(image_name)
236 self.ctrl_img_vw.active_viewer(_factory)
237 self._mode = 'IMAGE_VIEW'
238
111 def open(self, project_path = None) :239 def open(self, project_path = None) :
112 """ open an existing project """240 """ open an existing project """
113 # no project path open a Dialog241 # no project path : leave
114 if project_path == None :242 if project_path == None :
115 _path = self.__gui_windows.open_project()243 return
116 else :244 self.debug("Starting project load of %s", project_path)
117 _path = project_path
118245
119 # if a project is loaded close it246 # if a project is loaded close it
120 if self.__data != None :247 if self._mode != 'NO_PROJECT' :
121 # (None, None) params for compat with gobject signal248 # (None, None) params for compat with gobject signal
122 self.__cbs['close-project'](None, None)249 # TODO : manage close project
123 250 #self.__cbs['close-project'](None, None)
124 # check if a path entered ( from gui)251 pass
125 if _path != None :252
126 try :253 self.project = Project()
127 (_is_valid, self.__data) =self.__pjt_file.open(_path)254 try :
128 except LEXCEP.LucioException, _err_msg :255 (_is_valid, self.project.props) = \
129 # Transmit error to controller: not stop application256 self.project.parser.open(project_path)
130 raise LEXCEP.LucioException, _err_msg257 except LEXCEP.LucioException, _err_msg :
131 else :258 # Transmit error to controller: not stop application
132 if not _is_valid :259 self.error(_err_msg)
133 _msg = _(" Webcam data not valid in project.\260 # TODO : manage exceptions
134 Please restart webcam detection")261 raise LEXCEP.LucioException, _err_msg
135 self.__gui_windows.error_message( _msg)262 else :
136 263 if not _is_valid :
137 # load project in application264 _msg = _(" Webcam data not valid in project.\
138 self.__load_project_in_app()265 Please restart webcam detection")
139266 self.gui.windows.error_message( _msg)
140267
141 268 # initiate project load
142 def save_as(self, dir_path) :269 self._start_project_load()
143 """ save as current project """270
144 271 def new_project(self ) :
145 # project saved and self.__data updated272 """ new project creation """
146 try :273 if self._mode != 'NO_PROJECT' :
147 self.__pjt_file.save_as(dir_path , self.__data)274 # close current project
148 except LEXCEP.LucioException, _err_msg :275 self.close()
149 raise LEXCEP.LucioException, _err_msg276
150 else :277 _ass = AssistantNewProject(self._new_project_cb)
151 self.__data['is_modified'] = False278
152 # saved project needs now to be reloaded279 # TODO : fot test purpose only
153 # close it 280 import os.path
154 self.__set_mode_no_project()281 import shutil
155 # reload project282 PROJECT_NAME = 'test_luciole'
156283 PROJECT_PATH = '/home/nico/temp/testLuciole'
157 self.__set_mode_project_loaded()284
158 self.__load_project_in_app()285 _p_path = os.path.join(PROJECT_PATH, PROJECT_NAME)
159286 if os.path.exists(_p_path) :
160287 # delete folder
161 def save(self) :288 self.debug('Deleting folder %s', _p_path)
162 """ save current project """289 shutil.rmtree(_p_path)
163 try :290
164 self.__pjt_file.save(self.__data)291 # set on assistant project name
165 except LEXCEP.LucioException, _err_msg :292 _ass._pg1.entry_project_name.set_text(PROJECT_NAME)
166 raise LEXCEP.LucioException, _err_msg293 # set a folder
167 else :294 _ass._pg1._file_chooser.set_current_folder(PROJECT_PATH)
168 self.__data['is_modified'] = False295
169296
170297
171298 def _new_project_cb(self, project_data) :
172
173
174 def append_snapshot(self, rusher, acquirer) :
175 """ move acquired image to rush dir :
176 1. move image to tmp dir.
177 2. resize it.
178 3. move it to rush image dir """
179 # get acquired image name
180 l_acq_image = acquirer.image2save
181
182 # build temp impage path
183 l_temp_dir = os.path.join(self.__data['project_dir'], 'tmp')
184 # copy name
185 l_ac_image_temp = os.path.join(l_temp_dir, LCONST.ACQUIRED_IMAGE_NAME)
186 # resized copy name
187 l_ac_image_temp_rz = \
188 os.path.join(l_temp_dir,LCONST.ACQUIRED_IMAGE_NAME_RZ)
189
190 # build rush image name
191 l_basename = LCONST.RUSH_FILENAME_TPL % rusher.rush_index
192 l_rush_image = \
193 os.path.join(self.__data['project_dir'], self.__data['rush_dir'])
194 l_rush_image = os.path.join(l_rush_image, l_basename)
195
196 try :
197 # 1. move image acquired image to tmp dir
198 LT.movef(l_acq_image, l_ac_image_temp)
199
200 # 2. resize image result is in l_ac_image_temp_rz
201 l_rz_obj = LI.ImageResize(l_ac_image_temp, l_ac_image_temp_rz )
202 l_rz_obj.convert()
203
204 # 3. move resized image to rush dire
205 LT.movef(l_ac_image_temp_rz, l_rush_image)
206
207 except LEXCEP.LucioException, _err_msg :
208 raise LEXCEP.LucioException, _err_msg
209 else :
210 # 4. append image to rush list
211 rusher.append(l_basename)
212 # indicate project change (rush list)
213 self.__change_project('rush_images', rusher.dump_image_name())
214
215
216 # 5. append image object to capture list
217 l_rush_image = rusher.get_image(l_basename)
218 # 6. always update the image 2 mix, even if mixer is not active
219 # used to memorize the last capture
220 acquirer.Image2Mix = l_rush_image.path
221
222 #7 append capture to treeview
223 self.__gui.append_capture(l_rush_image)
224
225 def change_framerate(self, fpi) :
226 """ change framerate """
227 # update framerate only if project exist
228 if self.__data and isinstance(fpi, int):
229 self.__change_project('fpi', str(fpi))
230
231 def change_project(self, project_key, data) :
232 """ Change project : External mathod """
233 self.__change_project(project_key, data)
234
235 def finish_project_load(self) :
236 """ 299 """
237 Method called to finsih project load.300 new project callback :
301 signal generated by new project assistant
238 """302 """
239 # add project loaded to recent manager303 self.debug(" create project with %s", project_data)
240 self.__recent_mngr.add_project(os.path.join(304 if project_data is None :
241 self.__data['project_dir'],305 self.error('No data to load')
242 self.__data['xml_filename'] )306 return
243 )307 # create project on disk
244 308 self.project = Project()
245 # Set project as not modified
246 self.__data['is_modified'] = False
247 #
248 # def callbacks
249 #
250 def _cb_new_project(self, project_data) :
251 """ callback when new project assistant is done """
252 # 1. copy data in a project_dico object
253 project_dico_data = LPF.ProjectDico()
254 for _key in project_data.keys() :
255 project_dico_data[_key] = project_data[_key]
256
257 # 2. create project ( folder structure ,etc ...)
258 # mising raise exception in case of create failure
259 try :309 try :
260 self.__pjt_file.create(project_dico_data)310 _path = self.project.parser.create(project_data)
261 except LEXCEP.LucioException, _err :311 except LEXCEP.LucioException, _err :
262 raise LEXCEP.LucioException, _err312 raise LEXCEP.LucioException, _err
263 else :313
264 self.__data = project_dico_data314 self.debug(" created project on disk : %s", _path)
265 315 # load project on luciole
266 # 3. load created project in application316 self.open(_path)
267 self.__load_project_in_app()317
268318
269319 def close(self) :
270320 """ close current project """
271 #321 if self._mode in self.PROJECT_LOADED_MODES :
272 # Private methods322 if self.project.props['is_modified'] == True :
273 #323 if (self.emit('project-closing', self.project) == True) :
274 def __set_mode_no_project(self): 324 self.save()
275 """ No project mode """325
276 326 self.debug(" Closing current project ")
277 #327 self.release()
278 # clear gui 328 self.no_project_mode()
279 #329 self.debug('emit signal project-closed')
280 # clear treeviews 330 self.emit('project-closed')
281 self.__gui.clear_treeviews()331 self.project = None
282 332
283 # set control widget in open_Load mode 333 def save(self, dir_path = None ) :
284 self.__gui.control_widget.mode = GUI_CONST.LOAD_OR_CREATE334 if self._mode not in self.PROJECT_LOADED_MODES :
285335 self.error('invalid mode for save : %s'%self._mode)
286 # clear program bar336 return
287 self.__gui.set_program_bar('', False)337 if dir_path and not os.path.exists(dir_path) :
288 338 self.error('Path %s exists. Impossible to save project', dir_path)
289 339 return
290 self.__mode = CTRL_CONST.NO_PROJECT340 self.debug("%s",dir_path)
291341 try :
292 def __set_mode_project_loaded(self): 342 _project_path = self.project.save(dir_path)
293 """project loaded mode """343 except LEXCEP.LucioException, _err_msg :
294 self.__mode = CTRL_CONST.PROJECT_LOADED344 self.error('%s', _err_msg)
295345 else :
296 def __save(self) :346 self.debug('Project saved in %s', _project_path)
297 """ private method for save """347 self.is_modified = False
348 if dir_path is not None and _project_path is not None :
349 # reload project is corretcly saved
350 self.close()
351 self.debug('Opening project with path %s',_project_path)
352 self.open(_project_path)
353
354 def set_framerate(self, fpi, force = False) :
355 if self._mode != "TIMELINE" and force == False :
356 self.info('update of fpi only availabe in TIMELINE mode')
357 return
358 if int(self.project.props['fpi']) != fpi :
359 self.project.props['fpi'] = str(fpi)
360 self.is_modified = True
361 # update timeline
362 self.ctrl_tmln.update_image_duration(fpi)
363
364
365
366 def new(self, project_data = None) :
367
368 # create new project
369
370 if project_data :
371 # TODO case with assistant
372 pass
373
374
375
376 def release(self) :
377
378 # release signals between aquirer and project
379 if self.project.props['hardtype'] in \
380 (LCONST.FAKE, LCONST.DVCAM, LCONST.WEBCAM ) :
381 #self._disconnect_from_acq(self.ctrl_acq)
382 pass
383
384 # release signals between timeline and project
385 # self._disconnect_from_timeline(self.ctrl_tmln)
386
387 # disconnect from gui
388 # TODO : Analyse when to be done on close project or quit ?
389 #self.disconnect_from_gui(self.gui)
390
391
392 def import_images(self, paths) :
393 if self._mode not in self.PROJECT_LOADED_MODES :
394 self.error('invalid mode for import: %s'%self._mode)
395 return
396
397 if paths != [] :
398 self.debug('Starting image importer for %s',paths)
399 importer = ImportController( paths,
400 self.project.props,
401 self.project.rush,
402 self.gui.status_bar
403 )
404 importer.connect('import-image-done', self._import_img_done_cb)
405 else :
406 self.info('No image to import')
407
408
409 def _import_img_done_cb(self, imager, images) :
410 """ import-image-done callback """
411 self.debug("RX import-image-done : %s", images)
412 if images != [] :
413 self.emit('import-image-done', images)
414
415 # update project
416 # get image names
417 _names = [ _img.name for _img in images]
418 self.project.props['rush_images'].extend(_names)
419 self.project.props['capture_images'].extend(_names)
420 self.is_modified = True
421 else :
422 self.warning('No image imported')
423
424 def show_project_properties(self) :
425 """ show project properties
426 webcam setup can be modified. So before displaying window all
427 pipelines and associated stuff should be stopped
428 """
429 if self._mode not in self.PROJECT_LOADED_MODES :
430 self.error('invalid mode for import: %s'%self._mode)
431 return
432 self.debug('%s',self.project.props)
433 self._old_mode = self._mode
434 if self._mode == 'ACQUISITION' :
435 # switch to TIMELINE to free the webcam
436 self.timleline_mode()
437
438
439 _properties = ProjectProperties(self.gui.main_window,
440 self.project.props)
441 _properties.connect('project-changed', self._project_modified_cb)
442 _properties.run()
443
444 def _project_modified_cb(self, dialog, project_data) :
445 """ callback for modified data from project properties dialog """
446 self.is_modified = True
447 if self._old_mode == 'ACQUISITION' :
448 # go back to acqusition mode
449 self.acquisition_mode(True)
450 self._old_mode = None
451
452
453
454 def _start_project_load(self) :
455
456
457 # start rusher controller
458 _rush_ctrl = LRUSH.ControllerLoadRush( self.project,
459 self.gui.status_bar )
460 self._connect_to_rush(_rush_ctrl)
461
462 # start acquirer controller
463 if self.project.props['hardtype'] in \
464 (LCONST.FAKE, LCONST.DVCAM, LCONST.WEBCAM ) :
465 # start acquisition controller
466 #self.acq_ctrl = CTRL_ACQ.CtrlAcq(self)
467 self.debug('Acquirer Object loaded %s', self.ctrl_acq)
468 else :
469 # No acquisition (only import and timeline)
470 self._mode = "TIMELINE"
471
472 # start timeline controller
473 #self.ctrl_tmln = CTRL_TMLN.CtrlTimeline(self)
474
475
476 def _connect_to_rush(self, rush_ctrl) :
477 rush_ctrl.connect('rush-loaded', self._rush_load_finish_cb)
478
479 def _disconnect_from_rush(self, rush_ctrl) :
480 rush_ctrl.disconnect_by_function( self._rush_load_finish_cb)
481
482 def _rush_load_finish_cb(self, rush_ctrl, rush_obj) :
483 self.debug(" Rush Load Finish :%s ", rush_obj.dump_image_name() )
484 if rush_obj != None :
485 self._terminate_project_load(rush_obj)
486 # Load rush is done only one time at load so disconnect it
487 self._disconnect_from_rush(rush_ctrl)
488
489 def _terminate_project_load(self, rush_obj) :
490 self.project.rush = rush_obj
491
492 # set project as not modified as it is just loaded
493 self.project.set_non_pjt_dpdt_props()
494
495 # set path for image to mix
496 self.project.props['image2mix'] =\
497 os.path.join(
498 self.project.props['project_dir'],
499 LCONST.IMAGE2MIX_NAME
500 )
501
502 # if a capture image exist ,
503 # mix with last one by copying it in 'image2mix'
504
505 if self.project.props['capture_images'] != []:
506 self.debug(' image 2 copy %s',
507 self.project.props['capture_images'][-1])
508 self._copy_to_image2mix(
509 self.project.props['capture_images'][-1])
510
511 self.debug("emit project-loaded : %s " , self.project)
512 self.emit('project-loaded', self.project )
513
514 def _connect_to_acq(self , ctrl_acq) :
515 ctrl_acq.connect('acquirer-ready', self._acquirer_ready_cb)
516 ctrl_acq.connect('acquirer-error', self._acquirer_error_cb)
517
518 def _disconnect_from_acq(self , ctrl_acq) :
519 ctrl_acq.disconnect_by_function( self._acquirer_ready_cb)
520 ctrl_acq.disconnect_by_function( self._acquirer_error_cb)
521
522
523 def _acquirer_ready_cb(self, ctrl_acq ) :
524 self.debug('RX acquirer-ready signal')
525 # TODO : work around on mode
526 # FIXME : set to image to force transtion to acquisition mode
527 # default mode when project is started
528 self._mode = 'IMAGE_VIEW'
529 self.acquisition_mode()
530
531
532
533 def _acquirer_error_cb(self, ctrl_acq, msg) :
534 self.error(msg)
535
536 def _connect_to_timeline(self, ctrl_tmln) :
537 ctrl_tmln.connect('timeline-loaded', self._timeline_loaded_cb)
538
539 def _disconnect_from_timeline(self, ctrl_tmln) :
540 ctrl_tmln.disconnect_from_project(self)
541 ctrl_tmln.disconnect_by_function(self._timeline_loaded_cb)
542
543 def _timeline_loaded_cb(self, ctrl_tmln):
298 pass544 pass
299545 #self.project.timeline = timeline
300 def __load_project_in_app(self) :546
301 """ load project data in luciole :547
302 1. Load rusher548 def _copy_to_image2mix(self, image_name) :
303 2. when rusher done a callback is generated. 549
304 handled in main controller. Main controller550 _source = os.path.join( self.project.props['project_dir'],
305 load the acquistion object, and display551 self.project.props['rush_dir'],
306 project in gui552 image_name)
307 """553 try :
308 if self.__data != None :554 LT.copyf(_source, self.project.props['image2mix'])
309 # set modified attibute 555 except LEXCEP.LucioException, err :
310 self.__data['is_modified'] = False556 self.error('impossible to copy %s in %s -- %s ',
311 557 _source,
312 # set set mode as PROJECT LOADED558 self.project.props['image2mix'],
313 self.__mode = CTRL_CONST.PROJECT_LOADED559 err)
314
315 self.logger.\
316 debug("-----------------------------------------------------")
317 self.logger.debug("Luciole_controller project info: ")
318 self.logger.\
319 debug("-----------------------------------------------------")
320 for _key, _value in self.__data.iteritems() :
321 self.logger.debug("**%s** : %s "%(_key, _value))
322 self.logger.\
323 debug("-----------------------------------------------------")
324
325 # Initilaisation of rush obj is threaded because its take a while
326 # (generation of images pixbufs)
327 # When rush load is finish : __on_rush_finish is called
328 LRUSH.ControllerLoadRush( self.__data,
329 self.__gui.status_bar,
330 self.__cbs['done-rush'])
331
332
333 def __change_project(self, project_key, data) :
334 """ function called to update the current project """
335 if self.__data != None :
336 # is key exist ?
337 if self.__data.has_key(project_key) :
338 # update only on change
339 if self.__data[project_key] != data :
340 self.__data[project_key] = data
341 self.__data['is_modified'] = True
342 self.__gui.set_program_bar( self.__data['project_name'],
343 self.__data['is_modified'])
344 else :
345 #raise Error/Exception
346 _err_msg = "key %s not in project " % project_key
347 raise LEXCEP.LucioException, _err_msg
348 else :560 else :
349 # raise Error/Exception 561 self.debug(' copied %s in %s',
350 _err_msg = " Project not loaded "562 _source,
351 raise LEXCEP.LucioException, _err_msg563 self.project.props['image2mix'])
352 564
353565
354566
355567
356568
=== added file 'luciole/ctrl/ctrl_timeline.py'
--- luciole/ctrl/ctrl_timeline.py 1970-01-01 00:00:00 +0000
+++ luciole/ctrl/ctrl_timeline.py 2011-02-28 13:21:06 +0000
@@ -0,0 +1,953 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# -*- Mode: Python -*-
4# vim:si:ai:et:sw=4:sts=4:ts=4
5#
6#
7# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010
8#
9# This file is part of Luciole.
10#
11# Luciole is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# Luciole is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with Luciole. If not, see <http://www.gnu.org/licenses/>.
23#
24#
25"""
26ctrl_acq.py :
27 manages acquisition
28"""
29# standard library imports
30import os.path
31import urllib
32import gettext
33_ = gettext.gettext
34
35# related third party imports
36import gst
37import copy
38import gobject
39
40from pitivi.signalgroup import SignalGroup
41from pitivi.pipeline import Pipeline
42from pitivi.action import ViewAction
43from pitivi.timeline.timeline import Timeline, TimelineObject, TimelineError
44
45from pitivi.factories.timeline import TimelineSourceFactory
46
47from pitivi.stream import AudioStream, VideoStream
48from pitivi.settings import GlobalSettings
49from pitivi.settings import ExportSettings
50from pitivi.sourcelist import SourceList
51from pitivi.timeline.track import Track, SourceTrackObject
52
53from pitivi.utils import Seeker, getNextObject
54# local application/library specific imports
55from luciole.ctrl.base import ProjectMode
56import luciole.base.tools as LT
57import luciole.constants as LCONST
58import luciole.base.exceptions as LEXCEP
59
60class LucioleTimeline(Timeline) :
61 DEFAULT_DURATION = 1 * gst.SECOND
62
63 def __init__(self) :
64 Timeline.__init__(self)
65 # TODO : Temporary usage of GlobalSettings
66 self.settings = GlobalSettings()
67 self.default_duration = self.DEFAULT_DURATION
68
69 def change_order(self, images_list, position_dest) :
70 self.info("order changed move images %s at position %s",
71 images_list,
72 position_dest)
73
74 _tl_objects_to_move = [self.video_objects[_elem[0]] for _elem in images_list]
75 self.debug( " Objs to move :%s", [ obj.factory.name for obj in _tl_objects_to_move])
76
77 if position_dest[0] < len(self.video_objects ) :
78 # Move images inside sequence
79 _tl_obj_dest = self.video_objects[position_dest[0]]
80
81 for tl_obj in _tl_objects_to_move :
82
83 self.info(" Moving image %s (position %s) before %s (position : %s)",\
84 tl_obj.factory.name,
85 self.video_objects.index(tl_obj),
86 _tl_obj_dest.factory.name,
87 self.video_objects.index(_tl_obj_dest))
88
89 _tl_obj_tmp = self._move_timeline_object(tl_obj,_tl_obj_dest)
90 _tl_obj_dest = \
91 self.video_objects\
92 [self.video_objects.index(_tl_obj_tmp) + 1]
93
94 else :
95 # move images at the end of sequnce
96 for tl_obj in _tl_objects_to_move :
97 self.info(" Moving image %s (position %s) at end of sequence",\
98 tl_obj.factory.name,
99 self.video_objects.index(tl_obj) )
100 self._move_at_end(tl_obj)
101
102
103 def insert_factories(self, factories, position_dest) :
104 self.info("images to insert %s at position %s",
105 factories, position_dest)
106
107 if position_dest[0] < len(self.video_objects ) :
108 self._insert_factories_at_position( position_dest[0], factories)
109 else :
110 for _factory in factories :
111 # TODO : verify if append merhod usage is correct. regarding
112 # _insert_factories_at_position method (i.e. tl_obj creation and
113 # disable/enable timeleine methods)
114 self._append_obj(_factory)
115
116
117
118
119 def remove_images(self, images_list) :
120 self.info("images to remove on timeline %s", images_list)
121 _tl_objs_to_remove = \
122 [self.video_objects[_elem[0]] for _elem in images_list]
123
124 for tl_obj in _tl_objs_to_remove :
125 self._remove_object(tl_obj)
126
127
128
129
130
131 def create_timeline_object(self, factory) :
132
133 _stream_map = self._getSourceFactoryStreamMap(factory)
134 _timeline_object = TimelineObject(factory)
135 for stream, track in _stream_map.iteritems():
136 track_object = SourceTrackObject(factory, stream)
137 track.addTrackObject(track_object)
138 self.info("Stream: %s, Track: %s, Track duration: %d", str(stream),
139 str(track), track.duration)
140
141 _timeline_object.addTrackObject(track_object)
142 _timeline_object.start = 0
143 return _timeline_object
144
145
146
147
148
149 def add_sound_track(self, audio_factory, duration, is_video_duration = True, audio_offset = 0,) :
150 # TODO : only a offset of 0 is vlaid. with ofsset !=0 --> pieleine error
151 audio_offset = 0
152 # TODO : undestand better disable/ Enable updates.
153 # the usage od disable/enable avoid a fade transition at start ?
154 _vtrack = self._get_video_track()
155 self.debug('Video track duration %s, ', _vtrack.duration)
156 if is_video_duration :
157 _duration = _vtrack.duration
158 else :
159 _duration = duration
160
161 self.debug('Sound track %s, duration %s, audio offest %s',
162 audio_factory.name, _duration, audio_offset)
163
164 #self.disableUpdates()
165 _tl_obj = self.create_timeline_object(audio_factory)
166 _tl_obj.start = audio_offset
167 _tl_obj.duration = _duration
168 self.debug('timeline object duration = %s', _tl_obj.duration)
169 self.addTimelineObject( _tl_obj)
170 #self.enableUpdates()
171 return _tl_obj
172
173 def remove_sound_track(self, factory) :
174 # remove factory from timeline
175 self.removeFactory(factory)
176 # update duration
177 _vtrack = self._get_video_track()
178 if _vtrack.duration != self.duration :
179 self.duration = _vtrack.duration
180
181
182
183
184
185
186
187
188
189 def update_timeline_duration(self, duration, sound_stop_with_video = True ) :
190
191
192 # copy objects for vaoiding update problems
193 _video_objs = copy.copy(self.video_objects)
194 self.debug('New duration %s image to update %s', duration, [ _obj.factory.name for _obj in _video_objs ] )
195
196 _old_duration = _video_objs[0].duration
197 # check type of image duration update
198 # if duration update increase, start update by the last image
199 # in timeline (right to left order)
200 # if duration update decrease, start update fu the first image
201 # in timeline (left to riht order )
202
203 _reverse = False
204 if duration > _old_duration :
205 # new duration > old duration
206 # start update of images in reverse order
207 _start = (len(_video_objs) -1) * duration
208 _video_objs.reverse()
209 _reverse = True
210 else :
211 # new duration < old duration
212 _start = 0
213
214 for _obj in _video_objs :
215
216 self.debug('Before Update %s : start %s duration %s, factory duration %s',
217 _obj.factory.name,
218 _obj.start,
219 _obj.duration,
220 _obj.factory.duration)
221
222 _obj.setDuration(duration, True)
223 _obj.setStart(_start, True)
224 if _reverse == True :
225 _start = _start - duration
226 else :
227 _start = _start + duration
228
229 self.debug('Updated %s : start %s duration %s ',
230 _obj.factory.name,
231 _obj._getStart(),
232 _obj._getDuration())
233
234 _total_duration = len(_video_objs) * duration
235 self.debug(' Total duration %s', _total_duration)
236 if sound_stop_with_video == True :
237 _obj = self._get_audio_object()
238 #TODO ; check case of audio track duration < images duration
239 if _obj is not None :
240 _obj.setDuration(_total_duration, True, True)
241 self.debug('NEW audio %s track duration %s factory duration %s',_obj, _obj.duration, _obj.factory.duration)
242 self.debug('NEW timeline duration %s', self.duration )
243
244
245 def _move_timeline_object(self, tl_src, tl_dest) :
246 _idx_src = self.video_objects.index(tl_src)
247 _idx_dest = self.video_objects.index(tl_dest)
248 # select objects to shift on timeline
249 if _idx_src < _idx_dest :
250 _objs_to_move = self.video_objects[_idx_src+1:_idx_dest]
251 _duration = -1 * tl_src.duration
252 #set start duration of src before dest
253 tl_src.start = self.video_objects[_idx_dest -1].start
254 else :
255 _objs_to_move = self.video_objects[_idx_dest:_idx_src]
256 _duration = 1 * tl_src.duration
257 #set start duration at dest
258 tl_src.start = self.video_objects[_idx_dest].start
259
260 # shift objects to move
261 self._shift_object(_objs_to_move, _duration)
262
263 return tl_src
264
265 def _move_at_end(self, tl_src):
266
267 # get objecs to move
268 _objs_to_move = self._getVideoObjsAfter(tl_src)
269
270 # set last postion fo tl_src
271 tl_src.start = self.video_objects[-1].start
272
273 # shift back the objects to move
274 _duration = -1*tl_src.duration
275 self._shift_object(_objs_to_move, _duration)
276
277 def _move_object(self, pos_src, pos_dest) :
278
279 if pos_src == pos_dest :
280 # nothing to move
281 return
282 _pos_dest = self.video_objects[pos_dest].start
283 _obj_src = self.video_objects[pos_src]
284 # select objects to move
285 if pos_src > pos_dest :
286 _objs_to_move = self.video_objects[pos_dest:pos_src]
287 _duration = 1 * _obj_src.duration
288 else :
289 _objs_to_move = self.video_objects[pos_src+1:pos_dest+1]
290 _duration = -1 * _obj_src.duration
291 # shift objects to move
292 self._shift_object(_objs_to_move, _duration)
293
294 # move object at despoistion
295 _obj_src.start = _pos_dest
296
297 def _get_track_objects(self, objs, track) :
298 track_objs = []
299 for _obj in objs :
300 if track == _obj.track_objects[0].track :
301 track_objs.append(_obj)
302
303 return track_objs
304
305
306
307
308
309 def _insert_factories_at_position(self, position, factories_to_insert) :
310 # TODO : undestand better disable/ Enable updates.
311 # the usage od disable/enable avoid a fade transition between images: Why ?
312 self.disableUpdates()
313 _timeline_objects = []
314 _position_obj = self.video_objects[position]
315 self.debug( "_position_obj :%s", _position_obj.factory.name)
316 _objs_after = self._getVideoObjsAfter(_position_obj)
317 _objs_after.insert(0, _position_obj)
318
319 _dur = _position_obj.start
320
321 for _factory in factories_to_insert :
322 _timeline_obect = self.create_timeline_object(_factory)
323 _timeline_objects.append(_timeline_obect)
324
325 _insert_duration = self._calc_objs_duration(_timeline_objects)
326 self._shift_object(_objs_after, _insert_duration)
327
328 for _tl_obj in _timeline_objects :
329 _tl_obj.start = _dur
330 _dur += _tl_obj.duration
331 self.addTimelineObject( _tl_obj)
332
333 self.enableUpdates()
334
335 def _calc_objs_duration(self, objs_to_insert) :
336 _duration = 0
337 for _obj in objs_to_insert :
338 _duration += _obj.duration
339 self.info("shift duration = %s", _duration)
340 return _duration
341
342 def _shift_object(self, timeline_objects, duration) :
343 for _obj in timeline_objects :
344 self.debug("%s", _obj.factory.name)
345 _obj.start += duration
346 self.info ( "%s timeline objects shifted from %s"%(len(timeline_objects), duration))
347
348 def _append_obj(self, factory):
349
350 timeline_object = self.addSourceFactory(factory)
351 self.info("appended %s"%timeline_object)
352 return timeline_object
353
354 def _remove_object(self, tl_obj) :
355 _objs_after = self._getVideoObjsAfter(tl_obj)
356 _duration = -1* tl_obj.duration
357 self.debug("obj to remove %s ", tl_obj)
358 _vtrack = self._get_video_track()
359 _vtrack.removeTrackObject(tl_obj)
360
361 # update start point of objects after deleted one
362 self._shift_object(_objs_after, _duration)
363
364
365 def _get_video_track(self) :
366 for track in self.tracks:
367 if isinstance(track.stream, VideoStream) :
368 return track
369
370 def _get_audio_track(self):
371 for track in self.tracks:
372 if isinstance(track.stream, AudioStream) :
373 return track
374
375
376 def _get_video_track_objects(self) :
377 _vtrack = self._get_video_track()
378
379 _res = _vtrack.track_objects
380 #self.debug("Video Track objects : %s",
381 # [ obj.factory.name for obj in _res])
382
383 return _res
384 video_objects = property(_get_video_track_objects)
385
386 def _get_audio_object(self) :
387 _audio_obj = []
388 _res = None
389 for _obj in self.timeline_objects :
390 if isinstance(_obj.track_objects[0].stream, AudioStream) :
391 _audio_obj.append(_obj)
392
393 if len(_audio_obj) > 1 :
394 raise TimelineError(" More then one audio stream in timeline")
395 elif len(_audio_obj) == 1 :
396 _res = _audio_obj[0]
397
398
399 return _res
400 audio_object = property(_get_audio_object)
401
402
403 def _getVideoObjsAfter(self, timeline_object) :
404 """
405 get video objects after timeline objects
406 This function retrieves objects only from video track
407 """
408 objects = []
409 priority= None
410 if timeline_object in self.video_objects :
411 _res = getNextObject(timeline_object, self.video_objects, priority)
412 while _res is not None :
413 objects.append(_res)
414 _res = getNextObject(_res, self.video_objects, priority)
415 else :
416 self.error('timeline_object %s not in video_track',
417 timeline_object.factory.name)
418
419 self.debug(" video objs after %s : %s",
420 timeline_object.factory.name,
421 [ obj.factory.name for obj in objects])
422 return objects
423
424 def _get_image_sequence(self) :
425 _list = [ _obj.factory.name for _obj in self.video_objects]
426 return _list
427
428
429
430
431class ProjectModeTimeline(ProjectMode):
432 __signals__ = {
433 'timeline-loaded' : ['timeline'] ,
434 'sound-props-changed' : ['active', 'stop_with_video'],
435 }
436
437 _25_FPS = (1 * gst.SECOND)/25
438
439 def __init__(self, ctrl_project) :
440 ProjectMode.__init__(self, ctrl_project, "TIMELINE")
441
442 self.acq_signals = SignalGroup()
443 self.timeline_signals = SignalGroup()
444 self.debug('ProjectModeTimeline initialized ')
445 self._is_stop_sound_with_video = False
446
447 # prepare connection to acquisiton mode
448 self._connect_to_ctrl_acq(self.ctrl_project.ctrl_acq)
449
450 def prepare(self, project) :
451 ProjectMode.prepare(self, project)
452
453 # prepare sources
454 self.sources = SourceList()
455 self.sources.connect("source-added", self._sourceAddedCb)
456 self.sources.connect("source-removed", self._sourceRemovedCb)
457 self.uris = []
458
459
460
461 # prepare timeline
462 self.timeline = LucioleTimeline()
463 self._timeline_factory = TimelineSourceFactory(self.timeline)
464
465 # prepare pipeline and resource
466 self._pipeline = Pipeline()
467 # TODO : connect to pipeline signals ?
468 #self._pipeline.connect('eos', self._eos_cb)
469
470 self._view_action = ViewAction()
471 self._view_action.addProducers(self._timeline_factory)
472 self._pipeline.addAction(self._view_action)
473 # why seeker ?
474 self.seeker = Seeker(80)
475
476 #
477 # initialize video settings
478 #
479 settings = self.getAutoSettings()
480 self._videocaps = settings.getVideoCaps()
481
482 # fill timeline with audio and video track
483 video = VideoStream(gst.Caps(settings.getVideoCaps()))
484 track = Track(video)
485 self.timeline.addTrack(track)
486 audio = AudioStream(gst.Caps(settings.getAudioCaps()))
487 track = Track(audio)
488 self.timeline.addTrack(track)
489
490
491 self._load_timeline(self.project)
492
493 self.debug("Prepare Timeline : %s", self.timeline )
494
495
496 def release(self) :
497 self._mode = "INVALID"
498 self._pipeline.release()
499 self._pipeline = None
500 del self._timeline_factory
501 del self.timeline
502 self._view_action = None
503
504
505
506 def active_viewer(self) :
507 if self._viewer :
508 self.debug(" Timeline :%s, timeline duration :%s ", self.timeline, self.timeline.duration)
509
510 self._viewer.setPipeline(None)
511 self._viewer.showSlider()
512 self._viewer.setAction(self._view_action)
513 self._viewer.setPipeline(self._pipeline, "TIMELINE")
514
515 # chnage pipeline status to activate it ( seek, duration)
516 self._pipeline.pause()
517 self.debug("Pipeline state : %s", self._pipeline.getState())
518 self._viewer.prepare_viewer(self.timeline.duration)
519 # FIXME : hack to make the gtk.HScale seek slider UI behave properly
520 #self._viewer._durationChangedCb(None, self.timeline.duration)
521 self.debug("actual sequence %s", [_obj.factory.name for _obj in self.timeline.video_objects])
522
523 else :
524 self.error("No viewer defined")
525
526 def change_order(self, images, target) :
527 self.debug('images : %s , target :%s', images, target)
528 self.timeline.change_order(images, target)
529 # update project
530 self._update_project()
531
532 def insert(self, images, target) :
533 self.debug('images : %s , target :%s', images, target)
534 # get factories from source
535 _factories = [ self._get_factory(_elem) for _elem in images ]
536 # insert factories on timeline
537 self.timeline.insert_factories(_factories, target)
538 # update project
539 self._update_project()
540
541 def remove(self, images) :
542 self.debug('images : %s' , images)
543 self.timeline.remove_images(images)
544
545 # update project
546 self._update_project()
547
548 def _update_project(self) :
549 """ update project properties according image sequence """
550 _img_seq = self.timeline._get_image_sequence()
551 self.info('Image sequence : %s', _img_seq)
552 # update project according image sequence
553 self.project.props['chrono_images'] =_img_seq
554 self.ctrl_project.is_modified = True
555
556
557 def add_sound_track(self, path) :
558 """ add a sound track to timeline """
559 if os.path.splitext(path)[1] != '.wav' :
560 _msg = _('Invalid sound file : %s. Only wav files allowed.' % path)
561 self.error(_msg)
562 self.ctrl_project.gui.status_bar.display_message(_msg)
563 return
564
565 self.debug('Add sound track : %s', path )
566 # check if a sound track is avlbl, if yes remove it
567 _dest_path = os.path.join( self.project.props['project_dir'],
568 LCONST.SOUND_TRACK_NAME)
569
570 # first remove current sound track from timeline and sources
571 self._remove_sound_track(True)
572
573
574
575
576 try :
577 LT.copyf(path, _dest_path)
578 except LEXCEP.LucioException, err:
579 self.error(err)
580 else :
581 self.AddSources([_dest_path])
582 self.ctrl_project.is_modified = True
583 self.project.props['sound']['name'] = LCONST.SOUND_TRACK_NAME
584 self._update_sound_props(self.project.props, active='no')
585 _msg = _('Sound track %s loaded in project'% path )
586 self.info(_msg)
587 self.ctrl_project.gui.status_bar.display_message(_msg)
588
589
590
591 def go_to_position(self, position) :
592
593 self._viewer.seek(position*self.default_duration)
594
595 def update_image_duration(self , fpi ) :
596 self.default_duration = self._25_FPS * fpi
597 # upadte image factory duration
598 for _factory in self.sources.getSources() :
599 if _factory.name == LCONST.SOUND_TRACK_NAME :
600 # dont update sound track duration
601 continue
602 _factory.duration = self.default_duration
603 self.debug('Updated duration (%s) of factory %s',
604 _factory.duration,
605 _factory.name)
606
607
608 self.timeline.update_timeline_duration(self.default_duration, self._is_stop_sound_with_video)
609
610
611
612 def _remove_sound_track(self, force = True) :
613 """
614 if force = True --> rm from timeline and source list
615 if force = False --> rm from timeline only
616 """
617 _factory = self._get_factory(LCONST.SOUND_TRACK_NAME)
618 if _factory is None :
619 # nothing to suppress
620 self.debug('No factory to suppress')
621 return
622
623 if self.project.props['sound']['active'] == 'yes' :
624 self.timeline.remove_sound_track(_factory)
625 # disconnect callback who updates audio duration regarding
626 # video duration
627 try :
628 self.timeline.disconnect_by_function(self._video_track_added)
629 except :
630 #TODO : what we do ?
631 pass
632 if force == True :
633 self.sources.removeUri(_factory.uri)
634
635 self._is_stop_sound_with_video = False
636
637
638
639
640 def add_sound_track_to_timeline(self) :
641 _factory = self._get_factory(LCONST.SOUND_TRACK_NAME)
642 if _factory :
643 self.debug('Sound Factory %s, %r , duration %s',
644 _factory.name,
645 _factory,
646 _factory.duration)
647 self._is_stop_sound_with_video = False
648
649 _tl_obj = self.timeline.add_sound_track(
650 _factory,
651 _factory.duration,
652 self._is_stop_sound_with_video,
653 0)
654 self.timeline.connect('timeline-object-added', self._video_track_added, _tl_obj)
655 self.debug('Timeline duration : %s', self.timeline.duration)
656 self.debug('Pipeline duration : %s', self._pipeline.getDuration())
657 else :
658 self.warning ("Sound factory does not exists")
659
660 self._update_sound_props(self.project.props,
661 active ='yes',
662 stop_with_video = 'no'
663 )
664
665
666
667
668 def remove_sound_track_from_timeline(self) :
669 _factory = self._get_factory(LCONST.SOUND_TRACK_NAME)
670 if _factory :
671 self.debug('remove Sound Factory %s from timeline', _factory.name)
672 self.timeline.removeFactory(_factory)
673 self.timeline.disconnect_by_function(self._video_track_added)
674 else :
675 self.info('No sound track to remove')
676
677 self._update_sound_props(self.project.props, active ='no')
678
679
680
681 def _video_track_added(self, timeline , timeline_object , audio_object) :
682 if timeline_object.track_objects[0] in timeline.video_objects :
683 # a video object is added
684 _vtrack = timeline._get_video_track()
685 self.debug('video track duration %s , object added %s , audio_object duration :%s ',
686 _vtrack.duration, timeline_object.factory.name, audio_object.duration)
687
688 if _vtrack.duration > audio_object.duration :
689 audio_object.duration = _vtrack.duration
690 self.debug ('Audio object duration updated')
691 else :
692 self.info('timeline _object %s not a video track', timeline_object.factory.name)
693
694 def stop_sound_with_video(self, is_stop = True) :
695 self._is_stop_sound_with_video = is_stop
696 # check if audio_track yet in timeline
697 try :
698 _obj = self.timeline.audio_object
699 except TimelineError, err :
700 self.error('Timeline error : %s',err)
701 else :
702 self.debug("Update duration")
703 if _obj :
704 if self._is_stop_sound_with_video :
705 _vtrack = self.timeline._get_video_track()
706 _duration = _vtrack.duration
707 _stop_with_video = 'yes'
708
709 else :
710 self.debug('Set audio track duration')
711 _factory = self._get_factory(LCONST.SOUND_TRACK_NAME)
712 _duration = _factory.duration
713 _stop_with_video = 'no'
714
715 self._update_sound_props(self.project.props,
716 stop_with_video = _stop_with_video)
717 _obj.duration = _duration
718 self.debug("timeline duration %s", self.timeline.duration)
719
720
721
722 def _connect_to_ctrl_acq(self, ctrl_acq) :
723 self.debug(" Connecting to ProjectModeAcquisition ")
724 self.acq_signals.connect(
725 ctrl_acq,'snapshot-taken', None, self._snapshot_taken_cb)
726
727
728 def _snapshot_taken_cb(self, ctrl_acq, rush_image) :
729 self.debug('RX signal snapshot-taken : %s', rush_image.name)
730 #TODO : set the correct duration
731 self.AddSources([rush_image.path] )
732
733 def _load_timeline(self, project) :
734 _project = project.props
735 self.debug('rush images to load : %s', _project['capture_images'] )
736 _sources = \
737 [ os.path.join( _project['project_dir'],
738 _project['rush_dir'],
739 _image) \
740 for _image in _project['capture_images'] ]
741 _image_duration = self._25_FPS * int(_project['fpi'])
742 uris = ["file://" + urllib.quote(os.path.abspath(path))\
743 for path in _sources]
744
745 # check if sound track active
746 if _project['sound']['active'] == 'yes' :
747 # check if sound track really present
748 _sound_track = os.path.join(
749 _project['project_dir'],
750 LCONST.SOUND_TRACK_NAME)
751 if os.path.exists(_sound_track) :
752 _uri = "file://" + urllib.quote(os.path.abspath(_sound_track))
753 uris.append(_uri)
754 else :
755 self.error(
756 'Sound track active but no sound file present (%s)' %
757 _sound_track)
758
759 # discover the factories
760 closure = {"rediscovered": 0}
761 discoverer = self.sources.discoverer
762 discoverer.connect("discovery-done", self._discovererDiscoveryDoneCb,
763 _project, _image_duration, uris, closure)
764 discoverer.connect("discovery-error", self._discovererDiscoveryErrorCb,
765 _project, _image_duration, uris, closure)
766
767 # start discoverer
768
769 self.sources.addUris(uris)
770 self.default_duration = _image_duration
771
772
773 def _finishLoadingProject(self, project, duration) :
774 _start = 0
775 self.debug ('project %s', project)
776 for _image in project['chrono_images'] :
777 _factory = self._get_factory(_image)
778 _factory.default_duration = duration
779 _tl_obj = self.timeline.create_timeline_object(_factory)
780 _tl_obj.start = _start
781 _tl_obj.duration = duration
782 _start += duration
783 self.timeline.addTimelineObject( _tl_obj)
784 if project['sound']['active'] == 'yes' :
785 _factory = self._get_factory(LCONST.SOUND_TRACK_NAME)
786 if _factory :
787
788 self.debug('Sound Factory %s, %r , duration %s',
789 _factory.name,
790 _factory,
791 _factory.duration)
792 self._is_stop_sound_with_video = False
793 if project['sound']['stop_with_video'] == 'yes' :
794 self._is_stop_sound_with_video = True
795 _tl_obj = self.timeline.add_sound_track(
796 _factory,
797 _factory.duration,
798 self._is_stop_sound_with_video,
799 0)
800 self.timeline.connect('timeline-object-added', self._video_track_added, _tl_obj)
801 # force emit of project sound props
802 self._update_sound_props(
803 project = project,
804 active = project['sound']['active'],
805 stop_with_video = project['sound']['stop_with_video'])
806
807
808 self.debug('Emit timeline-loaded')
809 self.emit('timeline-loaded')
810
811
812
813 def _update_sound_props(self, project, active = None, stop_with_video = None) :
814 _is_modified = False
815 _is_active = None
816 _is_stop_with_video = None
817 if project is None :
818 self.error("No project defined")
819 return
820
821 if active is not None :
822 if active not in ('yes','no') :
823 self.error('invalid value for prop active (%s)',
824 active)
825 return
826 if active != project['sound']['active'] :
827 project['sound']['active'] = active
828 _is_modified = True
829
830 if active == 'yes' :
831 _is_active = True
832 else :
833 _is_active = False
834 if stop_with_video :
835 if stop_with_video not in ('yes','no') :
836 self.error('invalid value for prop active (%s)',
837 stop_with_video)
838 return
839
840 if stop_with_video != project['sound']['stop_with_video'] :
841 project['sound']['stop_with_video'] = stop_with_video
842 _is_modified = True
843 if stop_with_video == 'yes' :
844 _is_stop_with_video = True
845 else :
846 _is_stop_with_video = False
847
848 self.debug('sound is_active :%s is_stop_with_video : %s, project %s',
849 active,
850 stop_with_video,
851 project['sound'] )
852
853 if _is_modified == True :
854 self.ctrl_project.is_modified = True
855
856 if _is_stop_with_video is not None \
857 or \
858 _is_active is not None :
859 self.debug('Emit sound-props-changed : active %s stop_with_video %s',
860 _is_active, _is_stop_with_video )
861 self.emit('sound-props-changed', _is_active, _is_stop_with_video)
862
863
864
865 # Sources associated functions
866 #
867 def AddSources(self, file_list) :
868
869 uris = ["file://" + urllib.quote(os.path.abspath(path))\
870 for path in file_list]
871 self.uris = uris
872 self.debug("Sources to add %s", uris)
873 # add uris
874 self.sources.addUris(uris)
875
876 def _discovererDiscoveryDoneCb(self, discoverer, uri, factory,
877 project , duration, uris, closure):
878 self.debug("discovery done for %s",factory.name)
879 if factory.uri not in uris:
880 # someone else is using discoverer, this signal isn't for us
881 return
882
883 # update duration of image factory
884 if factory.name != LCONST.SOUND_TRACK_NAME :
885 factory.duration = self.default_duration
886
887
888 if factory not in self.sources.getSources() :
889 self.debug("Factory duration :%s", factory.duration)
890 self.sources.addFactory(factory)
891
892 closure["rediscovered"] += 1
893 if closure["rediscovered"] == len(uris):
894 self._finishLoadingProject(project, duration)
895
896 def _discovererDiscoveryErrorCb(self, discoverer, uri, factory,
897 project, duration, uris, closure):
898 self.warning('Impossible to load %s'%uri)
899
900 def _get_factory(self, image_name) :
901 _factory = None
902 for _source in self.sources.getSources() :
903 if _source.name == image_name :
904 _factory = _source
905 break
906 if _factory == None :
907 self.error("No factory found for %s", image_name)
908 return _factory
909
910 def _sourceAddedCb(self, sourcelist, factory):
911 #self.info("source added to timeline : %s %s"%(sourcelist, factory))
912 factory.setFilterCaps(self._videocaps)
913 factory.default_duration = self.default_duration
914 # #timeline_object = self.timeline.addSourceFactory(factory)
915 #self.info("Object %s starts at %s", factory.name, timeline_object.start)
916 self.debug("RX source-added Source %s added -- %s", factory.name, factory)
917
918 def _sourceRemovedCb(self, sourcelist, uri, factory):
919 self.timeline.removeFactory(factory)
920 self.info('Source %s removed call back'%uri)
921 # TODO : emit source-removed ?
922 #self.emit('source-removed', uri, factory)
923
924 def getAutoSettings(self):
925 """
926 Computes and returns smart settings for the project.
927 If the project only has one source, it will be that source's settings.
928 If it has more than one, it will return the largest setting that suits
929 all contained sources.
930 """
931 settings = ExportSettings()
932 if not self.timeline:
933 self.warning\
934 ("project doesn't have a timeline, returning default settings")
935 return settings
936
937 # FIXME: this is ugly, but rendering for now assumes at most one audio
938 # and one video tracks
939 have_audio = have_video = False
940 for track in self.timeline.tracks:
941 if isinstance(track.stream, VideoStream) and track.duration != 0:
942 have_video = True
943 elif isinstance(track.stream, AudioStream) and track.duration != 0:
944 have_audio = True
945
946 if not have_audio:
947 settings.aencoder = None
948
949 if not have_video:
950 settings.vencoder = None
951
952 return settings
953
0954
=== modified file 'luciole/ctrl/import_image.py'
--- luciole/ctrl/import_image.py 2010-09-04 15:17:48 +0000
+++ luciole/ctrl/import_image.py 2011-02-28 13:21:06 +0000
@@ -36,6 +36,9 @@
36# N/A36# N/A
37import gobject37import gobject
3838
39from pitivi.signalinterface import Signallable
40from pitivi.log.loggable import Loggable
41
39# local application/library specific imports42# local application/library specific imports
40import luciole.base.exceptions as LEXCEP43import luciole.base.exceptions as LEXCEP
41import luciole.base.tools as LT44import luciole.base.tools as LT
@@ -62,6 +65,7 @@
62 """ run thread """65 """ run thread """
63 # create rush list object66 # create rush list object
64 _image_objs = []67 _image_objs = []
68
65 for (_index, _filename) in enumerate(self.__filenames) :69 for (_index, _filename) in enumerate(self.__filenames) :
66 # copy image to rush folder and resize it70 # copy image to rush folder and resize it
67 try : 71 try :
@@ -116,8 +120,11 @@
116 LT.copyf(p_image_path, _ac_image_temp)120 LT.copyf(p_image_path, _ac_image_temp)
117121
118 # 2. resize image result is in _ac_image_temp_rz122 # 2. resize image result is in _ac_image_temp_rz
119 _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz )123 if self.__pjt_data['resize'] == 'yes' :
120 _rz_obj.convert()124 _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz )
125 _rz_obj.convert()
126 else :
127 _ac_image_temp_rz = _ac_image_temp
121 128
122 # 3. move resized image to rush dire 129 # 3. move resized image to rush dire
123 LT.movef(_ac_image_temp_rz, _rush_image)130 LT.movef(_ac_image_temp_rz, _rush_image)
@@ -127,7 +134,7 @@
127 return _basename134 return _basename
128 135
129136
130class ImportController(object):137class ImportController(Signallable, Loggable):
131 """ This class is used to manage the import of image in project .138 """ This class is used to manage the import of image in project .
132 The class use a Thread for importing images (it can takes) long.139 The class use a Thread for importing images (it can takes) long.
133 The thread and this class communicate via a queue.140 The thread and this class communicate via a queue.
@@ -135,17 +142,21 @@
135 The queue is checked cyclicly via a gobject timer142 The queue is checked cyclicly via a gobject timer
136 """143 """
137 _TIMEOUT = 50 # 50 ms timer144 _TIMEOUT = 50 # 50 ms timer
138145 __signals__ = {
139 def __init__(self, filenames, project_data, rusher, status_bar, cbs ) : 146 'import-image-done' : ['image_name'],
147 }
148 def __init__(self, filenames, project_data, rusher, status_bar) :
140 """ Init Thread, init gobject timer and clear progressbar """149 """ Init Thread, init gobject timer and clear progressbar """
150 Signallable.__init__(self)
151 Loggable.__init__(self)
141 self.__filenames = filenames152 self.__filenames = filenames
142 self.__pjt_data = project_data153 self.__pjt_data = project_data
143 self.__rusher = rusher154 self.__rusher = rusher
144 self.__status_bar = status_bar155 self.__status_bar = status_bar
145 self.__cbs = cbs
146156
147 157
148 # initialize thread158 # initialize thread
159 self.debug('Starting thread')
149 self.queue = Queue.Queue()160 self.queue = Queue.Queue()
150 self.t_rush = ImportThread(self.__filenames, 161 self.t_rush = ImportThread(self.__filenames,
151 self.__pjt_data,162 self.__pjt_data,
@@ -176,11 +187,15 @@
176 pass187 pass
177 else :188 else :
178 if _qmsg.has_key('progression') : 189 if _qmsg.has_key('progression') :
190
191 self.debug(' on progress')
179 self._progress_bar_on_progress(_qmsg['progression'])192 self._progress_bar_on_progress(_qmsg['progression'])
180 if _qmsg.has_key('finish') :193 if _qmsg.has_key('finish') :
181 # finisg project load ( Non threaded section )194 # finisg project load ( Non threaded section )
182 self.__cbs['done-import'](_qmsg['finish'])195 self.debug('emit import-image-done : %s',_qmsg['finish'])
183 196 self.emit('import-image-done', _qmsg['finish'])
197
198
184 #update as finsih staus/progress bard199 #update as finsih staus/progress bard
185 self._progress_bar_complete()200 self._progress_bar_complete()
186 # stop the timer201 # stop the timer
@@ -198,6 +213,7 @@
198 _msg = _('All images imported')213 _msg = _('All images imported')
199 # use of idle_add because gui update not in the same thread214 # use of idle_add because gui update not in the same thread
200 self.__status_bar.stop(_msg)215 self.__status_bar.stop(_msg)
216
201217
202 def _progress_bar_on_progress(self, ratio = None):218 def _progress_bar_on_progress(self, ratio = None):
203 """ indicate that import is going on """219 """ indicate that import is going on """
204220
=== modified file 'luciole/ctrl/load_rush.py'
--- luciole/ctrl/load_rush.py 2010-09-04 15:17:48 +0000
+++ luciole/ctrl/load_rush.py 2011-02-28 13:21:06 +0000
@@ -35,6 +35,7 @@
3535
36# related third party imports36# related third party imports
37import gobject37import gobject
38from pitivi.signalinterface import Signallable
3839
39# local application/library specific imports40# local application/library specific imports
40import luciole.media.image as LI41import luciole.media.image as LI
@@ -80,24 +81,27 @@
80 self.queue.put(_qmsg, block=False)81 self.queue.put(_qmsg, block=False)
8182
8283
83class ControllerLoadRush(object):84class ControllerLoadRush(Signallable):
84 """ This class is used to manage the load of a project in application.85 """ This class is used to manage the load of a project in application.
85 The class use a Thread for launch rush object init. (it can takes) long.86 The class use a Thread for launch rush object init. (it can takes) long.
86 The thread and this class communicate vai a queue. Information on queue are progression and finish.87 The thread and this class communicate vai a queue. Information on queue are progression and finish.
87 The queue is checked cyclicly via a gobject timer88 The queue is checked cyclicly via a gobject timer
88 """89 """
89 _TIMEOUT = 10 # 10 ms timer90 _TIMEOUT = 10 # 10 ms timer
91
92 __signals__ = {
93 "rush-loaded" : ["rush_obj"],
94 }
9095
91 def __init__(self, project, status_bar, cb_on_finish) : 96 def __init__(self, project, status_bar) :
92 """ Init Thread, init gobject timer and clear progressbar """97 """ Init Thread, init gobject timer and clear progressbar """
93 self.project = project98 Signallable.__init__(self)
94 self._cb_on_finish = cb_on_finish 99 self.project = project.props
95
96 self.__status_bar = status_bar100 self.__status_bar = status_bar
97 101
98 # initialize thread102 # initialize thread
99 self.queue = Queue.Queue()103 self.queue = Queue.Queue()
100 self.t_rush = RushLoaderThread(project, self.queue)104 self.t_rush = RushLoaderThread(self.project, self.queue)
101 105
102 #clear progressbar106 #clear progressbar
103 self._progress_bar_clear()107 self._progress_bar_clear()
@@ -144,6 +148,7 @@
144148
145 def _progress_bar_complete(self):149 def _progress_bar_complete(self):
146 """ Progress bar full : Project loaded """150 """ Progress bar full : Project loaded """
151 # TODO : This message should not displayed here
147 _msg = _( 'Project %s is loaded')%self.project['project_name'] 152 _msg = _( 'Project %s is loaded')%self.project['project_name']
148 # use of idle_add because gui update not in the same thread153 # use of idle_add because gui update not in the same thread
149 self.__status_bar.stop(_msg)154 self.__status_bar.stop(_msg)
@@ -157,7 +162,7 @@
157 def _on_rush_finish(self, rush_obj):162 def _on_rush_finish(self, rush_obj):
158 """ Finish the load after rush generation """163 """ Finish the load after rush generation """
159 # call back to indicate finish at controller164 # call back to indicate finish at controller
160 self._cb_on_finish(rush_obj)165 self.emit("rush-loaded",rush_obj )
161 166
162 167
163168
164169
=== modified file 'luciole/ctrl/viewer_ctrl.py'
--- luciole/ctrl/viewer_ctrl.py 2010-09-06 09:00:55 +0000
+++ luciole/ctrl/viewer_ctrl.py 2011-02-28 13:21:06 +0000
@@ -225,7 +225,14 @@
225 # send error message225 # send error message
226 _msg = _("Can not play animation : No image on montage view ")226 _msg = _("Can not play animation : No image on montage view ")
227 self.__gui.window.error_message(_msg)227 self.__gui.window.error_message(_msg)
228228
229 def play_query_position(self) :
230 """ query position """
231 _position = LCONST.CLOCK_TIME_NONE
232 _duration = LCONST.CLOCK_TIME_NONE
233 if self.__mode == self.PLAYER :
234 (_position, _duration) = self.__lucio_player.query_postion()
235 return (_position, _duration)
229236
230 def play_video(self, app_data) :237 def play_video(self, app_data) :
231 """ play a video from chrono images """238 """ play a video from chrono images """
232239
=== added file 'luciole/data/images/white.jpg'
233Binary files luciole/data/images/white.jpg 1970-01-01 00:00:00 +0000 and luciole/data/images/white.jpg 2011-02-28 13:21:06 +0000 differ240Binary files luciole/data/images/white.jpg 1970-01-01 00:00:00 +0000 and luciole/data/images/white.jpg 2011-02-28 13:21:06 +0000 differ
=== modified file 'luciole/data/templates/project_template.xml'
--- luciole/data/templates/project_template.xml 2010-03-30 07:18:34 +0000
+++ luciole/data/templates/project_template.xml 2011-02-28 13:21:06 +0000
@@ -3,7 +3,8 @@
3 <metas>3 <metas>
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: