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.
- checksums.yaml +7 -0
- data/bin/sonycam +3 -0
- data/lib/sonycam.rb +8 -0
- data/lib/sonycam/api.rb +23 -0
- data/lib/sonycam/cli.rb +38 -0
- data/lib/sonycam/device_description.rb +32 -0
- data/lib/sonycam/error.rb +41 -0
- data/lib/sonycam/liveview.rb +42 -0
- data/lib/sonycam/packet.rb +28 -0
- data/lib/sonycam/scanner.rb +40 -0
- data/lib/sonycam/version.rb +3 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -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
|
data/bin/sonycam
ADDED
data/lib/sonycam.rb
ADDED
data/lib/sonycam/api.rb
ADDED
@@ -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
|
data/lib/sonycam/cli.rb
ADDED
@@ -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
|
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: []
|