ffi-gphoto2 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/Rakefile +2 -0
  4. data/examples/intervalometer.rb +9 -7
  5. data/examples/list_config.rb +3 -5
  6. data/examples/live_view.rb +6 -12
  7. data/examples/record_movie.rb +10 -13
  8. data/ffi-gphoto2.gemspec +2 -1
  9. data/lib/ffi/gphoto2_port.rb +2 -2
  10. data/lib/gphoto2/camera/capture.rb +59 -0
  11. data/lib/gphoto2/camera/configuration.rb +136 -0
  12. data/lib/gphoto2/camera/event.rb +56 -0
  13. data/lib/gphoto2/camera/filesystem.rb +44 -0
  14. data/lib/gphoto2/camera.rb +95 -166
  15. data/lib/gphoto2/camera_abilities.rb +6 -6
  16. data/lib/gphoto2/camera_abilities_list.rb +7 -6
  17. data/lib/gphoto2/camera_event.rb +1 -8
  18. data/lib/gphoto2/camera_file.rb +15 -5
  19. data/lib/gphoto2/camera_file_path.rb +3 -5
  20. data/lib/gphoto2/camera_folder.rb +11 -0
  21. data/lib/gphoto2/camera_list.rb +3 -10
  22. data/lib/gphoto2/camera_widgets/camera_widget.rb +27 -14
  23. data/lib/gphoto2/camera_widgets/radio_camera_widget.rb +1 -0
  24. data/lib/gphoto2/camera_widgets/range_camera_widget.rb +1 -0
  25. data/lib/gphoto2/context.rb +2 -6
  26. data/lib/gphoto2/entry.rb +2 -0
  27. data/lib/gphoto2/port_info.rb +8 -6
  28. data/lib/gphoto2/port_info_list.rb +5 -6
  29. data/lib/gphoto2/port_result.rb +2 -0
  30. data/lib/gphoto2/struct.rb +11 -0
  31. data/lib/gphoto2/version.rb +1 -1
  32. data/lib/gphoto2.rb +13 -0
  33. data/spec/gphoto2/camera_abilities_list_spec.rb +5 -5
  34. data/spec/gphoto2/camera_abilities_spec.rb +7 -7
  35. data/spec/gphoto2/camera_file_path_spec.rb +2 -2
  36. data/spec/gphoto2/camera_file_spec.rb +7 -7
  37. data/spec/gphoto2/camera_folder_spec.rb +3 -3
  38. data/spec/gphoto2/camera_list_spec.rb +3 -3
  39. data/spec/gphoto2/camera_spec.rb +37 -272
  40. data/spec/gphoto2/camera_widgets/date_camera_widget_spec.rb +1 -1
  41. data/spec/gphoto2/camera_widgets/radio_camera_widget_spec.rb +4 -4
  42. data/spec/gphoto2/camera_widgets/range_camera_widget_spec.rb +2 -2
  43. data/spec/gphoto2/camera_widgets/text_camera_widget_spec.rb +1 -1
  44. data/spec/gphoto2/camera_widgets/toggle_camera_widget_spec.rb +2 -2
  45. data/spec/gphoto2/context_spec.rb +1 -1
  46. data/spec/gphoto2/entry_spec.rb +2 -2
  47. data/spec/gphoto2/port_info_list_spec.rb +4 -4
  48. data/spec/gphoto2/port_info_spec.rb +9 -10
  49. data/spec/spec_helper.rb +0 -7
  50. data/spec/support/shared_examples/camera/capture_examples.rb +41 -0
  51. data/spec/support/shared_examples/camera/configuration_examples.rb +161 -0
  52. data/spec/support/shared_examples/camera/event_examples.rb +39 -0
  53. data/spec/support/shared_examples/camera/filesystem_examples.rb +46 -0
  54. data/spec/support/shared_examples/camera_widget_examples.rb +13 -13
  55. metadata +32 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b4805a380f8f02eb6694864ac81a98762bf7b46
