sonycam 0.0.1

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.
@@ -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: []