sonycam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66e31989753b84b73e0d25c3e149082feebccabf
4
+ data.tar.gz: 91f347e53cc61ca868b757184370d2d354c2726c
5
+ SHA512:
6
+ metadata.gz: f98bce7464db042a3b758b90af3c5ad153f3c443b1a55416340b5b7fef3349be7af0b0b1f7744f46096fcea2281544c34c1ac6398f0b40e3c37b7adfd4a24a4a
7
+ data.tar.gz: e7298aeece3a259de2c3c259f8813dfe4cf1d4972fd286a347c29aa465ed777c7274986d60ef4dc64cae8a6c1d156af7831b3552b71211622445eaa38d0ae084
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'sonycam/cli'
3
+ Sonycam::CLI.start(ARGV)
@@ -0,0 +1,8 @@
1
+ require 'sonycam/scanner'
2
+ require 'sonycam/device_description'
3
+ require 'sonycam/api'
4
+ require 'sonycam/error'
5
+ require 'sonycam/liveview'
6
+
7
+ module Sonycam
8
+ end
@@ -0,0 +1,23 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Sonycam
5
+ class API
6
+ def initialize api_url
7
+ @uri = URI(api_url)
8
+ @http = Net::HTTP.start(@uri.host, @uri.port)
9
+ end
10
+
11
+ # id property of JSON-RPC seems to be totally useless over HTTP since it's request-response pattern.
12
+ def request method, *params, **options
13
+ json = {method: method, params: params, id: 1, version: '1.0'}.merge!(options).to_json
14
+ ret = JSON.parse(@http.request_post(@uri.path, json).body)
15
+ if ret['error']
16
+ error_code, error_message = ret['error']
17
+ raise Sonycam::Error.new_from_code(error_code), error_message
18
+ else
19
+ ret
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ require 'thor'
2
+ require 'sonycam'
3
+ require 'open-uri'
4
+
5
+ module Sonycam
6
+ class CLI < Thor
7
+ DD_PATH = File.join(ENV['HOME'], '.sonycam')
8
+
9
+ desc 'scan', 'Discover devices'
10
+ def scan
11
+ location = Scanner.scan.first
12
+ puts "Found location: #{location}"
13
+ File.write File.join(DD_PATH), open(location).read
14
+ puts "Device description file saved to #{DD_PATH}"
15
+ end
16
+
17
+ desc 'list QUERY', 'List all API or search'
18
+ def list query
19
+ api = API.new DeviceDescription.new(DD_PATH).api_url(:camera)
20
+ puts api.request(:getAvailableApiList)['result'].first.select{|method| method =~ /#{query}/i }
21
+ end
22
+
23
+ desc 'api METHOD PARAMS', 'Send API request'
24
+ def api method, *params
25
+ api = API.new DeviceDescription.new(DD_PATH).api_url(:camera)
26
+ jj api.request(method, *params)
27
+ end
28
+
29
+ desc 'liveview', 'Start Liveview and output to STDOUT'
30
+ def liveview
31
+ api = API.new DeviceDescription.new(DD_PATH).api_url(:camera)
32
+ liveview_url = api.request('startLiveview')['result'][0]
33
+ Liveview.stream(liveview_url) do |packet|
34
+ puts packet[:payload_data][:jpeg_data]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ class DeviceDescription
5
+ attr_reader :doc
6
+
7
+ # document: String or Nokogiri::XML::Node
8
+ def initialize document
9
+ @doc = Nokogiri::XML(open(document))
10
+ @camera_name = @doc.at_xpath('//xmlns:friendlyName').content
11
+ @api_url_hash = {}
12
+ @doc.xpath('//av:X_ScalarWebAPI_Service', av: 'urn:schemas-sony-com:av').each do |node|
13
+ type = node.at_xpath('av:X_ScalarWebAPI_ServiceType', av: 'urn:schemas-sony-com:av').content
14
+ url = node.at_xpath('av:X_ScalarWebAPI_ActionList_URL', av: 'urn:schemas-sony-com:av').content
15
+ @api_url_hash[type.to_sym] = url
16
+ end
17
+ end
18
+
19
+ # type: :camera, :system, :guide
20
+ def api_url type = :camera
21
+ # Hush, it's a secret, don't tell anyone.
22
+ if @camera_name == 'DSC-RX100M2'
23
+ url = @api_url_hash[type].sub('sony', 'camera')
24
+ puts "DSC-RX100M2 detected, API URL \"#{url}\" was used insteadly."
25
+ return url
26
+ end
27
+
28
+ if action_list_url = @api_url_hash[type] then "#{action_list_url}/#{type}"
29
+ else raise "Can't not find service type \"#{type}\"."
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ module Sonycam
2
+ module Error
3
+ MAPPING = {
4
+ 1 => :Any,
5
+ 2 => :Timeout,
6
+ 3 => :IllegalArgument,
7
+ 4 => :IllegalDataFormat,
8
+ 5 => :IllegalRequest,
9
+ 6 => :IllegalResponse,
10
+ 7 => :IllegalState,
11
+ 8 => :IllegalType,
12
+ 9 => :IndexOutOfBounds,
13
+ 10 => :NoSuchElement,
14
+ 11 => :NoSuchField,
15
+ 12 => :NoSuchMethod,
16
+ 13 => :NULLPointer,
17
+ 14 => :UnsupportedVersion,
18
+ 15 => :UnsupportedOperation,
19
+ 40400 => :Shootingfail,
20
+ 40401 => :CameraNotReady,
21
+ 40402 => :AlreadyRunningPollingAPI147,
22
+ 40403 => :StillCapturingNotFinished,
23
+ 401 => :Unauthorized,
24
+ 403 => :Forbidden,
25
+ 404 => :NotFound,
26
+ 406 => :NotAcceptable,
27
+ 413 => :RequestEntityTooLarge,
28
+ 414 => :RequestURITooLong,
29
+ 501 => :NotImplemented,
30
+ 503 => :ServiceUnavailable
31
+ }
32
+
33
+ MAPPING.each_value do |error_name|
34
+ const_set error_name, Class.new(StandardError)
35
+ end
36
+
37
+ def self.new_from_code code
38
+ const_get(MAPPING[code])
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ require 'net/http'
2
+ require 'sonycam/packet'
3
+
4
+ module Liveview
5
+ extend self
6
+ def stream liveview_url
7
+ uri = URI(liveview_url)
8
+ Net::HTTP.start(uri.host, uri.port) do |http|
9
+ request = Net::HTTP::Get.new uri
10
+ http.request request do |response|
11
+ state = :commen_header
12
+ buf = ''.force_encoding('BINARY')
13
+ commen_header = nil
14
+ payload_header = nil
15
+ response.read_body do |chunk|
16
+ buf += chunk
17
+ until buf.empty?
18
+ case state
19
+ when :commen_header # 8 bytes
20
+ break if buf.size < 8
21
+ commen_header = buf.slice!(0, 8).unpack('H2CnN')
22
+ state = :payload_header
23
+ when :payload_header # 128 bytes
24
+ break if buf.size < 128
25
+ payload_header = buf.slice!(0, 128).unpack('H8H6CH8CH*')
26
+ state = :payload_data
27
+ when :payload_data
28
+ jpeg_data_size = payload_header[1].to_i(16)
29
+ padding_size = payload_header[2]
30
+ break if buf.size < jpeg_data_size + padding_size
31
+ jpeg_data = buf.slice!(0, jpeg_data_size)
32
+ padding_data = buf.slice!(0, padding_size)
33
+ state = :commen_header
34
+ yield Packet.new(commen_header, payload_header, jpeg_data, padding_data)
35
+ end # case state
36
+ # gets
37
+ end # until buf.empty?
38
+ end # response.read_body do |chunk|
39
+ end # http.request request do |response|
40
+ end rescue retry # Net::HTTP.start(uri.host, uri.port) do |http|
41
+ end # def stream
42
+ end # class Liveview
@@ -0,0 +1,28 @@
1
+ class Packet
2
+ def initialize commen_header, payload_header, jpeg_data, padding_data
3
+ @packet = {
4
+ commen_header: {
5
+ start_byte: commen_header[0], # hex, fixed "FF"
6
+ payload_type: commen_header[1], # int
7
+ sequence_number: commen_header[2], # int
8
+ time_stamp: commen_header[3] # int
9
+ },
10
+ payload_header: {
11
+ star_code: payload_header[0], # hex, fixed "24356879"
12
+ jpeg_data_size: payload_header[1].to_i(16), # int
13
+ padding_size: payload_header[2], # int
14
+ reserved: payload_header[3], # hex
15
+ flag: payload_header[4], # int, fixed "00"
16
+ reserved_2: payload_header[5]
17
+
18
+ },
19
+ payload_data: {
20
+ jpeg_data: jpeg_data,
21
+ padding_data: padding_data
22
+ },
23
+ }
24
+ end
25
+ def [] key
26
+ @packet[key]
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ require 'socket'
2
+ require 'timeout'
3
+
4
+ module Sonycam
5
+ module Scanner
6
+ extend self
7
+ # returns array of device description XML URL
8
+ def scan ip = nil, timeout: 10
9
+ m_search = <<-EOS
10
+ M-SEARCH * HTTP/1.1\r
11
+ HOST: 239.255.255.250:1900\r
12
+ MAN: ssdp:discover\r
13
+ MX: #{timeout}\r
14
+ ST: urn:schemas-sony-com:service:ScalarWebAPI:1\r
15
+ \r
16
+ EOS
17
+
18
+ addresses = ip ? Array(Addrinfo.ip(ip)) : Socket.ip_address_list.reject{ |a| a.ipv4_loopback? || a.ipv6_loopback? || a.ipv6_linklocal? }
19
+ locations = []
20
+ addresses.map do |addr_info|
21
+ Thread.new do
22
+ begin
23
+ sock = UDPSocket.new
24
+ sock.bind(addr_info.ip_address, 0)
25
+ sock.send(m_search, 0, '239.255.255.250', 1900)
26
+ Timeout::timeout(timeout) do
27
+ response = sock.recv(1024)
28
+ headers = Hash[response.split("\r\n").map{|x| x[/^([^:]*)\s*:\s*(.*)$/]; [$1, $2]}]
29
+ locations << headers['LOCATION'] if headers['ST'] == 'urn:schemas-sony-com:service:ScalarWebAPI:1'
30
+ end
31
+ rescue Timeout::Error
32
+ rescue
33
+ puts $!.inspect, $@
34
+ end
35
+ end
36
+ end.each(&:join)
37
+ locations
38
+ end # def scan
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Sonycam
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sonycam
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tony Jian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Sony Camera Remote API Wrapper
42
+ email: tonytonyjan@gmail.com
43
+ executables:
44
+ - sonycam
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - bin/sonycam
49
+ - lib/sonycam.rb
50
+ - lib/sonycam/api.rb
51
+ - lib/sonycam/cli.rb
52
+ - lib/sonycam/device_description.rb
53
+ - lib/sonycam/error.rb
54
+ - lib/sonycam/liveview.rb
55
+ - lib/sonycam/packet.rb
56
+ - lib/sonycam/scanner.rb
57
+ - lib/sonycam/version.rb
58
+ homepage: https://github.com/tonytonyjan/sonycam
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.4.1
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Sony Camera Remote API Wrapper
82
+ test_files: []