4
- data.tar.gz: 9e902a992bfd63ea3c34cb86309d7b0856f5790f
3
+ metadata.gz: 06a37e1529e44e76317ee65554d7c1ddacea1cec
4
+ data.tar.gz: fd9cb5732c86302822891de0530a3b6dd822c078
5
5
  SHA512:
6
- metadata.gz: a661c2b35d4437e187d1b70841819b968c78fe4adef6a13921872c2e539c8bca320b07f1aad88ed965848d88ea746a97ff19a0162aff3ece0cea4488d2c7d67d
7
- data.tar.gz: fd8d1791990063bfe0945774a61dd3d0c287ee1cfd554c24ff2b2e528ec88071013b1d7bde689d2abc0cf922866ba1ca597d1af9c9d326b60d3f84af6edd9f68
6
+ metadata.gz: 63f6a9cc345afbc0c9e90fd4396927d054e24825c6579de635e0a12ab97fce77606960c0c9a79771b005b08e91537a262decef0e7015c3aa60010fa51b1bed3b
7
+ data.tar.gz: e1cd250dc551227e6425991e8aabe5e1dcb92452186caa6f33980081784f33c5733e96aadb99806abca94697c3090e2cd898dc6fa140d582655b0f2155ada2fc
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
- --format progress
2
+ --warnings
3
+ --require spec_helper
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'yard'
3
4
 
4
5
  RSpec::Core::RakeTask.new('spec')
6
+ YARD::Rake::YardocTask.new
5
7
 
6
8
  task default: :spec
@@ -1,12 +1,14 @@
1
1
  require 'gphoto2'
2
2
 
3
- camera = GPhoto2::Camera.first
3
+ # Take a photo every 10 seconds for 2 hours.
4
4
 
5
- six_hours = 60 * 60 * 6
6
- stop_time = Time.now + six_hours
5
+ interval = 10 # seconds
6
+ two_hours = 60 * 60 * 2 # (2 hours)
7
+ stop_time = Time.now + two_hours
7
8
 
8
- # take a photo every 30 seconds for 6 hours
9
- until Time.now >= stop_time
10
- camera.capture.save
11
- sleep 30
9
+ GPhoto2::Camera.first do |camera|
10
+ until Time.now >= stop_time
11
+ camera.capture.save
12
+ sleep interval
13
+ end
12
14
  end
@@ -1,5 +1,7 @@
1
1
  require 'gphoto2'
2
2
 
3
+ # List all configuration values in tree form.
4
+
3
5
  def visit(widget, level = 0)
4
6
  indent = ' ' * level
5
7
 
@@ -28,10 +30,6 @@ def visit(widget, level = 0)
28
30
  end
29
31
  end
30
32
 
31
- camera = GPhoto2::Camera.first
32
-
33
- begin
33
+ GPhoto2::Camera.first do |camera|
34
34
  visit(camera.window)
35
- ensure
36
- camera.finalize
37
35
  end
@@ -1,21 +1,15 @@
1
+ require 'gphoto2'
2
+
1
3
  # Run and pipe to a video player that can demux raw mjpeg. For example,
2
4
  #
3
- # ruby live_view.rb | mpv --demuxer=+lavf --demuxer-lavf-format=mjpeg --demuxer-lavf-analyzeduration=0.1 -
5
+ # ruby live_view.rb | mpv --demuxer-lavf-format=mjpeg -
4
6
 
5
- require 'gphoto2'
6
-
7
- # automatically flush the io buffer
7
+ # Automatically flush the IO buffer.
8
8
  STDOUT.sync = true
9
9
 
10
- camera = GPhoto2::Camera.first
11
-
12
- begin
10
+ GPhoto2::Camera.first do |camera|
13
11
  loop do
14
- # write the preview image to stdout
12
+ # Write the preview image to stdout.
15
13
  print camera.preview.data
16
14
  end
17
- ensure
18
- # make sure to close (or #exit) the camera
19
- # otherwise, the mirror will stay up
20
- camera.finalize
21
15
  end
