ffi-gphoto2 0.5.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +11 -0
- data/CHANGELOG.md +44 -0
- data/README.md +22 -12
- data/examples/autofocus.rb +21 -0
- data/examples/capture.rb +8 -0
- data/examples/continuous_burst.rb +27 -0
- data/examples/list_config.rb +1 -0
- data/examples/list_files.rb +52 -0
- data/ffi-gphoto2.gemspec +9 -8
- data/lib/ffi/gphoto2.rb +14 -4
- data/lib/ffi/gphoto2/camera_abilities_list.rb +1 -1
- data/lib/ffi/gphoto2/camera_file_info.rb +10 -0
- data/lib/ffi/gphoto2/camera_file_info_audio.rb +11 -0
- data/lib/ffi/gphoto2/camera_file_info_fields.rb +14 -0
- data/lib/ffi/gphoto2/camera_file_info_file.rb +15 -0
- data/lib/ffi/gphoto2/camera_file_info_preview.rb +13 -0
- data/lib/ffi/gphoto2/camera_file_permissions.rb +9 -0
- data/lib/ffi/gphoto2/camera_file_status.rb +7 -0
- data/lib/ffi/gphoto2/camera_list.rb +1 -1
- data/lib/ffi/gphoto2/camera_widget.rb +1 -1
- data/lib/ffi/gphoto2_port.rb +21 -1
- data/lib/ffi/gphoto2_port/gp_port.rb +17 -0
- data/lib/ffi/gphoto2_port/gp_port_info_list.rb +1 -1
- data/lib/ffi/gphoto2_port/gp_port_serial_parity.rb +6 -0
- data/lib/ffi/gphoto2_port/gp_port_settings.rb +11 -0
- data/lib/ffi/gphoto2_port/gp_port_settings_serial.rb +12 -0
- data/lib/ffi/gphoto2_port/gp_port_settings_usb.rb +15 -0
- data/lib/ffi/gphoto2_port/gp_port_settings_usb_disk_direct.rb +8 -0
- data/lib/ffi/gphoto2_port/gp_port_settings_usb_scsi.rb +8 -0
- data/lib/gphoto2.rb +24 -4
- data/lib/gphoto2/camera.rb +10 -2
- data/lib/gphoto2/camera/capture.rb +22 -0
- data/lib/gphoto2/camera/configuration.rb +2 -1
- data/lib/gphoto2/camera/event.rb +3 -6
- data/lib/gphoto2/camera_abilities.rb +9 -0
- data/lib/gphoto2/camera_abilities_list.rb +1 -1
- data/lib/gphoto2/camera_file.rb +26 -8
- data/lib/gphoto2/camera_file_info/camera_file_info.rb +52 -0
- data/lib/gphoto2/camera_file_info/file_camera_file_info.rb +30 -0
- data/lib/gphoto2/camera_list.rb +1 -1
- data/lib/gphoto2/camera_widgets/camera_widget.rb +3 -7
- data/lib/gphoto2/camera_widgets/radio_camera_widget.rb +0 -4
- data/lib/gphoto2/camera_widgets/text_camera_widget.rb +1 -3
- data/lib/gphoto2/entry.rb +0 -4
- data/lib/gphoto2/port.rb +61 -0
- data/lib/gphoto2/port_info.rb +0 -4
- data/lib/gphoto2/port_info_list.rb +1 -1
- data/lib/gphoto2/version.rb +1 -1
- data/spec/gphoto2/camera_abilities_spec.rb +23 -0
- data/spec/gphoto2/camera_file_info/file_camera_file_info_spec.rb +56 -0
- data/spec/gphoto2/camera_file_spec.rb +17 -0
- data/spec/gphoto2/camera_spec.rb +11 -5
- data/spec/gphoto2/port_spec.rb +22 -0
- data/spec/gphoto2_spec.rb +18 -0
- data/spec/support/shared_examples/camera/capture_examples.rb +13 -0
- data/spec/support/shared_examples/camera/configuration_examples.rb +26 -18
- data/spec/support/shared_examples/camera_file_info_examples.rb +42 -0
- metadata +54 -24
data/lib/ffi/gphoto2_port.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
1
3
|
module FFI
|
2
4
|
module GPhoto2Port
|
3
5
|
extend FFI::Library
|
@@ -8,9 +10,16 @@ module FFI
|
|
8
10
|
require 'ffi/gphoto2_port/gp_port_result'
|
9
11
|
|
10
12
|
# enums
|
13
|
+
require 'ffi/gphoto2_port/gp_port_serial_parity'
|
11
14
|
require 'ffi/gphoto2_port/gp_port_type'
|
12
15
|
|
13
16
|
# structs
|
17
|
+
require 'ffi/gphoto2_port/gp_port_settings_serial'
|
18
|
+
require 'ffi/gphoto2_port/gp_port_settings_usb'
|
19
|
+
require 'ffi/gphoto2_port/gp_port_settings_usb_disk_direct'
|
20
|
+
require 'ffi/gphoto2_port/gp_port_settings_usb_scsi'
|
21
|
+
require 'ffi/gphoto2_port/gp_port_settings'
|
22
|
+
require 'ffi/gphoto2_port/gp_port'
|
14
23
|
require 'ffi/gphoto2_port/gp_port_info'
|
15
24
|
require 'ffi/gphoto2_port/gp_port_info_list'
|
16
25
|
|
@@ -20,12 +29,23 @@ module FFI
|
|
20
29
|
attach_function :gp_port_info_get_type, [GPPortInfo, :pointer], :int
|
21
30
|
|
22
31
|
attach_function :gp_port_info_list_new, [:pointer], :int
|
23
|
-
attach_function :gp_port_info_list_free, [
|
32
|
+
attach_function :gp_port_info_list_free, [:pointer], :int
|
24
33
|
attach_function :gp_port_info_list_load, [GPPortInfoList.by_ref], :int
|
25
34
|
attach_function :gp_port_info_list_lookup_path, [GPPortInfoList.by_ref, :string], :int
|
26
35
|
attach_function :gp_port_info_list_get_info, [GPPortInfoList.by_ref, :int, :pointer], :int
|
27
36
|
|
28
37
|
# libgphoto2_port/gphoto2/gphoto2-port-result.h
|
29
38
|
attach_function :gp_port_result_as_string, [:int], :string
|
39
|
+
|
40
|
+
# libgphoto2_port/gphoto2/gphoto2-port.h
|
41
|
+
attach_function :gp_port_new, [:pointer], :int
|
42
|
+
attach_function :gp_port_free, [:pointer], :int
|
43
|
+
|
44
|
+
attach_function :gp_port_set_info, [GPPort.by_ref, GPPortInfo], :int
|
45
|
+
|
46
|
+
attach_function :gp_port_open, [GPPort.by_ref], :int
|
47
|
+
attach_function :gp_port_close, [GPPort.by_ref], :int
|
48
|
+
|
49
|
+
attach_function :gp_port_reset, [GPPort.by_ref], :int
|
30
50
|
end
|
31
51
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module FFI
|
2
|
+
module GPhoto2Port
|
3
|
+
class GPPort < FFI::ManagedStruct
|
4
|
+
# libgphoto2_port/libgphoto2_port/gphoto2-port.h
|
5
|
+
layout :type, GPPortType,
|
6
|
+
:settings, GPPortSettings,
|
7
|
+
:settings_pending, GPPortSettings,
|
8
|
+
:timeout, :int,
|
9
|
+
:pl, :pointer, # GPPortPrivateLibrary *
|
10
|
+
:pc, :pointer # GPPortPrivateCore *
|
11
|
+
|
12
|
+
def self.release(ptr)
|
13
|
+
FFI::GPhoto2.gp_port_free(ptr)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module FFI
|
2
|
+
module GPhoto2Port
|
3
|
+
class GPPortSettings < FFI::Struct
|
4
|
+
# libgphoto2_port/libgphoto2_port/gphoto2-port.h
|
5
|
+
layout :serial, GPPortSettingsSerial,
|
6
|
+
:usb, GPPortSettingsUSB,
|
7
|
+
:usbdiskdirect, GPPortSettingsUsbDiskDirect,
|
8
|
+
:usbscsi, GPPortSettingsUsbScsi
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module FFI
|
2
|
+
module GPhoto2Port
|
3
|
+
class GPPortSettingsUSB < FFI::Struct
|
4
|
+
# libgphoto2_port/libgphoto2_port/gphoto2-port.h
|
5
|
+
layout :inep, :int,
|
6
|
+
:outep, :int,
|
7
|
+
:intep, :int,
|
8
|
+
:config, :int,
|
9
|
+
:interface, :int,
|
10
|
+
:altsetting, :int,
|
11
|
+
:maxpacketsize, :int,
|
12
|
+
:port, [:char, 64]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/gphoto2.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'logger'
|
2
2
|
|
3
|
-
require 'ffi'
|
4
|
-
|
5
3
|
require 'ffi/gphoto2'
|
6
4
|
require 'ffi/gphoto2_port'
|
7
5
|
|
@@ -17,6 +15,9 @@ require 'gphoto2/camera_widgets/text_camera_widget'
|
|
17
15
|
require 'gphoto2/camera_widgets/toggle_camera_widget'
|
18
16
|
require 'gphoto2/camera_widgets/window_camera_widget'
|
19
17
|
|
18
|
+
require 'gphoto2/camera_file_info/camera_file_info'
|
19
|
+
require 'gphoto2/camera_file_info/file_camera_file_info'
|
20
|
+
|
20
21
|
require 'gphoto2/camera/capture'
|
21
22
|
require 'gphoto2/camera/configuration'
|
22
23
|
require 'gphoto2/camera/event'
|
@@ -32,6 +33,7 @@ require 'gphoto2/camera_folder'
|
|
32
33
|
require 'gphoto2/camera_list'
|
33
34
|
require 'gphoto2/context'
|
34
35
|
require 'gphoto2/entry'
|
36
|
+
require 'gphoto2/port'
|
35
37
|
require 'gphoto2/port_info'
|
36
38
|
require 'gphoto2/port_info_list'
|
37
39
|
require 'gphoto2/port_result'
|
@@ -39,6 +41,24 @@ require 'gphoto2/port_result'
|
|
39
41
|
require 'gphoto2/version'
|
40
42
|
|
41
43
|
module GPhoto2
|
44
|
+
# A runtime error for unsuccessful return codes.
|
45
|
+
class Error < RuntimeError
|
46
|
+
# @return [Integer]
|
47
|
+
attr_reader :code
|
48
|
+
|
49
|
+
# @param [String] message
|
50
|
+
# @param [Integer] code
|
51
|
+
def initialize(message, code)
|
52
|
+
super(message)
|
53
|
+
@code = code
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String]
|
57
|
+
def to_s
|
58
|
+
"#{super} (#{code})"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
42
62
|
# @return [Logger]
|
43
63
|
def self.logger
|
44
64
|
@logger ||= Logger.new(STDERR)
|
@@ -46,10 +66,10 @@ module GPhoto2
|
|
46
66
|
|
47
67
|
# @param [Integer] rc
|
48
68
|
# @return [void]
|
49
|
-
# @raise
|
69
|
+
# @raise [GPhoto2::Error] when the return code is not {FFI::GPhoto2Port::GP_OK}
|
50
70
|
def self.check!(rc)
|
51
71
|
logger.debug "#{caller.first} => #{rc}" if ENV['DEBUG']
|
52
72
|
return if rc >= FFI::GPhoto2Port::GP_OK
|
53
|
-
raise
|
73
|
+
raise Error.new(PortResult.as_string(rc), rc)
|
54
74
|
end
|
55
75
|
end
|
data/lib/gphoto2/camera.rb
CHANGED
@@ -83,10 +83,18 @@ module GPhoto2
|
|
83
83
|
autorelease(camera, &blk)
|
84
84
|
end
|
85
85
|
|
86
|
+
# Filters devices by a given condition.
|
87
|
+
#
|
88
|
+
# Filter keys can be either `model` or `port`. Only the first filter is
|
89
|
+
# used.
|
90
|
+
#
|
86
91
|
# @example
|
87
92
|
# # Find the cameras whose model names contain Nikon.
|
88
93
|
# cameras = GPhoto2::Camera.where(model: /nikon/i)
|
89
94
|
#
|
95
|
+
# # Select a camera by its port.
|
96
|
+
# camera = GPhoto2::Camera.where(port: 'usb:250,004').first
|
97
|
+
#
|
90
98
|
# @param [Hash] condition
|
91
99
|
# @return [Array<GPhoto2::Camera>]
|
92
100
|
def self.where(condition)
|
@@ -143,7 +151,7 @@ module GPhoto2
|
|
143
151
|
# @param [CameraOperation] operation
|
144
152
|
# @return [Boolean]
|
145
153
|
def can?(operation)
|
146
|
-
(abilities
|
154
|
+
(abilities.operations & (CameraOperation[operation] || 0)) != 0
|
147
155
|
end
|
148
156
|
|
149
157
|
private
|
@@ -171,7 +179,7 @@ module GPhoto2
|
|
171
179
|
end
|
172
180
|
|
173
181
|
def new
|
174
|
-
ptr = FFI::MemoryPointer.new(
|
182
|
+
ptr = FFI::MemoryPointer.new(:pointer)
|
175
183
|
rc = gp_camera_new(ptr)
|
176
184
|
GPhoto2.check!(rc)
|
177
185
|
@ptr = FFI::GPhoto2::Camera.new(ptr.read_pointer)
|
@@ -16,6 +16,23 @@ module GPhoto2
|
|
16
16
|
CameraFile.new(self, path.folder, path.name)
|
17
17
|
end
|
18
18
|
|
19
|
+
# Triggers a capture and immedately returns.
|
20
|
+
#
|
21
|
+
# A camera trigger is the first half of a {#capture}. Instead of
|
22
|
+
# returning a {GPhoto2::CameraFile}, a trigger immediately returns and
|
23
|
+
# the caller has to poll for events.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# camera.trigger
|
27
|
+
# event = camera.wait_for(:file_added)
|
28
|
+
# event.data # => CameraFile
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
def trigger
|
32
|
+
save
|
33
|
+
trigger_capture
|
34
|
+
end
|
35
|
+
|
19
36
|
# Captures a preview from the camera.
|
20
37
|
#
|
21
38
|
# Previews are not stored on the camera but are returned as data in a
|
@@ -54,6 +71,11 @@ module GPhoto2
|
|
54
71
|
GPhoto2.check!(rc)
|
55
72
|
file
|
56
73
|
end
|
74
|
+
|
75
|
+
def trigger_capture
|
76
|
+
rc = gp_camera_trigger_capture(ptr, context.ptr)
|
77
|
+
GPhoto2.check!(rc)
|
78
|
+
end
|
57
79
|
end
|
58
80
|
end
|
59
81
|
end
|
@@ -70,6 +70,7 @@ module GPhoto2
|
|
70
70
|
# @param [Object] value
|
71
71
|
# @return [Object]
|
72
72
|
def []=(key, value)
|
73
|
+
raise ArgumentError, "invalid key: #{key}" unless self[key]
|
73
74
|
self[key].value = value
|
74
75
|
@dirty = true
|
75
76
|
value
|
@@ -137,7 +138,7 @@ module GPhoto2
|
|
137
138
|
end
|
138
139
|
|
139
140
|
def get_config
|
140
|
-
widget_ptr = FFI::MemoryPointer.new(
|
141
|
+
widget_ptr = FFI::MemoryPointer.new(:pointer)
|
141
142
|
rc = gp_camera_get_config(ptr, widget_ptr, context.ptr)
|
142
143
|
GPhoto2.check!(rc)
|
143
144
|
widget = FFI::GPhoto2::CameraWidget.new(widget_ptr.read_pointer)
|
data/lib/gphoto2/camera/event.rb
CHANGED
@@ -21,16 +21,13 @@ module GPhoto2
|
|
21
21
|
|
22
22
|
def wait_for_event(timeout)
|
23
23
|
# assume CameraEventType is an int
|
24
|
-
|
25
|
-
|
26
|
-
data = FFI::MemoryPointer.new(:pointer)
|
24
|
+
type_ptr = FFI::MemoryPointer.new(:int)
|
27
25
|
data_ptr = FFI::MemoryPointer.new(:pointer)
|
28
|
-
data_ptr.write_pointer(data)
|
29
26
|
|
30
|
-
rc = gp_camera_wait_for_event(ptr, timeout,
|
27
|
+
rc = gp_camera_wait_for_event(ptr, timeout, type_ptr, data_ptr, context.ptr)
|
31
28
|
GPhoto2.check!(rc)
|
32
29
|
|
33
|
-
type = FFI::GPhoto2::CameraEventType[
|
30
|
+
type = FFI::GPhoto2::CameraEventType[type_ptr.read_int]
|
34
31
|
data = data_ptr.read_pointer
|
35
32
|
|
36
33
|
data =
|
@@ -30,6 +30,15 @@ module GPhoto2
|
|
30
30
|
ptr[field]
|
31
31
|
end
|
32
32
|
|
33
|
+
# @return [Integer] a bit field of supported operations
|
34
|
+
def operations
|
35
|
+
if self[:operations] == :none
|
36
|
+
CameraOperation[:none]
|
37
|
+
else
|
38
|
+
self[:operations]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
33
42
|
private
|
34
43
|
|
35
44
|
def get_abilities
|
@@ -32,7 +32,7 @@ module GPhoto2
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def new
|
35
|
-
ptr = FFI::MemoryPointer.new(
|
35
|
+
ptr = FFI::MemoryPointer.new(:pointer)
|
36
36
|
rc = gp_abilities_list_new(ptr)
|
37
37
|
GPhoto2.check!(rc)
|
38
38
|
@ptr = FFI::GPhoto2::CameraAbilitiesList.new(ptr.read_pointer)
|
data/lib/gphoto2/camera_file.rb
CHANGED
@@ -3,6 +3,9 @@ module GPhoto2
|
|
3
3
|
include FFI::GPhoto2
|
4
4
|
include GPhoto2::Struct
|
5
5
|
|
6
|
+
# The preview data is assumed to be a jpg.
|
7
|
+
PREVIEW_FILENAME = 'capture_preview.jpg'.freeze
|
8
|
+
|
6
9
|
# @return [String]
|
7
10
|
attr_reader :folder
|
8
11
|
|
@@ -44,6 +47,11 @@ module GPhoto2
|
|
44
47
|
data_and_size.last
|
45
48
|
end
|
46
49
|
|
50
|
+
# @return [GPhoto2::CameraFileInfo, nil]
|
51
|
+
def info
|
52
|
+
preview? ? nil : get_info
|
53
|
+
end
|
54
|
+
|
47
55
|
private
|
48
56
|
|
49
57
|
def data_and_size
|
@@ -54,30 +62,40 @@ module GPhoto2
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def default_filename
|
57
|
-
|
58
|
-
preview? ? 'capture_preview.jpg' : @name
|
65
|
+
preview? ? PREVIEW_FILENAME : @name
|
59
66
|
end
|
60
67
|
|
61
68
|
def new
|
62
|
-
ptr = FFI::MemoryPointer.new(
|
69
|
+
ptr = FFI::MemoryPointer.new(:pointer)
|
63
70
|
rc = gp_file_new(ptr)
|
64
71
|
GPhoto2.check!(rc)
|
65
72
|
@ptr = FFI::GPhoto2::CameraFile.new(ptr.read_pointer)
|
66
73
|
end
|
67
74
|
|
68
75
|
def get_data_and_size
|
69
|
-
data = FFI::MemoryPointer.new(:uchar)
|
70
76
|
data_ptr = FFI::MemoryPointer.new(:pointer)
|
71
|
-
|
72
|
-
size = FFI::MemoryPointer.new(:ulong)
|
77
|
+
size_ptr = FFI::MemoryPointer.new(:ulong)
|
73
78
|
|
74
|
-
rc = gp_file_get_data_and_size(ptr, data_ptr,
|
79
|
+
rc = gp_file_get_data_and_size(ptr, data_ptr, size_ptr)
|
75
80
|
GPhoto2.check!(rc)
|
76
81
|
|
77
|
-
size =
|
82
|
+
size = size_ptr.read_ulong
|
78
83
|
data = data_ptr.read_pointer.read_bytes(size)
|
79
84
|
|
80
85
|
[data, size]
|
81
86
|
end
|
87
|
+
|
88
|
+
def get_info
|
89
|
+
info = FFI::GPhoto2::CameraFileInfo.new
|
90
|
+
|
91
|
+
rc = gp_camera_file_get_info(@camera.ptr,
|
92
|
+
@folder,
|
93
|
+
@name,
|
94
|
+
info,
|
95
|
+
@camera.context.ptr)
|
96
|
+
GPhoto2.check!(rc)
|
97
|
+
|
98
|
+
FileCameraFileInfo.new(info[:file])
|
99
|
+
end
|
82
100
|
end
|
83
101
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module GPhoto2
|
2
|
+
# @abstract
|
3
|
+
class CameraFileInfo
|
4
|
+
include FFI::GPhoto2
|
5
|
+
include GPhoto2::Struct
|
6
|
+
|
7
|
+
# @param [FFI::GPhoto2::CameraFileInfo] ptr
|
8
|
+
def initialize(ptr)
|
9
|
+
@ptr = ptr
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Integer] a bit field of set info fields
|
13
|
+
def fields
|
14
|
+
fields = ptr[:fields]
|
15
|
+
|
16
|
+
if fields.is_a?(Symbol)
|
17
|
+
CameraFileInfoFields[fields]
|
18
|
+
else
|
19
|
+
fields
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# return [Boolean] whether the given field is set
|
24
|
+
def has_field?(field)
|
25
|
+
(fields & CameraFileInfoFields[field]) != 0
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [CameraFileStatus, nil]
|
29
|
+
def status
|
30
|
+
fetch(:status)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Integer, nil] the size of the file in bytes
|
34
|
+
def size
|
35
|
+
fetch(:size)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String, nil] the media type of the file
|
39
|
+
def type
|
40
|
+
type = fetch(:type)
|
41
|
+
type ? type.to_s : nil
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# param [Symbol] key
|
47
|
+
# @return [Object, nil]
|
48
|
+
def fetch(key)
|
49
|
+
ptr[key] if has_field?(key)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|