ffi-gphoto2 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +90 -0
  7. data/Rakefile +6 -0
  8. data/examples/intervalometer.rb +12 -0
  9. data/examples/live_view.rb +21 -0
  10. data/ffi-gphoto2.gemspec +30 -0
  11. data/lib/ffi/gphoto2.rb +85 -0
  12. data/lib/ffi/gphoto2/camera.rb +12 -0
  13. data/lib/ffi/gphoto2/camera_abilities.rb +37 -0
  14. data/lib/ffi/gphoto2/camera_abilities_list.rb +15 -0
  15. data/lib/ffi/gphoto2/camera_capture_type.rb +8 -0
  16. data/lib/ffi/gphoto2/camera_driver_status.rb +9 -0
  17. data/lib/ffi/gphoto2/camera_event_type.rb +10 -0
  18. data/lib/ffi/gphoto2/camera_file.rb +28 -0
  19. data/lib/ffi/gphoto2/camera_file_access_type.rb +8 -0
  20. data/lib/ffi/gphoto2/camera_file_operation.rb +11 -0
  21. data/lib/ffi/gphoto2/camera_file_path.rb +9 -0
  22. data/lib/ffi/gphoto2/camera_file_type.rb +11 -0
  23. data/lib/ffi/gphoto2/camera_folder_operation.rb +10 -0
  24. data/lib/ffi/gphoto2/camera_list.rb +15 -0
  25. data/lib/ffi/gphoto2/camera_operation.rb +12 -0
  26. data/lib/ffi/gphoto2/camera_widget.rb +33 -0
  27. data/lib/ffi/gphoto2/camera_widget_type.rb +14 -0
  28. data/lib/ffi/gphoto2/entry.rb +9 -0
  29. data/lib/ffi/gphoto2/gp_context.rb +31 -0
  30. data/lib/ffi/gphoto2/gphoto_device_type.rb +7 -0
  31. data/lib/ffi/gphoto2_port.rb +31 -0
  32. data/lib/ffi/gphoto2_port/gp_port_info.rb +11 -0
  33. data/lib/ffi/gphoto2_port/gp_port_info_list.rb +14 -0
  34. data/lib/ffi/gphoto2_port/gp_port_result.rb +35 -0
  35. data/lib/ffi/gphoto2_port/gp_port_type.rb +12 -0
  36. data/lib/gphoto2.rb +41 -0
  37. data/lib/gphoto2/camera.rb +219 -0
  38. data/lib/gphoto2/camera_abilities.rb +44 -0
  39. data/lib/gphoto2/camera_abilities_list.rb +64 -0
  40. data/lib/gphoto2/camera_file.rb +69 -0
  41. data/lib/gphoto2/camera_file_path.rb +21 -0
  42. data/lib/gphoto2/camera_folder.rb +82 -0
  43. data/lib/gphoto2/camera_list.rb +43 -0
  44. data/lib/gphoto2/camera_widgets/camera_widget.rb +112 -0
  45. data/lib/gphoto2/camera_widgets/date_camera_widget.rb +19 -0
  46. data/lib/gphoto2/camera_widgets/menu_camera_widget.rb +4 -0
  47. data/lib/gphoto2/camera_widgets/radio_camera_widget.rb +41 -0
  48. data/lib/gphoto2/camera_widgets/range_camera_widget.rb +37 -0
  49. data/lib/gphoto2/camera_widgets/section_camera_widget.rb +4 -0
  50. data/lib/gphoto2/camera_widgets/text_camera_widget.rb +14 -0
  51. data/lib/gphoto2/camera_widgets/toggle_camera_widget.rb +18 -0
  52. data/lib/gphoto2/camera_widgets/window_camera_widget.rb +4 -0
  53. data/lib/gphoto2/context.rb +31 -0
  54. data/lib/gphoto2/entry.rb +38 -0
  55. data/lib/gphoto2/port_info.rb +70 -0
  56. data/lib/gphoto2/port_info_list.rb +46 -0
  57. data/lib/gphoto2/port_result.rb +7 -0
  58. data/lib/gphoto2/version.rb +3 -0
  59. data/spec/gphoto2/camera_abilities_list_spec.rb +45 -0
  60. data/spec/gphoto2/camera_abilities_spec.rb +38 -0
  61. data/spec/gphoto2/camera_file_path_spec.rb +27 -0
  62. data/spec/gphoto2/camera_file_spec.rb +71 -0
  63. data/spec/gphoto2/camera_folder_spec.rb +110 -0
  64. data/spec/gphoto2/camera_list_spec.rb +35 -0
  65. data/spec/gphoto2/camera_spec.rb +292 -0
  66. data/spec/gphoto2/camera_widgets/date_camera_widget_spec.rb +15 -0
  67. data/spec/gphoto2/camera_widgets/radio_camera_widget_spec.rb +31 -0
  68. data/spec/gphoto2/camera_widgets/range_camera_widget_spec.rb +27 -0
  69. data/spec/gphoto2/camera_widgets/text_camera_widget_spec.rb +15 -0
  70. data/spec/gphoto2/camera_widgets/toggle_camera_widget_spec.rb +21 -0
  71. data/spec/gphoto2/context_spec.rb +17 -0
  72. data/spec/gphoto2/entry_spec.rb +27 -0
  73. data/spec/gphoto2/port_info_list_spec.rb +33 -0
  74. data/spec/gphoto2/port_info_spec.rb +53 -0
  75. data/spec/spec_helper.rb +12 -0
  76. data/spec/support/shared_examples/camera_widget_examples.rb +89 -0
  77. metadata +194 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4fb2cb331909ca8e1522318738dfbfe6675c6aba
