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.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/Rakefile +2 -0
- data/examples/intervalometer.rb +9 -7
- data/examples/list_config.rb +3 -5
- data/examples/live_view.rb +6 -12
- data/examples/record_movie.rb +10 -13
- data/ffi-gphoto2.gemspec +2 -1
- data/lib/ffi/gphoto2_port.rb +2 -2
- data/lib/gphoto2/camera/capture.rb +59 -0
- data/lib/gphoto2/camera/configuration.rb +136 -0
- data/lib/gphoto2/camera/event.rb +56 -0
- data/lib/gphoto2/camera/filesystem.rb +44 -0
- data/lib/gphoto2/camera.rb +95 -166
- data/lib/gphoto2/camera_abilities.rb +6 -6
- data/lib/gphoto2/camera_abilities_list.rb +7 -6
- data/lib/gphoto2/camera_event.rb +1 -8
- data/lib/gphoto2/camera_file.rb +15 -5
- data/lib/gphoto2/camera_file_path.rb +3 -5
- data/lib/gphoto2/camera_folder.rb +11 -0
- data/lib/gphoto2/camera_list.rb +3 -10
- data/lib/gphoto2/camera_widgets/camera_widget.rb +27 -14
- data/lib/gphoto2/camera_widgets/radio_camera_widget.rb +1 -0
- data/lib/gphoto2/camera_widgets/range_camera_widget.rb +1 -0
- data/lib/gphoto2/context.rb +2 -6
- data/lib/gphoto2/entry.rb +2 -0
- data/lib/gphoto2/port_info.rb +8 -6
- data/lib/gphoto2/port_info_list.rb +5 -6
- data/lib/gphoto2/port_result.rb +2 -0
- data/lib/gphoto2/struct.rb +11 -0
- data/lib/gphoto2/version.rb +1 -1
- data/lib/gphoto2.rb +13 -0
- data/spec/gphoto2/camera_abilities_list_spec.rb +5 -5
- data/spec/gphoto2/camera_abilities_spec.rb +7 -7
- data/spec/gphoto2/camera_file_path_spec.rb +2 -2
- data/spec/gphoto2/camera_file_spec.rb +7 -7
- data/spec/gphoto2/camera_folder_spec.rb +3 -3
- data/spec/gphoto2/camera_list_spec.rb +3 -3
- data/spec/gphoto2/camera_spec.rb +37 -272
- data/spec/gphoto2/camera_widgets/date_camera_widget_spec.rb +1 -1
- data/spec/gphoto2/camera_widgets/radio_camera_widget_spec.rb +4 -4
- data/spec/gphoto2/camera_widgets/range_camera_widget_spec.rb +2 -2
- data/spec/gphoto2/camera_widgets/text_camera_widget_spec.rb +1 -1
- data/spec/gphoto2/camera_widgets/toggle_camera_widget_spec.rb +2 -2
- data/spec/gphoto2/context_spec.rb +1 -1
- data/spec/gphoto2/entry_spec.rb +2 -2
- data/spec/gphoto2/port_info_list_spec.rb +4 -4
- data/spec/gphoto2/port_info_spec.rb +9 -10
- data/spec/spec_helper.rb +0 -7
- data/spec/support/shared_examples/camera/capture_examples.rb +41 -0
- data/spec/support/shared_examples/camera/configuration_examples.rb +161 -0
- data/spec/support/shared_examples/camera/event_examples.rb +39 -0
- data/spec/support/shared_examples/camera/filesystem_examples.rb +46 -0
- data/spec/support/shared_examples/camera_widget_examples.rb +13 -13
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06a37e1529e44e76317ee65554d7c1ddacea1cec
|
4
|
+
data.tar.gz: fd9cb5732c86302822891de0530a3b6dd822c078
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63f6a9cc345afbc0c9e90fd4396927d054e24825c6579de635e0a12ab97fce77606960c0c9a79771b005b08e91537a262decef0e7015c3aa60010fa51b1bed3b
|
7
|
+
data.tar.gz: e1cd250dc551227e6425991e8aabe5e1dcb92452186caa6f33980081784f33c5733e96aadb99806abca94697c3090e2cd898dc6fa140d582655b0f2155ada2fc
|
data/.rspec
CHANGED
data/Rakefile
CHANGED
data/examples/intervalometer.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'gphoto2'
|
2
2
|
|
3
|
-
|
3
|
+
# Take a photo every 10 seconds for 2 hours.
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
interval = 10 # seconds
|
6
|
+
two_hours = 60 * 60 * 2 # (2 hours)
|
7
|
+
stop_time = Time.now + two_hours
|
7
8
|
|
8
|
-
|
9
|
-
until Time.now >= stop_time
|
10
|
-
|
11
|
-
|
9
|
+
GPhoto2::Camera.first do |camera|
|
10
|
+
until Time.now >= stop_time
|
11
|
+
camera.capture.save
|
12
|
+
sleep interval
|
13
|
+
end
|
12
14
|
end
|
data/examples/list_config.rb
CHANGED
@@ -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
|
-
|
32
|
-
|
33
|
-
begin
|
33
|
+
GPhoto2::Camera.first do |camera|
|
34
34
|
visit(camera.window)
|
35
|
-
ensure
|
36
|
-
camera.finalize
|
37
35
|
end
|
data/examples/live_view.rb
CHANGED
@@ -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
|
5
|
+
# ruby live_view.rb | mpv --demuxer-lavf-format=mjpeg -
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
# automatically flush the io buffer
|
7
|
+
# Automatically flush the IO buffer.
|
8
8
|
STDOUT.sync = true
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
begin
|
10
|
+
GPhoto2::Camera.first do |camera|
|
13
11
|
loop do
|
14
|
-
#
|
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
|
data/examples/record_movie.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
13
|
+
# Record for ~10 seconds.
|
16
14
|
sleep 10
|
17
15
|
|
18
|
-
#
|
16
|
+
# Stop recording.
|
19
17
|
camera.update(movie: false)
|
20
18
|
|
21
|
-
#
|
19
|
+
# Block until the camera finishes with the file.
|
22
20
|
event = camera.wait_for(:file_added)
|
23
21
|
|
24
|
-
#
|
25
|
-
event.data
|
26
|
-
|
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', '~>
|
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
|
data/lib/ffi/gphoto2_port.rb
CHANGED
@@ -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
|