Merge lp:~nico-inattendu/luciole/luciole-with-sound into lp:luciole
- luciole-with-sound
- Merge into 0.9
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 | ||||
Related bugs: |
|
||||
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
NicoInattendu | Pending | ||
Review via email: mp+51532@code.launchpad.net |
Commit message
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 ...)
Preview Diff
1 | === added file 'Examples_launch.txt' |
2 | --- Examples_launch.txt 1970-01-01 00:00:00 +0000 |
3 | +++ Examples_launch.txt 2011-02-28 13:21:06 +0000 |
4 | @@ -0,0 +1,5 @@ |
5 | +PITIVI_DEBUG=2,*action*:4,*ctrlp*:4 bin/luciole |
6 | + |
7 | +PITIVI_DEBUG=2,*webcam*:4,*tester:4* PYTHONPATH=".:$PYTHONPATH" python test/test_project_etree.py |
8 | + |
9 | + |
10 | |
11 | === added file 'WebcamProps.py' |
12 | --- WebcamProps.py 1970-01-01 00:00:00 +0000 |
13 | +++ WebcamProps.py 2011-02-28 13:21:06 +0000 |
14 | @@ -0,0 +1,390 @@ |
15 | +#!/usr/bin/env python |
16 | +# -*- coding: utf-8 -*- |
17 | +# -*- Mode: Python -*- |
18 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
19 | + |
20 | +import gst |
21 | + |
22 | +from pitivi.log.loggable import Loggable |
23 | +from pitivi.signalinterface import Signallable |
24 | + |
25 | +from luciole.media.lgst.webcam_caps import WebcamCaps |
26 | + |
27 | +class WebcamProps(Loggable, Signallable): |
28 | + |
29 | + __signals__ = { |
30 | + 'caps-selected' : ['caps'], |
31 | + } |
32 | + |
33 | + BEST_RATIO = (720.0/576.0) |
34 | + BEST_VIDEOTYPES = ['video/x-raw-yuv', 'video/x-raw-rgb'] |
35 | + BEST_YUV_FORMATS = [gst.Fourcc('I420'), gst.Fourcc('YUY2')] |
36 | + |
37 | + VIDEOTYPE_PROPS = [ |
38 | + 'red_mask', |
39 | + 'blue_mask', |
40 | + 'green_mask', |
41 | + 'bpp', |
42 | + 'depth', |
43 | + 'format' |
44 | + ] |
45 | + |
46 | + |
47 | + FIELDS_TYPE = { |
48 | + 'width' : 'int' , |
49 | + 'height' : 'int' , |
50 | + 'framerate' : 'fraction' , |
51 | + 'definition' : 'definition', |
52 | + 'videotype' : 'videotype', |
53 | + 'red_mask' : 'int' , |
54 | + 'blue_mask' : 'int' , |
55 | + 'green_mask' : 'int' , |
56 | + 'bpp' :'int', |
57 | + 'depth' :'int', |
58 | + 'format': 'fourcc', |
59 | + } |
60 | + |
61 | + |
62 | + |
63 | + def __init__(self, widget) : |
64 | + Loggable.__init__(self) |
65 | + Signallable.__init__(self) |
66 | + |
67 | + self.widget = widget |
68 | + self._filter_caps = gst.caps_new_any() |
69 | + self._webcam_caps = gst.caps_new_any() |
70 | + self._selected_props = {} |
71 | + self._selected_videotype = "" |
72 | + |
73 | + self.PROPS_WITH_BEST_VALUE = { |
74 | + 'format' : self.get_best_yuv_format, |
75 | + 'definition' : self.get_best_definition, |
76 | + 'videotype' : self.get_best_videotype, |
77 | + } |
78 | + |
79 | + self._active_videotype_props = [] |
80 | + self._active_common_props = [] |
81 | + |
82 | + |
83 | + def prepare(self, caps) : |
84 | + # store caps for webcam |
85 | + self._webcam_caps = WebcamCaps(caps) |
86 | + |
87 | + |
88 | + self.widget.connect('prop-changed', self._prop_changed_cb) |
89 | + |
90 | + # first show definition |
91 | + self.manage_prop('definition', None) |
92 | + |
93 | + |
94 | + |
95 | + def get_best_value(self, prop, prop_values) : |
96 | + _best = None |
97 | + if prop in self.PROPS_WITH_BEST_VALUE : |
98 | + _best = self.PROPS_WITH_BEST_VALUE[prop](prop_values) |
99 | + else : |
100 | + _best = prop_values[0] |
101 | + self.info('No best value algorithm for %s. Fist value taken %s', |
102 | + prop, _best) |
103 | + return _best |
104 | + |
105 | + def get_best_definition(self, definitions) : |
106 | + """ algorithms who search first for best ratios, |
107 | + then for higher width |
108 | + """ |
109 | + _def_dict = {} |
110 | + # Write algotithm |
111 | + _def_dict['width'] = definitions[-1]['width'] |
112 | + _def_dict['height'] = definitions[-1]['height'] |
113 | + return _def_dict |
114 | + |
115 | + def get_best_videotype(self, videotypes) : |
116 | + """ parse BEST_VIDEOTYPES list in preference order. |
117 | + if videotype not in BEST_VIDEOTYPES return the first |
118 | + videotype in input list |
119 | + """ |
120 | + _best_vtype = None |
121 | + for _vtype in self.BEST_VIDEOTYPES : |
122 | + if _vtype in videotypes : |
123 | + _best_vtype = _vtype |
124 | + break |
125 | + if _best_vtype == None : |
126 | + _best_vtype = videotypes[0] |
127 | + self.warning( ("No best video type founds. selelcted %s." |
128 | + "First in list") ) \ |
129 | + % _best_vtype |
130 | + return _best_vtype |
131 | + |
132 | + def get_best_yuv_format(self, yuv_formats) : |
133 | + """ parse BEST_YUV_FORMAT list in preference order. |
134 | + if videotype not in BEST_YUV_FORMAT return the first |
135 | + videotype in input list |
136 | + """ |
137 | + _best = None |
138 | + self.debug('avlb values : "%s', yuv_formats) |
139 | + for _format in self.BEST_YUV_FORMATS : |
140 | + if _format in yuv_formats : |
141 | + _best = _format |
142 | + break |
143 | + if _best == None : |
144 | + _best = yuv_formats[0] |
145 | + print _best |
146 | + self.warning("No best yuv format found. selelcted %s. First in list", _best) |
147 | + self.debug("Best yuv fomat is %s",_best ) |
148 | + return _best |
149 | + |
150 | + def get_best_framerate(self, framerates) : |
151 | + """ take a framerate on the middle. |
152 | + Not too high, not too low |
153 | + """ |
154 | + _len = len(framerates) |
155 | + _best = _len/2 |
156 | + return _best |
157 | + |
158 | + |
159 | + |
160 | + def _prop2str_single(self, name, value) : |
161 | + _prop_str = "" |
162 | + if self.FIELDS_TYPE[name] == 'fourcc' : |
163 | + _prop_str = "%s" % value.fourcc |
164 | + elif self.FIELDS_TYPE[name] == 'fraction' : |
165 | + _prop_str = "%s/%s" % (value.num, value.denom) |
166 | + elif self.FIELDS_TYPE[name] == 'definition' : |
167 | + _prop_str = "%sx%s" % (value['width'], value['height']) |
168 | + else : |
169 | + _prop_str = "%s" % (value) |
170 | + return _prop_str |
171 | + |
172 | + def _prop2str(self, prop_name, values) : |
173 | + _prop_out = None |
174 | + if isinstance(values, list) : |
175 | + _prop_out = [ self._prop2str_single(prop_name, _val) \ |
176 | + for _val in values ] |
177 | + else : |
178 | + _prop_out = self._prop2str_single(prop_name, values) |
179 | + return _prop_out |
180 | + |
181 | + return _prop_out |
182 | + |
183 | + |
184 | + def _str2def(self, string) : |
185 | + if string is not None : |
186 | + _list = string.split('x') |
187 | + _def_dict = { |
188 | + 'width' : _list[0], |
189 | + 'height' : _list[1] |
190 | + } |
191 | + return _def_dict |
192 | + else : |
193 | + self.error('Impossible to understand/interpret %s'%string) |
194 | + return None |
195 | + |
196 | + |
197 | + def set_prop_widget(self, prop, prop_values , active_value, wdg_type = 'combo' ) : |
198 | + self.widget.set_prop(prop, prop_values , wdg_type ) |
199 | + |
200 | + # TODO : manage signal connect |
201 | + |
202 | + self.debug("Active value for %s is %s", prop, active_value) |
203 | + self.widget.set_active_prop(prop, active_value) |
204 | + |
205 | + def set_framerates_widget(self, framerates, best_framerate ) : |
206 | + pass |
207 | + |
208 | + |
209 | + def _prop_changed_cb(self, widget, prop, value) : |
210 | + if prop == 'definition' : |
211 | + self._definition_changed_cb(value) |
212 | + elif prop == 'videotype' : |
213 | + self._videotype_changed_cb(value) |
214 | + elif prop == 'framerate' : |
215 | + self._framerate_changed_cb(value) |
216 | + elif prop in self.VIDEOTYPE_PROPS : |
217 | + self._videotype_prop_changed_cb(prop, value) |
218 | + |
219 | + |
220 | + |
221 | + |
222 | + def _definition_changed_cb(self, str_definition) : |
223 | + """ definition changed cb : |
224 | + _ store definition |
225 | + _ look videotype |
226 | + |
227 | + """ |
228 | + _selected_def = self._str2def(str_definition) |
229 | + self._selected_props.update(_selected_def) |
230 | + |
231 | + self._filter_caps = \ |
232 | + self._webcam_caps.get_caps_from_definition( _selected_def) |
233 | + |
234 | + # when defintion changed clear videotype ( if exists) |
235 | + self._clear_videotype() |
236 | + self._clear_framerate() |
237 | + |
238 | + #now look for videotypes |
239 | + self.manage_prop('videotype', self._filter_caps) |
240 | + |
241 | + def _videotype_changed_cb(self, videotype) : |
242 | + self._selected_videotype = videotype |
243 | + self.debug("RX videotype-changed : %s", self._selected_videotype) |
244 | + |
245 | + _caps = self._make_caps(self._selected_videotype, self._selected_props) |
246 | + self._filter_caps = self._webcam_caps.intersect(_caps) |
247 | + _size = self._filter_caps.get_size() |
248 | + self.debug(" %s selected caps :%s", |
249 | + _size, |
250 | + self._filter_caps , |
251 | + ) |
252 | + if _size == 0 : |
253 | + self.error("No caps found for tpl %s ", _caps) |
254 | + return |
255 | + self._clear_videotype_props() |
256 | + self._clear_framerate() |
257 | + if _size == 1 : |
258 | + # no more selection of props needed |
259 | + # request for available framerates according definition |
260 | + _is_single = self.manage_prop('framerate', self._filter_caps) |
261 | + if _is_single == True : |
262 | + self._check_single_caps(self._filter_caps) |
263 | + |
264 | + |
265 | + else : |
266 | + # more than one struct in caps |
267 | + if self._selected_videotype == 'video/x-raw-yuv' : |
268 | + self.manage_prop('format', self._filter_caps) |
269 | + |
270 | + if self._selected_videotype == 'video/x-raw-rgb' : |
271 | + self.manage_prop('red_mask', self._filter_caps) |
272 | + |
273 | + def _videotype_prop_changed_cb(self, prop, value) : |
274 | + |
275 | + self.debug("RX prop %s changed = %s", prop, value) |
276 | + self._selected_props[prop] = value |
277 | + |
278 | + self._clear_framerate() |
279 | + |
280 | + _caps = self._make_caps(self._selected_videotype, self._selected_props) |
281 | + self._filter_caps = self._webcam_caps.intersect(_caps) |
282 | + |
283 | + _size = self._filter_caps.get_size() |
284 | + self.debug(" %s selected caps :%s", |
285 | + _size, |
286 | + self._filter_caps , |
287 | + ) |
288 | + |
289 | + |
290 | + if _size == 0 : |
291 | + self.error("No caps found for tpl %s ", _caps) |
292 | + return |
293 | + if _size > 1 : |
294 | + self.warning(" Now what we do ???. What prop to select ? take the fitst_srtuct") |
295 | + _single_caps = gst.Caps(self._filter_caps[0]) |
296 | + self._filter_caps = _single_caps |
297 | + |
298 | + # no more selection of props needed |
299 | + # request for available framerates according definition |
300 | + _is_single = self.manage_prop('framerate', self._filter_caps) |
301 | + if _is_single == True : |
302 | + self._check_single_caps(self._filter_caps) |
303 | + |
304 | + |
305 | + def _framerate_changed_cb(self, value) : |
306 | + self.debug("RX framerate changed = %s (%s)", value, type(value)) |
307 | + if isinstance(value, float) : |
308 | + value = gst.Fraction(value) |
309 | + value = "%s/%s"%(int(value.num), int(value.denom)) |
310 | + print value |
311 | + self._selected_props['framerate'] = value |
312 | + |
313 | + _caps = self._make_caps(self._selected_videotype, self._selected_props) |
314 | + self._filter_caps = self._webcam_caps.intersect(_caps) |
315 | + |
316 | + self._filter_caps.get_size() |
317 | + self.debug(" %s selected caps :%s", |
318 | + self._filter_caps.get_size(), |
319 | + self._filter_caps , |
320 | + ) |
321 | + # check if a single caps is set |
322 | + self._check_single_caps(self._filter_caps) |
323 | + |
324 | + |
325 | + def _check_single_caps(self, caps) : |
326 | + if caps.get_size() == 1 \ |
327 | + and\ |
328 | + caps.is_fixed() == True : |
329 | + self.debug("Hourra ! . caps %s can be applied to webcam", caps ) |
330 | + self.emit('caps-selected', caps) |
331 | + |
332 | + |
333 | + |
334 | + def manage_prop(self, prop_name, caps) : |
335 | + """ generic manage of a prop """ |
336 | + _is_single_value = False |
337 | + is_range, _prop_values = \ |
338 | + self._webcam_caps.get_prop_values(prop_name , caps) |
339 | + self.debug("%s = %s",prop_name, _prop_values) |
340 | + if len(_prop_values) > 1 or is_range == True : |
341 | + if is_range == False : |
342 | + _best = self.get_best_value(prop_name, _prop_values) |
343 | + _prop_values = self._prop2str(prop_name, _prop_values) |
344 | + _best = self._prop2str(prop_name, _best) |
345 | + self.set_prop_widget(prop_name, _prop_values , _best) |
346 | + else : |
347 | + _best = _prop_values[0] |
348 | + self.set_prop_widget(prop_name, _prop_values , _best, 'range') |
349 | + |
350 | + else : |
351 | + self.info("Only one value %s for %s",_prop_values,prop_name ) |
352 | + _is_single_value = True |
353 | + if prop_name in self.VIDEOTYPE_PROPS and _is_single_value == False : |
354 | + self._active_videotype_props.append(prop_name) |
355 | + |
356 | + return _is_single_value |
357 | + |
358 | + |
359 | + def _clear_videotype(self) : |
360 | + self._clear_videotype_props() |
361 | + _prop = 'videotype' |
362 | + if self._selected_videotype != "" : |
363 | + self.widget.remove_prop(_prop) |
364 | + self._selected_videotype = "" |
365 | + |
366 | + def _clear_videotype_props(self) : |
367 | + while self._active_videotype_props != [] : |
368 | + _prop = self._active_videotype_props.pop() |
369 | + self.widget.remove_prop(_prop) |
370 | + del self._selected_props[_prop] |
371 | + |
372 | + def _clear_framerate(self) : |
373 | + _prop = 'framerate' |
374 | + if _prop in self._selected_props : |
375 | + self.widget.remove_prop(_prop) |
376 | + del self._selected_props[_prop] |
377 | + |
378 | + def clear_all(self) : |
379 | + self.widget.remove_all_props() |
380 | + self._selected_props.clear() |
381 | + self._active_videotype_props = [] |
382 | + self._selected_videotype = "" |
383 | + |
384 | + try : |
385 | + self.widget.disconnect_by_function(self._prop_changed_cb) |
386 | + except : |
387 | + self.warning('Impossible to disconnect') |
388 | + pass |
389 | + |
390 | + def _make_caps(self, mediatype, fields) : |
391 | + _caps_str = "" |
392 | + for _key, _value in fields.iteritems() : |
393 | + _caps_str = "%s, %s=(%s)%s" % ( \ |
394 | + _caps_str, |
395 | + _key, |
396 | + self.FIELDS_TYPE[_key], |
397 | + _value |
398 | + ) |
399 | + |
400 | + _caps_str = "%s %s" % (mediatype, _caps_str) |
401 | + _caps = gst.Caps(_caps_str) |
402 | + self.debug(" Caps created : %s", _caps.to_string()) |
403 | + return _caps |
404 | + |
405 | |
406 | === modified file 'luciole/base/options.py' |
407 | --- luciole/base/options.py 2010-09-01 09:29:58 +0000 |
408 | +++ luciole/base/options.py 2011-02-28 13:21:06 +0000 |
409 | @@ -34,7 +34,7 @@ |
410 | |
411 | # local application/library specific imports |
412 | |
413 | -def options_parser() : |
414 | +def options_parser(args) : |
415 | """ parse application options """ |
416 | option_list = [ |
417 | make_option("-f", "--file", |
418 | @@ -50,6 +50,5 @@ |
419 | |
420 | usage = "usage: %prog [options] " |
421 | parser = OptionParser(option_list=option_list, usage=usage) |
422 | - (options, args2) = parser.parse_args() |
423 | - return options |
424 | + return parser.parse_args(args) |
425 | |
426 | |
427 | === modified file 'luciole/conf.py' |
428 | --- luciole/conf.py 2010-09-06 05:04:33 +0000 |
429 | +++ luciole/conf.py 2011-02-28 13:21:06 +0000 |
430 | @@ -37,13 +37,14 @@ |
431 | # related third party imports |
432 | import gtk |
433 | |
434 | +from pitivi.log.loggable import Loggable |
435 | |
436 | # local application/library specific imports |
437 | import luciole.base.exceptions as LEXCEP |
438 | import luciole.base.tools as LT |
439 | import luciole.constants as LCONST |
440 | import luciole.base.lcl_et as LE |
441 | - |
442 | +import luciole.gui.constants as GUI_CONST |
443 | |
444 | class RecentPjtMngr(object): |
445 | """ Manage recent projects, display |
446 | @@ -114,7 +115,9 @@ |
447 | """ Callback on menu activation. open selected project """ |
448 | try : |
449 | # respect strucuture or emit signals |
450 | - self.__cbs['open-project'](self, {'path':project}) |
451 | + # TODO : emit open-project signal |
452 | + #self.__cbs['open-project'](self, {'path':project}) |
453 | + pass |
454 | except LEXCEP.LucioException, _err : |
455 | _msg = _("Project %s no more exist"%project) |
456 | self.__gui_window.error_message(_msg) |
457 | @@ -122,12 +125,11 @@ |
458 | |
459 | |
460 | |
461 | -class LucioleConf(object): |
462 | +class LucioleConf(Loggable): |
463 | """ Manage the configuration file of luciole """ |
464 | |
465 | __USER_LUCIOLE_DIR = ".luciole" |
466 | __CONF_FILE_NAME = "lucioleConf.xml" |
467 | - __ORIGINAL_DIR = "templates" |
468 | __THEME_DIR = LCONST.THEMES_DIR |
469 | |
470 | def __get_conf_options(self): |
471 | @@ -148,8 +150,7 @@ |
472 | - if conf file does not exist in user dir create it |
473 | - parse xml conf file |
474 | """ |
475 | - |
476 | - self.logger = logging.getLogger('luciole') |
477 | + Loggable.__init__(self) |
478 | self._home_dir = os.path.expandvars('$HOME') |
479 | self._option_dict = dict() |
480 | self._option_dict["LastProjects"] = list() |
481 | @@ -165,7 +166,7 @@ |
482 | else : |
483 | try : |
484 | # copy file to local dir |
485 | - self._copy_conf_file(os.path.join(self.__ORIGINAL_DIR, |
486 | + self._copy_conf_file(os.path.join(LCONST.TEMPLATE_DIR, |
487 | self.__CONF_FILE_NAME)) |
488 | # and parse it |
489 | self._parse_conf_file() |
490 | @@ -232,18 +233,27 @@ |
491 | |
492 | def load_theme(self) : |
493 | """ Load a gtk theme """ |
494 | - self.logger.debug('Entering theme load') |
495 | + self.debug('Entering theme load') |
496 | if self._option_dict.has_key('Theme') : |
497 | l_path = os.path.join(self.__THEME_DIR, self._option_dict['Theme']) |
498 | if os.path.exists(l_path) : |
499 | gtk.rc_parse(l_path) |
500 | + self.debug('Theme %s loaded', l_path) |
501 | + # load specidic icons |
502 | + factory = gtk.IconFactory() |
503 | + for _stock in GUI_CONST.STOCKS : |
504 | + gtk.stock_add([GUI_CONST.STOCK_ACQUISITION_ITEM]) |
505 | + iconset = gtk.IconSet() |
506 | + factory.add(_stock, iconset) |
507 | + factory.add_default() |
508 | + |
509 | else : |
510 | msg = _('Theme %s does not exist'%l_path) |
511 | - self.logger.info(msg) |
512 | + self.info(msg) |
513 | else : |
514 | msg = _('Impossible to load theme') |
515 | - self.logger.info(msg) |
516 | - self.logger.debug('Exiting theme load') |
517 | + self.info(msg) |
518 | + self.debug('Exiting theme load') |
519 | |
520 | # |
521 | # Private methods |
522 | |
523 | === modified file 'luciole/constants.py' |
524 | --- luciole/constants.py 2010-09-01 06:11:33 +0000 |
525 | +++ luciole/constants.py 2011-02-28 13:21:06 +0000 |
526 | @@ -31,7 +31,9 @@ |
527 | import os.path |
528 | |
529 | # related third party imports |
530 | -# N/A |
531 | +import pygst |
532 | +pygst.require('0.10') |
533 | +import gst |
534 | |
535 | # local application/library specific imports |
536 | # N/A |
537 | @@ -93,8 +95,9 @@ |
538 | ######################################## |
539 | # IMAGE FORMATS |
540 | ######################################## |
541 | -THUMB_RATIO = 4 # ration normal/thumbnail. |
542 | - # To set size ot thumbnail images in treeview. |
543 | +# Thumb width displayed in treeviews, the height is calcuated in app |
544 | +# according image ratio |
545 | +THUMB_WIDTH = 120 |
546 | # clor in rgb format muliply by 255 each level to go in gtk.gdk.Color format |
547 | THUMB_COLOR_RATIO = 256 |
548 | THUMB_TEXT_COLOR = ( |
549 | @@ -106,7 +109,6 @@ |
550 | THUMB_TEXT_FAMILY = 'sans' # font family |
551 | |
552 | |
553 | -ALPHA_DEFAULT = 0.4 # default alpha value used for mixer |
554 | |
555 | ############################################ |
556 | # PROGRAM_PATH |
557 | @@ -117,9 +119,47 @@ |
558 | TEMPLATE_DIR = os.path.join(BASE_DIR, 'data/templates') |
559 | IMAGE_DIR = os.path.join(BASE_DIR, 'data', 'images') |
560 | LOCALE_DIR = os.path.join(BASE_DIR, 'po') |
561 | +SOUNDS_DIR = os.path.join(BASE_DIR, 'data', 'sounds') |
562 | + |
563 | ############################################### |
564 | # For GLADE |
565 | ############################################### |
566 | MAIN_GLADE_FILE = "luciole.glade" |
567 | MAIN_GLADE_FILE_PATH = os.path.join(UI_DIR, MAIN_GLADE_FILE) |
568 | +MAIN_WINDOW_NAME = 'window1' |
569 | + |
570 | + |
571 | + |
572 | +ACQ_MODE_GLADE_FILE = "acquisition_mode.glade" |
573 | +ACQ_MODE_GLADE_PATH = os.path.join(UI_DIR, ACQ_MODE_GLADE_FILE) |
574 | + |
575 | +NO_PROJECT_MODE_GLADE_FILE = "no_project_mode.glade" |
576 | +NO_PROJECT_MODE_GLADE_PATH = os.path.join(UI_DIR, NO_PROJECT_MODE_GLADE_FILE) |
577 | + |
578 | +VIEWER_GLADE_FILE = "viewer.ui" |
579 | +VIEWER_GLADE_PATH = os.path.join(UI_DIR, VIEWER_GLADE_FILE) |
580 | + |
581 | + |
582 | +MODE_MAIN_WINDOW = 'window_main' |
583 | + |
584 | + |
585 | +############################################### |
586 | +# GST constants |
587 | +############################################### |
588 | +CLOCK_TIME_NONE = gst.CLOCK_TIME_NONE |
589 | + |
590 | +############################################### |
591 | +# SOUNDS_FILE |
592 | +############################################### |
593 | +SOUND_SNAPSHOT_FILE = 'camera.ogg' |
594 | +SOUND_SNAPSHOT_PATH = os.path.join(SOUNDS_DIR, SOUND_SNAPSHOT_FILE) |
595 | + |
596 | +SOUND_TRACK_NAME = 'sound_track.wav' |
597 | +################################################# |
598 | +# For mixer |
599 | +################################################ |
600 | +IMAGE2MIX_NAME = 'ToMix.jpeg' |
601 | +DEFAULT_IMAGE2MIX = os.path.join(IMAGE_DIR,'white.jpg') |
602 | +DEFAULT_ALPHA_IMAGE = 0.5 |
603 | +DEFAULT_ALPHA_STREAM = 1.0 |
604 | |
605 | |
606 | === added file 'luciole/ctrl/base.py' |
607 | --- luciole/ctrl/base.py 1970-01-01 00:00:00 +0000 |
608 | +++ luciole/ctrl/base.py 2011-02-28 13:21:06 +0000 |
609 | @@ -0,0 +1,105 @@ |
610 | +# -*- Mode: Python -*- |
611 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
612 | +# |
613 | +# |
614 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
615 | +# |
616 | +# This file is part of Luciole. |
617 | +# |
618 | +# Luciole is free software: you can redistribute it and/or modify |
619 | +# it under the terms of the GNU General Public License as published by |
620 | +# the Free Software Foundation, either version 3 of the License, or |
621 | +# (at your option) any later version. |
622 | +# |
623 | +# Luciole is distributed in the hope that it will be useful, |
624 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
625 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
626 | +# GNU General Public License for more details. |
627 | +# |
628 | +# You should have received a copy of the GNU General Public License |
629 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
630 | +# |
631 | +# |
632 | +""" |
633 | +ctrl_base.py : |
634 | + Base class for ctrl class |
635 | +""" |
636 | +# standard library imports |
637 | + |
638 | +# related third party imports |
639 | +from pitivi.signalinterface import Signallable |
640 | +from pitivi.log.loggable import Loggable |
641 | +# local application/library specific imports |
642 | + |
643 | + |
644 | +class ProjectMode(Signallable, Loggable) : |
645 | + """ Base class """ |
646 | + __signals__ = {} |
647 | + |
648 | + def __init__(self, ctrl_project, mode_type = None) : |
649 | + Signallable.__init__(self) |
650 | + Loggable.__init__(self) |
651 | + self.mode_type = mode_type |
652 | + self.ctrl_project = ctrl_project |
653 | + self.gui = ctrl_project.gui |
654 | + self._viewer = None |
655 | + |
656 | + self.project = None |
657 | + self._pipeline = None |
658 | + |
659 | + self.connect_to_project() |
660 | + |
661 | + def set_viewer(self, viewer) : |
662 | + """ set the gui viewer """ |
663 | + self._viewer = viewer |
664 | + |
665 | + def connect_to_project(self) : |
666 | + """ conecto project controller """ |
667 | + self.debug('Connect to project') |
668 | + self.ctrl_project.connect('project-loaded', self.project_loaded_cb) |
669 | + self.ctrl_project.connect('project-closed', self.project_closed_cb) |
670 | + |
671 | + def project_loaded_cb(self, project_ctrl, project) : |
672 | + """ project-loaded callback """ |
673 | + self.debug('RX project-loaded') |
674 | + self.prepare(project) |
675 | + |
676 | + def project_closed_cb(self, project_ctrl) : |
677 | + """ project-closed callback """ |
678 | + self.debug('RX project-closed') |
679 | + self.release() |
680 | + |
681 | + def connect_to_gui(self, gui) : |
682 | + """ Meta function. |
683 | + connect to gui signals """ |
684 | + self.debug('Connect to gui') |
685 | + |
686 | + |
687 | + def active_viewer(self) : |
688 | + """ Meta function. |
689 | + active displays associted to mode """ |
690 | + pass |
691 | + |
692 | + def prepare(self, project) : |
693 | + """ Meta function. |
694 | + prepare the mode """ |
695 | + |
696 | + self.debug("project : %s", project) |
697 | + self.project = project |
698 | + if project == None : |
699 | + self.error('No project to prepare') |
700 | + else : |
701 | + self.project = project |
702 | + |
703 | + |
704 | + def release(self) : |
705 | + """ Meta function |
706 | + release """ |
707 | + self.debug('Release') |
708 | + self._viewer.setPipeline(None) |
709 | + pass |
710 | + |
711 | + |
712 | + |
713 | + |
714 | + |
715 | |
716 | === modified file 'luciole/ctrl/constants.py' |
717 | --- luciole/ctrl/constants.py 2010-04-06 13:57:15 +0000 |
718 | +++ luciole/ctrl/constants.py 2011-02-28 13:21:06 +0000 |
719 | @@ -43,7 +43,8 @@ |
720 | "move-down-chrono", |
721 | "move-to-chrono", |
722 | "move-up-chrono", |
723 | - "open-project", |
724 | +# "open-project", |
725 | + "play-query-position", |
726 | "play-video", |
727 | "quit-app", |
728 | "save-as-project", |
729 | |
730 | === modified file 'luciole/ctrl/ctrl.py' |
731 | --- luciole/ctrl/ctrl.py 2010-09-03 12:44:41 +0000 |
732 | +++ luciole/ctrl/ctrl.py 2011-02-28 13:21:06 +0000 |
733 | @@ -28,13 +28,15 @@ |
734 | """ |
735 | # standard library imports |
736 | import sys |
737 | +import os |
738 | +import os.path |
739 | import locale |
740 | import gettext |
741 | _ = gettext.gettext |
742 | |
743 | # related third party imports |
744 | import gobject |
745 | -import os.path |
746 | +from pitivi.log import log |
747 | |
748 | # local application/library specific imports |
749 | import luciole.base.app_logging as LOGGING |
750 | @@ -52,6 +54,8 @@ |
751 | ##### |
752 | |
753 | |
754 | + |
755 | + |
756 | class LucioleController(object): |
757 | """ |
758 | Main luciole controller |
759 | @@ -59,7 +63,12 @@ |
760 | |
761 | def __init__(self, args) : |
762 | """ Controller initialisation. |
763 | + |
764 | Is the main appication intialisation """ |
765 | + # init logging as early as possible so we can log startup code |
766 | + enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0') |
767 | + log.init('PITIVI_DEBUG', enable_color) |
768 | + |
769 | # |
770 | # init attributes |
771 | # |
772 | @@ -91,7 +100,7 @@ |
773 | |
774 | |
775 | # load recent projects |
776 | - _cbs = {'open-project' : self.on_open_project} |
777 | + _cbs = None |
778 | _recent_mnger = self.__configurer.init_recent_manager( |
779 | self.__gui.file_recent_menu, |
780 | self.__gui.windows, |
781 | @@ -103,7 +112,7 @@ |
782 | |
783 | |
784 | # start project controller |
785 | - self.__project = \ |
786 | + self._project = \ |
787 | CTRL_PROJECT.ProjectController(self.__gui,_recent_mnger ,_cbs) |
788 | |
789 | _cbs = { |
790 | @@ -120,7 +129,12 @@ |
791 | |
792 | # conncet gui signals after the controller initialisation |
793 | self.__gui.connect_gui_signals() |
794 | - |
795 | + # Connect GUI with app |
796 | + self.__gui.connect_to_app_ctrl(self) |
797 | + |
798 | + # TODO : load timeline |
799 | + #TMLN.Timeline() |
800 | + |
801 | def __connect_signals(self): |
802 | """ connect controller signals """ |
803 | self.ctrl_signals = gobject.GObject() |
804 | @@ -152,42 +166,42 @@ |
805 | def on_change_framerate(self, *args) : |
806 | """ Request of framerate(fpi) change """ |
807 | _args = self.__extract_signal_args(args) |
808 | - self.__project.change_framerate(_args['framerate']) |
809 | + self._project.change_framerate(_args['framerate']) |
810 | |
811 | def on_change_project(self, *args) : |
812 | """ Request for prohect change """ |
813 | _args = self.__extract_signal_args(args) |
814 | for _key, _value in _args.iteritems() : |
815 | - self.__project.change_project(_key, _value) |
816 | + self._project.change_project(_key, _value) |
817 | |
818 | def on_close_project(self, *args ) : |
819 | """ Request for project close """ |
820 | _args = self.__extract_signal_args(args) |
821 | # test if project exists |
822 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
823 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
824 | # check if project is modified |
825 | - if self.__project.data['is_modified'] : |
826 | + if self._project.data['is_modified'] : |
827 | # ask for save it before close |
828 | _res = self.__gui.windows.\ |
829 | question_message(_('Save Project before closing')) |
830 | if _res : |
831 | - self.__project.save() |
832 | + self._project.save() |
833 | |
834 | # set viewer in default mode |
835 | self._viewer.mode = self._viewer.DEFAULT |
836 | |
837 | # clear project controller |
838 | - self.__project.mode = CTRL_CONST.NO_PROJECT |
839 | + self._project.mode = CTRL_CONST.NO_PROJECT |
840 | |
841 | # display close message |
842 | - _msg = _('Project %s is closed' % self.__project.data['project_name']) |
843 | + _msg = _('Project %s is closed' % self._project.data['project_name']) |
844 | self.__gui.status_bar.display_message(_msg) |
845 | |
846 | |
847 | def on_create_project(self, *args) : |
848 | """ Request new project """ |
849 | _args = self.__extract_signal_args(args) |
850 | - self.__project.new() |
851 | + self._project.new() |
852 | |
853 | |
854 | def on_delete_capture(self, *args) : |
855 | @@ -248,7 +262,7 @@ |
856 | """ Request display project properties """ |
857 | _args = self.__extract_signal_args(args) |
858 | # test if project exists |
859 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
860 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
861 | # go in default mode before dispalying window |
862 | # due to webcam detection and avoid confilcts with acquisition |
863 | self._viewer.mode = self._viewer.DEFAULT |
864 | @@ -262,30 +276,30 @@ |
865 | # launch project poperties window |
866 | _cbs = \ |
867 | {'change-project-properties' : self.on_change_project_properties} |
868 | - self.__gui.windows.project_properties(self.__project.data, _cbs) |
869 | + self.__gui.windows.project_properties(self._project.data, _cbs) |
870 | |
871 | |
872 | def on_export_to_tool(self, *args) : |
873 | """ Request export to tool """ |
874 | _args = self.__extract_signal_args(args) |
875 | # test if project exists |
876 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
877 | - self.__gui.windows.export_tool(self.__project.data) |
878 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
879 | + self.__gui.windows.export_tool(self._project.data) |
880 | |
881 | |
882 | def on_export_to_video(self, *args) : |
883 | """ Request export to video """ |
884 | _args = self.__extract_signal_args(args) |
885 | # test if project exists |
886 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
887 | - self.__gui.windows.export_video(self.__project.data) |
888 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
889 | + self.__gui.windows.export_video(self._project.data) |
890 | |
891 | |
892 | def on_import_image(self, *args) : |
893 | """ Request image import """ |
894 | _args = self.__extract_signal_args(args) |
895 | # check if project exists |
896 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
897 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
898 | # open filename chooser dialog |
899 | _filenames = self.__gui.windows.import_dialog() |
900 | |
901 | @@ -293,7 +307,7 @@ |
902 | _cbs = { 'done-import' : self.on_done_import} |
903 | # start import controller |
904 | CTRL_IMPORT.ImportController(_filenames, |
905 | - self.__project.data, |
906 | + self._project.data, |
907 | self.__rusher, |
908 | self.__gui.status_bar, |
909 | _cbs) |
910 | @@ -307,7 +321,7 @@ |
911 | def on_move_down_chrono(self, *args) : |
912 | """ request to move down an image """ |
913 | _args = self.__extract_signal_args(args) |
914 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
915 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
916 | self.__gui.treeviews[LCONST.CHRONO].move_down() |
917 | |
918 | |
919 | @@ -326,40 +340,35 @@ |
920 | def on_move_up_chrono(self, *args) : |
921 | """ request to move up an image """ |
922 | _args = self.__extract_signal_args(args) |
923 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
924 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
925 | self.__gui.treeviews[LCONST.CHRONO].move_up() |
926 | |
927 | |
928 | - def on_open_project(self, *args ) : |
929 | - """ Request open project """ |
930 | + |
931 | + |
932 | + |
933 | + def on_play_query_position(self, *args) : |
934 | + """ Query for player position """ |
935 | _args = self.__extract_signal_args(args) |
936 | - if 'path' in _args : |
937 | - if _args['path'] : |
938 | - # path is givan as param |
939 | - _path = _args['path'] |
940 | - else : |
941 | - # launch open project window |
942 | - _path = self.__gui.windows.open_project() |
943 | - try : |
944 | - self.__project.open(_path) |
945 | - except LEXCEP.LucioException, _err_msg : |
946 | - _msg = _("Project load impossible\n%s" % _err_msg) |
947 | - self.__gui.windows.error_message( _msg) |
948 | + (_position, _duration) = self._viewer.play_query_position() |
949 | + if _args['cb'] : |
950 | + # callback call to gui |
951 | + _args['cb'](_position, _duration) |
952 | |
953 | |
954 | def on_play_video(self, *args) : |
955 | """ Play video request """ |
956 | _args = self.__extract_signal_args(args) |
957 | # test if project exists |
958 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
959 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
960 | try : |
961 | - self._viewer.init_video_player(self.__project.data) |
962 | + self._viewer.init_video_player(self._project.data) |
963 | except LEXCEP.LucioException, _err_msg : |
964 | raise LEXCEP.LucioException, _err_msg |
965 | else : |
966 | self._viewer.mode = self._viewer.PLAYER |
967 | try : |
968 | - self._viewer.play_video(self.__project.data) |
969 | + self._viewer.play_video(self._project.data) |
970 | except LEXCEP.LucioException, _err_msg : |
971 | raise LEXCEP.LucioException, _err_msg |
972 | else : |
973 | @@ -371,15 +380,15 @@ |
974 | def on_quit_app(self, *args) : |
975 | """ Request quit application """ |
976 | _args = self.__extract_signal_args(args) |
977 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED and \ |
978 | - self.__project.data['is_modified'] : |
979 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED and \ |
980 | + self._project.data['is_modified'] : |
981 | _msg = _('Project modified. Save project before exit ?') |
982 | _res = self.__gui.windows.question_message(_msg) |
983 | # if response is not a bool : cancel clicked |
984 | if isinstance(_res, bool) : |
985 | if _res : |
986 | # if True save project before exit |
987 | - self.__project.save() |
988 | + self._project.save() |
989 | GUI_CTRL.GuiController.quit() |
990 | else : |
991 | GUI_CTRL.GuiController.quit() |
992 | @@ -387,45 +396,45 @@ |
993 | def on_save_as_project(self, *args) : |
994 | """ save as project request """ |
995 | _args = self.__extract_signal_args(args) |
996 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
997 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
998 | _dir = self.__gui.windows.dir_chooser() |
999 | if _dir != None : |
1000 | # set viewer in default mode due to project reload |
1001 | self._viewer.mode = self._viewer.DEFAULT |
1002 | try : |
1003 | - self.__project.save_as(_dir) |
1004 | + self._project.save_as(_dir) |
1005 | except LEXCEP.LucioException, _err_msg : |
1006 | _msg = _('Impossible to save as project : %s' % _err_msg) |
1007 | self.__gui.windows.error_message(_msg) |
1008 | else : |
1009 | _msg = _('Project saved as %s' % \ |
1010 | - self.__project.data['project_name']) |
1011 | + self._project.data['project_name']) |
1012 | self.__gui.status_bar.display_message(_msg) |
1013 | |
1014 | |
1015 | def on_save_project(self, *args) : |
1016 | """ save project request """ |
1017 | _args = self.__extract_signal_args(args) |
1018 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1019 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1020 | try : |
1021 | - self.__project.save() |
1022 | + self._project.save() |
1023 | except LEXCEP.LucioException, _err_msg : |
1024 | _msg = _('Impossible to save project : %s' % _err_msg) |
1025 | self.__gui.windows.error_message(_msg) |
1026 | else : |
1027 | _msg = _('Project %s saved' % \ |
1028 | - self.__project.data['project_name']) |
1029 | + self._project.data['project_name']) |
1030 | self.__gui.status_bar.display_message(_msg) |
1031 | # update project name on Main bar |
1032 | - self.__gui.set_program_bar(self.__project.data['project_name'], |
1033 | - self.__project.data['is_modified']) |
1034 | + self.__gui.set_program_bar(self._project.data['project_name'], |
1035 | + self._project.data['is_modified']) |
1036 | |
1037 | |
1038 | def on_start_acquisition(self, *args ) : |
1039 | """Request for starting acqusisition """ |
1040 | _args = self.__extract_signal_args(args) |
1041 | # check if a project is loaded |
1042 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1043 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1044 | self._viewer.mode = self._viewer.ACQUIRER |
1045 | |
1046 | # change control widget in acquisition mode |
1047 | @@ -441,7 +450,7 @@ |
1048 | """ Request mixer start """ |
1049 | _args = self.__extract_signal_args(args) |
1050 | # check if a project is loaded |
1051 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1052 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1053 | # switch to viewer with acqusisition and mixer |
1054 | self._viewer.mode = self._viewer.ACQUIRER_WITH_MIXER |
1055 | # change control widget in no acquisition mode |
1056 | @@ -462,7 +471,7 @@ |
1057 | """ Request to stop acquistion """ |
1058 | _args = self.__extract_signal_args(args) |
1059 | # check if a project is loaded |
1060 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1061 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1062 | # switch to viewer default |
1063 | self._viewer.mode = self._viewer.DEFAULT |
1064 | # change control widget in no acquisition mode |
1065 | @@ -478,7 +487,7 @@ |
1066 | """ Request mixer stop """ |
1067 | _args = self.__extract_signal_args(args) |
1068 | # check if a project is loaded |
1069 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1070 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1071 | # switch to viewer with acqusisition and mixer |
1072 | self._viewer.mode = self._viewer.ACQUIRER |
1073 | # change control widget in no acquisition mode |
1074 | @@ -507,7 +516,7 @@ |
1075 | def on_take_snapshot(self, *args) : |
1076 | """ take image snapshot request """ |
1077 | _args = self.__extract_signal_args(args) |
1078 | - if self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1079 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1080 | self._viewer.take_snapshot() |
1081 | else : |
1082 | # robustness |
1083 | @@ -558,10 +567,8 @@ |
1084 | if self.__options.filename \ |
1085 | and os.path.exists(self.__options.filename): |
1086 | # emit open project signal |
1087 | - self.ctrl_signals.emit( |
1088 | - "open-project", |
1089 | - {'path' : self.__options.filename} |
1090 | - ) |
1091 | + self.__gui.emit( |
1092 | + "open-project", self.__options.filename) |
1093 | |
1094 | GUI_CTRL.GuiController.main_loop() |
1095 | GUI_CTRL.GuiController.threads_leave() |
1096 | @@ -572,32 +579,32 @@ |
1097 | def on_done_rush(self, rusher) : |
1098 | """ callback : when tush images are loaded """ |
1099 | if rusher != None \ |
1100 | - and self.__project.mode == CTRL_CONST.PROJECT_LOADED : |
1101 | + and self._project.mode == CTRL_CONST.PROJECT_LOADED : |
1102 | # load treeviews |
1103 | # prepare data |
1104 | _app_data = {} |
1105 | - _app_data['capture_images'] = self.__project.data['capture_images'] |
1106 | - _app_data['chrono_images'] = self.__project.data['chrono_images'] |
1107 | + _app_data['capture_images'] = self._project.data['capture_images'] |
1108 | + _app_data['chrono_images'] = self._project.data['chrono_images'] |
1109 | _app_data['rush'] = rusher |
1110 | |
1111 | self.__gui.load_treeviews(_app_data) |
1112 | |
1113 | # init acquisition |
1114 | - self._viewer.init_acquisition(self.__project.data) |
1115 | + self._viewer.init_acquisition(self._project.data) |
1116 | |
1117 | |
1118 | # update fpi on gui and show it |
1119 | - self.__gui.update_framerate(int(self.__project.data['fpi'])) |
1120 | + self.__gui.update_framerate(int(self._project.data['fpi'])) |
1121 | |
1122 | # show project acquisition widgets |
1123 | self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
1124 | |
1125 | # update project name on Main bar |
1126 | - self.__gui.set_program_bar(self.__project.data['project_name'], |
1127 | - self.__project.data['is_modified']) |
1128 | + self.__gui.set_program_bar(self._project.data['project_name'], |
1129 | + self._project.data['is_modified']) |
1130 | |
1131 | # finish project load (project aspect) |
1132 | - self.__project.finish_project_load() |
1133 | + self._project.finish_project_load() |
1134 | |
1135 | # store rusher |
1136 | self.__rusher = rusher |
1137 | @@ -609,7 +616,7 @@ |
1138 | callback : image snapshot is done. |
1139 | Now image can be processed into project |
1140 | """ |
1141 | - self.__project.append_snapshot(self.__rusher, self._viewer.acquirer) |
1142 | + self._project.append_snapshot(self.__rusher, self._viewer.acquirer) |
1143 | |
1144 | def on_done_preferences(self , modified_options) : |
1145 | """ callback : preferences modified fome diaog """ |
1146 | @@ -632,17 +639,17 @@ |
1147 | # Test if webcam data change key |
1148 | if key == 'webcam_data': |
1149 | # make a local copy of webcam_data |
1150 | - webcam_dict = self.__project.data['webcam_data'] |
1151 | + webcam_dict = self._project.data['webcam_data'] |
1152 | #test if webcam dict key exists |
1153 | if webcam_dict.has_key(key_webcam) : |
1154 | # update webcam key and call project change |
1155 | webcam_dict[key_webcam] = data |
1156 | - self.__project.change_project('webcam_data', webcam_dict) |
1157 | + self._project.change_project('webcam_data', webcam_dict) |
1158 | |
1159 | def on_done_import(self, image_objs = [] ) : |
1160 | """ callback : image import (rush generation) is done """ |
1161 | if image_objs != [] : |
1162 | - self.__project.change_project( 'rush_images', |
1163 | + self._project.change_project( 'rush_images', |
1164 | self.__rusher.dump_image_name()) |
1165 | # Not to be done in thread : interacts with gui |
1166 | for _image_obj in image_objs : |
1167 | |
1168 | === added file 'luciole/ctrl/ctrl_acq.py' |
1169 | --- luciole/ctrl/ctrl_acq.py 1970-01-01 00:00:00 +0000 |
1170 | +++ luciole/ctrl/ctrl_acq.py 2011-02-28 13:21:06 +0000 |
1171 | @@ -0,0 +1,634 @@ |
1172 | +#!/usr/bin/env python |
1173 | +# -*- coding: utf-8 -*- |
1174 | +# -*- Mode: Python -*- |
1175 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
1176 | +# |
1177 | +# |
1178 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
1179 | +# |
1180 | +# This file is part of Luciole. |
1181 | +# |
1182 | +# Luciole is free software: you can redistribute it and/or modify |
1183 | +# it under the terms of the GNU General Public License as published by |
1184 | +# the Free Software Foundation, either version 3 of the License, or |
1185 | +# (at your option) any later version. |
1186 | +# |
1187 | +# Luciole is distributed in the hope that it will be useful, |
1188 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1189 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1190 | +# GNU General Public License for more details. |
1191 | +# |
1192 | +# You should have received a copy of the GNU General Public License |
1193 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
1194 | +# |
1195 | +# |
1196 | +""" |
1197 | +ctrl_acq.py : |
1198 | + manages acquisition |
1199 | +""" |
1200 | +# standard library imports |
1201 | +import os.path |
1202 | + |
1203 | +# related third party imports |
1204 | +import gst |
1205 | +from pitivi.signalinterface import Signallable |
1206 | +from pitivi.log.loggable import Loggable |
1207 | +from pitivi.signalgroup import SignalGroup |
1208 | +from pitivi.action import ViewAction |
1209 | +from pitivi.pipeline import Pipeline |
1210 | + |
1211 | +# local application/library specific imports |
1212 | +from luciole.media.lgst.webcam_factory import WebcamSourceFactory |
1213 | +import luciole.constants as LCONST |
1214 | +import luciole.base.exceptions as LEXCEP |
1215 | +import luciole.base.tools as LT |
1216 | +import luciole.media.image as LI |
1217 | + |
1218 | +from luciole.ctrl.base import ProjectMode |
1219 | + |
1220 | +class ProjectModeAcquisition(ProjectMode) : |
1221 | + MODES = ( "INVALID", |
1222 | + "NO_ACQUISITION", |
1223 | + "ACQUISITION", |
1224 | + "ACQUISITION_WITH_MIXER", |
1225 | + ) |
1226 | + |
1227 | + __signals__ = { |
1228 | + 'acquirer-ready' : [], |
1229 | + 'acquirer-error' : ['msg'], |
1230 | + 'acquisition-started' : ['with_mixer'], |
1231 | + 'acquisition-stopped' : [], |
1232 | + 'snapshot-on-progress' : [], |
1233 | + 'snapshot-taken' : ['rush_image'], |
1234 | + } |
1235 | + |
1236 | + def __init__(self, ctrl_project) : |
1237 | + self._mode ="INVALID" |
1238 | + ProjectMode.__init__(self, ctrl_project, "ACQUISITION") |
1239 | + self.gui_signals = SignalGroup() |
1240 | + |
1241 | + def prepare(self, project) : |
1242 | + """ prepare acquistion """ |
1243 | + ProjectMode.prepare(self, project) |
1244 | + # prepare acqusisition |
1245 | + _project = project.props |
1246 | + if _project != None : |
1247 | + if _project['hardtype'] == LCONST.WEBCAM : |
1248 | + # create webcam source |
1249 | + _capture_path = os.path.join( |
1250 | + _project['project_dir'], |
1251 | + LCONST.ACQUIRED_IMAGE_NAME) |
1252 | + self.debug ("Image capture path : %s", _capture_path) |
1253 | + self._source_factory = \ |
1254 | + WebcamSourceFactory(capture_path = _capture_path, |
1255 | + with_mixer = False) |
1256 | + |
1257 | + self.debug(' webcam filter : %s',self.project.props['webcam_data']) |
1258 | + self._set_webcam_params( |
1259 | + self.project.props['webcam_data'], |
1260 | + self._source_factory) |
1261 | + |
1262 | + # set video resize/scale mode (or not) |
1263 | + if _project['resize'] == 'yes' : |
1264 | + self._source_factory.with_scale = True |
1265 | + else : |
1266 | + self._source_factory.with_scale = False |
1267 | + |
1268 | + self._connect_to_factory(self._source_factory, _capture_path ) |
1269 | + |
1270 | + # create pipeline and resource |
1271 | + self._pipeline = Pipeline() |
1272 | + self._view_action = ViewAction() |
1273 | + self._view_action.addProducers(self._source_factory) |
1274 | + |
1275 | + self._mode = "NO_ACQUISITION" |
1276 | + self.debug('emit acquirer-ready, Webcam factory : %s, pipeline : %s', |
1277 | + self._source_factory, self._pipeline ) |
1278 | + self.emit('acquirer-ready') |
1279 | + |
1280 | + |
1281 | + def release(self) : |
1282 | + ProjectMode.release(self) |
1283 | + self.debug('releasing acquisition') |
1284 | + self._disconnect_from_factory(self._source_factory) |
1285 | + self._mode = "INVALID" |
1286 | + self._pipeline.release() |
1287 | + self._pipeline = None |
1288 | + self._source_factory = None |
1289 | + self._view_action = None |
1290 | + |
1291 | + def active_viewer(self) : |
1292 | + if self._viewer : |
1293 | + self._viewer.setPipeline(None) |
1294 | + self._viewer.hideSlider() |
1295 | + self.debug('active viewer - Pipeline : %s, action %s', |
1296 | + self._pipeline, |
1297 | + self._view_action) |
1298 | + self._viewer.setAction(self._view_action) |
1299 | + self._viewer.setPipeline(self._pipeline, "ACQUISITION") |
1300 | + else : |
1301 | + self.error("No viewer set") |
1302 | + |
1303 | + |
1304 | + def start(self, with_mixer = False) : |
1305 | + """ start acquisition """ |
1306 | + # check if image view mode |
1307 | + # in that case restart acquisition mode |
1308 | + if self.ctrl_project.mode == 'IMAGE_VIEW' : |
1309 | + self.ctrl_project.acquistion_mode() |
1310 | + return |
1311 | + |
1312 | + if self._mode == "NO_ACQUISITION" : |
1313 | + if with_mixer == True : |
1314 | + self._source_factory.active_mixer(True) |
1315 | + self._mode = "ACQUISITION_WITH_MIXER" |
1316 | + else : |
1317 | + #self._source_factory.active_mixer(False) |
1318 | + self._mode = "ACQUISITION" |
1319 | + |
1320 | + self._pipeline.play() |
1321 | + self.debug('send acquisition-started - mode : %s',self._mode ) |
1322 | + self.emit('acquisition-started', with_mixer) |
1323 | + |
1324 | + |
1325 | + else : |
1326 | + self.error("Cannot start acquisition. Controller in incorrect mode (%s)", |
1327 | + self._mode) |
1328 | + |
1329 | + def stop(self) : |
1330 | + """ stop acquisition """ |
1331 | + self.debug('stop acquisition - mode %s', self._mode) |
1332 | + if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") : |
1333 | + if self._mode == "ACQUISITION_WITH_MIXER" : |
1334 | + pass |
1335 | + #self.acquirer.deactive_onion_skin() |
1336 | + self._pipeline.stop() |
1337 | + self._mode = "NO_ACQUISITION" |
1338 | + self.debug('emit acquisition-stopped') |
1339 | + self.emit('acquisition-stopped') |
1340 | + else : |
1341 | + self.error( |
1342 | + "Cannot stop acquistion. Controller in incorrect mode (%s)", |
1343 | + self._mode) |
1344 | + |
1345 | + |
1346 | + def do_snapshot(self) : |
1347 | + if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") : |
1348 | + self._source_factory.capture() |
1349 | + |
1350 | + else : |
1351 | + self.error( |
1352 | + "Cannot take snapshot. Controller in incorrect mode (%s)", |
1353 | + self._mode) |
1354 | + |
1355 | + def active_mixer(self, is_active) : |
1356 | + # check with current mixer status |
1357 | + self.debug('active_mixer : %s', is_active) |
1358 | + if self.project.props['is-mixer-active'] == is_active : |
1359 | + self.info('No mixer status to change') |
1360 | + return |
1361 | + |
1362 | + |
1363 | + |
1364 | + # stop pipeline, deactivate producer and remove old webcam factory |
1365 | + self._pipeline.stop() |
1366 | + self._view_action.deactivate() |
1367 | + self._view_action.removeProducers(self._source_factory) |
1368 | + |
1369 | + # create new webcam factory |
1370 | + _capture_path = os.path.join( |
1371 | + self.project.props['project_dir'], |
1372 | + LCONST.ACQUIRED_IMAGE_NAME) |
1373 | + self.debug ("Image capture path : %s", _capture_path) |
1374 | + |
1375 | + self._source_factory = WebcamSourceFactory( |
1376 | + capture_path = _capture_path, |
1377 | + with_mixer = is_active, |
1378 | + image2mix = self.project.props['image2mix'] ) |
1379 | + # set video resize/scale mode (or not) |
1380 | + if self.project.props['resize'] == 'yes' : |
1381 | + self._source_factory.with_scale = True |
1382 | + else : |
1383 | + self._source_factory.with_scale = False |
1384 | + |
1385 | + |
1386 | + # add producer |
1387 | + self._view_action.addProducers(self._source_factory) |
1388 | + # activate it --> regeneration of pipeline bins |
1389 | + self._view_action.activate() |
1390 | + # set image alpha if webcam active |
1391 | + if is_active == True : |
1392 | + self._source_factory.alpha_value = self.project.props['alpha'] |
1393 | + |
1394 | + self.debug(' image to mix is %s with alpha = %s', |
1395 | + self._source_factory.image2mix, |
1396 | + self._source_factory.alpha_value) |
1397 | + |
1398 | + # start acquisition |
1399 | + self._pipeline.play() |
1400 | + |
1401 | + self.project.props['is-mixer-active'] = is_active |
1402 | + |
1403 | + def _connect_to_factory(self, factory, _capture_path) : |
1404 | + factory.connect("image-capture-done", self._image_capture_done_cb, _capture_path ) |
1405 | + |
1406 | + def _disconnect_from_factory(self, factory) : |
1407 | + factory.disconnect_by_function(self._image_capture_done_cb) |
1408 | + |
1409 | + def _image_capture_done_cb( self, factory, _capture_path) : |
1410 | + self.debug('RX image-capture-done') |
1411 | + _rush_image = self._process_snapshot(_capture_path) |
1412 | + self.debug('emit snapshot-taken : %s', _rush_image.name) |
1413 | + self.emit('snapshot-taken', _rush_image) |
1414 | + |
1415 | + def _process_snapshot(self, capture_path) : |
1416 | + # get acquired image name |
1417 | + _acq_image = capture_path |
1418 | + |
1419 | + # build temp impage path |
1420 | + _temp_dir = os.path.join(self.project.props['project_dir'], 'tmp') |
1421 | + # copy name |
1422 | + _ac_image_temp = os.path.join(_temp_dir, _acq_image) |
1423 | + # resized copy name |
1424 | + _ac_image_temp_rz = \ |
1425 | + os.path.join(_temp_dir, LCONST.ACQUIRED_IMAGE_NAME_RZ) |
1426 | + |
1427 | + # build rush image name |
1428 | + _basename = LCONST.RUSH_FILENAME_TPL % self.project.rush.rush_index |
1429 | + _rush_image = \ |
1430 | + os.path.join(self.project.props['project_dir'], |
1431 | + self.project.props['rush_dir'], |
1432 | + _basename) |
1433 | + |
1434 | + try : |
1435 | + # 1. move image acquired image to tmp dir |
1436 | + LT.movef(_acq_image, _ac_image_temp) |
1437 | + # 2. resize image result is in _ac_image_temp_rz |
1438 | + if self.project.props['resize'] == 'yes' : |
1439 | + _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz ) |
1440 | + _rz_obj.convert() |
1441 | + else : |
1442 | + _ac_image_temp_rz = _ac_image_temp |
1443 | + |
1444 | + # 3. move resized image to rush dire |
1445 | + LT.movef(_ac_image_temp_rz, _rush_image) |
1446 | + |
1447 | + except LEXCEP.LucioException, _err_msg : |
1448 | + self.error(_err_msg) |
1449 | + else : |
1450 | + self.debug("create rush image : %s", _basename) |
1451 | + # 4. append image to rush list |
1452 | + _rush_image = self.project.rush.append(_basename) |
1453 | + |
1454 | + # always update the image 2 mix, even if mixer is not active |
1455 | + # used to memorize the last capture |
1456 | + #self.acquirer.Image2Mix = _rush_image.path |
1457 | + |
1458 | + # project modified |
1459 | + self.project.props['rush_images'].append(_basename) |
1460 | + self.project.props['capture_images'].append(_basename) |
1461 | + |
1462 | + self.ctrl_project.is_modified = True |
1463 | + |
1464 | + self.debug("capture post-treatemnt done ") |
1465 | + return _rush_image |
1466 | + |
1467 | + def _set_webcam_params(self, webcam_props, factory) : |
1468 | + """ |
1469 | + set webcam parameters according project propperties |
1470 | + """ |
1471 | + #retrieve params value |
1472 | + _wcam_params = { |
1473 | + 'wcam_driver': { |
1474 | + 'element': webcam_props['v4ldriver'] , |
1475 | + 'properties' : { |
1476 | + 'device':webcam_props['device-file'], |
1477 | + }, |
1478 | + }, |
1479 | + |
1480 | + 'wcam_filter' : { |
1481 | + 'media_type' :gst.Caps(webcam_props['caps']), |
1482 | + }, |
1483 | + } |
1484 | + |
1485 | + # set params on factory |
1486 | + factory.webcam_params = _wcam_params |
1487 | + self.debug(" webcam params set : %s", factory.webcam_params) |
1488 | + |
1489 | + |
1490 | + |
1491 | + |
1492 | + |
1493 | + |
1494 | +# TODO : suppress CtrlAcq |
1495 | + |
1496 | +class CtrlAcq(Signallable, Loggable) : |
1497 | + MODES = ( "INVALID", |
1498 | + "NO_ACQUISITION", |
1499 | + "ACQUISITION", |
1500 | + "ACQUISITION_WITH_MIXER", |
1501 | + ) |
1502 | + |
1503 | + |
1504 | + __signals__ = { |
1505 | + 'acquirer-ready' : ['acquirer'], |
1506 | + 'acquirer-error' : ['msg'], |
1507 | + 'acquisition-started' : ['with_mixer'], |
1508 | + 'acquisition-stopped' : [], |
1509 | + 'snapshot-on-progress' : [], |
1510 | + 'snapshot-taken' : ['rush_image'], |
1511 | + } |
1512 | + |
1513 | + |
1514 | + def __init__(self, ctrl_project) : |
1515 | + """ initialize the acquirer """ |
1516 | + Signallable.__init__(self) |
1517 | + Loggable.__init__(self) |
1518 | + self.ctrl_project = ctrl_project |
1519 | + |
1520 | + self.acquirer = None |
1521 | + self.project = None |
1522 | + self._mode = "INVALID" |
1523 | + |
1524 | + # signal connections |
1525 | + self._connect_to_project(self.ctrl_project) |
1526 | + |
1527 | + self.gui_signals = SignalGroup() |
1528 | + |
1529 | + self.debug('CtrlAcq intialized : %s %s',self.ctrl_project, ctrl_project ) |
1530 | + |
1531 | + |
1532 | + def init_acquirer(self, project) : |
1533 | + self.debug("Init acquirer") |
1534 | + _project = project.props |
1535 | + if _project != None : |
1536 | + # Create acquisition object |
1537 | + _acq_obj = None |
1538 | + # remark; acquisition object for webcam and dv cam |
1539 | + if _project['hardtype'] == LCONST.WEBCAM : |
1540 | + # init webcam factory |
1541 | + self.factory = WebcamSourceFactory() |
1542 | + self.debug('Webcam factory : %s', self.factory ) |
1543 | + self.pipeline = PTV_PPLN.Pipeline() |
1544 | + self.view_action = ViewAction() |
1545 | + self.view_action.addProducers(self.factory) |
1546 | + |
1547 | + self.viewer = self.ctrl_project.gui._get_drawarea() |
1548 | + self.viewer.hideSlider() |
1549 | + self.viewer.setPipeline(None) |
1550 | + self.viewer.setAction(self.view_action) |
1551 | + self.viewer.setPipeline(self.pipeline, "ACQUISITION") |
1552 | + #self.viewer.play() |
1553 | + # just for debug |
1554 | + _acq_obj =self.pipeline |
1555 | + |
1556 | + |
1557 | + |
1558 | + elif _project['hardtype'] == LCONST.DVCAM : |
1559 | + # default acquisition load i.e. DVCAM |
1560 | + _acq_obj = LACQ.Acquisition( |
1561 | + self._get_drawarea(self.ctrl_project.gui), |
1562 | + False, |
1563 | + _project['hardtype'], |
1564 | + project_dir = _project['project_dir'], |
1565 | + cb_error = self._on_error_acqusition, |
1566 | + cb_capture_done = self._on_done_snaphot) |
1567 | + else : |
1568 | + # Nothing to do |
1569 | + self.emit('acquirer-error', 'No valid acquirer found' ) |
1570 | + if _acq_obj != None : |
1571 | + self._mode = "NO_ACQUISITION" |
1572 | + # for mixer initialisation set image to mix with the last image |
1573 | + # of the capture view. only if capture image is not empty |
1574 | + if len(_project['capture_images'] ) > 0 : |
1575 | + _last_image_path = \ |
1576 | + os.path.join( _project['project_dir'], |
1577 | + _project['rush_dir'], |
1578 | + _project['capture_images'][-1]) |
1579 | + if os.path.exists(_last_image_path) : |
1580 | + _acq_obj.Image2Mix = _last_image_path |
1581 | + |
1582 | + self.emit('acquirer-ready', _acq_obj ) |
1583 | + self.acquirer = _acq_obj |
1584 | + |
1585 | + |
1586 | + def start_acquisition(self) : |
1587 | + if self._mode == "NO_ACQUISITION" : |
1588 | + self.pipeline.play() |
1589 | + self._mode = "ACQUISITION" |
1590 | + self.emit('acquisition-started', False) |
1591 | + else : |
1592 | + self.error( |
1593 | + "Cannot start acquisition. Controller in incorrect mode (%s)", |
1594 | + self._mode) |
1595 | + |
1596 | + def stop_acquisition(self) : |
1597 | + if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") : |
1598 | + if self._mode == "ACQUISITION_WITH_MIXER" : |
1599 | + pass |
1600 | + #self.acquirer.deactive_onion_skin() |
1601 | + self.pipeline.stop() |
1602 | + |
1603 | + self._mode = "NO_ACQUISITION" |
1604 | + self.debug('emit acquisition-stopped') |
1605 | + self.emit('acquisition-stopped') |
1606 | + else : |
1607 | + self.error( |
1608 | + "Cannot stop acquistion. Controller in incorrect mode (%s)", |
1609 | + self._mode) |
1610 | + |
1611 | + def active_mixer(self) : |
1612 | + if self._mode == "ACQUISITION" : |
1613 | + # the active onion skin stop the acquistion |
1614 | + self.acquirer.active_onion_skin() |
1615 | + # so start acqusition |
1616 | + self.acquirer.start_acqusition() |
1617 | + |
1618 | + self._mode = "ACQUISITION_WITH_MIXER" |
1619 | + self.emit('acquisition-started', True) |
1620 | + else : |
1621 | + self.error( |
1622 | + "Cannot active mixer. Controller in incorrect mode (%s)", |
1623 | + self._mode) |
1624 | + |
1625 | + |
1626 | + def deactive_mixer(self) : |
1627 | + if self._mode == "ACQUISITION_WITH_MIXER" : |
1628 | + # deactivate onion skin stop the acquistion |
1629 | + self.acquirer.deactive_onion_skin() |
1630 | + # so start acqusition |
1631 | + self.acquirer.start_acqusition() |
1632 | + |
1633 | + self._mode = "ACQUISITION" |
1634 | + self.emit('acquisition-started', False) |
1635 | + else : |
1636 | + self.error( |
1637 | + "Cannot deactive mixer. Controller in incorrect mode (%s)", |
1638 | + self._mode) |
1639 | + |
1640 | + def take_snapshot(self) : |
1641 | + if self._mode in ("ACQUISITION", "ACQUISITION_WITH_MIXER") : |
1642 | + if self.acquirer.is_streaming_active == True : |
1643 | + |
1644 | + self.debug('emit snapshot-on-progress') |
1645 | + self.emit('snapshot-on-progress') |
1646 | + self.acquirer.capture_image() |
1647 | + |
1648 | + |
1649 | + |
1650 | + def release(self) : |
1651 | + # ensure acquisition stop |
1652 | + self.debug('Enterin release in mode=%s', self._mode) |
1653 | + if self._mode in ("ACQUISITION","ACQUISITION_WITH_MIXER") : |
1654 | + self.debug('Force acquistion stop : %s', self._mode) |
1655 | + self.stop_acquisition() |
1656 | + else : |
1657 | + self.debug('Bad state : %s', self._mode) |
1658 | + self.acquirer.release() |
1659 | + |
1660 | + #self._disconnect_from_project(self.ctrl_project) |
1661 | + #self._disconnect_from_gui(self.ctrl_project.gui) |
1662 | + #self.gui_signals.disconnectAll() |
1663 | + |
1664 | + def _connect_to_project(self, ctrl_project) : |
1665 | + ctrl_project.connect('project-loaded', self._project_loaded_cb) |
1666 | + ctrl_project.connect('project-closed', self._project_closed_cb) |
1667 | + """ |
1668 | + def _disconnect_from_project(self, ctrl_project) : |
1669 | + ctrl_project.disconnect_by_function(self._project_loaded_cb) |
1670 | + ctrl_project.disconnect_by_function(self._project_closed_cb) |
1671 | + """ |
1672 | + def _project_closed_cb(self, project_ctrl) : |
1673 | + self.debug('RX project-closed') |
1674 | + self.release() |
1675 | + |
1676 | + |
1677 | + def _project_loaded_cb(self, project_ctrl, project) : |
1678 | + self.debug('RX project-loaded') |
1679 | + self.init_acquirer(project) |
1680 | + self._connect_to_gui(self.ctrl_project.gui) |
1681 | + self.pipeline.play() |
1682 | + self.project = project |
1683 | + |
1684 | + """" |
1685 | + def _connect_to_gui(self, gui) : |
1686 | + # connection GUI to ctrl_acq |
1687 | + gui.connect('start-acqusition', self._start_acq_cb) |
1688 | + gui.connect('stop-acqusition', self._stop_acq_cb) |
1689 | + gui.connect('take-snapshot', self._take_snapshot_cb) |
1690 | + gui.connect('active-mixer', self._active_mixer_cb) |
1691 | + gui.connect('deactive-mixer', self._deactive_mixer_cb) |
1692 | + |
1693 | + # connection ctrl_acq to gui |
1694 | + gui._project_mode.connect_to_ctrl('ACQUISITION', self) |
1695 | + """ |
1696 | + |
1697 | + def _connect_to_gui(self, gui) : |
1698 | + self.gui_signals.connect( |
1699 | + gui, "start-acqusition", None, self._start_acq_cb) |
1700 | + self.gui_signals.connect( |
1701 | + gui, "stop-acqusition", None, self._stop_acq_cb) |
1702 | + self.gui_signals.connect( |
1703 | + gui, "take-snapshot", None, self._take_snapshot_cb) |
1704 | + self.gui_signals.connect( |
1705 | + gui, "active-mixer", None, self._active_mixer_cb) |
1706 | + self.gui_signals.connect( |
1707 | + gui, "deactive-mixer", None, self._deactive_mixer_cb) |
1708 | + |
1709 | + # connection ctrl_acq to gui |
1710 | + gui._project_mode.connect_to_ctrl('ACQUISITION', self) |
1711 | + |
1712 | + |
1713 | + def _disconnect_from_gui(self,gui) : |
1714 | + # dicconnection GUI to ctrl_acq |
1715 | + gui.disconnect_by_function(self._start_acq_cb) |
1716 | + gui.disconnect_by_function(self._stop_acq_cb) |
1717 | + gui.disconnect_by_function(self._take_snapshot_cb) |
1718 | + gui.disconnect_by_function(self._active_mixer_cb) |
1719 | + gui.disconnect_by_function(self._deactive_mixer_cb) |
1720 | + |
1721 | + |
1722 | + def _start_acq_cb(self, gui) : |
1723 | + if self.ctrl_project.mode == 'ACQUISITION' : |
1724 | + self.start_acquisition() |
1725 | + |
1726 | + def _stop_acq_cb(self, gui) : |
1727 | + if self.ctrl_project.mode == 'ACQUISITION' : |
1728 | + self.stop_acquisition() |
1729 | + |
1730 | + def _take_snapshot_cb(self, gui) : |
1731 | + if self.ctrl_project.mode == 'ACQUISITION' : |
1732 | + self.take_snapshot() |
1733 | + |
1734 | + def _active_mixer_cb(self, gui) : |
1735 | + self.active_mixer() |
1736 | + |
1737 | + def _deactive_mixer_cb(self, gui) : |
1738 | + self.deactive_mixer() |
1739 | + |
1740 | + def _on_error_acqusition(self, message) : |
1741 | + """ |
1742 | + callback error |
1743 | + propagate it to main controller. |
1744 | + """ |
1745 | + print " acqusistion error\n ", message |
1746 | + self.emit('acquirer-error', message) |
1747 | + |
1748 | + def _on_done_snaphot(self, *unsued) : |
1749 | + """ |
1750 | + callback when snaphot done. |
1751 | + """ |
1752 | + _rush_image = self._process_snapshot() |
1753 | + self.debug('emit snapshot-taken : %s', _rush_image.name) |
1754 | + self.emit('snapshot-taken', _rush_image) |
1755 | + |
1756 | + def _process_snapshot(self) : |
1757 | + # get acquired image name |
1758 | + _acq_image = self.acquirer.image2save |
1759 | + |
1760 | + # build temp impage path |
1761 | + _temp_dir = os.path.join(self.project.props['project_dir'], 'tmp') |
1762 | + # copy name |
1763 | + _ac_image_temp = os.path.join(_temp_dir, _acq_image) |
1764 | + # resized copy name |
1765 | + _ac_image_temp_rz = \ |
1766 | + os.path.join(_temp_dir, LCONST.ACQUIRED_IMAGE_NAME_RZ) |
1767 | + |
1768 | + # build rush image name |
1769 | + _basename = LCONST.RUSH_FILENAME_TPL % self.project.rush.rush_index |
1770 | + _rush_image = \ |
1771 | + os.path.join(self.project.props['project_dir'], |
1772 | + self.project.props['rush_dir'], |
1773 | + _basename) |
1774 | + |
1775 | + try : |
1776 | + # 1. move image acquired image to tmp dir |
1777 | + LT.movef(_acq_image, _ac_image_temp) |
1778 | + |
1779 | + # 2. resize image result is in _ac_image_temp_rz |
1780 | + _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz ) |
1781 | + _rz_obj.convert() |
1782 | + |
1783 | + # 3. move resized image to rush dire |
1784 | + LT.movef(_ac_image_temp_rz, _rush_image) |
1785 | + |
1786 | + except LEXCEP.LucioException, _err_msg : |
1787 | + self.error(_err_msg) |
1788 | + else : |
1789 | + # 4. append image to rush list |
1790 | + _rush_image = self.project.rush.append(_basename) |
1791 | + |
1792 | + # always update the image 2 mix, even if mixer is not active |
1793 | + # used to memorize the last capture |
1794 | + self.acquirer.Image2Mix = _rush_image.path |
1795 | + |
1796 | + # project modified |
1797 | + self.ctrl_project.is_modified = True |
1798 | + |
1799 | + return _rush_image |
1800 | + |
1801 | + def _get_drawarea(self, gui) : |
1802 | + _da = gui._get_drawarea() |
1803 | + self.debug(" Drawarea used : %s", _da) |
1804 | + return _da |
1805 | + |
1806 | |
1807 | === added file 'luciole/ctrl/ctrl_app.py' |
1808 | --- luciole/ctrl/ctrl_app.py 1970-01-01 00:00:00 +0000 |
1809 | +++ luciole/ctrl/ctrl_app.py 2011-02-28 13:21:06 +0000 |
1810 | @@ -0,0 +1,1050 @@ |
1811 | +#!/usr/bin/env python |
1812 | +# -*- coding: utf-8 -*- |
1813 | +# -*- Mode: Python -*- |
1814 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
1815 | +# |
1816 | +# |
1817 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
1818 | +# |
1819 | +# This file is part of Luciole. |
1820 | +# |
1821 | +# Luciole is free software: you can redistribute it and/or modify |
1822 | +# it under the terms of the GNU General Public License as published by |
1823 | +# the Free Software Foundation, either version 3 of the License, or |
1824 | +# (at your option) any later version. |
1825 | +# |
1826 | +# Luciole is distributed in the hope that it will be useful, |
1827 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1828 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1829 | +# GNU General Public License for more details. |
1830 | +# |
1831 | +# You should have received a copy of the GNU General Public License |
1832 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
1833 | +# |
1834 | +# |
1835 | +""" |
1836 | +ctrl.py : |
1837 | + Main application controller |
1838 | +""" |
1839 | +# standard library imports |
1840 | +import sys |
1841 | +import os |
1842 | +import os.path |
1843 | +import locale |
1844 | +import gettext |
1845 | +_ = gettext.gettext |
1846 | + |
1847 | +# related third party imports |
1848 | +import gobject |
1849 | +from pitivi.log import log |
1850 | + |
1851 | +from pitivi.signalinterface import Signallable |
1852 | +from pitivi.log.loggable import Loggable |
1853 | +from pitivi.log import log |
1854 | + |
1855 | +# local application/library specific imports |
1856 | +import luciole.base.app_logging as LOGGING |
1857 | +import luciole.base.exceptions as LEXCEP |
1858 | +import luciole.base.options as OPTIONS |
1859 | +import luciole.conf as LCONF |
1860 | +import luciole.constants as LCONST |
1861 | +import luciole.ctrl.constants as CTRL_CONST |
1862 | +import luciole.ctrl.import_image as CTRL_IMPORT |
1863 | +import luciole.ctrl.ctrl_project as CTRL_PROJECT |
1864 | +import luciole.gui.constants as GUI_CONST |
1865 | +import luciole.gui.main_window as GUI_MAIN |
1866 | +import luciole.gui.gui_ctrl as GUI_CTRL |
1867 | +import luciole.info as INFO |
1868 | +import luciole.media.lgst.play_sound as LSOUND |
1869 | +##### |
1870 | + |
1871 | + |
1872 | + |
1873 | +class CtrlAppGui(Signallable, Loggable) : |
1874 | + """ Interface between gui and App""" |
1875 | + |
1876 | + def __init__(self, app) : |
1877 | + Signallable.__init__(self) |
1878 | + Loggable.__init__(self) |
1879 | + self.app = app |
1880 | + self.project_ctrl = self.app.project_ctrl |
1881 | + self.gui = self.app.gui |
1882 | + self._connect_gui_and_project() |
1883 | + |
1884 | + def _connect_gui_and_project(self) : |
1885 | + |
1886 | + # application high level actions |
1887 | + self.gui.gui_actions.connect('open-project', self._open_project_cb) |
1888 | + self.gui.gui_actions.connect('new-project', self._new_project_cb) |
1889 | + self.gui.gui_actions.connect('quit-app', self._quit_cb) |
1890 | + |
1891 | + # Project level actions |
1892 | + self.gui.gui_actions.connect('save-project', self._save_project_cb) |
1893 | + self.gui.gui_actions.connect('save-as-project', self._save_as_project_cb) |
1894 | + self.gui.gui_actions.connect('close-project', self._close_project_cb) |
1895 | + self.gui.gui_actions.connect('acquisition-mode', self._acq_mode_cb) |
1896 | + self.gui.gui_actions.connect('timeline-mode', self._timeline_mode_cb) |
1897 | + self.gui.gui_actions.connect('import-sound', self._import_sound_cb) |
1898 | + self.gui.gui_actions.connect('image-view', self._image_view_cb) |
1899 | + self.gui.gui_actions.connect('import-images', self._import_images_cb) |
1900 | + self.gui.gui_actions.connect('show-project-properties', self._show_project_properties_cb) |
1901 | + |
1902 | + |
1903 | + # Acqusition actions |
1904 | + self.gui.gui_actions.connect('start-acquistion', self._start_acq_cb) |
1905 | + self.gui.gui_actions.connect('stop-acquistion', self._stop_acq_cb) |
1906 | + self.gui.gui_actions.connect('take-snapshot', self._take_snapshot_cb) |
1907 | + self.gui.gui_actions.connect('active-mixer', self._active_mixer_cb) |
1908 | + |
1909 | + # Timeline actions |
1910 | + self.gui.gui_actions.connect('change-timeline-order', self._change_timeline_order_cb) |
1911 | + self.gui.gui_actions.connect('insert-image-on-timeline', self._insert_image_on_timeline_cb) |
1912 | + self.gui.gui_actions.connect('remove-image-on-timeline', self._remove_image_on_timeline_cb) |
1913 | + self.gui.gui_actions.connect('active-sound', self._active_sound_cb) |
1914 | + self.gui.gui_actions.connect('deactive-sound', self._deactive_sound_cb) |
1915 | + self.gui.gui_actions.connect('sound-stop-with-video', self._sound_stop_with_video_cb) |
1916 | + self.gui.gui_actions.connect('change-framerate', self._change_framerate_cb) |
1917 | + |
1918 | + |
1919 | + |
1920 | + |
1921 | + # |
1922 | + # application high level callbacks |
1923 | + # |
1924 | + |
1925 | + def _open_project_cb(self, gui, path) : |
1926 | + self.debug('RX close-project signal') |
1927 | + self.project_ctrl.open(path) |
1928 | + |
1929 | + def _new_project_cb(self, gui ) : |
1930 | + self.debug('RX new-project signal' ) |
1931 | + self.project_ctrl.new_project() |
1932 | + |
1933 | + |
1934 | + def _quit_cb(self, gui): |
1935 | + self.app.quit() |
1936 | + return True |
1937 | + # |
1938 | + # Project level callbacks |
1939 | + # |
1940 | + def _save_project_cb(self, gui) : |
1941 | + self.debug('RX save-project signal') |
1942 | + self.project_ctrl.save() |
1943 | + |
1944 | + def _save_as_project_cb(self, gui, path) : |
1945 | + self.debug('RX save-as-project signal') |
1946 | + self.project_ctrl.save(path) |
1947 | + |
1948 | + |
1949 | + def _close_project_cb(self, gui) : |
1950 | + self.debug('RX close-project signal') |
1951 | + self.project_ctrl.close() |
1952 | + |
1953 | + |
1954 | + def _acq_mode_cb(self, gui) : |
1955 | + self.debug('RX acquisition-mode signal') |
1956 | + self.project_ctrl.acquisition_mode() |
1957 | + |
1958 | + def _timeline_mode_cb(self, gui) : |
1959 | + self.debug('RX timeline-mode signal') |
1960 | + self.project_ctrl.timleline_mode() |
1961 | + |
1962 | + def _import_sound_cb(self, gui, path) : |
1963 | + self.debug('RX import-sound signal : %s', path) |
1964 | + self.project_ctrl.ctrl_tmln.add_sound_track(path) |
1965 | + |
1966 | + def _image_view_cb(self, gui, origin, image_name, position = 0) : |
1967 | + self.debug('RX image-view signal : origin %s image %s', origin, image_name) |
1968 | + self.project_ctrl.img_vw_mode(origin, image_name, position) |
1969 | + |
1970 | + def _import_images_cb(self, gui, paths) : |
1971 | + self.debug('RX import-images signal : %s',paths ) |
1972 | + self.project_ctrl.import_images(paths) |
1973 | + |
1974 | + def _show_project_properties_cb(self, gui) : |
1975 | + """ show project properties callback """ |
1976 | + self.debug('RX show-project-properties' ) |
1977 | + self.project_ctrl.show_project_properties() |
1978 | + |
1979 | + |
1980 | + |
1981 | + # |
1982 | + # Acqusition callbacks |
1983 | + # |
1984 | + |
1985 | + def _start_acq_cb(self, gui) : |
1986 | + self.debug(' RX start-acquistion signal') |
1987 | + self.project_ctrl.ctrl_acq.start() |
1988 | + |
1989 | + def _stop_acq_cb(self, gui) : |
1990 | + self.debug(' RX stop-acquistion signal') |
1991 | + self.project_ctrl.ctrl_acq.stop() |
1992 | + |
1993 | + def _take_snapshot_cb(self, gui) : |
1994 | + self.debug('RX take-snapshot signal') |
1995 | + self.project_ctrl.ctrl_acq.do_snapshot() |
1996 | + |
1997 | + def _active_mixer_cb(self, gui, is_active) : |
1998 | + self.debug('RX active-mixer signal : %s', is_active) |
1999 | + self.project_ctrl.ctrl_acq.active_mixer(is_active) |
2000 | + |
2001 | + # |
2002 | + # Timeline callbacks |
2003 | + # |
2004 | + def _change_timeline_order_cb(self, gui, objs, target): |
2005 | + self.debug('RX change-timeline-order signal - objs %s target %s', objs, target) |
2006 | + self.project_ctrl.ctrl_tmln.change_order(objs, target) |
2007 | + |
2008 | + def _insert_image_on_timeline_cb(self, gui, objs, target): |
2009 | + self.debug('RX insert-image-on-timeline signal - objs %s target %s', objs, target) |
2010 | + self.project_ctrl.ctrl_tmln.insert(objs, target) |
2011 | + |
2012 | + def _remove_image_on_timeline_cb(self, gui, objs): |
2013 | + self.debug('RX remove-image-on-timeline signal - objs %s', objs) |
2014 | + self.project_ctrl.ctrl_tmln.remove(objs) |
2015 | + |
2016 | + def _active_sound_cb(self, gui) : |
2017 | + self.debug('RX active-sound signal') |
2018 | + self.project_ctrl.ctrl_tmln.add_sound_track_to_timeline() |
2019 | + |
2020 | + def _deactive_sound_cb(self, gui) : |
2021 | + self.debug('RX deactive-sound signal') |
2022 | + self.project_ctrl.ctrl_tmln.remove_sound_track_from_timeline() |
2023 | + |
2024 | + def _sound_stop_with_video_cb(self, gui, is_stop) : |
2025 | + self.debug('RX sound-stop-with-video, is_stop %s', is_stop) |
2026 | + self.project_ctrl.ctrl_tmln.stop_sound_with_video(is_stop) |
2027 | + |
2028 | + def _change_framerate_cb(self, gui, fpi) : |
2029 | + self.debug('RXchange-framerate, fpi %s', fpi) |
2030 | + self.project_ctrl.set_framerate(fpi) |
2031 | + |
2032 | +class CtrlApp(Signallable, Loggable) : |
2033 | + # CONSTANTS |
2034 | + MODES = ("INIT", "RUN", "QUIT") |
2035 | + |
2036 | + def __init__(self) : |
2037 | + """ |
2038 | + Controller initialisation. |
2039 | + Is the main appication intialisation |
2040 | + """ |
2041 | + # initialize threads |
2042 | + gobject.threads_init() |
2043 | + |
2044 | + Loggable.__init__(self) |
2045 | + |
2046 | + self.mode = "INIT" |
2047 | + # init logging as early as possible so we can log startup code |
2048 | + enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0') |
2049 | + log.init('PITIVI_DEBUG', enable_color) |
2050 | + |
2051 | + self.mainloop = gobject.MainLoop() |
2052 | + |
2053 | + # initializes i18n |
2054 | + self._init_i18n() |
2055 | + |
2056 | + # load configuration : ie. saved user options |
2057 | + self.configuration = LCONF.LucioleConf() |
2058 | + # load GUI theme i.e parse rc file |
2059 | + self.configuration.load_theme() |
2060 | + |
2061 | + |
2062 | + |
2063 | + |
2064 | + def run(self, args) : |
2065 | + |
2066 | + # TODO : seems no more needed : check |
2067 | + import gtk.gdk |
2068 | + gtk.gdk.threads_enter() |
2069 | + |
2070 | + |
2071 | + # intializes options : |
2072 | + (_options, _args) = OPTIONS.options_parser(args) |
2073 | + |
2074 | + # initialize sound player i.e from snapshot sound |
2075 | + self.soundplayer = LSOUND.SoundPlayer(LCONST.SOUND_SNAPSHOT_PATH) |
2076 | + |
2077 | + # start Project controller |
2078 | + self.project_ctrl = CTRL_PROJECT.CtrlProject() |
2079 | + |
2080 | + # start GUI |
2081 | + self.gui = GUI_MAIN.LucioleMainWindow(self) |
2082 | + |
2083 | + |
2084 | + # connect project controller with GUI |
2085 | + self.project_ctrl.connect_to_gui(self.gui) |
2086 | + |
2087 | + self.connect_to_project(self.project_ctrl) |
2088 | + |
2089 | + self.gui_interface = CtrlAppGui(self) |
2090 | + |
2091 | + # load a project ? |
2092 | + if _options.filename \ |
2093 | + and os.path.exists(_options.filename): |
2094 | + self.project_ctrl.open(_options.filename) |
2095 | + else : |
2096 | + self.project_ctrl.no_project_mode() |
2097 | + self.mode = "RUN" |
2098 | + |
2099 | + |
2100 | + |
2101 | + self.mainloop.run() |
2102 | + |
2103 | + # TODO : seems no more needed : check |
2104 | + import gtk.gdk |
2105 | + gtk.gdk.threads_leave() |
2106 | + |
2107 | + def quit(self) : |
2108 | + self.mainloop.quit() |
2109 | + |
2110 | + def _init_i18n(self) : |
2111 | + """ intialize locales """ |
2112 | + _locale_path = LCONST.LOCALE_DIR |
2113 | + |
2114 | + # Init the list of languages to support |
2115 | + _langs = [] |
2116 | + #Check the default locale |
2117 | + _lc, _encoding = locale.getdefaultlocale() |
2118 | + if (_lc): |
2119 | + #If we have a default, it's the first in the list |
2120 | + _langs = [_lc] |
2121 | + # Now lets get all of the supported languages on the system |
2122 | + _language = os.environ.get('LANGUAGE', None) |
2123 | + _msg = "language : " , _language |
2124 | + self.debug(_msg) |
2125 | + if (_language): |
2126 | + # langage comes back something like en_CA:en_US:en_GB:en |
2127 | + # on linuxy systems, on Win32 it's nothing, so we need to |
2128 | + # split it up into a list |
2129 | + _langs += _language.split(":") |
2130 | + # Now add on to the back of the list the translations that we |
2131 | + # know that we have, our defaults""" |
2132 | + _langs += ["en_US"] |
2133 | + |
2134 | + # Now langs is a list of all of the languages that we are going |
2135 | + # to try to use. First we check the default, then what the system |
2136 | + # told us, and finally the 'known' list |
2137 | + _msg = "locale path", _locale_path |
2138 | + self.debug(_msg) |
2139 | + |
2140 | + GUI_CTRL.GuiController.init_18n(_locale_path) |
2141 | + #import gtk.glade |
2142 | + #gtk.glade.bindtextdomain(LCONST.APP_NAME, _locale_path) |
2143 | + #gtk.glade.textdomain(LCONST.APP_NAME) |
2144 | + |
2145 | + gettext.bindtextdomain(LCONST.APP_NAME, _locale_path) |
2146 | + gettext.textdomain(LCONST.APP_NAME) |
2147 | + |
2148 | + # Get the language to use |
2149 | + _lang = gettext.translation(LCONST.APP_NAME, _locale_path |
2150 | + , languages=_langs, fallback = True) |
2151 | + # Install the language, map _() (which we marked our |
2152 | + # strings to translate with) to self.lang.gettext() which will |
2153 | + # translate them.""" |
2154 | + global _ |
2155 | + _ = _lang.gettext |
2156 | + |
2157 | + def connect_to_project(self, project_ctrl) : |
2158 | + project_ctrl.connect('project-loaded', self._project_loaded_cb) |
2159 | + project_ctrl.connect('project-closed', self._project_closed_cb) |
2160 | + project_ctrl.connect('quit', self._quit_cb) |
2161 | + |
2162 | + def disconnect_from_project(self, project_ctrl) : |
2163 | + project_ctrl.disconnect_by_function(self._project_loaded_cb) |
2164 | + project_ctrl.disconnect_by_function(self._project_closed_cb) |
2165 | + |
2166 | + def _project_loaded_cb(self, project_ctrl, project) : |
2167 | + self.soundplayer.connect_to_app(project_ctrl) |
2168 | + |
2169 | + def _project_closed_cb(self, project_ctrl) : |
2170 | + pass |
2171 | + #self.soundplayer.disconnect_from_app(project_ctrl) |
2172 | + |
2173 | + def _quit_cb(self, project) : |
2174 | + self.quit() |
2175 | + |
2176 | +class LucioleController(object): |
2177 | + """ |
2178 | + Main luciole controller |
2179 | + """ |
2180 | + |
2181 | + def __init__(self, args) : |
2182 | + """ Controller initialisation. |
2183 | + |
2184 | + Is the main appication intialisation """ |
2185 | + # init logging as early as possible so we can log startup code |
2186 | + enable_color = os.environ.get('PITIVI_DEBUG_NO_COLOR', '0') in ('', '0') |
2187 | + log.init('PITIVI_DEBUG', enable_color) |
2188 | + |
2189 | + # |
2190 | + # init attributes |
2191 | + # |
2192 | + self.ctrl_signals = None |
2193 | + self.__rusher = None |
2194 | + |
2195 | + # parse program options |
2196 | + self.__options = OPTIONS.options_parser() |
2197 | + |
2198 | + # init logging |
2199 | + self.logger = LOGGING.init_logging( |
2200 | + self.__options.is_verbose, |
2201 | + log_to_file = self.__options.is_logfile |
2202 | + ) |
2203 | + self.logger.info("Starting luciole") |
2204 | + |
2205 | + # Init i18n |
2206 | + self.__init_i18n() |
2207 | + |
2208 | + # load configuration : ie. saved user options |
2209 | + self.__configurer = LCONF.LucioleConf() |
2210 | + # load GUI theme |
2211 | + self.__configurer.load_theme() |
2212 | + # create controller signals |
2213 | + self.__connect_signals() |
2214 | + # initialize gui and BTW threads |
2215 | + self.__gui = GUI_CTRL.GuiController(self.ctrl_signals) |
2216 | + |
2217 | + |
2218 | + |
2219 | + # load recent projects |
2220 | + _cbs = None |
2221 | + _recent_mnger = self.__configurer.init_recent_manager( |
2222 | + self.__gui.file_recent_menu, |
2223 | + self.__gui.windows, |
2224 | + _cbs) |
2225 | + |
2226 | + # prepare callbacks for Project controller |
2227 | + _cbs = { 'done-rush' : self.on_done_rush, |
2228 | + 'close-project' : self.on_close_project } |
2229 | + |
2230 | + |
2231 | + # start project controller |
2232 | + self._project = \ |
2233 | + CTRL_PROJECT.ProjectController(self.__gui,_recent_mnger ,_cbs) |
2234 | + |
2235 | + _cbs = { |
2236 | + 'on-done-snapshot' : self.on_done_snapshot, |
2237 | + 'on-error':self.on_error |
2238 | + } |
2239 | + self._viewer = CTRL_VIEWER.Viewer(self.__gui, _cbs) |
2240 | + |
2241 | + # display capture trash if option is set |
2242 | + if self.__configurer.conf_options['CaptureTrashDisplay'] == 'yes' : |
2243 | + self.__gui.capture_trash = True |
2244 | + |
2245 | + |
2246 | + |
2247 | + # conncet gui signals after the controller initialisation |
2248 | + self.__gui.connect_gui_signals() |
2249 | + # Connect GUI with app |
2250 | + self.__gui.connect_to_app_ctrl(self) |
2251 | + |
2252 | + # TODO : load timeline |
2253 | + #TMLN.Timeline() |
2254 | + |
2255 | + def __connect_signals(self): |
2256 | + """ connect controller signals """ |
2257 | + self.ctrl_signals = gobject.GObject() |
2258 | + for _signal in CTRL_CONST.CTRL_SIGNALS : |
2259 | + |
2260 | + # declare signal |
2261 | + gobject.signal_new(_signal, self.ctrl_signals, |
2262 | + gobject.SIGNAL_RUN_LAST, |
2263 | + gobject.TYPE_NONE, |
2264 | + (gobject.TYPE_PYOBJECT,)) |
2265 | + |
2266 | + # Build signal callback function name from signal name |
2267 | + # example : signal "change-alpha-mixer" |
2268 | + # have callback self.on_change_alpha_mixer |
2269 | + _cb = 'self.on_' + _signal.replace('-', '_') |
2270 | + # connect signal |
2271 | + self.ctrl_signals.connect(_signal, eval(_cb)) |
2272 | + |
2273 | + |
2274 | + def on_change_alpha_mixer(self, *args) : |
2275 | + """ Request change mixer alpha value """ |
2276 | + _args = self.__extract_signal_args(args) |
2277 | + try : |
2278 | + self._viewer.change_mixer_alpha(_args['alpha']) |
2279 | + except LEXCEP.LucioException, _err_msg : |
2280 | + raise LEXCEP.LucioException, _err_msg |
2281 | + |
2282 | + |
2283 | + def on_change_framerate(self, *args) : |
2284 | + """ Request of framerate(fpi) change """ |
2285 | + _args = self.__extract_signal_args(args) |
2286 | + self._project.change_framerate(_args['framerate']) |
2287 | + |
2288 | + def on_change_project(self, *args) : |
2289 | + """ Request for prohect change """ |
2290 | + _args = self.__extract_signal_args(args) |
2291 | + for _key, _value in _args.iteritems() : |
2292 | + self._project.change_project(_key, _value) |
2293 | + |
2294 | + def on_close_project(self, *args ) : |
2295 | + """ Request for project close """ |
2296 | + _args = self.__extract_signal_args(args) |
2297 | + # test if project exists |
2298 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2299 | + # check if project is modified |
2300 | + if self._project.data['is_modified'] : |
2301 | + # ask for save it before close |
2302 | + _res = self.__gui.windows.\ |
2303 | + question_message(_('Save Project before closing')) |
2304 | + if _res : |
2305 | + self._project.save() |
2306 | + |
2307 | + # set viewer in default mode |
2308 | + self._viewer.mode = self._viewer.DEFAULT |
2309 | + |
2310 | + # clear project controller |
2311 | + self._project.mode = CTRL_CONST.NO_PROJECT |
2312 | + |
2313 | + # display close message |
2314 | + _msg = _('Project %s is closed' % self._project.data['project_name']) |
2315 | + self.__gui.status_bar.display_message(_msg) |
2316 | + |
2317 | + |
2318 | + def on_create_project(self, *args) : |
2319 | + """ Request new project """ |
2320 | + _args = self.__extract_signal_args(args) |
2321 | + self._project.new() |
2322 | + |
2323 | + |
2324 | + def on_delete_capture(self, *args) : |
2325 | + """ Request delete on capture """ |
2326 | + _args = self.__extract_signal_args(args) |
2327 | + # remove on treeview |
2328 | + self.__gui.treeviews[LCONST.CAPTURE].remove() |
2329 | + |
2330 | + # The image selected for delete is displayed before delete |
2331 | + # so after go in default mode to nom more display the deleted image |
2332 | + self._viewer.mode = self._viewer.DEFAULT |
2333 | + |
2334 | + # change control widget in no acquisition mode |
2335 | + if self.__gui.control_widget.mode in\ |
2336 | + ( GUI_CONST.ACQUISITION_ACTIVE, |
2337 | + GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) : |
2338 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2339 | + |
2340 | + |
2341 | + def on_delete_chrono(self, *args) : |
2342 | + """ Request delete on capture """ |
2343 | + _args = self.__extract_signal_args(args) |
2344 | + # remove on treeview |
2345 | + self.__gui.treeviews[LCONST.CHRONO].remove() |
2346 | + |
2347 | + # The image selected for delete is displayed before delete |
2348 | + # so after go in default mode to nom more display the deleted image |
2349 | + self._viewer.mode = self._viewer.DEFAULT |
2350 | + |
2351 | + # change control widget in no acquisition mode |
2352 | + if self.__gui.control_widget.mode in \ |
2353 | + ( GUI_CONST.ACQUISITION_ACTIVE, |
2354 | + GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) : |
2355 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2356 | + |
2357 | + |
2358 | + def on_display_about(self, *args) : |
2359 | + """ request display about """ |
2360 | + _args = self.__extract_signal_args(args) |
2361 | + # compute version |
2362 | + __version = "\n %s (%s)" % (INFO.VERSION, INFO.REVNO) |
2363 | + # display window |
2364 | + self.__gui.windows.about(__version) |
2365 | + |
2366 | + |
2367 | + def on_display_luciole_preferences(self, *args) : |
2368 | + """ Request display of application preferences """ |
2369 | + _args = self.__extract_signal_args(args) |
2370 | + _cbs = {'done-preferences' : self.on_done_preferences} |
2371 | + self.__gui.windows.preferences( self.__configurer.conf_options, |
2372 | + _cbs) |
2373 | + |
2374 | + |
2375 | + |
2376 | + |
2377 | + |
2378 | + def on_display_project_properties(self, *args) : |
2379 | + """ Request display project properties """ |
2380 | + _args = self.__extract_signal_args(args) |
2381 | + # test if project exists |
2382 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2383 | + # go in default mode before dispalying window |
2384 | + # due to webcam detection and avoid confilcts with acquisition |
2385 | + self._viewer.mode = self._viewer.DEFAULT |
2386 | + |
2387 | + # change control widget in no acquisition mode |
2388 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2389 | + |
2390 | + # display message |
2391 | + self.__gui.status_bar.display_message(_('No Acquistion')) |
2392 | + |
2393 | + # launch project poperties window |
2394 | + _cbs = \ |
2395 | + {'change-project-properties' : self.on_change_project_properties} |
2396 | + self.__gui.windows.project_properties(self._project.data, _cbs) |
2397 | + |
2398 | + |
2399 | + def on_export_to_tool(self, *args) : |
2400 | + """ Request export to tool """ |
2401 | + _args = self.__extract_signal_args(args) |
2402 | + # test if project exists |
2403 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2404 | + self.__gui.windows.export_tool(self._project.data) |
2405 | + |
2406 | + |
2407 | + def on_export_to_video(self, *args) : |
2408 | + """ Request export to video """ |
2409 | + _args = self.__extract_signal_args(args) |
2410 | + # test if project exists |
2411 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2412 | + self.__gui.windows.export_video(self._project.data) |
2413 | + |
2414 | + |
2415 | + def on_import_image(self, *args) : |
2416 | + """ Request image import """ |
2417 | + _args = self.__extract_signal_args(args) |
2418 | + # check if project exists |
2419 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2420 | + # open filename chooser dialog |
2421 | + _filenames = self.__gui.windows.import_dialog() |
2422 | + |
2423 | + if _filenames != [] : |
2424 | + _cbs = { 'done-import' : self.on_done_import} |
2425 | + # start import controller |
2426 | + CTRL_IMPORT.ImportController(_filenames, |
2427 | + self._project.data, |
2428 | + self.__rusher, |
2429 | + self.__gui.status_bar, |
2430 | + _cbs) |
2431 | + else : |
2432 | + _msg = _("No files or valid files choosen for image import.") |
2433 | + self.__gui.windows.error_message( _msg) |
2434 | + else : |
2435 | + _msg = _("Impossible to import images when no project are loaded.") |
2436 | + self.__gui.windows.error_message( _msg) |
2437 | + |
2438 | + def on_move_down_chrono(self, *args) : |
2439 | + """ request to move down an image """ |
2440 | + _args = self.__extract_signal_args(args) |
2441 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2442 | + self.__gui.treeviews[LCONST.CHRONO].move_down() |
2443 | + |
2444 | + |
2445 | + def on_move_to_chrono(self, *args) : |
2446 | + """ request for move imge from capture to chrono""" |
2447 | + _args = self.__extract_signal_args(args) |
2448 | + # get images to move from capture widget |
2449 | + _images_name = self.__gui.treeviews[LCONST.CAPTURE].images_to_move() |
2450 | + # append this images in chrono widget |
2451 | + _images_obj = \ |
2452 | + [ self.__rusher.get_image(_image) for _image in _images_name ] |
2453 | + for _image in _images_obj : |
2454 | + self.__gui.treeviews[LCONST.CHRONO].append_image(_image) |
2455 | + |
2456 | + |
2457 | + def on_move_up_chrono(self, *args) : |
2458 | + """ request to move up an image """ |
2459 | + _args = self.__extract_signal_args(args) |
2460 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2461 | + self.__gui.treeviews[LCONST.CHRONO].move_up() |
2462 | + |
2463 | + |
2464 | + |
2465 | + |
2466 | + |
2467 | + def on_play_query_position(self, *args) : |
2468 | + """ Query for player position """ |
2469 | + _args = self.__extract_signal_args(args) |
2470 | + (_position, _duration) = self._viewer.play_query_position() |
2471 | + if _args['cb'] : |
2472 | + # callback call to gui |
2473 | + _args['cb'](_position, _duration) |
2474 | + |
2475 | + |
2476 | + def on_play_video(self, *args) : |
2477 | + """ Play video request """ |
2478 | + _args = self.__extract_signal_args(args) |
2479 | + # test if project exists |
2480 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2481 | + try : |
2482 | + self._viewer.init_video_player(self._project.data) |
2483 | + except LEXCEP.LucioException, _err_msg : |
2484 | + raise LEXCEP.LucioException, _err_msg |
2485 | + else : |
2486 | + self._viewer.mode = self._viewer.PLAYER |
2487 | + try : |
2488 | + self._viewer.play_video(self._project.data) |
2489 | + except LEXCEP.LucioException, _err_msg : |
2490 | + raise LEXCEP.LucioException, _err_msg |
2491 | + else : |
2492 | + # send error message |
2493 | + msg = _("Can not play animation : No project loaded") |
2494 | + self.__gui.windows.error_message( msg) |
2495 | + |
2496 | + |
2497 | + def on_quit_app(self, *args) : |
2498 | + """ Request quit application """ |
2499 | + _args = self.__extract_signal_args(args) |
2500 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED and \ |
2501 | + self._project.data['is_modified'] : |
2502 | + _msg = _('Project modified. Save project before exit ?') |
2503 | + _res = self.__gui.windows.question_message(_msg) |
2504 | + # if response is not a bool : cancel clicked |
2505 | + if isinstance(_res, bool) : |
2506 | + if _res : |
2507 | + # if True save project before exit |
2508 | + self._project.save() |
2509 | + GUI_CTRL.GuiController.quit() |
2510 | + else : |
2511 | + GUI_CTRL.GuiController.quit() |
2512 | + |
2513 | + def on_save_as_project(self, *args) : |
2514 | + """ save as project request """ |
2515 | + _args = self.__extract_signal_args(args) |
2516 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2517 | + _dir = self.__gui.windows.dir_chooser() |
2518 | + if _dir != None : |
2519 | + # set viewer in default mode due to project reload |
2520 | + self._viewer.mode = self._viewer.DEFAULT |
2521 | + try : |
2522 | + self._project.save_as(_dir) |
2523 | + except LEXCEP.LucioException, _err_msg : |
2524 | + _msg = _('Impossible to save as project : %s' % _err_msg) |
2525 | + self.__gui.windows.error_message(_msg) |
2526 | + else : |
2527 | + _msg = _('Project saved as %s' % \ |
2528 | + self._project.data['project_name']) |
2529 | + self.__gui.status_bar.display_message(_msg) |
2530 | + |
2531 | + |
2532 | + def on_save_project(self, *args) : |
2533 | + """ save project request """ |
2534 | + _args = self.__extract_signal_args(args) |
2535 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2536 | + try : |
2537 | + self._project.save() |
2538 | + except LEXCEP.LucioException, _err_msg : |
2539 | + _msg = _('Impossible to save project : %s' % _err_msg) |
2540 | + self.__gui.windows.error_message(_msg) |
2541 | + else : |
2542 | + _msg = _('Project %s saved' % \ |
2543 | + self._project.data['project_name']) |
2544 | + self.__gui.status_bar.display_message(_msg) |
2545 | + # update project name on Main bar |
2546 | + self.__gui.set_program_bar(self._project.data['project_name'], |
2547 | + self._project.data['is_modified']) |
2548 | + |
2549 | + |
2550 | + def on_start_acquisition(self, *args ) : |
2551 | + """Request for starting acqusisition """ |
2552 | + _args = self.__extract_signal_args(args) |
2553 | + # check if a project is loaded |
2554 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2555 | + self._viewer.mode = self._viewer.ACQUIRER |
2556 | + |
2557 | + # change control widget in acquisition mode |
2558 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISITION_ACTIVE |
2559 | + |
2560 | + else : |
2561 | + _msg = \ |
2562 | + _(' Can not start acquisition when no project are loaded.') |
2563 | + self.__gui.windows.error_message(_msg) |
2564 | + |
2565 | + |
2566 | + def on_start_mixer(self, *args) : |
2567 | + """ Request mixer start """ |
2568 | + _args = self.__extract_signal_args(args) |
2569 | + # check if a project is loaded |
2570 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2571 | + # switch to viewer with acqusisition and mixer |
2572 | + self._viewer.mode = self._viewer.ACQUIRER_WITH_MIXER |
2573 | + # change control widget in no acquisition mode |
2574 | + self.__gui.control_widget.mode = \ |
2575 | + GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER |
2576 | + |
2577 | + # display message |
2578 | + self.__gui.status_bar.display_message(_('Acquiring with mixer')) |
2579 | + else : |
2580 | + # robustness |
2581 | + _msg = \ |
2582 | + _('No project are loaded.') |
2583 | + self.__gui.windows.error_message(_msg) |
2584 | + |
2585 | + |
2586 | + |
2587 | + def on_stop_acquisition(self, *args) : |
2588 | + """ Request to stop acquistion """ |
2589 | + _args = self.__extract_signal_args(args) |
2590 | + # check if a project is loaded |
2591 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2592 | + # switch to viewer default |
2593 | + self._viewer.mode = self._viewer.DEFAULT |
2594 | + # change control widget in no acquisition mode |
2595 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2596 | + |
2597 | + else : |
2598 | + # robustness |
2599 | + _msg = \ |
2600 | + _('No project are loaded.') |
2601 | + self.__gui.windows.error_message(_msg) |
2602 | + |
2603 | + def on_stop_mixer(self, *args) : |
2604 | + """ Request mixer stop """ |
2605 | + _args = self.__extract_signal_args(args) |
2606 | + # check if a project is loaded |
2607 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2608 | + # switch to viewer with acqusisition and mixer |
2609 | + self._viewer.mode = self._viewer.ACQUIRER |
2610 | + # change control widget in no acquisition mode |
2611 | + self.__gui.control_widget.mode = \ |
2612 | + GUI_CONST.ACQUISITION_ACTIVE |
2613 | + |
2614 | + # display message |
2615 | + self.__gui.status_bar.display_message(_('Acquiring')) |
2616 | + else : |
2617 | + # robustness |
2618 | + _msg = \ |
2619 | + _('No project are loaded.') |
2620 | + self.__gui.windows.error_message(_msg) |
2621 | + |
2622 | + def on_stop_video(self, *args) : |
2623 | + """ Stop video request """ |
2624 | + _args = self.__extract_signal_args(args) |
2625 | + try : |
2626 | + self._viewer.stop_video() |
2627 | + except LEXCEP.LucioException, _err_msg : |
2628 | + raise LEXCEP.LucioException, _err_msg |
2629 | + else : |
2630 | + self._viewer.mode = self._viewer.DEFAULT |
2631 | + |
2632 | + |
2633 | + def on_take_snapshot(self, *args) : |
2634 | + """ take image snapshot request """ |
2635 | + _args = self.__extract_signal_args(args) |
2636 | + if self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2637 | + self._viewer.take_snapshot() |
2638 | + else : |
2639 | + # robustness |
2640 | + _msg = \ |
2641 | + _('No project are loaded.') |
2642 | + self.__gui.windows.error_message(_msg) |
2643 | + |
2644 | + _msg = \ |
2645 | + _('No project are loaded.') |
2646 | + self.__gui.windows.error_message(_msg) |
2647 | + |
2648 | + |
2649 | + def on_view_image(self, *args) : |
2650 | + """ |
2651 | + request image view/display : |
2652 | + Mix image with stream if viewer mode is acquirer with mixer |
2653 | + """ |
2654 | + _args = self.__extract_signal_args(args) |
2655 | + #if mode is player does not allow image view |
2656 | + if self._viewer.mode == self._viewer.PLAYER : |
2657 | + pass |
2658 | + |
2659 | + # if mode is acquirer with mixer and image is from capture treeview : |
2660 | + # mix image with acqusisition stream |
2661 | + elif self._viewer.mode == self._viewer.ACQUIRER_WITH_MIXER \ |
2662 | + and _args['tv_type'] == LCONST.CAPTURE : |
2663 | + self._viewer.mix_image_in_stream(_args['image']) |
2664 | + |
2665 | + # on other cases view image |
2666 | + else : |
2667 | + self._viewer.mode = self._viewer.IMAGER |
2668 | + self._viewer.view_image(_args['image']) |
2669 | + |
2670 | + #change control widget in no acquisition mode |
2671 | + if self.__gui.control_widget.mode in \ |
2672 | + ( GUI_CONST.ACQUISITION_ACTIVE, |
2673 | + GUI_CONST.ACQUISITION_ACTIVE_WITH_MIXER) : |
2674 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2675 | + |
2676 | + |
2677 | + def run(self) : |
2678 | + """ main program ininite loop """ |
2679 | + # TODO : |
2680 | + GUI_CTRL.GuiController.threads_enter() |
2681 | + # before starting main loop check for |
2682 | + # options given to application |
2683 | + |
2684 | + # load a project ? |
2685 | + if self.__options.filename \ |
2686 | + and os.path.exists(self.__options.filename): |
2687 | + # emit open project signal |
2688 | + self.__gui.emit( |
2689 | + "open-project", self.__options.filename) |
2690 | + |
2691 | + GUI_CTRL.GuiController.main_loop() |
2692 | + GUI_CTRL.GuiController.threads_leave() |
2693 | + self.logger.info("Leaving luciole") |
2694 | + # |
2695 | + # Callbacks from controller |
2696 | + # |
2697 | + def on_done_rush(self, rusher) : |
2698 | + """ callback : when tush images are loaded """ |
2699 | + if rusher != None \ |
2700 | + and self._project.mode == CTRL_CONST.PROJECT_LOADED : |
2701 | + # load treeviews |
2702 | + # prepare data |
2703 | + _app_data = {} |
2704 | + _app_data['capture_images'] = self._project.data['capture_images'] |
2705 | + _app_data['chrono_images'] = self._project.data['chrono_images'] |
2706 | + _app_data['rush'] = rusher |
2707 | + |
2708 | + self.__gui.load_treeviews(_app_data) |
2709 | + |
2710 | + # init acquisition |
2711 | + self._viewer.init_acquisition(self._project.data) |
2712 | + |
2713 | + |
2714 | + # update fpi on gui and show it |
2715 | + self.__gui.update_framerate(int(self._project.data['fpi'])) |
2716 | + |
2717 | + # show project acquisition widgets |
2718 | + self.__gui.control_widget.mode = GUI_CONST.ACQUISTION_INACTIVE |
2719 | + |
2720 | + # update project name on Main bar |
2721 | + self.__gui.set_program_bar(self._project.data['project_name'], |
2722 | + self._project.data['is_modified']) |
2723 | + |
2724 | + # finish project load (project aspect) |
2725 | + self._project.finish_project_load() |
2726 | + |
2727 | + # store rusher |
2728 | + self.__rusher = rusher |
2729 | + |
2730 | + |
2731 | + |
2732 | + def on_done_snapshot(self) : |
2733 | + """ |
2734 | + callback : image snapshot is done. |
2735 | + Now image can be processed into project |
2736 | + """ |
2737 | + self._project.append_snapshot(self.__rusher, self._viewer.acquirer) |
2738 | + |
2739 | + def on_done_preferences(self , modified_options) : |
2740 | + """ callback : preferences modified fome diaog """ |
2741 | + # update prefernces |
2742 | + (_capture_trash, _ask_restart) = \ |
2743 | + self.__configurer.update_preferences(modified_options) |
2744 | + |
2745 | + # check if gui update is needed |
2746 | + if isinstance(_capture_trash, bool) : |
2747 | + self.__gui.capture_trash = _capture_trash |
2748 | + |
2749 | + if isinstance(_ask_restart, bool) and _ask_restart == True : |
2750 | + _msg = \ |
2751 | + _('Please restart Luciole to take into account the new theme ') |
2752 | + self.__gui.status_bar.display_message(_msg) |
2753 | + |
2754 | + def on_change_project_properties(self, key, key_webcam= None, data =None) : |
2755 | + """ callback project change from properties window """ |
2756 | + # webcam_data field of project dictionaty is a dictionaty |
2757 | + # Test if webcam data change key |
2758 | + if key == 'webcam_data': |
2759 | + # make a local copy of webcam_data |
2760 | + webcam_dict = self._project.data['webcam_data'] |
2761 | + #test if webcam dict key exists |
2762 | + if webcam_dict.has_key(key_webcam) : |
2763 | + # update webcam key and call project change |
2764 | + webcam_dict[key_webcam] = data |
2765 | + self._project.change_project('webcam_data', webcam_dict) |
2766 | + |
2767 | + def on_done_import(self, image_objs = [] ) : |
2768 | + """ callback : image import (rush generation) is done """ |
2769 | + if image_objs != [] : |
2770 | + self._project.change_project( 'rush_images', |
2771 | + self.__rusher.dump_image_name()) |
2772 | + # Not to be done in thread : interacts with gui |
2773 | + for _image_obj in image_objs : |
2774 | + self.__gui.append_capture(_image_obj) |
2775 | + |
2776 | + |
2777 | + |
2778 | + # |
2779 | + # Error calbacks |
2780 | + # |
2781 | + def on_error(self, err_type, message) : |
2782 | + """ Main error callback """ |
2783 | + _msg = _("error type : %s. %s" % (err_type, message)) |
2784 | + # Display error on log window |
2785 | + self.logger.info(_msg) |
2786 | + # |
2787 | + # private methods |
2788 | + # |
2789 | + def __init_i18n(self) : |
2790 | + """ intialize locales """ |
2791 | + _locale_path = LCONST.LOCALE_DIR |
2792 | + |
2793 | + # Init the list of languages to support |
2794 | + _langs = [] |
2795 | + #Check the default locale |
2796 | + _lc, _encoding = locale.getdefaultlocale() |
2797 | + if (_lc): |
2798 | + #If we have a default, it's the first in the list |
2799 | + _langs = [_lc] |
2800 | + # Now lets get all of the supported languages on the system |
2801 | + _language = os.environ.get('LANGUAGE', None) |
2802 | + _msg = "language : " , _language |
2803 | + self.logger.debug(_msg) |
2804 | + if (_language): |
2805 | + # langage comes back something like en_CA:en_US:en_GB:en |
2806 | + # on linuxy systems, on Win32 it's nothing, so we need to |
2807 | + # split it up into a list |
2808 | + _langs += _language.split(":") |
2809 | + # Now add on to the back of the list the translations that we |
2810 | + # know that we have, our defaults""" |
2811 | + _langs += ["en_US"] |
2812 | + |
2813 | + # Now langs is a list of all of the languages that we are going |
2814 | + # to try to use. First we check the default, then what the system |
2815 | + # told us, and finally the 'known' list |
2816 | + _msg = "locale path", _locale_path |
2817 | + self.logger.debug(_msg) |
2818 | + |
2819 | + GUI_CTRL.GuiController.init_18n(_locale_path) |
2820 | + #import gtk.glade |
2821 | + #gtk.glade.bindtextdomain(LCONST.APP_NAME, _locale_path) |
2822 | + #gtk.glade.textdomain(LCONST.APP_NAME) |
2823 | + |
2824 | + gettext.bindtextdomain(LCONST.APP_NAME, _locale_path) |
2825 | + gettext.textdomain(LCONST.APP_NAME) |
2826 | + |
2827 | + # Get the language to use |
2828 | + _lang = gettext.translation(LCONST.APP_NAME, _locale_path |
2829 | + , languages=_langs, fallback = True) |
2830 | + # Install the language, map _() (which we marked our |
2831 | + # strings to translate with) to self.lang.gettext() which will |
2832 | + # translate them.""" |
2833 | + global _ |
2834 | + _ = _lang.gettext |
2835 | + |
2836 | + |
2837 | + |
2838 | + # |
2839 | + # static_method |
2840 | + # |
2841 | + @staticmethod |
2842 | + def __extract_signal_args(args) : |
2843 | + """ |
2844 | + extract the signals arguments : |
2845 | + arfs is a tuple |
2846 | + The first argument is the emitter object ( gobject) |
2847 | + the second argument is a dict with all the parameter given |
2848 | + to signal. |
2849 | + |
2850 | + The function returns the paramter dictionary |
2851 | + """ |
2852 | + return args[1] |
2853 | + |
2854 | + |
2855 | +if __name__ == '__main__' : |
2856 | + X = LucioleController(sys.argv) |
2857 | + X.run() |
2858 | + |
2859 | + |
2860 | + |
2861 | |
2862 | === added file 'luciole/ctrl/ctrl_img_vw.py' |
2863 | --- luciole/ctrl/ctrl_img_vw.py 1970-01-01 00:00:00 +0000 |
2864 | +++ luciole/ctrl/ctrl_img_vw.py 2011-02-28 13:21:06 +0000 |
2865 | @@ -0,0 +1,82 @@ |
2866 | +# -*- Mode: Python -*- |
2867 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
2868 | +# |
2869 | +# |
2870 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
2871 | +# |
2872 | +# This file is part of Luciole. |
2873 | +# |
2874 | +# Luciole is free software: you can redistribute it and/or modify |
2875 | +# it under the terms of the GNU General Public License as published by |
2876 | +# the Free Software Foundation, either version 3 of the License, or |
2877 | +# (at your option) any later version. |
2878 | +# |
2879 | +# Luciole is distributed in the hope that it will be useful, |
2880 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2881 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2882 | +# GNU General Public License for more details. |
2883 | +# |
2884 | +# You should have received a copy of the GNU General Public License |
2885 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
2886 | +# |
2887 | +# |
2888 | +""" |
2889 | +ctrl_img_vw.py : |
2890 | + manage image viewer |
2891 | +""" |
2892 | +# standard library imports |
2893 | + |
2894 | +# related third party imports |
2895 | + |
2896 | +from pitivi.action import ViewAction |
2897 | +from pitivi.pipeline import Pipeline |
2898 | + |
2899 | +# local application/library specific imports |
2900 | +from luciole.ctrl.base import ProjectMode |
2901 | + |
2902 | + |
2903 | +class ProjectModeImgVw(ProjectMode) : |
2904 | + """ Base class """ |
2905 | + __signals__ = {} |
2906 | + |
2907 | + def __init__(self, ctrl_project, mode_type = None) : |
2908 | + ProjectMode.__init__(self, ctrl_project, "IMAGE_VIEW") |
2909 | + |
2910 | + |
2911 | + |
2912 | + |
2913 | + |
2914 | + |
2915 | + |
2916 | + def prepare(self, project) : |
2917 | + """ prepare image viewer """ |
2918 | + ProjectMode.prepare(self, project) |
2919 | + |
2920 | + |
2921 | + def release(self) : |
2922 | + """ Meta function |
2923 | + release """ |
2924 | + ProjectMode.release(self) |
2925 | + |
2926 | + |
2927 | + def active_viewer(self, factory) : |
2928 | + self._pipeline = Pipeline() |
2929 | + self._view_action = ViewAction() |
2930 | + |
2931 | + self.debug(" factory to play :%s", factory) |
2932 | + self._view_action.addProducers(factory) |
2933 | + if self._viewer : |
2934 | + self._viewer.setPipeline(None, 'IMAGE_VIEW') |
2935 | + self._viewer.hideSlider() |
2936 | + |
2937 | + self._viewer.setAction(self._view_action) |
2938 | + self._viewer.setPipeline(self._pipeline, 'IMAGE_VIEW') |
2939 | + self._pipeline.play() |
2940 | + |
2941 | + def stop(self) : |
2942 | + |
2943 | + self._pipeline.stop() |
2944 | + |
2945 | + |
2946 | + |
2947 | + |
2948 | |
2949 | === added file 'luciole/ctrl/ctrl_no_project.py' |
2950 | --- luciole/ctrl/ctrl_no_project.py 1970-01-01 00:00:00 +0000 |
2951 | +++ luciole/ctrl/ctrl_no_project.py 2011-02-28 13:21:06 +0000 |
2952 | @@ -0,0 +1,60 @@ |
2953 | +#!/usr/bin/env python |
2954 | +# -*- coding: utf-8 -*- |
2955 | +# -*- Mode: Python -*- |
2956 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
2957 | +# |
2958 | +# |
2959 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
2960 | +# |
2961 | +# This file is part of Luciole. |
2962 | +# |
2963 | +# Luciole is free software: you can redistribute it and/or modify |
2964 | +# it under the terms of the GNU General Public License as published by |
2965 | +# the Free Software Foundation, either version 3 of the License, or |
2966 | +# (at your option) any later version. |
2967 | +# |
2968 | +# Luciole is distributed in the hope that it will be useful, |
2969 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2970 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2971 | +# GNU General Public License for more details. |
2972 | +# |
2973 | +# You should have received a copy of the GNU General Public License |
2974 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
2975 | +# |
2976 | +# |
2977 | +""" |
2978 | +ctrl_no_project.py : |
2979 | + manages no project loaded mode |
2980 | +""" |
2981 | +# standard library imports |
2982 | +# N/A |
2983 | + |
2984 | +# related third party imports |
2985 | +from pitivi.signalgroup import SignalGroup |
2986 | + |
2987 | +# local application/library specific imports |
2988 | +from luciole.ctrl.base import ProjectMode |
2989 | + |
2990 | +class ProjectModeNoProject(ProjectMode) : |
2991 | + __signals__ = { |
2992 | + } |
2993 | + |
2994 | + def __init__(self, ctrl_project) : |
2995 | + ProjectMode.__init__(self, ctrl_project, "NO_PROJECT") |
2996 | + self.gui_signals = SignalGroup() |
2997 | + |
2998 | + def prepare(self, project) : |
2999 | + """ Nothing to prepare """ |
3000 | + pass |
3001 | + |
3002 | + def release(self) : |
3003 | + ProjectMode.release(self) |
3004 | + |
3005 | + def active_viewer(self) : |
3006 | + if self._viewer : |
3007 | + self._viewer.setPipeline(None, "NO_PROJECT") |
3008 | + else : |
3009 | + self.error("No viewer set") |
3010 | + |
3011 | + |
3012 | + |
3013 | |
3014 | === renamed file 'luciole/ctrl/project_ctrl.py' => 'luciole/ctrl/ctrl_project.py' |
3015 | --- luciole/ctrl/project_ctrl.py 2010-09-07 06:27:09 +0000 |
3016 | +++ luciole/ctrl/ctrl_project.py 2011-02-28 13:21:06 +0000 |
3017 | @@ -22,7 +22,7 @@ |
3018 | # |
3019 | # |
3020 | """ |
3021 | -project_ctrl.py : |
3022 | +ctrl_project.py : |
3023 | Manage interactions with a luciole Project |
3024 | """ |
3025 | |
3026 | @@ -31,325 +31,537 @@ |
3027 | from gettext import gettext as _ |
3028 | |
3029 | # related third party imports |
3030 | -# N/A |
3031 | +from pitivi.signalinterface import Signallable |
3032 | +from pitivi.log.loggable import Loggable |
3033 | + |
3034 | |
3035 | # local application/library specific imports |
3036 | import luciole.base.exceptions as LEXCEP |
3037 | import luciole.base.tools as LT |
3038 | import luciole.constants as LCONST |
3039 | import luciole.ctrl.constants as CTRL_CONST |
3040 | +import luciole.ctrl.ctrl_acq as CTRL_ACQ |
3041 | import luciole.ctrl.ctrl_base as CTRL_BASE |
3042 | +import luciole.ctrl.ctrl_timeline as CTRL_TMLN |
3043 | +import luciole.ctrl.ctrl_no_project as CTRL_NO_PROJECT |
3044 | +import luciole.ctrl.ctrl_img_vw as CTRL_IMG_VW |
3045 | import luciole.ctrl.load_rush as LRUSH |
3046 | import luciole.gui.constants as GUI_CONST |
3047 | import luciole.media.image as LI |
3048 | -import luciole.project.project as LPF |
3049 | +import luciole.project.project_etree as LPF |
3050 | + |
3051 | +from luciole.gui.windows.assistant_new_project import AssistantNewProject |
3052 | +from luciole.gui.windows.dialog_project_properties import ProjectProperties |
3053 | +from luciole.ctrl.import_image import ImportController |
3054 | |
3055 | # NULL tuple constant defined for compat with gobject signals on ctrl.py file |
3056 | _NULL_TUPLE = None, None |
3057 | |
3058 | -class ProjectController(CTRL_BASE.CtrlBase) : |
3059 | - """ |
3060 | - Class to manage luciole project |
3061 | - SubClass of controller |
3062 | - """ |
3063 | - # |
3064 | - # CONSTANTS |
3065 | - # |
3066 | - __MODES = (CTRL_CONST.NO_PROJECT, CTRL_CONST.PROJECT_LOADED) |
3067 | - |
3068 | - def __get_data(self): |
3069 | - """ getter for is_project""" |
3070 | - return self.__data |
3071 | - data = property( __get_data, |
3072 | - None, |
3073 | - None, |
3074 | - """Retrieve project data """ |
3075 | - ) |
3076 | - |
3077 | - def __get_mode(self): |
3078 | - """ getter for project controller mode """ |
3079 | - return self.__mode |
3080 | - def __set_mode(self, mode) : |
3081 | - """ setter for project controller mode """ |
3082 | - # change mode only on transition |
3083 | - if mode in self.__MODES and mode != self.__mode : |
3084 | - self.__mode_call[mode]() |
3085 | - mode = property( __get_mode, __set_mode, None, |
3086 | - """ Project controller mode """) |
3087 | - |
3088 | - def __init__(self, gui, recent_mnger, cbs): |
3089 | - """ |
3090 | - ctrl_cbs: dictionary of main main controller callbacks |
3091 | - """ |
3092 | - CTRL_BASE.CtrlBase.__init__(self) |
3093 | - self.__data = None |
3094 | - self.__mode = CTRL_CONST.NO_PROJECT |
3095 | - # init mode |
3096 | - self.__mode_call = { |
3097 | - CTRL_CONST.NO_PROJECT : \ |
3098 | - self.__set_mode_no_project, |
3099 | - CTRL_CONST.PROJECT_LOADED : \ |
3100 | - self.__set_mode_project_loaded, |
3101 | - } |
3102 | - |
3103 | - self.__gui = gui |
3104 | - self.__gui_windows = gui.windows |
3105 | - self.__cbs = cbs |
3106 | - self.__recent_mngr = recent_mnger |
3107 | - |
3108 | - self.__pjt_file = LPF.ProjectToFile() |
3109 | - |
3110 | - def new(self) : |
3111 | - """ create a new project """ |
3112 | - # if project exist : close it |
3113 | - if self.__data != None : |
3114 | - # (None, None) params for compat with gobject signal |
3115 | - self.__cbs['close-project'](None, None) |
3116 | - # launch New project assitant window |
3117 | - self.__gui_windows.new_project_assistant(self._cb_new_project) |
3118 | - |
3119 | +class Project(object) : |
3120 | + |
3121 | + |
3122 | + def __init__(self) : |
3123 | + self.parser = LPF.ProjectToFile() |
3124 | + self.props = LPF.ProjectDico() |
3125 | + self.timeline = None |
3126 | + self.rush = None |
3127 | + self.acquirer = None |
3128 | + |
3129 | + |
3130 | + def save(self, path) : |
3131 | + _project_path = None |
3132 | + if path is not None : |
3133 | + try : |
3134 | + _project_path = self.parser.save_as(path , self.props) |
3135 | + except LEXCEP.LucioException, _err_msg : |
3136 | + raise LEXCEP.LucioException, _err_msg |
3137 | + else : |
3138 | + try : |
3139 | + _project_path = self.parser.save(self.props) |
3140 | + except LEXCEP.LucioException, _err_msg : |
3141 | + raise LEXCEP.LucioException, _err_msg |
3142 | + return _project_path |
3143 | + |
3144 | + def set_non_pjt_dpdt_props(self) : |
3145 | + # init props not managed by porject in file |
3146 | + self.props['is_modified'] = False |
3147 | + self.props['is-mixer-active'] = False |
3148 | + self.props['image2mix'] = None |
3149 | + self.props['alpha'] = LCONST.DEFAULT_ALPHA_IMAGE |
3150 | + |
3151 | +class CtrlProject(Signallable, Loggable) : |
3152 | + |
3153 | + # CONSTANTS |
3154 | + MODES = ("NO_PROJECT", "ACQUISITION", "TIMELINE", "IMAGE_VIEW", "EXPORT") |
3155 | + PROJECT_LOADED_MODES = ("ACQUISITION", "TIMELINE", "IMAGE_VIEW", "EXPORT") |
3156 | + |
3157 | + |
3158 | + |
3159 | + __signals__ = { |
3160 | + "project-loaded": ["project"], |
3161 | + "project-closing" : ["project"], |
3162 | + "project-closed" : [], |
3163 | + 'project-modified' : ["is_modified"], |
3164 | + 'project-mode-changed' : ["mode"], |
3165 | + 'quit' : [], |
3166 | + 'import-image-done' : ['images'], |
3167 | + } |
3168 | + |
3169 | + def _get_mode(self) : |
3170 | + """ mode getter """ |
3171 | + return self._mode |
3172 | + mode = property(_get_mode, |
3173 | + None, |
3174 | + None, |
3175 | + """ project mode """) |
3176 | + |
3177 | + |
3178 | + def _get_is_modified(self): |
3179 | + """ is_modified getter """ |
3180 | + return self.project.props['is_modified'] |
3181 | + |
3182 | + def _set_is_modified(self, is_modified = True) : |
3183 | + """ is_modified getter """ |
3184 | + if isinstance(is_modified, bool)\ |
3185 | + and\ |
3186 | + is_modified != self.project.props['is_modified'] : |
3187 | + self.project.props['is_modified'] = is_modified |
3188 | + self.debug('emit signal project-modified : is_modified = %s', |
3189 | + is_modified) |
3190 | + self.emit('project-modified', is_modified) |
3191 | + |
3192 | + is_modified = property(_get_is_modified, |
3193 | + _set_is_modified, |
3194 | + None, |
3195 | + """ is_modified project property """) |
3196 | + |
3197 | + |
3198 | + def __init__(self) : |
3199 | + Signallable.__init__(self) |
3200 | + Loggable.__init__(self) |
3201 | + |
3202 | + self._mode = None |
3203 | + self.gui = None |
3204 | + self.project = Project() |
3205 | + |
3206 | + self.ctrl_acq = CTRL_ACQ.ProjectModeAcquisition(self) |
3207 | + self.ctrl_tmln = CTRL_TMLN.ProjectModeTimeline(self) |
3208 | + self.ctrl_no_project = CTRL_NO_PROJECT.ProjectModeNoProject(self) |
3209 | + self.ctrl_img_vw = CTRL_IMG_VW.ProjectModeImgVw(self) |
3210 | + |
3211 | + self._connect_to_acq(self.ctrl_acq) |
3212 | + self._connect_to_timeline(self.ctrl_tmln) |
3213 | + |
3214 | + |
3215 | + def connect_to_gui(self, gui) : |
3216 | + self.gui = gui |
3217 | + |
3218 | + self.ctrl_acq.set_viewer(gui.drawarea) |
3219 | + self.ctrl_tmln.set_viewer(gui.drawarea) |
3220 | + self.ctrl_no_project.set_viewer(gui.drawarea) |
3221 | + self.ctrl_img_vw.set_viewer(gui.drawarea) |
3222 | + |
3223 | + def no_project_mode(self) : |
3224 | + self.debug('No project mode') |
3225 | + |
3226 | + if self._mode == 'ACQUISITION' : |
3227 | + self.ctrl_acq.stop() |
3228 | + |
3229 | + if self._mode != 'NO_PROJECT' : |
3230 | + self._mode = 'NO_PROJECT' |
3231 | + self.ctrl_no_project.active_viewer() |
3232 | + else : |
3233 | + self.warning("impossible to switch to No Project mode (%s)", |
3234 | + self._mode ) |
3235 | + |
3236 | + |
3237 | + |
3238 | + |
3239 | + def acquisition_mode(self, force = False) : |
3240 | + """ activate acquisition mode """ |
3241 | + self.debug('Acquisition mode') |
3242 | + if self._mode in self.PROJECT_LOADED_MODES \ |
3243 | + and self._mode != 'ACQUISITION' : |
3244 | + self._mode = 'ACQUISITION' |
3245 | + #force = True |
3246 | + if force == True : |
3247 | + self.debug('switch to acquisition mode forced') |
3248 | + # force prepare for pipelline configuration |
3249 | + # the _acquirer_ready_cb will call acquisition_mode |
3250 | + # when aquirer(pipeline) is ready |
3251 | + self.ctrl_acq.release() |
3252 | + self.ctrl_acq.prepare(self.project) |
3253 | + return |
3254 | + |
3255 | + self.ctrl_acq.active_viewer() |
3256 | + self.ctrl_acq.start() |
3257 | + self.debug('Emit project-mode-changed : %s'%self._mode) |
3258 | + self.emit('project-mode-changed',self._mode) |
3259 | + |
3260 | + else : |
3261 | + self.warning("impossible to switch to acquisition mode (%s)", |
3262 | + self._mode ) |
3263 | + |
3264 | + def timleline_mode(self) : |
3265 | + """ activate timeline mode """ |
3266 | + if self._mode == 'ACQUISITION' : |
3267 | + self.ctrl_acq.stop() |
3268 | + |
3269 | + if self._mode in self.PROJECT_LOADED_MODES \ |
3270 | + and self._mode != 'TIMELINE' : |
3271 | + |
3272 | + self._mode = 'TIMELINE' |
3273 | + self.ctrl_tmln.active_viewer() |
3274 | + self.debug('Emit project-mode-changed : %s'%self._mode) |
3275 | + self.emit('project-mode-changed',self._mode) |
3276 | + else : |
3277 | + self.warning("impossible to switch to timeline mode") |
3278 | + |
3279 | + |
3280 | + def img_vw_mode(self, origin, image_name, position = 0) : |
3281 | + if self._mode in self.PROJECT_LOADED_MODES : |
3282 | + if self._mode == 'ACQUISITION' : |
3283 | + self.ctrl_acq.stop() |
3284 | + |
3285 | + if self._mode == 'TIMELINE' and origin == LCONST.CHRONO : |
3286 | + self.ctrl_tmln.go_to_position(position) |
3287 | + # TODO : implemnt better then return here |
3288 | + return |
3289 | + |
3290 | + if self._mode == 'IMAGE_VIEW' : |
3291 | + self.ctrl_img_vw.stop() |
3292 | + |
3293 | + |
3294 | + _factory = self.ctrl_tmln._get_factory(image_name) |
3295 | + self.ctrl_img_vw.active_viewer(_factory) |
3296 | + self._mode = 'IMAGE_VIEW' |
3297 | + |
3298 | def open(self, project_path = None) : |
3299 | """ open an existing project """ |
3300 | - # no project path open a Dialog |
3301 | + # no project path : leave |
3302 | if project_path == None : |
3303 | - _path = self.__gui_windows.open_project() |
3304 | - else : |
3305 | - _path = project_path |
3306 | + return |
3307 | + self.debug("Starting project load of %s", project_path) |
3308 | |
3309 | # if a project is loaded close it |
3310 | - if self.__data != None : |
3311 | + if self._mode != 'NO_PROJECT' : |
3312 | # (None, None) params for compat with gobject signal |
3313 | - self.__cbs['close-project'](None, None) |
3314 | - |
3315 | - # check if a path entered ( from gui) |
3316 | - if _path != None : |
3317 | - try : |
3318 | - (_is_valid, self.__data) =self.__pjt_file.open(_path) |
3319 | - except LEXCEP.LucioException, _err_msg : |
3320 | - # Transmit error to controller: not stop application |
3321 | - raise LEXCEP.LucioException, _err_msg |
3322 | - else : |
3323 | - if not _is_valid : |
3324 | - _msg = _(" Webcam data not valid in project.\ |
3325 | - Please restart webcam detection") |
3326 | - self.__gui_windows.error_message( _msg) |
3327 | - |
3328 | - # load project in application |
3329 | - self.__load_project_in_app() |
3330 | - |
3331 | - |
3332 | - |
3333 | - def save_as(self, dir_path) : |
3334 | - """ save as current project """ |
3335 | - |
3336 | - # project saved and self.__data updated |
3337 | - try : |
3338 | - self.__pjt_file.save_as(dir_path , self.__data) |
3339 | - except LEXCEP.LucioException, _err_msg : |
3340 | - raise LEXCEP.LucioException, _err_msg |
3341 | - else : |
3342 | - self.__data['is_modified'] = False |
3343 | - # saved project needs now to be reloaded |
3344 | - # close it |
3345 | - self.__set_mode_no_project() |
3346 | - # reload project |
3347 | - |
3348 | - self.__set_mode_project_loaded() |
3349 | - self.__load_project_in_app() |
3350 | - |
3351 | - |
3352 | - def save(self) : |
3353 | - """ save current project """ |
3354 | - try : |
3355 | - self.__pjt_file.save(self.__data) |
3356 | - except LEXCEP.LucioException, _err_msg : |
3357 | - raise LEXCEP.LucioException, _err_msg |
3358 | - else : |
3359 | - self.__data['is_modified'] = False |
3360 | - |
3361 | - |
3362 | - |
3363 | - |
3364 | - |
3365 | - def append_snapshot(self, rusher, acquirer) : |
3366 | - """ move acquired image to rush dir : |
3367 | - 1. move image to tmp dir. |
3368 | - 2. resize it. |
3369 | - 3. move it to rush image dir """ |
3370 | - # get acquired image name |
3371 | - l_acq_image = acquirer.image2save |
3372 | - |
3373 | - # build temp impage path |
3374 | - l_temp_dir = os.path.join(self.__data['project_dir'], 'tmp') |
3375 | - # copy name |
3376 | - l_ac_image_temp = os.path.join(l_temp_dir, LCONST.ACQUIRED_IMAGE_NAME) |
3377 | - # resized copy name |
3378 | - l_ac_image_temp_rz = \ |
3379 | - os.path.join(l_temp_dir,LCONST.ACQUIRED_IMAGE_NAME_RZ) |
3380 | - |
3381 | - # build rush image name |
3382 | - l_basename = LCONST.RUSH_FILENAME_TPL % rusher.rush_index |
3383 | - l_rush_image = \ |
3384 | - os.path.join(self.__data['project_dir'], self.__data['rush_dir']) |
3385 | - l_rush_image = os.path.join(l_rush_image, l_basename) |
3386 | - |
3387 | - try : |
3388 | - # 1. move image acquired image to tmp dir |
3389 | - LT.movef(l_acq_image, l_ac_image_temp) |
3390 | - |
3391 | - # 2. resize image result is in l_ac_image_temp_rz |
3392 | - l_rz_obj = LI.ImageResize(l_ac_image_temp, l_ac_image_temp_rz ) |
3393 | - l_rz_obj.convert() |
3394 | - |
3395 | - # 3. move resized image to rush dire |
3396 | - LT.movef(l_ac_image_temp_rz, l_rush_image) |
3397 | - |
3398 | - except LEXCEP.LucioException, _err_msg : |
3399 | - raise LEXCEP.LucioException, _err_msg |
3400 | - else : |
3401 | - # 4. append image to rush list |
3402 | - rusher.append(l_basename) |
3403 | - # indicate project change (rush list) |
3404 | - self.__change_project('rush_images', rusher.dump_image_name()) |
3405 | - |
3406 | - |
3407 | - # 5. append image object to capture list |
3408 | - l_rush_image = rusher.get_image(l_basename) |
3409 | - # 6. always update the image 2 mix, even if mixer is not active |
3410 | - # used to memorize the last capture |
3411 | - acquirer.Image2Mix = l_rush_image.path |
3412 | - |
3413 | - #7 append capture to treeview |
3414 | - self.__gui.append_capture(l_rush_image) |
3415 | - |
3416 | - def change_framerate(self, fpi) : |
3417 | - """ change framerate """ |
3418 | - # update framerate only if project exist |
3419 | - if self.__data and isinstance(fpi, int): |
3420 | - self.__change_project('fpi', str(fpi)) |
3421 | - |
3422 | - def change_project(self, project_key, data) : |
3423 | - """ Change project : External mathod """ |
3424 | - self.__change_project(project_key, data) |
3425 | - |
3426 | - def finish_project_load(self) : |
3427 | + # TODO : manage close project |
3428 | + #self.__cbs['close-project'](None, None) |
3429 | + pass |
3430 | + |
3431 | + self.project = Project() |
3432 | + try : |
3433 | + (_is_valid, self.project.props) = \ |
3434 | + self.project.parser.open(project_path) |
3435 | + except LEXCEP.LucioException, _err_msg : |
3436 | + # Transmit error to controller: not stop application |
3437 | + self.error(_err_msg) |
3438 | + # TODO : manage exceptions |
3439 | + raise LEXCEP.LucioException, _err_msg |
3440 | + else : |
3441 | + if not _is_valid : |
3442 | + _msg = _(" Webcam data not valid in project.\ |
3443 | + Please restart webcam detection") |
3444 | + self.gui.windows.error_message( _msg) |
3445 | + |
3446 | + # initiate project load |
3447 | + self._start_project_load() |
3448 | + |
3449 | + def new_project(self ) : |
3450 | + """ new project creation """ |
3451 | + if self._mode != 'NO_PROJECT' : |
3452 | + # close current project |
3453 | + self.close() |
3454 | + |
3455 | + _ass = AssistantNewProject(self._new_project_cb) |
3456 | + |
3457 | + # TODO : fot test purpose only |
3458 | + import os.path |
3459 | + import shutil |
3460 | + PROJECT_NAME = 'test_luciole' |
3461 | + PROJECT_PATH = '/home/nico/temp/testLuciole' |
3462 | + |
3463 | + _p_path = os.path.join(PROJECT_PATH, PROJECT_NAME) |
3464 | + if os.path.exists(_p_path) : |
3465 | + # delete folder |
3466 | + self.debug('Deleting folder %s', _p_path) |
3467 | + shutil.rmtree(_p_path) |
3468 | + |
3469 | + # set on assistant project name |
3470 | + _ass._pg1.entry_project_name.set_text(PROJECT_NAME) |
3471 | + # set a folder |
3472 | + _ass._pg1._file_chooser.set_current_folder(PROJECT_PATH) |
3473 | + |
3474 | + |
3475 | + |
3476 | + def _new_project_cb(self, project_data) : |
3477 | """ |
3478 | - Method called to finsih project load. |
3479 | + new project callback : |
3480 | + signal generated by new project assistant |
3481 | """ |
3482 | - # add project loaded to recent manager |
3483 | - self.__recent_mngr.add_project(os.path.join( |
3484 | - self.__data['project_dir'], |
3485 | - self.__data['xml_filename'] ) |
3486 | - ) |
3487 | - |
3488 | - # Set project as not modified |
3489 | - self.__data['is_modified'] = False |
3490 | - # |
3491 | - # def callbacks |
3492 | - # |
3493 | - def _cb_new_project(self, project_data) : |
3494 | - """ callback when new project assistant is done """ |
3495 | - # 1. copy data in a project_dico object |
3496 | - project_dico_data = LPF.ProjectDico() |
3497 | - for _key in project_data.keys() : |
3498 | - project_dico_data[_key] = project_data[_key] |
3499 | - |
3500 | - # 2. create project ( folder structure ,etc ...) |
3501 | - # mising raise exception in case of create failure |
3502 | + self.debug(" create project with %s", project_data) |
3503 | + if project_data is None : |
3504 | + self.error('No data to load') |
3505 | + return |
3506 | + # create project on disk |
3507 | + self.project = Project() |
3508 | try : |
3509 | - self.__pjt_file.create(project_dico_data) |
3510 | + _path = self.project.parser.create(project_data) |
3511 | except LEXCEP.LucioException, _err : |
3512 | raise LEXCEP.LucioException, _err |
3513 | - else : |
3514 | - self.__data = project_dico_data |
3515 | - |
3516 | - # 3. load created project in application |
3517 | - self.__load_project_in_app() |
3518 | - |
3519 | - |
3520 | - |
3521 | - # |
3522 | - # Private methods |
3523 | - # |
3524 | - def __set_mode_no_project(self): |
3525 | - """ No project mode """ |
3526 | - |
3527 | - # |
3528 | - # clear gui |
3529 | - # |
3530 | - # clear treeviews |
3531 | - self.__gui.clear_treeviews() |
3532 | - |
3533 | - # set control widget in open_Load mode |
3534 | - self.__gui.control_widget.mode = GUI_CONST.LOAD_OR_CREATE |
3535 | - |
3536 | - # clear program bar |
3537 | - self.__gui.set_program_bar('', False) |
3538 | - |
3539 | - |
3540 | - self.__mode = CTRL_CONST.NO_PROJECT |
3541 | - |
3542 | - def __set_mode_project_loaded(self): |
3543 | - """project loaded mode """ |
3544 | - self.__mode = CTRL_CONST.PROJECT_LOADED |
3545 | - |
3546 | - def __save(self) : |
3547 | - """ private method for save """ |
3548 | + |
3549 | + self.debug(" created project on disk : %s", _path) |
3550 | + # load project on luciole |
3551 | + self.open(_path) |
3552 | + |
3553 | + |
3554 | + def close(self) : |
3555 | + """ close current project """ |
3556 | + if self._mode in self.PROJECT_LOADED_MODES : |
3557 | + if self.project.props['is_modified'] == True : |
3558 | + if (self.emit('project-closing', self.project) == True) : |
3559 | + self.save() |
3560 | + |
3561 | + self.debug(" Closing current project ") |
3562 | + self.release() |
3563 | + self.no_project_mode() |
3564 | + self.debug('emit signal project-closed') |
3565 | + self.emit('project-closed') |
3566 | + self.project = None |
3567 | + |
3568 | + def save(self, dir_path = None ) : |
3569 | + if self._mode not in self.PROJECT_LOADED_MODES : |
3570 | + self.error('invalid mode for save : %s'%self._mode) |
3571 | + return |
3572 | + if dir_path and not os.path.exists(dir_path) : |
3573 | + self.error('Path %s exists. Impossible to save project', dir_path) |
3574 | + return |
3575 | + self.debug("%s",dir_path) |
3576 | + try : |
3577 | + _project_path = self.project.save(dir_path) |
3578 | + except LEXCEP.LucioException, _err_msg : |
3579 | + self.error('%s', _err_msg) |
3580 | + else : |
3581 | + self.debug('Project saved in %s', _project_path) |
3582 | + self.is_modified = False |
3583 | + if dir_path is not None and _project_path is not None : |
3584 | + # reload project is corretcly saved |
3585 | + self.close() |
3586 | + self.debug('Opening project with path %s',_project_path) |
3587 | + self.open(_project_path) |
3588 | + |
3589 | + def set_framerate(self, fpi, force = False) : |
3590 | + if self._mode != "TIMELINE" and force == False : |
3591 | + self.info('update of fpi only availabe in TIMELINE mode') |
3592 | + return |
3593 | + if int(self.project.props['fpi']) != fpi : |
3594 | + self.project.props['fpi'] = str(fpi) |
3595 | + self.is_modified = True |
3596 | + # update timeline |
3597 | + self.ctrl_tmln.update_image_duration(fpi) |
3598 | + |
3599 | + |
3600 | + |
3601 | + def new(self, project_data = None) : |
3602 | + |
3603 | + # create new project |
3604 | + |
3605 | + if project_data : |
3606 | + # TODO case with assistant |
3607 | + pass |
3608 | + |
3609 | + |
3610 | + |
3611 | + def release(self) : |
3612 | + |
3613 | + # release signals between aquirer and project |
3614 | + if self.project.props['hardtype'] in \ |
3615 | + (LCONST.FAKE, LCONST.DVCAM, LCONST.WEBCAM ) : |
3616 | + #self._disconnect_from_acq(self.ctrl_acq) |
3617 | + pass |
3618 | + |
3619 | + # release signals between timeline and project |
3620 | + # self._disconnect_from_timeline(self.ctrl_tmln) |
3621 | + |
3622 | + # disconnect from gui |
3623 | + # TODO : Analyse when to be done on close project or quit ? |
3624 | + #self.disconnect_from_gui(self.gui) |
3625 | + |
3626 | + |
3627 | + def import_images(self, paths) : |
3628 | + if self._mode not in self.PROJECT_LOADED_MODES : |
3629 | + self.error('invalid mode for import: %s'%self._mode) |
3630 | + return |
3631 | + |
3632 | + if paths != [] : |
3633 | + self.debug('Starting image importer for %s',paths) |
3634 | + importer = ImportController( paths, |
3635 | + self.project.props, |
3636 | + self.project.rush, |
3637 | + self.gui.status_bar |
3638 | + ) |
3639 | + importer.connect('import-image-done', self._import_img_done_cb) |
3640 | + else : |
3641 | + self.info('No image to import') |
3642 | + |
3643 | + |
3644 | + def _import_img_done_cb(self, imager, images) : |
3645 | + """ import-image-done callback """ |
3646 | + self.debug("RX import-image-done : %s", images) |
3647 | + if images != [] : |
3648 | + self.emit('import-image-done', images) |
3649 | + |
3650 | + # update project |
3651 | + # get image names |
3652 | + _names = [ _img.name for _img in images] |
3653 | + self.project.props['rush_images'].extend(_names) |
3654 | + self.project.props['capture_images'].extend(_names) |
3655 | + self.is_modified = True |
3656 | + else : |
3657 | + self.warning('No image imported') |
3658 | + |
3659 | + def show_project_properties(self) : |
3660 | + """ show project properties |
3661 | + webcam setup can be modified. So before displaying window all |
3662 | + pipelines and associated stuff should be stopped |
3663 | + """ |
3664 | + if self._mode not in self.PROJECT_LOADED_MODES : |
3665 | + self.error('invalid mode for import: %s'%self._mode) |
3666 | + return |
3667 | + self.debug('%s',self.project.props) |
3668 | + self._old_mode = self._mode |
3669 | + if self._mode == 'ACQUISITION' : |
3670 | + # switch to TIMELINE to free the webcam |
3671 | + self.timleline_mode() |
3672 | + |
3673 | + |
3674 | + _properties = ProjectProperties(self.gui.main_window, |
3675 | + self.project.props) |
3676 | + _properties.connect('project-changed', self._project_modified_cb) |
3677 | + _properties.run() |
3678 | + |
3679 | + def _project_modified_cb(self, dialog, project_data) : |
3680 | + """ callback for modified data from project properties dialog """ |
3681 | + self.is_modified = True |
3682 | + if self._old_mode == 'ACQUISITION' : |
3683 | + # go back to acqusition mode |
3684 | + self.acquisition_mode(True) |
3685 | + self._old_mode = None |
3686 | + |
3687 | + |
3688 | + |
3689 | + def _start_project_load(self) : |
3690 | + |
3691 | + |
3692 | + # start rusher controller |
3693 | + _rush_ctrl = LRUSH.ControllerLoadRush( self.project, |
3694 | + self.gui.status_bar ) |
3695 | + self._connect_to_rush(_rush_ctrl) |
3696 | + |
3697 | + # start acquirer controller |
3698 | + if self.project.props['hardtype'] in \ |
3699 | + (LCONST.FAKE, LCONST.DVCAM, LCONST.WEBCAM ) : |
3700 | + # start acquisition controller |
3701 | + #self.acq_ctrl = CTRL_ACQ.CtrlAcq(self) |
3702 | + self.debug('Acquirer Object loaded %s', self.ctrl_acq) |
3703 | + else : |
3704 | + # No acquisition (only import and timeline) |
3705 | + self._mode = "TIMELINE" |
3706 | + |
3707 | + # start timeline controller |
3708 | + #self.ctrl_tmln = CTRL_TMLN.CtrlTimeline(self) |
3709 | + |
3710 | + |
3711 | + def _connect_to_rush(self, rush_ctrl) : |
3712 | + rush_ctrl.connect('rush-loaded', self._rush_load_finish_cb) |
3713 | + |
3714 | + def _disconnect_from_rush(self, rush_ctrl) : |
3715 | + rush_ctrl.disconnect_by_function( self._rush_load_finish_cb) |
3716 | + |
3717 | + def _rush_load_finish_cb(self, rush_ctrl, rush_obj) : |
3718 | + self.debug(" Rush Load Finish :%s ", rush_obj.dump_image_name() ) |
3719 | + if rush_obj != None : |
3720 | + self._terminate_project_load(rush_obj) |
3721 | + # Load rush is done only one time at load so disconnect it |
3722 | + self._disconnect_from_rush(rush_ctrl) |
3723 | + |
3724 | + def _terminate_project_load(self, rush_obj) : |
3725 | + self.project.rush = rush_obj |
3726 | + |
3727 | + # set project as not modified as it is just loaded |
3728 | + self.project.set_non_pjt_dpdt_props() |
3729 | + |
3730 | + # set path for image to mix |
3731 | + self.project.props['image2mix'] =\ |
3732 | + os.path.join( |
3733 | + self.project.props['project_dir'], |
3734 | + LCONST.IMAGE2MIX_NAME |
3735 | + ) |
3736 | + |
3737 | + # if a capture image exist , |
3738 | + # mix with last one by copying it in 'image2mix' |
3739 | + |
3740 | + if self.project.props['capture_images'] != []: |
3741 | + self.debug(' image 2 copy %s', |
3742 | + self.project.props['capture_images'][-1]) |
3743 | + self._copy_to_image2mix( |
3744 | + self.project.props['capture_images'][-1]) |
3745 | + |
3746 | + self.debug("emit project-loaded : %s " , self.project) |
3747 | + self.emit('project-loaded', self.project ) |
3748 | + |
3749 | + def _connect_to_acq(self , ctrl_acq) : |
3750 | + ctrl_acq.connect('acquirer-ready', self._acquirer_ready_cb) |
3751 | + ctrl_acq.connect('acquirer-error', self._acquirer_error_cb) |
3752 | + |
3753 | + def _disconnect_from_acq(self , ctrl_acq) : |
3754 | + ctrl_acq.disconnect_by_function( self._acquirer_ready_cb) |
3755 | + ctrl_acq.disconnect_by_function( self._acquirer_error_cb) |
3756 | + |
3757 | + |
3758 | + def _acquirer_ready_cb(self, ctrl_acq ) : |
3759 | + self.debug('RX acquirer-ready signal') |
3760 | + # TODO : work around on mode |
3761 | + # FIXME : set to image to force transtion to acquisition mode |
3762 | + # default mode when project is started |
3763 | + self._mode = 'IMAGE_VIEW' |
3764 | + self.acquisition_mode() |
3765 | + |
3766 | + |
3767 | + |
3768 | + def _acquirer_error_cb(self, ctrl_acq, msg) : |
3769 | + self.error(msg) |
3770 | + |
3771 | + def _connect_to_timeline(self, ctrl_tmln) : |
3772 | + ctrl_tmln.connect('timeline-loaded', self._timeline_loaded_cb) |
3773 | + |
3774 | + def _disconnect_from_timeline(self, ctrl_tmln) : |
3775 | + ctrl_tmln.disconnect_from_project(self) |
3776 | + ctrl_tmln.disconnect_by_function(self._timeline_loaded_cb) |
3777 | + |
3778 | + def _timeline_loaded_cb(self, ctrl_tmln): |
3779 | pass |
3780 | - |
3781 | - def __load_project_in_app(self) : |
3782 | - """ load project data in luciole : |
3783 | - 1. Load rusher |
3784 | - 2. when rusher done a callback is generated. |
3785 | - handled in main controller. Main controller |
3786 | - load the acquistion object, and display |
3787 | - project in gui |
3788 | - """ |
3789 | - if self.__data != None : |
3790 | - # set modified attibute |
3791 | - self.__data['is_modified'] = False |
3792 | - |
3793 | - # set set mode as PROJECT LOADED |
3794 | - self.__mode = CTRL_CONST.PROJECT_LOADED |
3795 | - |
3796 | - self.logger.\ |
3797 | - debug("-----------------------------------------------------") |
3798 | - self.logger.debug("Luciole_controller project info: ") |
3799 | - self.logger.\ |
3800 | - debug("-----------------------------------------------------") |
3801 | - for _key, _value in self.__data.iteritems() : |
3802 | - self.logger.debug("**%s** : %s "%(_key, _value)) |
3803 | - self.logger.\ |
3804 | - debug("-----------------------------------------------------") |
3805 | - |
3806 | - # Initilaisation of rush obj is threaded because its take a while |
3807 | - # (generation of images pixbufs) |
3808 | - # When rush load is finish : __on_rush_finish is called |
3809 | - LRUSH.ControllerLoadRush( self.__data, |
3810 | - self.__gui.status_bar, |
3811 | - self.__cbs['done-rush']) |
3812 | - |
3813 | - |
3814 | - def __change_project(self, project_key, data) : |
3815 | - """ function called to update the current project """ |
3816 | - if self.__data != None : |
3817 | - # is key exist ? |
3818 | - if self.__data.has_key(project_key) : |
3819 | - # update only on change |
3820 | - if self.__data[project_key] != data : |
3821 | - self.__data[project_key] = data |
3822 | - self.__data['is_modified'] = True |
3823 | - self.__gui.set_program_bar( self.__data['project_name'], |
3824 | - self.__data['is_modified']) |
3825 | - else : |
3826 | - #raise Error/Exception |
3827 | - _err_msg = "key %s not in project " % project_key |
3828 | - raise LEXCEP.LucioException, _err_msg |
3829 | + #self.project.timeline = timeline |
3830 | + |
3831 | + |
3832 | + def _copy_to_image2mix(self, image_name) : |
3833 | + |
3834 | + _source = os.path.join( self.project.props['project_dir'], |
3835 | + self.project.props['rush_dir'], |
3836 | + image_name) |
3837 | + try : |
3838 | + LT.copyf(_source, self.project.props['image2mix']) |
3839 | + except LEXCEP.LucioException, err : |
3840 | + self.error('impossible to copy %s in %s -- %s ', |
3841 | + _source, |
3842 | + self.project.props['image2mix'], |
3843 | + err) |
3844 | else : |
3845 | - # raise Error/Exception |
3846 | - _err_msg = " Project not loaded " |
3847 | - raise LEXCEP.LucioException, _err_msg |
3848 | - |
3849 | + self.debug(' copied %s in %s', |
3850 | + _source, |
3851 | + self.project.props['image2mix']) |
3852 | + |
3853 | |
3854 | |
3855 | |
3856 | |
3857 | === added file 'luciole/ctrl/ctrl_timeline.py' |
3858 | --- luciole/ctrl/ctrl_timeline.py 1970-01-01 00:00:00 +0000 |
3859 | +++ luciole/ctrl/ctrl_timeline.py 2011-02-28 13:21:06 +0000 |
3860 | @@ -0,0 +1,953 @@ |
3861 | +#!/usr/bin/env python |
3862 | +# -*- coding: utf-8 -*- |
3863 | +# -*- Mode: Python -*- |
3864 | +# vim:si:ai:et:sw=4:sts=4:ts=4 |
3865 | +# |
3866 | +# |
3867 | +# Copyright Nicolas Bertrand (nico@inattendu.org), 2009-2010 |
3868 | +# |
3869 | +# This file is part of Luciole. |
3870 | +# |
3871 | +# Luciole is free software: you can redistribute it and/or modify |
3872 | +# it under the terms of the GNU General Public License as published by |
3873 | +# the Free Software Foundation, either version 3 of the License, or |
3874 | +# (at your option) any later version. |
3875 | +# |
3876 | +# Luciole is distributed in the hope that it will be useful, |
3877 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3878 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3879 | +# GNU General Public License for more details. |
3880 | +# |
3881 | +# You should have received a copy of the GNU General Public License |
3882 | +# along with Luciole. If not, see <http://www.gnu.org/licenses/>. |
3883 | +# |
3884 | +# |
3885 | +""" |
3886 | +ctrl_acq.py : |
3887 | + manages acquisition |
3888 | +""" |
3889 | +# standard library imports |
3890 | +import os.path |
3891 | +import urllib |
3892 | +import gettext |
3893 | +_ = gettext.gettext |
3894 | + |
3895 | +# related third party imports |
3896 | +import gst |
3897 | +import copy |
3898 | +import gobject |
3899 | + |
3900 | +from pitivi.signalgroup import SignalGroup |
3901 | +from pitivi.pipeline import Pipeline |
3902 | +from pitivi.action import ViewAction |
3903 | +from pitivi.timeline.timeline import Timeline, TimelineObject, TimelineError |
3904 | + |
3905 | +from pitivi.factories.timeline import TimelineSourceFactory |
3906 | + |
3907 | +from pitivi.stream import AudioStream, VideoStream |
3908 | +from pitivi.settings import GlobalSettings |
3909 | +from pitivi.settings import ExportSettings |
3910 | +from pitivi.sourcelist import SourceList |
3911 | +from pitivi.timeline.track import Track, SourceTrackObject |
3912 | + |
3913 | +from pitivi.utils import Seeker, getNextObject |
3914 | +# local application/library specific imports |
3915 | +from luciole.ctrl.base import ProjectMode |
3916 | +import luciole.base.tools as LT |
3917 | +import luciole.constants as LCONST |
3918 | +import luciole.base.exceptions as LEXCEP |
3919 | + |
3920 | +class LucioleTimeline(Timeline) : |
3921 | + DEFAULT_DURATION = 1 * gst.SECOND |
3922 | + |
3923 | + def __init__(self) : |
3924 | + Timeline.__init__(self) |
3925 | + # TODO : Temporary usage of GlobalSettings |
3926 | + self.settings = GlobalSettings() |
3927 | + self.default_duration = self.DEFAULT_DURATION |
3928 | + |
3929 | + def change_order(self, images_list, position_dest) : |
3930 | + self.info("order changed move images %s at position %s", |
3931 | + images_list, |
3932 | + position_dest) |
3933 | + |
3934 | + _tl_objects_to_move = [self.video_objects[_elem[0]] for _elem in images_list] |
3935 | + self.debug( " Objs to move :%s", [ obj.factory.name for obj in _tl_objects_to_move]) |
3936 | + |
3937 | + if position_dest[0] < len(self.video_objects ) : |
3938 | + # Move images inside sequence |
3939 | + _tl_obj_dest = self.video_objects[position_dest[0]] |
3940 | + |
3941 | + for tl_obj in _tl_objects_to_move : |
3942 | + |
3943 | + self.info(" Moving image %s (position %s) before %s (position : %s)",\ |
3944 | + tl_obj.factory.name, |
3945 | + self.video_objects.index(tl_obj), |
3946 | + _tl_obj_dest.factory.name, |
3947 | + self.video_objects.index(_tl_obj_dest)) |
3948 | + |
3949 | + _tl_obj_tmp = self._move_timeline_object(tl_obj,_tl_obj_dest) |
3950 | + _tl_obj_dest = \ |
3951 | + self.video_objects\ |
3952 | + [self.video_objects.index(_tl_obj_tmp) + 1] |
3953 | + |
3954 | + else : |
3955 | + # move images at the end of sequnce |
3956 | + for tl_obj in _tl_objects_to_move : |
3957 | + self.info(" Moving image %s (position %s) at end of sequence",\ |
3958 | + tl_obj.factory.name, |
3959 | + self.video_objects.index(tl_obj) ) |
3960 | + self._move_at_end(tl_obj) |
3961 | + |
3962 | + |
3963 | + def insert_factories(self, factories, position_dest) : |
3964 | + self.info("images to insert %s at position %s", |
3965 | + factories, position_dest) |
3966 | + |
3967 | + if position_dest[0] < len(self.video_objects ) : |
3968 | + self._insert_factories_at_position( position_dest[0], factories) |
3969 | + else : |
3970 | + for _factory in factories : |
3971 | + # TODO : verify if append merhod usage is correct. regarding |
3972 | + # _insert_factories_at_position method (i.e. tl_obj creation and |
3973 | + # disable/enable timeleine methods) |
3974 | + self._append_obj(_factory) |
3975 | + |
3976 | + |
3977 | + |
3978 | + |
3979 | + def remove_images(self, images_list) : |
3980 | + self.info("images to remove on timeline %s", images_list) |
3981 | + _tl_objs_to_remove = \ |
3982 | + [self.video_objects[_elem[0]] for _elem in images_list] |
3983 | + |
3984 | + for tl_obj in _tl_objs_to_remove : |
3985 | + self._remove_object(tl_obj) |
3986 | + |
3987 | + |
3988 | + |
3989 | + |
3990 | + |
3991 | + def create_timeline_object(self, factory) : |
3992 | + |
3993 | + _stream_map = self._getSourceFactoryStreamMap(factory) |
3994 | + _timeline_object = TimelineObject(factory) |
3995 | + for stream, track in _stream_map.iteritems(): |
3996 | + track_object = SourceTrackObject(factory, stream) |
3997 | + track.addTrackObject(track_object) |
3998 | + self.info("Stream: %s, Track: %s, Track duration: %d", str(stream), |
3999 | + str(track), track.duration) |
4000 | + |
4001 | + _timeline_object.addTrackObject(track_object) |
4002 | + _timeline_object.start = 0 |
4003 | + return _timeline_object |
4004 | + |
4005 | + |
4006 | + |
4007 | + |
4008 | + |
4009 | + def add_sound_track(self, audio_factory, duration, is_video_duration = True, audio_offset = 0,) : |
4010 | + # TODO : only a offset of 0 is vlaid. with ofsset !=0 --> pieleine error |
4011 | + audio_offset = 0 |
4012 | + # TODO : undestand better disable/ Enable updates. |
4013 | + # the usage od disable/enable avoid a fade transition at start ? |
4014 | + _vtrack = self._get_video_track() |
4015 | + self.debug('Video track duration %s, ', _vtrack.duration) |
4016 | + if is_video_duration : |
4017 | + _duration = _vtrack.duration |
4018 | + else : |
4019 | + _duration = duration |
4020 | + |
4021 | + self.debug('Sound track %s, duration %s, audio offest %s', |
4022 | + audio_factory.name, _duration, audio_offset) |
4023 | + |
4024 | + #self.disableUpdates() |
4025 | + _tl_obj = self.create_timeline_object(audio_factory) |
4026 | + _tl_obj.start = audio_offset |
4027 | + _tl_obj.duration = _duration |
4028 | + self.debug('timeline object duration = %s', _tl_obj.duration) |
4029 | + self.addTimelineObject( _tl_obj) |
4030 | + #self.enableUpdates() |
4031 | + return _tl_obj |
4032 | + |
4033 | + def remove_sound_track(self, factory) : |
4034 | + # remove factory from timeline |
4035 | + self.removeFactory(factory) |
4036 | + # update duration |
4037 | + _vtrack = self._get_video_track() |
4038 | + if _vtrack.duration != self.duration : |
4039 | + self.duration = _vtrack.duration |
4040 | + |
4041 | + |
4042 | + |
4043 | + |
4044 | + |
4045 | + |
4046 | + |
4047 | + |
4048 | + |
4049 | + def update_timeline_duration(self, duration, sound_stop_with_video = True ) : |
4050 | + |
4051 | + |
4052 | + # copy objects for vaoiding update problems |
4053 | + _video_objs = copy.copy(self.video_objects) |
4054 | + self.debug('New duration %s image to update %s', duration, [ _obj.factory.name for _obj in _video_objs ] ) |
4055 | + |
4056 | + _old_duration = _video_objs[0].duration |
4057 | + # check type of image duration update |
4058 | + # if duration update increase, start update by the last image |
4059 | + # in timeline (right to left order) |
4060 | + # if duration update decrease, start update fu the first image |
4061 | + # in timeline (left to riht order ) |
4062 | + |
4063 | + _reverse = False |
4064 | + if duration > _old_duration : |
4065 | + # new duration > old duration |
4066 | + # start update of images in reverse order |
4067 | + _start = (len(_video_objs) -1) * duration |
4068 | + _video_objs.reverse() |
4069 | + _reverse = True |
4070 | + else : |
4071 | + # new duration < old duration |
4072 | + _start = 0 |
4073 | + |
4074 | + for _obj in _video_objs : |
4075 | + |
4076 | + self.debug('Before Update %s : start %s duration %s, factory duration %s', |
4077 | + _obj.factory.name, |
4078 | + _obj.start, |
4079 | + _obj.duration, |
4080 | + _obj.factory.duration) |
4081 | + |
4082 | + _obj.setDuration(duration, True) |
4083 | + _obj.setStart(_start, True) |
4084 | + if _reverse == True : |
4085 | + _start = _start - duration |
4086 | + else : |
4087 | + _start = _start + duration |
4088 | + |
4089 | + self.debug('Updated %s : start %s duration %s ', |
4090 | + _obj.factory.name, |
4091 | + _obj._getStart(), |
4092 | + _obj._getDuration()) |
4093 | + |
4094 | + _total_duration = len(_video_objs) * duration |
4095 | + self.debug(' Total duration %s', _total_duration) |
4096 | + if sound_stop_with_video == True : |
4097 | + _obj = self._get_audio_object() |
4098 | + #TODO ; check case of audio track duration < images duration |
4099 | + if _obj is not None : |
4100 | + _obj.setDuration(_total_duration, True, True) |
4101 | + self.debug('NEW audio %s track duration %s factory duration %s',_obj, _obj.duration, _obj.factory.duration) |
4102 | + self.debug('NEW timeline duration %s', self.duration ) |
4103 | + |
4104 | + |
4105 | + def _move_timeline_object(self, tl_src, tl_dest) : |
4106 | + _idx_src = self.video_objects.index(tl_src) |
4107 | + _idx_dest = self.video_objects.index(tl_dest) |
4108 | + # select objects to shift on timeline |
4109 | + if _idx_src < _idx_dest : |
4110 | + _objs_to_move = self.video_objects[_idx_src+1:_idx_dest] |
4111 | + _duration = -1 * tl_src.duration |
4112 | + #set start duration of src before dest |
4113 | + tl_src.start = self.video_objects[_idx_dest -1].start |
4114 | + else : |
4115 | + _objs_to_move = self.video_objects[_idx_dest:_idx_src] |
4116 | + _duration = 1 * tl_src.duration |
4117 | + #set start duration at dest |
4118 | + tl_src.start = self.video_objects[_idx_dest].start |
4119 | + |
4120 | + # shift objects to move |
4121 | + self._shift_object(_objs_to_move, _duration) |
4122 | + |
4123 | + return tl_src |
4124 | + |
4125 | + def _move_at_end(self, tl_src): |
4126 | + |
4127 | + # get objecs to move |
4128 | + _objs_to_move = self._getVideoObjsAfter(tl_src) |
4129 | + |
4130 | + # set last postion fo tl_src |
4131 | + tl_src.start = self.video_objects[-1].start |
4132 | + |
4133 | + # shift back the objects to move |
4134 | + _duration = -1*tl_src.duration |
4135 | + self._shift_object(_objs_to_move, _duration) |
4136 | + |
4137 | + def _move_object(self, pos_src, pos_dest) : |
4138 | + |
4139 | + if pos_src == pos_dest : |
4140 | + # nothing to move |
4141 | + return |
4142 | + _pos_dest = self.video_objects[pos_dest].start |
4143 | + _obj_src = self.video_objects[pos_src] |
4144 | + # select objects to move |
4145 | + if pos_src > pos_dest : |
4146 | + _objs_to_move = self.video_objects[pos_dest:pos_src] |
4147 | + _duration = 1 * _obj_src.duration |
4148 | + else : |
4149 | + _objs_to_move = self.video_objects[pos_src+1:pos_dest+1] |
4150 | + _duration = -1 * _obj_src.duration |
4151 | + # shift objects to move |
4152 | + self._shift_object(_objs_to_move, _duration) |
4153 | + |
4154 | + # move object at despoistion |
4155 | + _obj_src.start = _pos_dest |
4156 | + |
4157 | + def _get_track_objects(self, objs, track) : |
4158 | + track_objs = [] |
4159 | + for _obj in objs : |
4160 | + if track == _obj.track_objects[0].track : |
4161 | + track_objs.append(_obj) |
4162 | + |
4163 | + return track_objs |
4164 | + |
4165 | + |
4166 | + |
4167 | + |
4168 | + |
4169 | + def _insert_factories_at_position(self, position, factories_to_insert) : |
4170 | + # TODO : undestand better disable/ Enable updates. |
4171 | + # the usage od disable/enable avoid a fade transition between images: Why ? |
4172 | + self.disableUpdates() |
4173 | + _timeline_objects = [] |
4174 | + _position_obj = self.video_objects[position] |
4175 | + self.debug( "_position_obj :%s", _position_obj.factory.name) |
4176 | + _objs_after = self._getVideoObjsAfter(_position_obj) |
4177 | + _objs_after.insert(0, _position_obj) |
4178 | + |
4179 | + _dur = _position_obj.start |
4180 | + |
4181 | + for _factory in factories_to_insert : |
4182 | + _timeline_obect = self.create_timeline_object(_factory) |
4183 | + _timeline_objects.append(_timeline_obect) |
4184 | + |
4185 | + _insert_duration = self._calc_objs_duration(_timeline_objects) |
4186 | + self._shift_object(_objs_after, _insert_duration) |
4187 | + |
4188 | + for _tl_obj in _timeline_objects : |
4189 | + _tl_obj.start = _dur |
4190 | + _dur += _tl_obj.duration |
4191 | + self.addTimelineObject( _tl_obj) |
4192 | + |
4193 | + self.enableUpdates() |
4194 | + |
4195 | + def _calc_objs_duration(self, objs_to_insert) : |
4196 | + _duration = 0 |
4197 | + for _obj in objs_to_insert : |
4198 | + _duration += _obj.duration |
4199 | + self.info("shift duration = %s", _duration) |
4200 | + return _duration |
4201 | + |
4202 | + def _shift_object(self, timeline_objects, duration) : |
4203 | + for _obj in timeline_objects : |
4204 | + self.debug("%s", _obj.factory.name) |
4205 | + _obj.start += duration |
4206 | + self.info ( "%s timeline objects shifted from %s"%(len(timeline_objects), duration)) |
4207 | + |
4208 | + def _append_obj(self, factory): |
4209 | + |
4210 | + timeline_object = self.addSourceFactory(factory) |
4211 | + self.info("appended %s"%timeline_object) |
4212 | + return timeline_object |
4213 | + |
4214 | + def _remove_object(self, tl_obj) : |
4215 | + _objs_after = self._getVideoObjsAfter(tl_obj) |
4216 | + _duration = -1* tl_obj.duration |
4217 | + self.debug("obj to remove %s ", tl_obj) |
4218 | + _vtrack = self._get_video_track() |
4219 | + _vtrack.removeTrackObject(tl_obj) |
4220 | + |
4221 | + # update start point of objects after deleted one |
4222 | + self._shift_object(_objs_after, _duration) |
4223 | + |
4224 | + |
4225 | + def _get_video_track(self) : |
4226 | + for track in self.tracks: |
4227 | + if isinstance(track.stream, VideoStream) : |
4228 | + return track |
4229 | + |
4230 | + def _get_audio_track(self): |
4231 | + for track in self.tracks: |
4232 | + if isinstance(track.stream, AudioStream) : |
4233 | + return track |
4234 | + |
4235 | + |
4236 | + def _get_video_track_objects(self) : |
4237 | + _vtrack = self._get_video_track() |
4238 | + |
4239 | + _res = _vtrack.track_objects |
4240 | + #self.debug("Video Track objects : %s", |
4241 | + # [ obj.factory.name for obj in _res]) |
4242 | + |
4243 | + return _res |
4244 | + video_objects = property(_get_video_track_objects) |
4245 | + |
4246 | + def _get_audio_object(self) : |
4247 | + _audio_obj = [] |
4248 | + _res = None |
4249 | + for _obj in self.timeline_objects : |
4250 | + if isinstance(_obj.track_objects[0].stream, AudioStream) : |
4251 | + _audio_obj.append(_obj) |
4252 | + |
4253 | + if len(_audio_obj) > 1 : |
4254 | + raise TimelineError(" More then one audio stream in timeline") |
4255 | + elif len(_audio_obj) == 1 : |
4256 | + _res = _audio_obj[0] |
4257 | + |
4258 | + |
4259 | + return _res |
4260 | + audio_object = property(_get_audio_object) |
4261 | + |
4262 | + |
4263 | + def _getVideoObjsAfter(self, timeline_object) : |
4264 | + """ |
4265 | + get video objects after timeline objects |
4266 | + This function retrieves objects only from video track |
4267 | + """ |
4268 | + objects = [] |
4269 | + priority= None |
4270 | + if timeline_object in self.video_objects : |
4271 | + _res = getNextObject(timeline_object, self.video_objects, priority) |
4272 | + while _res is not None : |
4273 | + objects.append(_res) |
4274 | + _res = getNextObject(_res, self.video_objects, priority) |
4275 | + else : |
4276 | + self.error('timeline_object %s not in video_track', |
4277 | + timeline_object.factory.name) |
4278 | + |
4279 | + self.debug(" video objs after %s : %s", |
4280 | + timeline_object.factory.name, |
4281 | + [ obj.factory.name for obj in objects]) |
4282 | + return objects |
4283 | + |
4284 | + def _get_image_sequence(self) : |
4285 | + _list = [ _obj.factory.name for _obj in self.video_objects] |
4286 | + return _list |
4287 | + |
4288 | + |
4289 | + |
4290 | + |
4291 | +class ProjectModeTimeline(ProjectMode): |
4292 | + __signals__ = { |
4293 | + 'timeline-loaded' : ['timeline'] , |
4294 | + 'sound-props-changed' : ['active', 'stop_with_video'], |
4295 | + } |
4296 | + |
4297 | + _25_FPS = (1 * gst.SECOND)/25 |
4298 | + |
4299 | + def __init__(self, ctrl_project) : |
4300 | + ProjectMode.__init__(self, ctrl_project, "TIMELINE") |
4301 | + |
4302 | + self.acq_signals = SignalGroup() |
4303 | + self.timeline_signals = SignalGroup() |
4304 | + self.debug('ProjectModeTimeline initialized ') |
4305 | + self._is_stop_sound_with_video = False |
4306 | + |
4307 | + # prepare connection to acquisiton mode |
4308 | + self._connect_to_ctrl_acq(self.ctrl_project.ctrl_acq) |
4309 | + |
4310 | + def prepare(self, project) : |
4311 | + ProjectMode.prepare(self, project) |
4312 | + |
4313 | + # prepare sources |
4314 | + self.sources = SourceList() |
4315 | + self.sources.connect("source-added", self._sourceAddedCb) |
4316 | + self.sources.connect("source-removed", self._sourceRemovedCb) |
4317 | + self.uris = [] |
4318 | + |
4319 | + |
4320 | + |
4321 | + # prepare timeline |
4322 | + self.timeline = LucioleTimeline() |
4323 | + self._timeline_factory = TimelineSourceFactory(self.timeline) |
4324 | + |
4325 | + # prepare pipeline and resource |
4326 | + self._pipeline = Pipeline() |
4327 | + # TODO : connect to pipeline signals ? |
4328 | + #self._pipeline.connect('eos', self._eos_cb) |
4329 | + |
4330 | + self._view_action = ViewAction() |
4331 | + self._view_action.addProducers(self._timeline_factory) |
4332 | + self._pipeline.addAction(self._view_action) |
4333 | + # why seeker ? |
4334 | + self.seeker = Seeker(80) |
4335 | + |
4336 | + # |
4337 | + # initialize video settings |
4338 | + # |
4339 | + settings = self.getAutoSettings() |
4340 | + self._videocaps = settings.getVideoCaps() |
4341 | + |
4342 | + # fill timeline with audio and video track |
4343 | + video = VideoStream(gst.Caps(settings.getVideoCaps())) |
4344 | + track = Track(video) |
4345 | + self.timeline.addTrack(track) |
4346 | + audio = AudioStream(gst.Caps(settings.getAudioCaps())) |
4347 | + track = Track(audio) |
4348 | + self.timeline.addTrack(track) |
4349 | + |
4350 | + |
4351 | + self._load_timeline(self.project) |
4352 | + |
4353 | + self.debug("Prepare Timeline : %s", self.timeline ) |
4354 | + |
4355 | + |
4356 | + def release(self) : |
4357 | + self._mode = "INVALID" |
4358 | + self._pipeline.release() |
4359 | + self._pipeline = None |
4360 | + del self._timeline_factory |
4361 | + del self.timeline |
4362 | + self._view_action = None |
4363 | + |
4364 | + |
4365 | + |
4366 | + def active_viewer(self) : |
4367 | + if self._viewer : |
4368 | + self.debug(" Timeline :%s, timeline duration :%s ", self.timeline, self.timeline.duration) |
4369 | + |
4370 | + self._viewer.setPipeline(None) |
4371 | + self._viewer.showSlider() |
4372 | + self._viewer.setAction(self._view_action) |
4373 | + self._viewer.setPipeline(self._pipeline, "TIMELINE") |
4374 | + |
4375 | + # chnage pipeline status to activate it ( seek, duration) |
4376 | + self._pipeline.pause() |
4377 | + self.debug("Pipeline state : %s", self._pipeline.getState()) |
4378 | + self._viewer.prepare_viewer(self.timeline.duration) |
4379 | + # FIXME : hack to make the gtk.HScale seek slider UI behave properly |
4380 | + #self._viewer._durationChangedCb(None, self.timeline.duration) |
4381 | + self.debug("actual sequence %s", [_obj.factory.name for _obj in self.timeline.video_objects]) |
4382 | + |
4383 | + else : |
4384 | + self.error("No viewer defined") |
4385 | + |
4386 | + def change_order(self, images, target) : |
4387 | + self.debug('images : %s , target :%s', images, target) |
4388 | + self.timeline.change_order(images, target) |
4389 | + # update project |
4390 | + self._update_project() |
4391 | + |
4392 | + def insert(self, images, target) : |
4393 | + self.debug('images : %s , target :%s', images, target) |
4394 | + # get factories from source |
4395 | + _factories = [ self._get_factory(_elem) for _elem in images ] |
4396 | + # insert factories on timeline |
4397 | + self.timeline.insert_factories(_factories, target) |
4398 | + # update project |
4399 | + self._update_project() |
4400 | + |
4401 | + def remove(self, images) : |
4402 | + self.debug('images : %s' , images) |
4403 | + self.timeline.remove_images(images) |
4404 | + |
4405 | + # update project |
4406 | + self._update_project() |
4407 | + |
4408 | + def _update_project(self) : |
4409 | + """ update project properties according image sequence """ |
4410 | + _img_seq = self.timeline._get_image_sequence() |
4411 | + self.info('Image sequence : %s', _img_seq) |
4412 | + # update project according image sequence |
4413 | + self.project.props['chrono_images'] =_img_seq |
4414 | + self.ctrl_project.is_modified = True |
4415 | + |
4416 | + |
4417 | + def add_sound_track(self, path) : |
4418 | + """ add a sound track to timeline """ |
4419 | + if os.path.splitext(path)[1] != '.wav' : |
4420 | + _msg = _('Invalid sound file : %s. Only wav files allowed.' % path) |
4421 | + self.error(_msg) |
4422 | + self.ctrl_project.gui.status_bar.display_message(_msg) |
4423 | + return |
4424 | + |
4425 | + self.debug('Add sound track : %s', path ) |
4426 | + # check if a sound track is avlbl, if yes remove it |
4427 | + _dest_path = os.path.join( self.project.props['project_dir'], |
4428 | + LCONST.SOUND_TRACK_NAME) |
4429 | + |
4430 | + # first remove current sound track from timeline and sources |
4431 | + self._remove_sound_track(True) |
4432 | + |
4433 | + |
4434 | + |
4435 | + |
4436 | + try : |
4437 | + LT.copyf(path, _dest_path) |
4438 | + except LEXCEP.LucioException, err: |
4439 | + self.error(err) |
4440 | + else : |
4441 | + self.AddSources([_dest_path]) |
4442 | + self.ctrl_project.is_modified = True |
4443 | + self.project.props['sound']['name'] = LCONST.SOUND_TRACK_NAME |
4444 | + self._update_sound_props(self.project.props, active='no') |
4445 | + _msg = _('Sound track %s loaded in project'% path ) |
4446 | + self.info(_msg) |
4447 | + self.ctrl_project.gui.status_bar.display_message(_msg) |
4448 | + |
4449 | + |
4450 | + |
4451 | + def go_to_position(self, position) : |
4452 | + |
4453 | + self._viewer.seek(position*self.default_duration) |
4454 | + |
4455 | + def update_image_duration(self , fpi ) : |
4456 | + self.default_duration = self._25_FPS * fpi |
4457 | + # upadte image factory duration |
4458 | + for _factory in self.sources.getSources() : |
4459 | + if _factory.name == LCONST.SOUND_TRACK_NAME : |
4460 | + # dont update sound track duration |
4461 | + continue |
4462 | + _factory.duration = self.default_duration |
4463 | + self.debug('Updated duration (%s) of factory %s', |
4464 | + _factory.duration, |
4465 | + _factory.name) |
4466 | + |
4467 | + |
4468 | + self.timeline.update_timeline_duration(self.default_duration, self._is_stop_sound_with_video) |
4469 | + |
4470 | + |
4471 | + |
4472 | + def _remove_sound_track(self, force = True) : |
4473 | + """ |
4474 | + if force = True --> rm from timeline and source list |
4475 | + if force = False --> rm from timeline only |
4476 | + """ |
4477 | + _factory = self._get_factory(LCONST.SOUND_TRACK_NAME) |
4478 | + if _factory is None : |
4479 | + # nothing to suppress |
4480 | + self.debug('No factory to suppress') |
4481 | + return |
4482 | + |
4483 | + if self.project.props['sound']['active'] == 'yes' : |
4484 | + self.timeline.remove_sound_track(_factory) |
4485 | + # disconnect callback who updates audio duration regarding |
4486 | + # video duration |
4487 | + try : |
4488 | + self.timeline.disconnect_by_function(self._video_track_added) |
4489 | + except : |
4490 | + #TODO : what we do ? |
4491 | + pass |
4492 | + if force == True : |
4493 | + self.sources.removeUri(_factory.uri) |
4494 | + |
4495 | + self._is_stop_sound_with_video = False |
4496 | + |
4497 | + |
4498 | + |
4499 | + |
4500 | + def add_sound_track_to_timeline(self) : |
4501 | + _factory = self._get_factory(LCONST.SOUND_TRACK_NAME) |
4502 | + if _factory : |
4503 | + self.debug('Sound Factory %s, %r , duration %s', |
4504 | + _factory.name, |
4505 | + _factory, |
4506 | + _factory.duration) |
4507 | + self._is_stop_sound_with_video = False |
4508 | + |
4509 | + _tl_obj = self.timeline.add_sound_track( |
4510 | + _factory, |
4511 | + _factory.duration, |
4512 | + self._is_stop_sound_with_video, |
4513 | + 0) |
4514 | + self.timeline.connect('timeline-object-added', self._video_track_added, _tl_obj) |
4515 | + self.debug('Timeline duration : %s', self.timeline.duration) |
4516 | + self.debug('Pipeline duration : %s', self._pipeline.getDuration()) |
4517 | + else : |
4518 | + self.warning ("Sound factory does not exists") |
4519 | + |
4520 | + self._update_sound_props(self.project.props, |
4521 | + active ='yes', |
4522 | + stop_with_video = 'no' |
4523 | + ) |
4524 | + |
4525 | + |
4526 | + |
4527 | + |
4528 | + def remove_sound_track_from_timeline(self) : |
4529 | + _factory = self._get_factory(LCONST.SOUND_TRACK_NAME) |
4530 | + if _factory : |
4531 | + self.debug('remove Sound Factory %s from timeline', _factory.name) |
4532 | + self.timeline.removeFactory(_factory) |
4533 | + self.timeline.disconnect_by_function(self._video_track_added) |
4534 | + else : |
4535 | + self.info('No sound track to remove') |
4536 | + |
4537 | + self._update_sound_props(self.project.props, active ='no') |
4538 | + |
4539 | + |
4540 | + |
4541 | + def _video_track_added(self, timeline , timeline_object , audio_object) : |
4542 | + if timeline_object.track_objects[0] in timeline.video_objects : |
4543 | + # a video object is added |
4544 | + _vtrack = timeline._get_video_track() |
4545 | + self.debug('video track duration %s , object added %s , audio_object duration :%s ', |
4546 | + _vtrack.duration, timeline_object.factory.name, audio_object.duration) |
4547 | + |
4548 | + if _vtrack.duration > audio_object.duration : |
4549 | + audio_object.duration = _vtrack.duration |
4550 | + self.debug ('Audio object duration updated') |
4551 | + else : |
4552 | + self.info('timeline _object %s not a video track', timeline_object.factory.name) |
4553 | + |
4554 | + def stop_sound_with_video(self, is_stop = True) : |
4555 | + self._is_stop_sound_with_video = is_stop |
4556 | + # check if audio_track yet in timeline |
4557 | + try : |
4558 | + _obj = self.timeline.audio_object |
4559 | + except TimelineError, err : |
4560 | + self.error('Timeline error : %s',err) |
4561 | + else : |
4562 | + self.debug("Update duration") |
4563 | + if _obj : |
4564 | + if self._is_stop_sound_with_video : |
4565 | + _vtrack = self.timeline._get_video_track() |
4566 | + _duration = _vtrack.duration |
4567 | + _stop_with_video = 'yes' |
4568 | + |
4569 | + else : |
4570 | + self.debug('Set audio track duration') |
4571 | + _factory = self._get_factory(LCONST.SOUND_TRACK_NAME) |
4572 | + _duration = _factory.duration |
4573 | + _stop_with_video = 'no' |
4574 | + |
4575 | + self._update_sound_props(self.project.props, |
4576 | + stop_with_video = _stop_with_video) |
4577 | + _obj.duration = _duration |
4578 | + self.debug("timeline duration %s", self.timeline.duration) |
4579 | + |
4580 | + |
4581 | + |
4582 | + def _connect_to_ctrl_acq(self, ctrl_acq) : |
4583 | + self.debug(" Connecting to ProjectModeAcquisition ") |
4584 | + self.acq_signals.connect( |
4585 | + ctrl_acq,'snapshot-taken', None, self._snapshot_taken_cb) |
4586 | + |
4587 | + |
4588 | + def _snapshot_taken_cb(self, ctrl_acq, rush_image) : |
4589 | + self.debug('RX signal snapshot-taken : %s', rush_image.name) |
4590 | + #TODO : set the correct duration |
4591 | + self.AddSources([rush_image.path] ) |
4592 | + |
4593 | + def _load_timeline(self, project) : |
4594 | + _project = project.props |
4595 | + self.debug('rush images to load : %s', _project['capture_images'] ) |
4596 | + _sources = \ |
4597 | + [ os.path.join( _project['project_dir'], |
4598 | + _project['rush_dir'], |
4599 | + _image) \ |
4600 | + for _image in _project['capture_images'] ] |
4601 | + _image_duration = self._25_FPS * int(_project['fpi']) |
4602 | + uris = ["file://" + urllib.quote(os.path.abspath(path))\ |
4603 | + for path in _sources] |
4604 | + |
4605 | + # check if sound track active |
4606 | + if _project['sound']['active'] == 'yes' : |
4607 | + # check if sound track really present |
4608 | + _sound_track = os.path.join( |
4609 | + _project['project_dir'], |
4610 | + LCONST.SOUND_TRACK_NAME) |
4611 | + if os.path.exists(_sound_track) : |
4612 | + _uri = "file://" + urllib.quote(os.path.abspath(_sound_track)) |
4613 | + uris.append(_uri) |
4614 | + else : |
4615 | + self.error( |
4616 | + 'Sound track active but no sound file present (%s)' % |
4617 | + _sound_track) |
4618 | + |
4619 | + # discover the factories |
4620 | + closure = {"rediscovered": 0} |
4621 | + discoverer = self.sources.discoverer |
4622 | + discoverer.connect("discovery-done", self._discovererDiscoveryDoneCb, |
4623 | + _project, _image_duration, uris, closure) |
4624 | + discoverer.connect("discovery-error", self._discovererDiscoveryErrorCb, |
4625 | + _project, _image_duration, uris, closure) |
4626 | + |
4627 | + # start discoverer |
4628 | + |
4629 | + self.sources.addUris(uris) |
4630 | + self.default_duration = _image_duration |
4631 | + |
4632 | + |
4633 | + def _finishLoadingProject(self, project, duration) : |
4634 | + _start = 0 |
4635 | + self.debug ('project %s', project) |
4636 | + for _image in project['chrono_images'] : |
4637 | + _factory = self._get_factory(_image) |
4638 | + _factory.default_duration = duration |
4639 | + _tl_obj = self.timeline.create_timeline_object(_factory) |
4640 | + _tl_obj.start = _start |
4641 | + _tl_obj.duration = duration |
4642 | + _start += duration |
4643 | + self.timeline.addTimelineObject( _tl_obj) |
4644 | + if project['sound']['active'] == 'yes' : |
4645 | + _factory = self._get_factory(LCONST.SOUND_TRACK_NAME) |
4646 | + if _factory : |
4647 | + |
4648 | + self.debug('Sound Factory %s, %r , duration %s', |
4649 | + _factory.name, |
4650 | + _factory, |
4651 | + _factory.duration) |
4652 | + self._is_stop_sound_with_video = False |
4653 | + if project['sound']['stop_with_video'] == 'yes' : |
4654 | + self._is_stop_sound_with_video = True |
4655 | + _tl_obj = self.timeline.add_sound_track( |
4656 | + _factory, |
4657 | + _factory.duration, |
4658 | + self._is_stop_sound_with_video, |
4659 | + 0) |
4660 | + self.timeline.connect('timeline-object-added', self._video_track_added, _tl_obj) |
4661 | + # force emit of project sound props |
4662 | + self._update_sound_props( |
4663 | + project = project, |
4664 | + active = project['sound']['active'], |
4665 | + stop_with_video = project['sound']['stop_with_video']) |
4666 | + |
4667 | + |
4668 | + self.debug('Emit timeline-loaded') |
4669 | + self.emit('timeline-loaded') |
4670 | + |
4671 | + |
4672 | + |
4673 | + def _update_sound_props(self, project, active = None, stop_with_video = None) : |
4674 | + _is_modified = False |
4675 | + _is_active = None |
4676 | + _is_stop_with_video = None |
4677 | + if project is None : |
4678 | + self.error("No project defined") |
4679 | + return |
4680 | + |
4681 | + if active is not None : |
4682 | + if active not in ('yes','no') : |
4683 | + self.error('invalid value for prop active (%s)', |
4684 | + active) |
4685 | + return |
4686 | + if active != project['sound']['active'] : |
4687 | + project['sound']['active'] = active |
4688 | + _is_modified = True |
4689 | + |
4690 | + if active == 'yes' : |
4691 | + _is_active = True |
4692 | + else : |
4693 | + _is_active = False |
4694 | + if stop_with_video : |
4695 | + if stop_with_video not in ('yes','no') : |
4696 | + self.error('invalid value for prop active (%s)', |
4697 | + stop_with_video) |
4698 | + return |
4699 | + |
4700 | + if stop_with_video != project['sound']['stop_with_video'] : |
4701 | + project['sound']['stop_with_video'] = stop_with_video |
4702 | + _is_modified = True |
4703 | + if stop_with_video == 'yes' : |
4704 | + _is_stop_with_video = True |
4705 | + else : |
4706 | + _is_stop_with_video = False |
4707 | + |
4708 | + self.debug('sound is_active :%s is_stop_with_video : %s, project %s', |
4709 | + active, |
4710 | + stop_with_video, |
4711 | + project['sound'] ) |
4712 | + |
4713 | + if _is_modified == True : |
4714 | + self.ctrl_project.is_modified = True |
4715 | + |
4716 | + if _is_stop_with_video is not None \ |
4717 | + or \ |
4718 | + _is_active is not None : |
4719 | + self.debug('Emit sound-props-changed : active %s stop_with_video %s', |
4720 | + _is_active, _is_stop_with_video ) |
4721 | + self.emit('sound-props-changed', _is_active, _is_stop_with_video) |
4722 | + |
4723 | + |
4724 | + |
4725 | + # Sources associated functions |
4726 | + # |
4727 | + def AddSources(self, file_list) : |
4728 | + |
4729 | + uris = ["file://" + urllib.quote(os.path.abspath(path))\ |
4730 | + for path in file_list] |
4731 | + self.uris = uris |
4732 | + self.debug("Sources to add %s", uris) |
4733 | + # add uris |
4734 | + self.sources.addUris(uris) |
4735 | + |
4736 | + def _discovererDiscoveryDoneCb(self, discoverer, uri, factory, |
4737 | + project , duration, uris, closure): |
4738 | + self.debug("discovery done for %s",factory.name) |
4739 | + if factory.uri not in uris: |
4740 | + # someone else is using discoverer, this signal isn't for us |
4741 | + return |
4742 | + |
4743 | + # update duration of image factory |
4744 | + if factory.name != LCONST.SOUND_TRACK_NAME : |
4745 | + factory.duration = self.default_duration |
4746 | + |
4747 | + |
4748 | + if factory not in self.sources.getSources() : |
4749 | + self.debug("Factory duration :%s", factory.duration) |
4750 | + self.sources.addFactory(factory) |
4751 | + |
4752 | + closure["rediscovered"] += 1 |
4753 | + if closure["rediscovered"] == len(uris): |
4754 | + self._finishLoadingProject(project, duration) |
4755 | + |
4756 | + def _discovererDiscoveryErrorCb(self, discoverer, uri, factory, |
4757 | + project, duration, uris, closure): |
4758 | + self.warning('Impossible to load %s'%uri) |
4759 | + |
4760 | + def _get_factory(self, image_name) : |
4761 | + _factory = None |
4762 | + for _source in self.sources.getSources() : |
4763 | + if _source.name == image_name : |
4764 | + _factory = _source |
4765 | + break |
4766 | + if _factory == None : |
4767 | + self.error("No factory found for %s", image_name) |
4768 | + return _factory |
4769 | + |
4770 | + def _sourceAddedCb(self, sourcelist, factory): |
4771 | + #self.info("source added to timeline : %s %s"%(sourcelist, factory)) |
4772 | + factory.setFilterCaps(self._videocaps) |
4773 | + factory.default_duration = self.default_duration |
4774 | + # #timeline_object = self.timeline.addSourceFactory(factory) |
4775 | + #self.info("Object %s starts at %s", factory.name, timeline_object.start) |
4776 | + self.debug("RX source-added Source %s added -- %s", factory.name, factory) |
4777 | + |
4778 | + def _sourceRemovedCb(self, sourcelist, uri, factory): |
4779 | + self.timeline.removeFactory(factory) |
4780 | + self.info('Source %s removed call back'%uri) |
4781 | + # TODO : emit source-removed ? |
4782 | + #self.emit('source-removed', uri, factory) |
4783 | + |
4784 | + def getAutoSettings(self): |
4785 | + """ |
4786 | + Computes and returns smart settings for the project. |
4787 | + If the project only has one source, it will be that source's settings. |
4788 | + If it has more than one, it will return the largest setting that suits |
4789 | + all contained sources. |
4790 | + """ |
4791 | + settings = ExportSettings() |
4792 | + if not self.timeline: |
4793 | + self.warning\ |
4794 | + ("project doesn't have a timeline, returning default settings") |
4795 | + return settings |
4796 | + |
4797 | + # FIXME: this is ugly, but rendering for now assumes at most one audio |
4798 | + # and one video tracks |
4799 | + have_audio = have_video = False |
4800 | + for track in self.timeline.tracks: |
4801 | + if isinstance(track.stream, VideoStream) and track.duration != 0: |
4802 | + have_video = True |
4803 | + elif isinstance(track.stream, AudioStream) and track.duration != 0: |
4804 | + have_audio = True |
4805 | + |
4806 | + if not have_audio: |
4807 | + settings.aencoder = None |
4808 | + |
4809 | + if not have_video: |
4810 | + settings.vencoder = None |
4811 | + |
4812 | + return settings |
4813 | + |
4814 | |
4815 | === modified file 'luciole/ctrl/import_image.py' |
4816 | --- luciole/ctrl/import_image.py 2010-09-04 15:17:48 +0000 |
4817 | +++ luciole/ctrl/import_image.py 2011-02-28 13:21:06 +0000 |
4818 | @@ -36,6 +36,9 @@ |
4819 | # N/A |
4820 | import gobject |
4821 | |
4822 | +from pitivi.signalinterface import Signallable |
4823 | +from pitivi.log.loggable import Loggable |
4824 | + |
4825 | # local application/library specific imports |
4826 | import luciole.base.exceptions as LEXCEP |
4827 | import luciole.base.tools as LT |
4828 | @@ -62,6 +65,7 @@ |
4829 | """ run thread """ |
4830 | # create rush list object |
4831 | _image_objs = [] |
4832 | + |
4833 | for (_index, _filename) in enumerate(self.__filenames) : |
4834 | # copy image to rush folder and resize it |
4835 | try : |
4836 | @@ -116,8 +120,11 @@ |
4837 | LT.copyf(p_image_path, _ac_image_temp) |
4838 | |
4839 | # 2. resize image result is in _ac_image_temp_rz |
4840 | - _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz ) |
4841 | - _rz_obj.convert() |
4842 | + if self.__pjt_data['resize'] == 'yes' : |
4843 | + _rz_obj = LI.ImageResize(_ac_image_temp, _ac_image_temp_rz ) |
4844 | + _rz_obj.convert() |
4845 | + else : |
4846 | + _ac_image_temp_rz = _ac_image_temp |
4847 | |
4848 | # 3. move resized image to rush dire |
4849 | LT.movef(_ac_image_temp_rz, _rush_image) |
4850 | @@ -127,7 +134,7 @@ |
4851 | return _basename |
4852 | |
4853 | |
4854 | -class ImportController(object): |
4855 | +class ImportController(Signallable, Loggable): |
4856 | """ This class is used to manage the import of image in project . |
4857 | The class use a Thread for importing images (it can takes) long. |
4858 | The thread and this class communicate via a queue. |
4859 | @@ -135,17 +142,21 @@ |
4860 | The queue is checked cyclicly via a gobject timer |
4861 | """ |
4862 | _TIMEOUT = 50 # 50 ms timer |
4863 | - |
4864 | - def __init__(self, filenames, project_data, rusher, status_bar, cbs ) : |
4865 | + __signals__ = { |
4866 | + 'import-image-done' : ['image_name'], |
4867 | + } |
4868 | + def __init__(self, filenames, project_data, rusher, status_bar) : |
4869 | """ Init Thread, init gobject timer and clear progressbar """ |
4870 | + Signallable.__init__(self) |
4871 | + Loggable.__init__(self) |
4872 | self.__filenames = filenames |
4873 | self.__pjt_data = project_data |
4874 | self.__rusher = rusher |
4875 | self.__status_bar = status_bar |
4876 | - self.__cbs = cbs |
4877 | |
4878 | |
4879 | # initialize thread |
4880 | + self.debug('Starting thread') |
4881 | self.queue = Queue.Queue() |
4882 | self.t_rush = ImportThread(self.__filenames, |
4883 | self.__pjt_data, |
4884 | @@ -176,11 +187,15 @@ |
4885 | pass |
4886 | else : |
4887 | if _qmsg.has_key('progression') : |
4888 | + |
4889 | + self.debug(' on progress') |
4890 | self._progress_bar_on_progress(_qmsg['progression']) |
4891 | if _qmsg.has_key('finish') : |
4892 | # finisg project load ( Non threaded section ) |
4893 | - self.__cbs['done-import'](_qmsg['finish']) |
4894 | - |
4895 | + self.debug('emit import-image-done : %s',_qmsg['finish']) |
4896 | + self.emit('import-image-done', _qmsg['finish']) |
4897 | + |
4898 | + |
4899 | #update as finsih staus/progress bard |
4900 | self._progress_bar_complete() |
4901 | # stop the timer |
4902 | @@ -198,6 +213,7 @@ |
4903 | _msg = _('All images imported') |
4904 | # use of idle_add because gui update not in the same thread |
4905 | self.__status_bar.stop(_msg) |
4906 | + |
4907 | |
4908 | def _progress_bar_on_progress(self, ratio = None): |
4909 | """ indicate that import is going on """ |
4910 | |
4911 | === modified file 'luciole/ctrl/load_rush.py' |
4912 | --- luciole/ctrl/load_rush.py 2010-09-04 15:17:48 +0000 |
4913 | +++ luciole/ctrl/load_rush.py 2011-02-28 13:21:06 +0000 |
4914 | @@ -35,6 +35,7 @@ |
4915 | |
4916 | # related third party imports |
4917 | import gobject |
4918 | +from pitivi.signalinterface import Signallable |
4919 | |
4920 | # local application/library specific imports |
4921 | import luciole.media.image as LI |
4922 | @@ -80,24 +81,27 @@ |
4923 | self.queue.put(_qmsg, block=False) |
4924 | |
4925 | |
4926 | -class ControllerLoadRush(object): |
4927 | +class ControllerLoadRush(Signallable): |
4928 | """ This class is used to manage the load of a project in application. |
4929 | The class use a Thread for launch rush object init. (it can takes) long. |
4930 | The thread and this class communicate vai a queue. Information on queue are progression and finish. |
4931 | The queue is checked cyclicly via a gobject timer |
4932 | """ |
4933 | _TIMEOUT = 10 # 10 ms timer |
4934 | + |
4935 | + __signals__ = { |
4936 | + "rush-loaded" : ["rush_obj"], |
4937 | + } |
4938 | |
4939 | - def __init__(self, project, status_bar, cb_on_finish) : |
4940 | + def __init__(self, project, status_bar) : |
4941 | """ Init Thread, init gobject timer and clear progressbar """ |
4942 | - self.project = project |
4943 | - self._cb_on_finish = cb_on_finish |
4944 | - |
4945 | + Signallable.__init__(self) |
4946 | + self.project = project.props |
4947 | self.__status_bar = status_bar |
4948 | |
4949 | # initialize thread |
4950 | self.queue = Queue.Queue() |
4951 | - self.t_rush = RushLoaderThread(project, self.queue) |
4952 | + self.t_rush = RushLoaderThread(self.project, self.queue) |
4953 | |
4954 | #clear progressbar |
4955 | self._progress_bar_clear() |
4956 | @@ -144,6 +148,7 @@ |
4957 | |
4958 | def _progress_bar_complete(self): |
4959 | """ Progress bar full : Project loaded """ |
4960 | + # TODO : This message should not displayed here |
4961 | _msg = _( 'Project %s is loaded')%self.project['project_name'] |
4962 | # use of idle_add because gui update not in the same thread |
4963 | self.__status_bar.stop(_msg) |
4964 | @@ -157,7 +162,7 @@ |
4965 | def _on_rush_finish(self, rush_obj): |
4966 | """ Finish the load after rush generation """ |
4967 | # call back to indicate finish at controller |
4968 | - self._cb_on_finish(rush_obj) |
4969 | + self.emit("rush-loaded",rush_obj ) |
4970 | |
4971 | |
4972 | |
4973 | |
4974 | === modified file 'luciole/ctrl/viewer_ctrl.py' |
4975 | --- luciole/ctrl/viewer_ctrl.py 2010-09-06 09:00:55 +0000 |
4976 | +++ luciole/ctrl/viewer_ctrl.py 2011-02-28 13:21:06 +0000 |
4977 | @@ -225,7 +225,14 @@ |
4978 | # send error message |
4979 | _msg = _("Can not play animation : No image on montage view ") |
4980 | self.__gui.window.error_message(_msg) |
4981 | - |
4982 | + |
4983 | + def play_query_position(self) : |
4984 | + """ query position """ |
4985 | + _position = LCONST.CLOCK_TIME_NONE |
4986 | + _duration = LCONST.CLOCK_TIME_NONE |
4987 | + if self.__mode == self.PLAYER : |
4988 | + (_position, _duration) = self.__lucio_player.query_postion() |
4989 | + return (_position, _duration) |
4990 | |
4991 | def play_video(self, app_data) : |
4992 | """ play a video from chrono images """ |
4993 | |
4994 | === added file 'luciole/data/images/white.jpg' |
4995 | Binary 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 |
4996 | === modified file 'luciole/data/templates/project_template.xml' |
4997 | --- luciole/data/templates/project_template.xml 2010-03-30 07:18:34 +0000 |
4998 | +++ luciole/data/templates/project_template.xml 2011-02-28 13:21:06 +0000 |
4999 | @@ -3,7 +3,8 @@ |
5000 | <metas> |