4
+ data.tar.gz: d895c48398334dc8c60565b7302673244402f06d
5
+ SHA512:
6
+ metadata.gz: 6dbbfd432cd4ad2516ba6ebfbeda293debd0a02587b41b5bf2d21f6c2d35c7bee9b70ed06265958158cec9a66b9b7d12c237bb636b668b289947c08c152971a8
7
+ data.tar.gz: c9c4ae048625002e761f87390d7a57d664c8e471344e88395d5581910efba2ca16edc0910c3cbbbc54dbd0432ea6a766b713ee0f6add001b999fb130cc11fc10
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ffi-gphoto2.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Michael Macias
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # ffi-gphoto2
2
+
3
+ **ffi-gphoto2** provides an FFI for common functions in [libgphoto2][1].
4
+ It also includes a facade to interact with the library in a more
5
+ canonical Ruby way.
6
+
7
+ ## Installation
8
+
9
+ ### Prerequisites
10
+
11
+ * Ruby >= 1.9
12
+ * libgphoto2 >= 2.5.0 (`brew install libgphoto2`)
13
+
14
+ ### Gem
15
+
16
+ $ gem install ffi-gphoto2
17
+
18
+ ## Usage
19
+
20
+ require 'gphoto2'
21
+
22
+ # list available cameras
23
+ cameras = GPhoto2::Camera.all
24
+ # => [#<GPhoto2::Camera>, ...]
25
+
26
+ # list found cameras by model and port path
27
+ cameras.map { |c| [c.model, c.port] }
28
+ # => [['Nikon DSC D5100 (PTP mode)', 'usb:250,006'], ...]
29
+
30
+ # use the first camera
31
+ camera = cameras.first
32
+
33
+ # ...or more conveniently
34
+ camera = GPhoto2::Camera.first
35
+
36
+ # ...or even search by model name
37
+ camera = GPhoto2::Camera.where(/nikon/i).first
38
+
39
+ # list camera configuration names
40
+ camera.config.keys
41
+ # => ['autofocusdrive', 'manualfocusdrive', 'controlmode', ...]
42
+
43
+ # read the current configuration value of an option
44
+ camera['whitebalance']
45
+ # => "Automatic"
46
+
47
+ # list valid choices of a configuration option
48
+ camera['whitebalance'].choices
49
+ # => ['Automatic', 'Daylight', 'Fluorescent', 'Tungsten', ...]
50
+
51
+ # check if the configuration has changed
52
+ camera.dirty?
53
+ # => false
54
+
55
+ # change camera configuration
56
+ camera['iso'] = 800
57
+ camera['f-number'] = 'f/4.5'
58
+ camera['shutterspeed2'] = '1/30'
59
+
60
+ # check if the configuration has changed
61
+ camera.dirty?
62
+ # => true
63
+
64
+ # apply the new configuration on the device
65
+ camera.save
66
+
67
+ # take a photo
68
+ file = camera.capture
69
+
70
+ # ...and save it to the current working directory
71
+ file.save
72
+
73
+ # traverse the camera filesystem
74
+ folder = camera/'store_00010001/DCIM/100D5100'
75
+
76
+ # list files
77
+ files = folder.files
78
+ folder.files.map(&:name)
79
+ => ["DSC_0001.JPG", "DSC_0002.JPG", ...]
80
+
81
+ # save a file
82
+ files.first.save
83
+
84
+ # close the camera
85
+ camera.finalize
86
+
87
+ More examples can be found in [`examples/`][2].
88
+
89
+ [1]: http://www.gphoto.org/
90
+ [2]: https://github.com/zaeleus/ffi-gphoto2/tree/master/examples
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task default: :spec
@@ -0,0 +1,12 @@
1
+ require 'gphoto2'
2
+
3
+ camera = GPhoto2::Camera.first
4
+
5
+ six_hours = 60 * 60 * 6
6
+ stop_time = Time.now + six_hours
7
+
8
+ # take a photo every 30 seconds for 6 hours
9
+ until Time.now >= stop_time
10
+ camera.capture.save
11
+ sleep 30
12
+ end
@@ -0,0 +1,21 @@
1
+ # Run and pipe to a video player that can demux raw mjpeg. For example,
2
+ #
3
+ # ruby live_view.rb | mpv --demuxer=+lavf --demuxer-lavf-format=mjpeg --demuxer-lavf-analyzeduration=0.1 -
4
+
5
+ require 'gphoto2'
6
+
7
+ # automatically flush the io buffer
8
+ STDOUT.sync = true
9
+
10
+ camera = GPhoto2::Camera.first
11
+
12
+ begin
13
+ loop do
14
+ # write the preview image to stdout
15
+ print camera.preview.data
16
+ end
17
+ ensure
18
+ # make sure to close (or #exit) the camera
19
+ # otherwise, the mirror will stay up
20
+ camera.finalize
21
+ end
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'gphoto2/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ffi-gphoto2'
8
+ spec.summary = 'A Ruby FFI for common functions in libgphoto2'
9
+ spec.homepage = 'https://github.com/zaeleus/ffi-gphoto2'
10
+
11
+ spec.authors = ['Michael Macias']
12
+ spec.email = ['mamacias@go.olemiss.edu']
13
+
14
+ spec.version = GPhoto2::VERSION
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.requirements << 'libgphoto2 >= 2.5.0'
23
+ spec.required_ruby_version = '>= 1.9'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'rspec', '~> 2.14.1'
28
+
29
+ spec.add_dependency 'ffi', '~> 1.9.0'
30
+ end
@@ -0,0 +1,85 @@
1
+ require 'ffi/gphoto2_port'
2
+
3
+ module FFI
4
+ module GPhoto2
5
+ extend FFI::Library
6
+
7
+ ffi_lib 'libgphoto2'
8
+
9
+ # enums
10
+ require 'ffi/gphoto2/camera_capture_type'
11
+ require 'ffi/gphoto2/camera_driver_status'
12
+ require 'ffi/gphoto2/camera_event_type'
13
+ require 'ffi/gphoto2/camera_file_access_type'
14
+ require 'ffi/gphoto2/camera_file_operation'
15
+ require 'ffi/gphoto2/camera_file_type'
16
+ require 'ffi/gphoto2/camera_folder_operation'
17
+ require 'ffi/gphoto2/camera_operation'
18
+ require 'ffi/gphoto2/camera_widget_type'
19
+ require 'ffi/gphoto2/gphoto_device_type'
20
+
21
+ # structs
22
+ require 'ffi/gphoto2/camera'
23
+ require 'ffi/gphoto2/camera_abilities'
24
+ require 'ffi/gphoto2/camera_abilities_list'
25
+ require 'ffi/gphoto2/camera_file'
26
+ require 'ffi/gphoto2/camera_file_path'
27
+ require 'ffi/gphoto2/camera_list'
28
+ require 'ffi/gphoto2/camera_widget'
29
+ require 'ffi/gphoto2/entry'
30
+ require 'ffi/gphoto2/gp_context'
31
+
32
+ # gphoto2/gphoto2-abilities-list.h
33
+ attach_function :gp_abilities_list_new, [:pointer], :int
34
+ attach_function :gp_abilities_list_free, [CameraAbilitiesList.by_ref], :int
35
+ attach_function :gp_abilities_list_load, [CameraAbilitiesList.by_ref, GPContext.by_ref], :int
36
+ attach_function :gp_abilities_list_detect, [CameraAbilitiesList.by_ref, GPhoto2Port::GPPortInfoList.by_ref, CameraList.by_ref, GPContext.by_ref], :int
37
+ attach_function :gp_abilities_list_lookup_model, [CameraAbilitiesList.by_ref, :string], :int
38
+ attach_function :gp_abilities_list_get_abilities, [CameraAbilitiesList.by_ref, :int, CameraAbilities.by_ref], :int
39
+
40
+ # gphoto2/gphoto2-camera.h
41
+ attach_function :gp_camera_new, [:pointer], :int
42
+ attach_function :gp_camera_set_abilities, [Camera.by_ref, CameraAbilities.by_value], :int
43
+ attach_function :gp_camera_set_port_info, [Camera.by_ref, GPhoto2Port::GPPortInfo], :int
44
+ attach_function :gp_camera_exit, [Camera.by_ref, GPContext.by_ref], :int
45
+ attach_function :gp_camera_ref, [Camera.by_ref], :int
46
+ attach_function :gp_camera_unref, [Camera.by_ref], :int
47
+ attach_function :gp_camera_get_config, [Camera.by_ref, :pointer, GPContext.by_ref], :int
48
+ attach_function :gp_camera_set_config, [Camera.by_ref, CameraWidget.by_ref, GPContext.by_ref], :int
49
+ attach_function :gp_camera_capture, [Camera.by_ref, CameraCaptureType, CameraFilePath.by_ref, GPContext.by_ref], :int
50
+ attach_function :gp_camera_capture_preview, [Camera.by_ref, CameraFile.by_ref, GPContext.by_ref], :int
51
+ attach_function :gp_camera_wait_for_event, [Camera.by_ref, :int, :pointer, :pointer, GPContext.by_ref], :int
52
+ attach_function :gp_camera_folder_list_files, [Camera.by_ref, :string, CameraList.by_ref, GPContext.by_ref], :int
53
+ attach_function :gp_camera_folder_list_folders, [Camera.by_ref, :string, CameraList.by_ref, GPContext.by_ref], :int
54
+ attach_function :gp_camera_file_get, [Camera.by_ref, :string, :string, CameraFileType, CameraFile.by_ref, GPContext.by_ref], :int
55
+
56
+ # gphoto2/gphoto2-context.h
57
+ attach_function :gp_context_new, [], :pointer
58
+ attach_function :gp_context_ref, [GPContext.by_ref], :void
59
+ attach_function :gp_context_unref, [GPContext.by_ref], :void
60
+
61
+ # gphoto2/gphoto2-file.h
62
+ attach_function :gp_file_new, [:pointer], :int
63
+ attach_function :gp_file_free, [CameraFile.by_ref], :int
64
+ attach_function :gp_file_get_data_and_size, [CameraFile.by_ref, :pointer, :pointer], :int
65
+
66
+ # gphoto2/gphoto2-list.h
67
+ attach_function :gp_list_new, [:pointer], :int
68
+ attach_function :gp_list_free, [CameraList.by_ref], :int
69
+ attach_function :gp_list_count, [CameraList.by_ref], :int
70
+ attach_function :gp_list_get_name, [CameraList.by_ref, :int, :pointer], :int
71
+ attach_function :gp_list_get_value, [CameraList.by_ref, :int, :pointer], :int
72
+
73
+ # gphoto2/gphoto2-widget.h
74
+ attach_function :gp_widget_free, [CameraWidget.by_ref], :int
75
+ attach_function :gp_widget_count_children, [CameraWidget.by_ref], :int
76
+ attach_function :gp_widget_get_child, [CameraWidget.by_ref, :int, :pointer], :int
77
+ attach_function :gp_widget_set_value, [CameraWidget.by_ref, :pointer], :int
78
+ attach_function :gp_widget_get_value, [CameraWidget.by_ref, :pointer], :int
79
+ attach_function :gp_widget_get_name, [CameraWidget.by_ref, :pointer], :int
80
+ attach_function :gp_widget_get_type, [CameraWidget.by_ref, :pointer], :int
81
+ attach_function :gp_widget_get_range, [CameraWidget.by_ref, :pointer, :pointer, :pointer], :int
82
+ attach_function :gp_widget_count_choices, [CameraWidget.by_ref], :int
83
+ attach_function :gp_widget_get_choice, [CameraWidget.by_ref, :int, :pointer], :int
84
+ end
85
+ end
@@ -0,0 +1,12 @@
1
+ module FFI
2
+ module GPhoto2
3
+ class Camera < FFI::Struct
4
+ # gphoto2/gphoto2-camera.h
5
+ layout :port, :pointer, # GPPort
6
+ :fs, :pointer, # CameraFilesystem
7
+ :functions, :pointer, # CameraFunctions
8
+ :pl, :pointer, # CameraPrivateLibrary
9
+ :pc, :pointer # CameraPrivateCore
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ require 'ffi/gphoto2_port/gp_port_type'
2
+
3
+ module FFI
4
+ module GPhoto2
5
+ class CameraAbilities < FFI::Struct
6
+ # gphoto2/gphoto2-abilities-list.h
7
+ layout :model, [:char, 128],
8
+ :status, CameraDriverStatus,
9
+
10
+ :port, GPhoto2Port::GPPortType,
11
+ :speed, [:int, 64],
12
+
13
+ :operations, CameraOperation,
14
+ :file_operations, CameraFileOperation,
15
+ :folder_operations, CameraFolderOperation,
16
+
17
+ :usb_vendor, :int,
18
+ :usb_product, :int,
19
+ :usb_class, :int,
20
+ :usb_subclass, :int,
21
+ :usb_protocol, :int,
22
+
23
+ :library, [:char, 1024],
24
+ :id, [:char, 1024],
25
+
26
+ :device_type, GphotoDeviceType,
27
+
28
+ :reserved2, :int,
29
+ :reserved3, :int,
30
+ :reserved4, :int,
31
+ :reserved5, :int,
32
+ :reserved6, :int,
33
+ :reserved7, :int,
34
+ :reserved8, :int
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ require 'ffi/gphoto2/camera_abilities'
2
+
3
+ module FFI
4
+ module GPhoto2
5
+ class CameraAbilitiesList < FFI::ManagedStruct
6
+ # libgphoto2/gphoto2-abilities-list.c
7
+ layout :count, :int,
8
+ :abilities, :pointer # CameraAbilities*
9
+
10
+ def self.release(ptr)
11
+ FFI::GPhoto2.gp_abilities_list_free(ptr)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module FFI
2
+ module GPhoto2
3
+ # gphoto2/gphoto2-camera.h
4
+ CameraCaptureType = enum :image,
5
+ :movie,
6
+ :sound
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module FFI
2
+ module GPhoto2
3
+ # gphoto2/gphoto2-abilities-list.h
4
+ CameraDriverStatus = enum :production,
5
+ :testing,
6
+ :experimental,
7
+ :deprecated
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module FFI
2
+ module GPhoto2
3
+ # gphoto2/gphoto2-camera.h
4
+ CameraEventType = enum :unknown,
5
+ :timeout,
6
+ :file_added,
7
+ :folder_added,
8
+ :capture_complete
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ module FFI
2
+ module GPhoto2
3
+ class CameraFile < FFI::ManagedStruct
4
+ MAX_PATH = 256
5
+
6
+ # libgphoto2/gphoto2-file.c
7
+ layout :mime_type, [:char, 64],
8
+ :name, [:char, MAX_PATH],
9
+ :ref_count, :int,
10
+ :mtime, :time_t,
11
+
12
+ :accesstype, CameraFileAccessType,
13
+
14
+ :size, :ulong,
15
+ :data, :pointer, # unsigned char*
16
+ :offset, :ulong,
17
+
18
+ :fd, :int,
19
+
20
+ :handler, :pointer, # CameraFileHandler
21
+ :private, :pointer # void*
22
+
23
+ def self.release(ptr)
24
+ FFI::GPhoto2.gp_file_free(ptr)
25
+ end
26
+ end
27
+ end
28
+ end