@@ -1,28 +1,25 @@
1
+ require 'gphoto2'
2
+
1
3
  # The camera must have a `movie` config key for this to work.
2
4
  #
3
5
  # Unlike capturing photos, which typically save to internal memory, videos
4
6
  # save to the memory card. The next `file_added` event from the camera after
5
7
  # stopping the recording will contain data pointing to the video file.
6
8
 
7
- require 'gphoto2'
8
-
9
- camera = GPhoto2::Camera.first
10
-
11
- begin
12
- # start recording
9
+ GPhoto2::Camera.first do |camera|
10
+ # Start recording.
13
11
  camera.update(movie: true)
14
12
 
15
- # record for ~10 seconds
13
+ # Record for ~10 seconds.
16
14
  sleep 10
17
15
 
18
- # stop recording
16
+ # Stop recording.
19
17
  camera.update(movie: false)
20
18
 
21
- # block until the camera finishes with the file
19
+ # Block until the camera finishes with the file.
22
20
  event = camera.wait_for(:file_added)
23
21
 
24
- # the event data has a camera file that can be saved
25
- event.data.save
26
- ensure
27
- camera.finalize
22
+ # The event data has a camera file that can be saved.
23
+ file = event.data
24
+ file.save
28
25
  end
data/ffi-gphoto2.gemspec CHANGED
@@ -24,7 +24,8 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_development_dependency 'bundler', '~> 1.3'
26
26
  spec.add_development_dependency 'rake'
27
- spec.add_development_dependency 'rspec', '~> 2.14.1'
27
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
28
+ spec.add_development_dependency 'yard', '~> 0.8.7'
28
29
 
29
30
  spec.add_dependency 'ffi', '~> 1.9.0'
30
31
  end
@@ -16,8 +16,8 @@ module FFI
16
16
 
17
17
  # libgphoto2_port/gphoto2/gphoto2-port-info-list.h
18
18
  attach_function :gp_port_info_get_name, [GPPortInfo, :pointer], :int
19
- attach_function :gp_port_info_get_path, [GPPortInfo, :pointer], :int
20
- attach_function :gp_port_info_get_type, [GPPortInfo, :pointer], :int
19
+ attach_function :gp_port_info_get_path, [GPPortInfo, :pointer], :int
20
+ attach_function :gp_port_info_get_type, [GPPortInfo, :pointer], :int
21
21
 
22
22
  attach_function :gp_port_info_list_new, [:pointer], :int
23
23
  attach_function :gp_port_info_list_free, [GPPortInfoList.by_ref], :int
