11# 2# Entangle: Tethered Camera Control & Capture 3# 4# Copyright (C) 2014 Daniel P. Berrange 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18# 19 20import time 21 22import gi 23 24from gi.repository import GObject 25gi.require_version("Gtk", "3.0") 26from gi.repository import Gtk 27from gi.repository import Gdk 28from gi.repository import GLib 29from gi.repository import Gio 30from gi.repository import Peas 31gi.require_version("PeasGtk", "1.0") 32from gi.repository import PeasGtk 33gi.require_version("Entangle", "0.1") 34from gi.repository import Entangle 35 36 37ISO_APERTURE_TO_INDEX_MAP = [ 38 ["25", ["1.4", "2", "2.8", "4", "5.6", "8", "11", "16", "22"]], 39 ["50", ["2", "2.8", "4", "5.6", "8", "11", "16", "22", "32"]], 40 ["100", ["2.8", "4", "5.6", "8", "11", "16", "22", "32", "44"]], 41 ["200", ["4", "5.6", "8", "11", "16", "22", "32", "44", "64"]], 42 ["400", ["5.6", "8", "11", "16", "22", "32", "44", "64", "88"]], 43 ["800", ["8", "11", "16", "22", "32", "44", "64", "88", "128"]], 44 ["1600", ["11", "16", "22", "32", "44", "64", "88", "128", "176"]], 45] 46 47INDEX_TO_SPEED_MAP = [ 48 [ 49 "1/2000", 50 "1/500", 51 "1/125", 52 "1/30", 53 "1/15", 54 "1/8", 55 "1/2", 56 ], 57 [ 58 "1/1000", 59 "1/250", 60 "1/60", 61 "1/15", 62 "1/8", 63 "1/4", 64 "1", 65 ], 66 [ 67 "1/500", 68 "1/125", 69 "1/30", 70 "1/8", 71 "1/4", 72 "1/2", 73 "2", 74 ], 75 76 [ 77 "1/250", 78 "1/60", 79 "1/15", 80 "1/4", 81 "1/2", 82 "1", 83 "4", 84 ], 85 [ 86 "1/125", 87 "1/30", 88 "1/8", 89 "1/2", 90 "1", 91 "2", 92 "8", 93 ], 94 [ 95 "1/60", 96 "1/15", 97 "1/4", 98 "1", 99 "2", 100 "4", 101 "15", 102 ], 103 104 [ 105 "1/30", 106 "1/8", 107 "1/2", 108 "2", 109 "4", 110 "8", 111 "30", 112 ], 113 [ 114 "1/15", 115 "1/4", 116 "1", 117 "4", 118 "8", 119 "15", 120 "60", 121 ], 122 [ 123 "1/8", 124 "1/2", 125 "2", 126 "8", 127 "15", 128 "30", 129 "120", 130 ] 131] 132 133 134class EclipsePluginWidget(Gtk.Grid): 135 '''The form for controlling parameters of 136 the batch mode shooting session and the progress''' 137 138 def do_iso_changed(self, data): 139 self.do_aperture_populate() 140 self.config.set_iso(self.iso.get_active_text()) 141 142 def do_aperture_changed(self, data): 143 self.config.set_aperture(self.aperture.get_active_text()) 144 145 def do_aperture_populate(self): 146 isoidx = self.iso.get_active() 147 if isoidx == -1: 148 self.aperture.set_sensitive(False) 149 else: 150 self.aperture.set_sensitive(True) 151 152 self.aperture.remove_all() 153 defset = False 154 for apertureidx in range(len(ISO_APERTURE_TO_INDEX_MAP[isoidx][1])): 155 aperture = ISO_APERTURE_TO_INDEX_MAP[isoidx][1][apertureidx] 156 self.aperture.append(aperture, aperture) 157 if aperture == self.config.get_aperture(): 158 self.aperture.set_active(apertureidx) 159 defset = True 160 if not defset: 161 self.aperture.set_active(0) 162 163 def __init__(self, config): 164 super(EclipsePluginWidget, self).__init__() 165 166 self.config = config 167 168 self.set_properties(column_spacing=6, 169 row_spacing=6, 170 expand=False) 171 172 self.attach(Gtk.Label("ISO:"), 0, 0, 1, 1) 173 self.iso = Gtk.ComboBoxText() 174 175 for isoidx in range(len(ISO_APERTURE_TO_INDEX_MAP)): 176 iso = ISO_APERTURE_TO_INDEX_MAP[isoidx][0] 177 self.iso.append(iso, iso) 178 if iso == config.get_iso(): 179 self.iso.set_active(isoidx) 180 self.iso.set_properties(expand=True) 181 self.iso.connect("changed", self.do_iso_changed) 182 self.attach(self.iso, 1, 0, 1, 1) 183 184 self.attach(Gtk.Label("Aperture:"), 0, 1, 1, 1) 185 self.aperture = Gtk.ComboBoxText() 186 self.do_aperture_populate() 187 self.aperture.connect("changed", self.do_aperture_changed) 188 self.attach(self.aperture, 1, 1, 1, 1) 189 190 self.show_all() 191 192class EclipsePluginData(GObject.Object): 193 194 __gtype_name__ = "EclipsePluginData" 195 196 def _get_speeds(self, iso, aperture): 197 for isoidx in range(len(ISO_APERTURE_TO_INDEX_MAP)): 198 thisiso = ISO_APERTURE_TO_INDEX_MAP[isoidx][0] 199 if thisiso != iso: 200 continue 201 202 for apertureidx in range(len(ISO_APERTURE_TO_INDEX_MAP[isoidx][1])): 203 thisaperture = ISO_APERTURE_TO_INDEX_MAP[isoidx][1][apertureidx] 204 if thisaperture != aperture: 205 continue 206 207 return INDEX_TO_SPEED_MAP[apertureidx] 208 209 return [] 210 211 return [] 212 213 def __init__(self, iso, aperture): 214 super(EclipsePluginData, self).__init__() 215 216 self.speeds = self._get_speeds(iso, aperture) 217 self.speedIndex = 0 218 self.automata = None 219 220 def speed(self): 221 speed = self.speeds[self.speedIndex] 222 self.speedIndex = self.speedIndex + 1 223 return speed 224 225 def finished(self): 226 return self.speedIndex == len(self.speeds) 227 228 def reset(self): 229 self.speedIndex = 0 230 231 232class EclipsePluginScript(Entangle.ScriptSimple): 233 '''The script for controlling the camera''' 234 235 def __init__(self, config): 236 super(EclipsePluginScript, self).__init__( 237 title="Eclipse Totality" 238 ) 239 240 self.config = config 241 self.widget = EclipsePluginWidget(config) 242 243 def do_get_config_widget(self): 244 return self.widget 245 246 def do_capture_callback(self, automata, result, script_result): 247 try: 248 automata.capture_finish(result) 249 except GLib.Error as e: 250 self.return_task_error(script_result, str(e)); 251 return 252 253 if script_result.return_error_if_cancelled(): 254 return 255 256 cancel = script_result.get_cancellable() 257 self.do_next_step(cancel, script_result) 258 259 def do_init_task_data(self): 260 return EclipsePluginData(self.config.get_iso(), 261 self.config.get_aperture()) 262 263 def do_next_step(self, cancel, script_result): 264 data = self.get_task_data(script_result) 265 if data.finished(): 266 script_result.return_boolean(True) 267 return 268 269 automata = script_result.automata 270 camera = automata.get_camera() 271 controls = camera.get_controls() 272 widget = controls.get_by_path("/main/capturesettings/shutterspeed2") 273 speed = data.speed() 274 widget.set_property("value", speed) 275 camera.save_controls_async(cancel, self.do_save_callback, script_result) 276 277 def do_save_callback(self, camera, result, script_result): 278 try: 279 camera.save_controls_finish(result) 280 except GLib.Error as e: 281 self.return_task_error(script_result, str(e)); 282 return 283 284 if script_result.return_error_if_cancelled(): 285 return 286 287 automata = script_result.automata 288 automata.capture_async(script_result.get_cancellable(), 289 self.do_capture_callback, 290 script_result) 291 292 def do_execute(self, automata, cancel, result): 293 result.automata = automata 294 295 self.do_next_step(cancel, result) 296 297 298class EclipsePluginConfig(): 299 '''Provides integration with GSettings to read/write 300 configuration parameters''' 301 302 def __init__(self, plugin_info): 303 settingsdir = plugin_info.get_data_dir() + "/schemas" 304 sssdef = Gio.SettingsSchemaSource.get_default() 305 sss = Gio.SettingsSchemaSource.new_from_directory(settingsdir, sssdef, False) 306 schema = sss.lookup("org.entangle-photo.plugins.eclipse", False) 307 self.settings = Gio.Settings.new_full(schema, None, None) 308 309 def get_iso(self): 310 return self.settings.get_string("iso") 311 312 def set_iso(self, iso): 313 self.settings.set_string("iso", iso) 314 315 def get_aperture(self): 316 return self.settings.get_string("aperture") 317 318 def set_aperture(self, fnum): 319 self.settings.set_string("aperture", fnum) 320 321 322class EclipsePlugin(GObject.Object, Peas.Activatable): 323 '''Handles the plugin activate/deactivation and 324 tracking of camera manager windows. When a window 325 appears, it enables the eclipse functionality on 326 that window''' 327 __gtype_name__ = "EclipsePlugin" 328 329 object = GObject.property(type=GObject.Object) 330 331 def __init__(self): 332 GObject.Object.__init__(self) 333 self.wins = [] 334 self.winsigadd = None 335 self.winsigrem = None 336 self.config = None 337 self.script = None 338 339 def do_activate_window(self, win): 340 if not isinstance(win, Entangle.CameraManager): 341 return 342 343 win.add_script(self.script) 344 self.wins.append(win) 345 346 def do_deactivate_window(self, win): 347 if not isinstance(win, Entangle.CameraManager): 348 return 349 350 win.remove_script(self.script) 351 oldwins = self.wins 352 self.wins = [] 353 for w in oldwins: 354 if w != win: 355 self.wins.append(w) 356 357 def do_activate(self): 358 if self.config is None: 359 self.config = EclipsePluginConfig(self.plugin_info) 360 self.script = EclipsePluginScript(self.config) 361 362 # Windows can be dynamically added/removed so we 363 # must track this 364 self.winsigadd = self.object.connect( 365 "window-added", 366 lambda app, win: self.do_activate_window(win)) 367 self.winsigrem = self.object.connect( 368 "window-removed", 369 lambda app, win: self.do_deactivate_window(win)) 370 371 for win in self.object.get_windows(): 372 self.do_activate_window(win) 373 374 def do_deactivate(self): 375 self.object.disconnect(self.winsigadd) 376 self.object.disconnect(self.winsigrem) 377 for win in self.object.get_windows(): 378 self.do_deactivate_window(win) 379 self.config = None 380 self.script = None 381