@@ -0,0 +1,59 @@
1
+ module GPhoto2
2
+ class Camera
3
+ module Capture
4
+ # @example
5
+ # # Take a photo.
6
+ # file = camera.capture
7
+ #
8
+ # # And save it to the current working directory.
9
+ # file.save
10
+ #
11
+ # @param [CameraCaptureType] type
12
+ # @return [GPhoto2::CameraFile]
13
+ def capture(type = :image)
14
+ save
15
+ path = _capture(type)
16
+ CameraFile.new(self, path.folder, path.name)
17
+ end
18
+
19
+ # Captures a preview from the camera.
20
+ #
21
+ # Previews are not stored on the camera but are returned as data in a
22
+ # {GPhoto2::CameraFile}.
23
+ #
24
+ # @example
25
+ # # Capturing a preview is like using `Camera#capture`.
26
+ # file = camera.preview
27
+ #
28
+ # # The resulting file will have neither a folder nor name.
29
+ # file.preview?
30
+ # # => true
31
+ #
32
+ # # But it will contain image data from the camera.
33
+ # file.data
34
+ # # => "\xFF\xD8\xFF\xDB\x00\x84\x00\x06..."
35
+ #
36
+ # @return [GPhoto2::CameraFile]
37
+ def preview
38
+ save
39
+ capture_preview
40
+ end
41
+
42
+ private
43
+
44
+ def _capture(type)
45
+ path = CameraFilePath.new
46
+ rc = gp_camera_capture(ptr, type, path.ptr, context.ptr)
47
+ GPhoto2.check!(rc)
48
+ path
49
+ end
50
+
51
+ def capture_preview
52
+ file = CameraFile.new(self)
53
+ rc = gp_camera_capture_preview(ptr, file.ptr, context.ptr)
54
+ GPhoto2.check!(rc)
55
+ file
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,136 @@
1
+ module GPhoto2
2
+ class Camera
3
+ module Configuration
4
+ # @param [String] model
5
+ # @param [String] port
6
+ def initialize(model, port)
7
+ reset
8
+ end
9
+
10
+ # @return [WindowCameraWidget]
11
+ def window
12
+ @window ||= get_config
13
+ end
14
+
15
+ # @example
16
+ # # List camera configuration keys.
17
+ # camera.config.keys
18
+ # # => ['autofocusdrive', 'manualfocusdrive', 'controlmode', ...]
19
+ #
20
+ # @return [Hash<String,GPhoto2::CameraWidget>] a flat map of camera
21
+ # configuration widgets
22
+ # @see #[]
23
+ # @see #[]=
24
+ def config
25
+ @config ||= window.flatten
26
+ end
27
+
28
+ # Reloads the camera configuration.
29
+ #
30
+ # All unsaved changes will be lost.
31
+ #
32
+ # @return [void]
33
+ def reload
34
+ @window.finalize if @window
35
+ reset
36
+ config
37
+ end
38
+
39
+ # @example
40
+ # camera['whitebalance'].to_s
41
+ # # => "Automatic"
42
+ #
43
+ # @param [#to_s] key
44
+ # @return [GPhoto2::CameraWidget] the widget identified by `key`
45
+ def [](key)
46
+ config[key.to_s]
47
+ end
48
+
49
+ # Updates the attribute identified by `key` with the specified `value`.
50
+ #
51
+ # This marks the configuration as "dirty", meaning a call to {#save} is
52
+ # needed to actually update the configuration on the camera.
53
+ #
54
+ # @example
55
+ # camera['iso'] = 800
56
+ # camera['f-number'] = 'f/2.8'
57
+ # camera['shutterspeed2'] = '1/60'
58
+ #
59
+ # @param [#to_s] key
60
+ # @param [Object] value
61
+ # @return [Object]
62
+ def []=(key, value)
63
+ self[key].value = value
64
+ @dirty = true
65
+ value
66
+ end
67
+
68
+ # Updates the configuration on the camera.
69
+ #
70
+ # @return [Boolean] whether setting the configuration was attempted
71
+ def save
72
+ return false unless dirty?
73
+ set_config
74
+ @dirty = false
75
+ true
76
+ end
77
+
78
+ # Updates the attributes of the camera from the given Hash and saves the
79
+ # configuration.
80
+ #
81
+ # @example
82
+ # camera['iso'] # => 800
83
+ # camera['shutterspeed2'] # => "1/30"
84
+ #
85
+ # camera.update(iso: 400, shutterspeed2: '1/60')
86
+ #
87
+ # camera['iso'] # => 400
88
+ # camera['shutterspeed2'] # => "1/60"
89
+ #
90
+ # @param [Hash<String,Object>] attributes
91
+ # @return [Boolean] whether the configuration saved
92
+ def update(attributes = {})
93
+ attributes.each do |key, value|
94
+ self[key] = value
95
+ end
96
+
97
+ save
98
+ end
99
+
100
+ # @example
101
+ # camera.dirty?
102
+ # # => false
103
+ #
104
+ # camera['iso'] = 400
105
+ #
106
+ # camera.dirty?
107
+ # # => true
108
+ #
109
+ # @return [Boolean] whether attributes have been changed
110
+ def dirty?
111
+ @dirty
112
+ end
113
+
114
+ private
115
+
116
+ def reset
117
+ @window = nil
118
+ @config = nil
119
+ @dirty = false
120
+ end
121
+
122
+ def get_config
123
+ widget_ptr = FFI::MemoryPointer.new(FFI::GPhoto2::CameraWidget)
124
+ rc = gp_camera_get_config(ptr, widget_ptr, context.ptr)
125
+ GPhoto2.check!(rc)
126
+ widget = FFI::GPhoto2::CameraWidget.new(widget_ptr.read_pointer)
127
+ CameraWidget.factory(widget)
128
+ end
129
+
130
+ def set_config
131
+ rc = gp_camera_set_config(ptr, window.ptr, context.ptr)
132
+ GPhoto2.check!(rc)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,56 @@
1
+ module GPhoto2
2
+ class Camera
3
+ module Event
4
+ # @param [Integer] timeout time to wait in milliseconds
5
+ # @return [CameraEvent]
6
+ def wait(timeout = 2000)
7
+ wait_for_event(timeout)
8
+ end
9
+
10
+ # @param [CameraEventType] event_type
11
+ # @return [CameraEvent]
12
+ def wait_for(event_type)
13
+ begin
14
+ event = wait
15
+ end until event.type == event_type
16
+
17
+ event
18
+ end
19
+
20
+ private
21
+
22
+ def wait_for_event(timeout)
23
+ # assume CameraEventType is an int
24
+ type = FFI::MemoryPointer.new(:int)
25
+
26
+ data = FFI::MemoryPointer.new(:pointer)
27
+ data_ptr = FFI::MemoryPointer.new(:pointer)
28
+ data_ptr.write_pointer(data)
29
+
30
+ rc = gp_camera_wait_for_event(ptr, timeout, type, data_ptr, context.ptr)
31
+ GPhoto2.check!(rc)
32
+
33
+ type = CameraEventType[type.read_int]
34
+ data = data_ptr.read_pointer
35
+
36
+ data =
37
+ case type
38
+ when :unknown
39
+ data.null? ? nil : data.read_string
40
+ when :file_added
41
+ path_ptr = FFI::GPhoto2::CameraFilePath.new(data)
42
+ path = CameraFilePath.new(path_ptr)
43
+ CameraFile.new(self, path.folder, path.name)
44
+ when :folder_added
45
+ path_ptr = FFI::GPhoto2::CameraFilePath.new(data)
46
+ path = CameraFilePath.new(path_ptr)
47
+ CameraFolder.new(self, '%s/%s' % [path.folder, path.name])
48
+ else
49
+ nil
50
+ end
51
+
52
+ CameraEvent.new(type, data)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,44 @@
1
+ module GPhoto2
2
+ class Camera
3
+ module Filesystem
4
+ # @example
5
+ # # Get a list of filenames in a path.
6
+ # folder = camera/'store_00010001/DCIM/100D5100'
7
+ # folder.files.map(&:name)
8
+ # # => ["DSC_0001.JPG", "DSC_0002.JPG", ...]
9
+ #
10
+ # @param [String] root
11
+ # @return [CameraFolder]
12
+ def filesystem(root = '/')
13
+ root = "/#{root}" if root[0] != '/'
14
+ CameraFolder.new(self, root)
15
+ end
16
+ alias_method :/, :filesystem
17
+
18
+ # @param [CameraFile] file
19
+ # @return [CameraFile]
20
+ def file(file)
21
+ file_get(file)
22
+ end
23
+
24
+ # @param [CameraFile] file
25
+ # @return [void]
26
+ def delete(file)
27
+ file_delete(file)
28
+ end
29
+
30
+ private
31
+
32
+ def file_get(file, type = :normal)
33
+ rc = gp_camera_file_get(ptr, file.folder, file.name, type, file.ptr, context.ptr)
34
+ GPhoto2.check!(rc)
35
+ file
36
+ end
37
+
38
+ def file_delete(file)
39
+ rc = gp_camera_file_delete(ptr, file.folder, file.name, context.ptr)
40
+ GPhoto2.check!(rc)
41
+ end
42
+ end
43
+ end
44
+ end