class SonyCameraRemoteAPI::Camera

Top-level class providing wrapper methods of Sony Camera Remote APIs.

Constants

CONT_SHOOT_SAVING_TIME

Timeout for saving images captured by continuous shooting.

LONG_ZOOM_FINISH_TIMEOUT
LONG_ZOOM_THRESHOLD
SHORT_ZOOM_FINISH_TIMEOUT
SHORT_ZOOM_THRESHOLD
TRACKING_FOCUS_TIMEOUT

Timeout for focusing by tracking focus.

TRANSFER_SIZE_LIST

Predefined transfer sizes

Attributes

endpoints[R]

Public Class Methods

finalize(this) click to toggle source

Destructor: this calls stopRecMode API to finish shooting.

# File lib/sony_camera_remote_api.rb, line 79
def self.finalize(this)
  proc do
    this.stopRecMode!
    this.log.info 'Finished remote shooting function.'
  end
end
new(shelf = nil, reconnect_by: nil, log_file: $stdout, log_level: Logger::INFO, finalize: false) click to toggle source

Creates a new Camera object. @note It is good idea to save endpoint URLs by each cameras somewhere to omit SSDP search. @param [Shelf] shelf Shelf class object that is used for connection. @param [Proc] reconnect_by Hook method to reconnect to the camera, which is called when Wi-Fi is disconnected.

Not necessary if +shelf+ is given.

@param [String, IO, Array<String, IO>] log_file File name or stream to output log. @param [Boolean] finalize If true, stopRecMode API is called in the destructor.

As far as I know, we have no problem even if we never call stopRecMode.
# File lib/sony_camera_remote_api.rb, line 48
def initialize(shelf = nil, reconnect_by: nil, log_file: $stdout, log_level: Logger::INFO, finalize: false)
  set_output log_file
  set_level  log_level
  if shelf
    @endpoints = shelf.ep || ssdp_search
    shelf.set_ep @endpoints
    @reconnect_by = shelf.method(:reconnect)
  else
    @endpoints = ssdp_search
  end
  @reconnect_by = reconnect_by if reconnect_by

  @api_manager = CameraAPIManager.new @endpoints, reconnect_by: @reconnect_by
  @http = HTTPClient.new
  @http.connect_timeout  = @http.send_timeout = @http.receive_timeout = 10
  @retrying = Retrying.new(@reconnect_by, @http).add_common_hook do
    startRecMode! timeout: 0
  end

  # Some cameras which use "Smart Remote Control" app must call this method before remote shooting.
  startRecMode! timeout: 0

  # As far as I know, we don't have to call stopRecMode method
  # It may be useful for power-saving because stopRecMode leads to stop liveview.
  if finalize
    ObjectSpace.define_finalizer(self, self.class.finalize(self))
  end
end

Public Instance Methods

act_focus() click to toggle source

Do focus, which is the same as half-pressing the shutter button. @note You have to set shooting mode to 'still' before calling this method. @return [Boolean] true if focus succeeded, false if failed. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still'

# Capture forever only when succeeded to focus.
loop do
  if cam.act_focus
    cam.capture_still
  end
end
# File lib/sony_camera_remote_api.rb, line 399
def act_focus
  return false unless support? :actHalfPressShutter
  cancel_focus
  actHalfPressShutter
  rsp = wait_event { |r| ['Focused', 'Failed'].include? r[35]['focusStatus'] }
  if rsp[35]['focusStatus'] =='Focused'
    log.info 'Focused.'
    true
  elsif rsp[35]['focusStatus'] =='Failed'
    log.info 'Focuse failed!'
    cancelHalfPressShutter
    wait_event { |r| r[35]['focusStatus'] == 'Not Focusing' }
    false
  end
end
act_touch_focus(x, y) click to toggle source

Do touch focus, by which we can specify the focus position. @note You have to set shooting mode to 'still' before calling this method. @note Tracking focus and Touch focus are exclusive functions.

So tracking focus is automatically disabled by calling this method.

@param [Fixnum] x Percentage of X-axis position. @param [Fixnum] y Percentage of Y-axis position. @return [Boolean] AFType ('Touch' or 'Wide') if focus succeeded. nil if failed. @see Touch AF position parameter in API reference @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still'

th = cam.start_liveview_thread do |img, info|
  focus_frame = info.frames.find { |f| f.category == 1 }
  if focus_frame
    # Get current focus position.
    puts "  top-left     = (#{focus_frame.top_left.x}, #{focus_frame.top_left.y})"
    puts "  bottom-right = (#{focus_frame.bottom_right.x}, #{focus_frame.bottom_right.y})"
  else
    puts 'No focus frame!'
  end
end

# Do touch focus ramdonly, and capture a still if focused.
loop do
  cam.act_touch_focus rand(101), rand(101)
  if cam.focused?
    cam.capture_still
  end
  sleep 1
end
# File lib/sony_camera_remote_api.rb, line 448
def act_touch_focus(x, y)
  return false unless support? :setTouchAFPosition
  cancel_focus
  set_parameter! :TrackingFocus, 'Off'

  x = [[x, 100].min, 0].max
  y = [[y, 100].min, 0].max
  result = setTouchAFPosition([x, y]).result
  if result[1]['AFResult'] == true
    log.info "Touch focus (#{x}, #{y}) OK."
    # result[1]['AFType']
    true
  else
    log.info "Touch focus (#{x}, #{y}) failed."
    false
  end
end
act_tracking_focus(x, y) click to toggle source

Do tracking focus, by which the focus position automatically track the object. The focus position is expressed by percentage to the origin of coordinates, which is upper left of liveview images. @note You have to set shooting mode to 'still' before calling this method. @param [Fixnum] x Percentage of X-axis position. @param [Fixnum] y Percentage of Y-axis position. @return [Boolean] true if focus succeeded, false if failed. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still'

th = cam.start_liveview_thread do |img, info|
  tracking_frame = info.frames.find { |f| f.category == 5 }
  if tracking_frame
    # Get tracking focus position from the liveview frame info
    puts "  top-left     = (#{tracking_frame.top_left.x}, #{tracking_frame.top_left.y})"
    puts "  bottom-right = (#{tracking_frame.bottom_right.x}, #{tracking_frame.bottom_right.y})"
  else
    puts 'No tracking frame!'
  end
end

# Capture a still image while tracking.
loop do
  if cam.tracking?
    cam.capture_still
  else
    cam.act_tracking_focus 50, 50
  end
  sleep 1
end
# File lib/sony_camera_remote_api.rb, line 498
def act_tracking_focus(x, y)
  return false unless support? :TrackingFocus
  cancel_focus
  set_parameter :TrackingFocus, 'On'

  x = [[x, 100].min, 0].max
  y = [[y, 100].min, 0].max
  actTrackingFocus(['xPosition' => x, 'yPosition' => y]).result
  begin
    wait_event(timeout: TRACKING_FOCUS_TIMEOUT) { |r| r[54]['trackingFocusStatus'] == 'Tracking' }
    log.info "Tracking focus (#{x}, #{y}) OK."
    true
  rescue EventTimeoutError => e
    log.info "Tracking focus (#{x}, #{y}) Failed."
    false
  end
end
act_zoom(absolute: nil, relative: nil) click to toggle source

Do zoom. Zoom position can be specified by relative and absolute percentage within the range of 0-100. If Both option are specified, absolute position is preceded. @param [Fixnum] absolute Absolute position of the lense. 0 is the Wide-end and 100 is the Tele-end. @param [Fixnum] relative Relative percecntage to current position of the lense. @return [Array<Fixnum>] Array of initial zoom position and current zoom position. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new

cam.act_zoom(absolute: 0)     # zoom out to the wide-end
cam.act_zoom(absolute: 100)   # zoom in to the tele-end
cam.act_zoom(relative: -50)   # zoom out by -50 from the current position
# File lib/sony_camera_remote_api.rb, line 347
def act_zoom(absolute: nil, relative: nil)
  # Check arguments
  return if [relative, absolute].none?
  relative = nil if [relative, absolute].all?

  # Get current position
  initial = getEvent(false).result[2]['zoomPosition']
  unless initial.between? 0, 100
    initial = wait_event { |r| r[2]['zoomPosition'].between? 0, 100 }[2]['zoomPosition']
  end
  # Return curent position if relative is 0
  return initial if relative == 0

  # Calculate target positions
  if relative
    absolute = [[initial + relative, 100].min, 0].max
  else
    absolute = [[absolute, 100].min, 0].max
  end
  relative = absolute - initial
  current = initial

  log.debug "Zoom started: #{initial} -> #{absolute} (relative: #{relative})"

  # If absolute position is wide or tele end, use only long push zoom.
  if [0, 100].include? absolute
    current = zoom_until_end absolute
  else
    # Otherwise, use both long push and 1shot zoom by relative position
    current, rest = zoom_by_long_push current, relative
    current, _    = zoom_by_1shot current, rest
  end

  log.debug "Zoom finished: #{initial} -> #{current} (target was #{absolute})"
  [initial, current]
end
cancel_focus() click to toggle source

Cancel focus If camera has been focused. @return [void] @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still'

# Try to focus on upper-middle position.
if cam.act_tracking_focus(50, 10)
  puts cam.focused?
  cam.capture_still
  puts cam.focused?
end
# File lib/sony_camera_remote_api.rb, line 549
def cancel_focus
  result = getEvent(false).result
  # Canceling tracking/touch focus should be preceded for half-press
  if result[54] && result[54]['trackingFocusStatus'] == 'Tracking'
    cancelTrackingFocus
    rsp = wait_event { |r| r[54]['trackingFocusStatus'] == 'Not Tracking' }
  end
  if result[34] && result[34]['currentSet'] == true
    cancelTouchAFPosition
    rsp = wait_event { |r| r[34]['currentSet'] == false }
  end
  if result[35] && result[35]['focusStatus'] != 'Not Focusing'
    cancelHalfPressShutter
    rsp = wait_event { |r| r[35]['focusStatus'] == 'Not Focusing' }
  end
end
capture_still(transfer: true, filename: nil, prefix: nil, dir: nil) click to toggle source

Capture still image(s) and transfer them to local storage. @note You have to set shooting mode to 'still' before calling this method.

This method can be used in following continuous-shooting mode if supported:
* Single     : take a single picture
* Burst      : take 10 pictures at a time
* MotionShot : take 10 pictures and render the movement into a single picture

@param [Boolean] transfer Flag to transfer the postview image. @param [String] filename Name of image file to be transferred. If not given, original name is used.

Only available in Single/MotionShot shooting mode.

@param [String] prefix Prefix of sequential image files to be transferred. If not given, original name is used.

Only available in Burst shooting mode.

@param [String] dir Directory where image file is saved. If not given, current directory is used. @return [String, Array<String>, nil] Filename of the transferred image(s). If 'transfer' is false, returns nil. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new

# Capture a single still image, and then save it as images/TEST.JPG.
cam.change_function_to_shoot 'still', 'Single'
cam.capture_still filename: 'TEST.JPG', dir: 'images'

# Capture 10 images by burst shooting and save them as 'TEST_0.jpg', ... 'TEST_9.jpg'.
cam.change_function_to_shoot 'still', 'Burst'
cam.capture_still prefix: 'TEST'
# File lib/sony_camera_remote_api.rb, line 140
def capture_still(transfer: true, filename: nil, prefix: nil, dir: nil)
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  log.info 'Capturing...'
  postview_url = ''
  time = Benchmark.realtime do
    postview_url = actTakePicture.result[0][0]
    wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  end

  log.debug postview_url
  log.info 'Capture finished. (%.2f sec)' % [time]

  if transfer
    case get_current!(:ContShootingMode)
      when 'Burst'
        transferred = transfer_in_burst_mode postview_url, prefix: prefix, dir: dir
      else
        filename = File.basename(URI.parse(postview_url).path) if filename.nil?
        transferred = transfer_postview(postview_url, filename, dir: dir)
    end
    transferred
  end
end
change_function_to_shoot(mode, cont = nil) click to toggle source

Change camera function to 'Remote Shooting' and then set shooting mode. @param [String] mode Shoot mode @param [String] cont Continuous shooting mode (only available when shoot mode is 'still') @return [void] @see 'Shoot mode parameters' in API reference @see 'Continuous shooting mode parameter' in API reference @todo make mode argument nullable

# File lib/sony_camera_remote_api.rb, line 94
def change_function_to_shoot(mode, cont = nil)
  # cameras that does not support CameraFunction API group has only 'Remote Shooting' function
  set_parameter! :CameraFunction, 'Remote Shooting'
  set_parameter :ShootMode, mode
  if mode == 'still' && cont
    set_parameter! :ContShootingMode, cont
  end
end
change_function_to_transfer() click to toggle source

Change camera function to 'Contents Transfer'. You should call this method before using contents-retrieving methods, which are following 4 methods:

@return [void]

# File lib/sony_camera_remote_api.rb, line 111
def change_function_to_transfer
  set_parameter :CameraFunction, 'Contents Transfer'
end
delete_contents(contents) click to toggle source

Delete content(s) of camera storage. @note You have to set camera function to 'Contents Transfer' before calling this method. @param [Array<Hash>] contents array of content hashes, which can be obtained by get_content_list @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_transfer

# Delete 10 newest still contents
contents = cam.get_content_list(type: 'still', count: 10)
cam.delete_contents(contents)
# File lib/sony_camera_remote_api.rb, line 875
def delete_contents(contents)
  contents = [contents].compact unless contents.is_a? Array
  count = contents.size
  (0..((count - 1) / 100)).each do |i|
    start = i * 100
    cnt = start + 100 < count ? 100 : count - start
    param = contents[start, cnt].map { |c| c['uri'] }
    deleteContent [{'uri' => param}]
  end
  log.info "Deleted #{contents.size} contents."
end
focused?() click to toggle source

Return whether the camera has focused or not. @return [Boolean] true if focused, false otherwise. @see act_focus @see act_touch_focus

# File lib/sony_camera_remote_api.rb, line 521
def focused?
  result = getEvent(false).result
  result[35] && result[35]['focusStatus'] == 'Focused'
end
get_content_list(type: nil, date: nil, sort: 'descending', count: nil) click to toggle source

Get a list of content information. Content information is Hash object that contains URI, file name, timestamp and other informations. You can transfer contents by calling 'transfer_contents' method with the content information Hash. This is basically the wrapper of getContentList API. For more information about request/response, see API reference. @note You have to set camera function to 'Contents Transfer' before calling this method. @param [String, Array<String>] type Same as 'type' request parameter of getContentList API. @param [Boolean] date Date in format of 'YYYYMMDD' used in date-view. If not specified, flat-view is used. @param [String] sort Same as 'sort' request parameter of getContentList API. @param [Fixnum] count Number of contents to get.

Unlike the one of request parameter of getContentList API, you can specify over 100.

@return [Array<Hash>] Content informations @see getContentList API in the API reference. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_transfer

# Get all contents
contents = cam.get_content_list
# Get still contents created on 2016/8/1
contents = cam.get_content_list(type: 'still', date: '20160801')
# Get 3 oldest movie contents
contents = cam.get_content_list(type: ['movie_xavcs', 'movie_mp4'], sort: 'ascending', count: 3)

# Get filenames and URL of each content
contents.each do |c|
  filename = c['content']['original'][0]['fileName']
  url = c['content']['original'][0]['url']
  puts "#{filename}, #{url}"
end

# Transfer contents
cam.transfer_contents(contents)
# File lib/sony_camera_remote_api.rb, line 715
def get_content_list(type: nil, date: nil, sort: 'descending', count: nil)
  type = Array(type) if type.is_a? String

  scheme = getSchemeList.result[0][0]['scheme']
  source = getSourceList([{'scheme' => scheme}]).result[0][0]['source']

  if date
    date_found = get_date_list.find { |d| d['title'] == date }
    if date_found
      contents = get_content_list_sub date_found['uri'], type: type, view: 'date', sort: sort, count: count
    else
      log.error "Cannot find any contents at date '#{date}'!"
      return []
    end
  else
    # type option is available ONLY FOR 'date' view.
    if type.present?
      # if 'type' option is specified, call getContentList with date view for every date.
      # this is because getContentList with flat view is extremely slow as a number of contents grows.
      dates = get_date_list type: type, sort: sort
      contents = []
      if count.present?
        dates.each do |date|
          num = [date['contentCount'], count - contents.size].min
          contents += get_content_list_sub date['uri'], type: type, view: 'date', sort: sort, count: num
          break if contents.size >= count
        end
        # it is no problem that a number of contents is less than count
        contents = contents[0, count]
      else
        dates.each do |date|
          contents += get_content_list_sub date['uri'], type: type, view: 'date', sort: sort, count: date['contentCount']
        end
      end
    else
      # contents = get_content_list_sub source, view: 'flat', sort: sort, count: count
      contents = get_content_list_sub source, view: 'flat', sort: sort, count: count
    end
  end
  contents
end
get_date_list(type: nil, sort: 'descending', date_count: nil, content_count: nil) click to toggle source

Gets a list of dates and the number of contents of each date. This is basically the wrapper of getContentList API. For more information about request/response, see API reference. @note You have to set camera function to 'Contents Transfer' before calling this method. @param [String, Array<String>] type Same as 'type' request parameter of getContentList API @param [String] sort Same as 'sort' request parameter of getContentList API @param [Fixnum] date_count Number of dates to get. @param [Fixnum] content_count Number of contents to get @return [Array<Hash>] An array of dates in format of 'YYYYMMdd' and an array of number of contents of the associated date. @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_transfer

# Get all dates and content counts of the associated date.
dates = cam.get_date_list
# Get 5 newest dates that contains XAVC-S movie contents.
dates = cam.get_date_list(type: 'movie_xavcs', date_count: 5)
# Get dates until the sum of still contents of each date exceeds 100.
dates = cam.get_date_list(type: 'still', content_count: 100)

dates.each do |date|
  puts "date:  #{date['title']}"            # Get date in the format 'YYYYMMdd'
  puts "count: #{date['contentCount']}"     # Get content count
  # Get contents of each date
  contents = cam.get_content_list date: date['title']
  # Transfer contents
  cam.transfer_contents contents
end
# File lib/sony_camera_remote_api.rb, line 786
def get_date_list(type: nil, sort: 'descending', date_count: nil, content_count: nil)
  type = Array(type) if type.is_a? String

  scheme = getSchemeList.result[0][0]['scheme']
  source = getSourceList([{'scheme' => scheme}]).result[0][0]['source']

  if type.present?
    # If type is specifid, get all dates and check the count of contents type later
    dates = get_content_list_sub(source, view: 'date', sort: sort)
  else
    # If not, simply get dates by date_count
    dates = get_content_list_sub(source, view: 'date', count: date_count, sort: sort)
  end

  # Filter by type, date_count and content_count.
  filtered_dates = []
  dates.each do |d|
    cnt = getContentCount([{'uri' => d['uri'], 'type' => type, 'view' => 'date'}]).result[0]['count']
    # Exclude days of 0 contents.
    if cnt > 0
      d['contentCount'] = cnt
      filtered_dates << d
    end
    # Break if contents count exceeds.
    break if content_count and filtered_dates.map { |d| d['contentCount'] }.inject(0, :+) > content_count
    # Break if date count exceeds.
    break if date_count and filtered_dates.size > date_count
  end
  filtered_dates
end
start_continuous_shooting() click to toggle source

Start continuous shooting. To stop shooting, call stop_continuous_shooting method. @note You have to set shooting mode to 'still' and continuous shooting mode to following modes:

* Continuous          : take pictures continuously until stopped.
* Spd Priority Cont.  : take pictures continuously at a rate faster than 'Continuous'.

@return [void] @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still', 'Continuous'

# Start continuous shooting and transfer all images.
cam.start_continuous_shooting
sleep 5
cam.stop_continuous_shooting(transfer: true)
# File lib/sony_camera_remote_api.rb, line 180
def start_continuous_shooting
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  startContShooting
  wait_event { |r| r[1]['cameraStatus'] == 'StillCapturing' }
  log.info 'Started continous shooting.'
end
start_interval_recording() click to toggle source

Start interval still recording (a.k.a Timelapse). To stop recording, call stop_interval_recording method. @note You have to set shooting mode to 'intervalstill' before calling this method. @return [void] @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot('intervalstill')

# Start interval still recording (does not transfer).
cam.start_interval_recording
sleep 5
cam.stop_interval_recording
# File lib/sony_camera_remote_api.rb, line 272
def start_interval_recording
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  startIntervalStillRec
  wait_event { |r| r[1]['cameraStatus'] == 'IntervalRecording' }
  log.info 'Started interval still recording.'
end
start_liveview_thread(size: nil, time: nil) { |liveview_image, frame_info| ... } click to toggle source

Starts a new thread that downloads streamed liveview images. This liveview thread continues downloading unless the one of the following conditions meets: The both hook method is called called each time after a liveview image or frame is downloaded. @param [String] size The liveview size. @param [Fixnum] time Time in seconds until finishing liveview streaming. @yield [LiveviewImage, LiveviewFrameInformation] The block called every time a liveview image is downloaded. @yieldparam [LiveviewImage] liveview image of each frame. @yieldparam [LiveviewFrameInformation] liveview frame information of each frame.

If liveview frame information is not supported, nil is always given.

@return [Thread] liveview downloading thread object @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot 'still', 'Single'

# Start liveview streaming
th = cam.start_liveview_thread do |img|
  filename = "liveview/#{img.sequence_number}.jpg"
  File.write filename, img.jpeg_data
  puts "wrote #{filename}."
end
th.join
# File lib/sony_camera_remote_api.rb, line 590
def start_liveview_thread(size: nil, time: nil)
  liveview_url, frame_info_enabled = init_liveview size: size
  log.debug "liveview URL: #{liveview_url}"

  th = Thread.new do
    thread_start = loop_end = Time.now
    count = 0
    buffer = ''
    frame_info= nil
    # Ensure to finalize if the thread is killed
    begin
      # Break from loop inside when timeout
      catch :finished do
        # For reconnection
        @retrying.reconnect_and_retry_forever do
          # Retrieve streaming data
          @http.get_content(liveview_url) do |chunk|
            loop_start = Time.now
            received_sec = loop_start - loop_end

            buffer << chunk
            log.debug "start--------------------buffer.size=#{buffer.size}, #{format("%.2f", received_sec * 1000)} ms"
            begin
              obj = LiveviewPacket.read(buffer)
            rescue EOFError => e
              # Simply read more data
            rescue IOError, BinData::ValidityError => e
              # Clear buffer and read data again
              buffer = ''
            else
              # Received an packet successfully!
              case obj.payload_type
                when 0x01
                  # When payload is jpeg data
                  log.debug "  sequence  : #{obj.sequence_number}"
                  log.debug "  data_size : #{obj.payload.payload_data_size_wo_padding}"
                  log.debug "  pad_size  : #{obj.payload.padding_size}"
                  if frame_info_enabled && frame_info.nil?
                    log.debug 'frame info is not present. skipping...'
                  else
                    block_time = Benchmark.realtime do
                      yield(LiveviewImage.new(obj), frame_info)
                    end
                    log.debug "block time     : #{format('%.2f', block_time*1000)} ms."
                  end
                  count += 1
                when 0x02
                  # When payload is liveview frame information
                  log.debug "frame count = #{obj.payload.frame_count}"
                  if obj.payload.frame_count > 0
                    obj.payload.frame_data.each do |d|
                      log.debug "  category     : #{d.category}"
                      log.debug "  status       : #{d.status}, #{d.additional_status}"
                      log.debug "  top-left     : #{d.top_left.x}, #{d.top_left.y}"
                      log.debug "  bottom-right : #{d.bottom_right.x}, #{d.bottom_right.y}"
                    end
                  end
                  # Keep until next liveview image comes.
                  frame_info = LiveviewFrameInformation.new obj
              end

              last_loop_end = loop_end
              loop_end = Time.now
              loop_elapsed = loop_end - last_loop_end
              log.debug "end----------------------#{format("%.2f", loop_elapsed * 1000)} ms, #{format("%.2f", 1 / loop_elapsed)} fps"

              # Delete the packet data from buffer
              buffer = buffer[obj.num_bytes..-1]

              # Finish if time exceeds total elapsed time
              throw :finished if time && (loop_end - thread_start > time)
            end
          end
        end
      end
    rescue StandardError => e
        log.error e.backtrace.join "\n"
    ensure
      # Comes here when liveview finished or killed by signal
      puts 'Stopping Liveview...'
      stopLiveview
      total_time = Time.now - thread_start
      log.info 'Liveview thread finished.'
      log.debug "  total time: #{format('%d', total_time)} sec"
      log.debug "  count: #{format('%d', count)} frames"
      log.debug "  rate: #{format('%.2f', count/total_time)} fps"
    end
  end
  th
end
start_loop_recording() click to toggle source

Start loop recording. To stop recording, call stop_loop_recording method. @note You have to set shooting mode to 'looprec' before calling this method. @return [void] @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot('looprec')

# Start loop movie recording (does not transfer).
cam.start_loop_recording
sleep 5
cam.stop_loop_recording
# File lib/sony_camera_remote_api.rb, line 310
def start_loop_recording
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  startLoopRec
  wait_event { |r| r[1]['cameraStatus'] == 'LoopRecording' }
  log.info 'Started loop recording.'
end
start_movie_recording() click to toggle source

Start movie recording. To stop recording, call stop_movie_recording method. @note You have to set shooting mode to 'movie' before calling this method. @return [void] @example

# Initialize
cam = SonyCameraRemoteAPI::Camera.new
cam.change_function_to_shoot('movie')

# Record movie and transfer it.
cam.start_movie_recording
sleep 5
cam.stop_movie_recording(transfer: true)
# File lib/sony_camera_remote_api.rb, line 235
def start_movie_recording
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  startMovieRec
  wait_event { |r| r[1]['cameraStatus'] == 'MovieRecording' }
  log.info 'Started movie recording.'
end
stop_continuous_shooting(transfer: false, prefix: nil, dir: nil) click to toggle source

Stop continuous shooting and transfers all still images. @note 'transfer' flag is set false as default, because transfer time is prone to be much longer. @param [Boolean] transfer Flag to transfer the captured images. @param [String] prefix Prefix of of sequencial image files to be transferred. If not given, original name is used. @param [String] dir Directory where image file is saved. If not given, current directory is used. @return [Array<String>, nil] List of filenames of the transferred images. If 'transfer' is false, returns nil.

# File lib/sony_camera_remote_api.rb, line 194
def stop_continuous_shooting(transfer: false, prefix: nil, dir: nil)
  stopContShooting
  log.info 'Stopped continuous shooting: saving...'
  urls_result = wait_event(timeout: CONT_SHOOT_SAVING_TIME) { |r| r[40].present? }
  urls = urls_result[40]['contShootingUrl'].map { |e| e['postviewUrl'] }
  log.debug 'Got URLs.'
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  log.info "Saving finished: #{urls.size} images."
  if transfer
    gen = generate_sequencial_filenames prefix, 'JPG' if prefix.present?
    transferred = []
    urls.each do |url|
      if prefix.present?
        filename = gen.next
      else
        filename = File.basename(URI.parse(url).path)
      end
      result = transfer_postview(url, filename, dir: dir)
      # If transfer failed, it is possible that Wi-Fi is disconnected,
      # that means subsequent postview images become unavailable.
      break if result.nil?
      transferred << result
    end
    transferred.compact
  end
end
stop_interval_recording(transfer: false, prefix: nil, dir: nil) click to toggle source

Stop interval still recording and transfers all still images. @note 'transfer' flag is set false as default, because transfer time is prone to be much longer. @param [Boolean] transfer Flag to transfer still images @param [String] prefix Prefix of sequencial image files to be transferred. If not given, original name is used. @param [String] dir Directory where image file is saved. If not given, current directory is used. @return [Array<String>, nil] List of filenames of the transferred images. If 'transfer' is false, returns nil.

# File lib/sony_camera_remote_api.rb, line 286
def stop_interval_recording(transfer: false, prefix: nil, dir: nil)
  stopIntervalStillRec
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  num_shots = getEvent([false]).result[58]['numberOfShots']
  log.info "Stopped interval still recording: #{num_shots} images."
  if transfer
    transfer_interval_stills num_shots, prefix: prefix, dir: dir
  end
end
stop_loop_recording(transfer: false, filename: nil, dir: nil) click to toggle source

Stop loop recording and transfers the movie file. @note 'transfer' flag is set false as default, because transfer time is prone to be much longer. @param [Boolean] transfer Flag to transfer the recorded movie file @param [String] filename Name of the movie file to be transferred. If not given, original name is used. @param [String] dir Directory where image file is saved. If not given, current directory is used. @return [String, nil] Filename of the transferred movie. If 'transfer' is false, returns nil.

# File lib/sony_camera_remote_api.rb, line 324
def stop_loop_recording(transfer: false, filename: nil, dir: nil)
  stopLoopRec
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  log.info 'Stopped loop recording.'
  if transfer
    transfer_recorded_movie(filename: filename, dir: dir)
  end
end
stop_movie_recording(transfer: false, filename: nil, dir: nil) click to toggle source

Stop movie recording and transfers the movie file. @note 'transfer' flag is set false as default, because transfer time is prone to be much longer. @param [Boolean] transfer Flag to transfer the recorded movie file. @param [String] filename Name of the movie file to be transferred. If not given, original name is used. @param [String] dir Directory where image file is saved. If not given, current directory is used. @return [String, nil] Filename of the transferred movie. If 'transfer' is false, returns nil.

# File lib/sony_camera_remote_api.rb, line 249
def stop_movie_recording(transfer: false, filename: nil, dir: nil)
  stopMovieRec
  wait_event { |r| r[1]['cameraStatus'] == 'IDLE' }
  log.info 'Stopped movie recording.'
  if transfer
    transfer_recorded_movie(filename: filename, dir: dir)
  end
end
tracking?() click to toggle source

Return whether the camera is tracking an object for tracking focus. @return [Boolean] true if focused, false otherwise. @see act_tracking_focus

# File lib/sony_camera_remote_api.rb, line 530
def tracking?
  result = getEvent(false).result
  result[54] && result[54]['trackingFocusStatus'] == 'Tracking'
end
transfer_contents(contents, filenames=[], dir: nil, size: 'original', add_postfix: true) click to toggle source

Transfer content(s) from the camera storage. @note You have to set camera function to 'Contents Transfer' before calling this method. @param [Array<Hash>] contents Array of content information, which can be obtained by get_content_list @param [Array<String>] filenames Array of filename strings @param [String, Array<String>] size Content size. available values are 'original', 'large', 'small', 'thumbnail'. @param [Boolean] add_postfix If true, postfix is appended for 'large', 'small' and 'thumbnail' content size. @see get_content_list @see get_date_list @todo If 'contents' is directory (date), get all contents of the directory.

# File lib/sony_camera_remote_api.rb, line 829
def transfer_contents(contents, filenames=[], dir: nil, size: 'original', add_postfix: true)
  contents = [contents].compact unless contents.is_a? Array
  filenames = [filenames].compact unless filenames.is_a? Array
  size = [size].compact unless size.is_a? Array
  unless size.map { |s| TRANSFER_SIZE_LIST.include? s }.all?
    log.error "'size' argument contains invalid size name!"
    log.error "Available sizes are: #{TRANSFER_SIZE_LIST}"
    return nil
  end

  if !filenames.empty?
    if contents.size > filenames.size
      log.warn 'Size of filename list is smaller than that of contents list!'
      filenames += Array.new(contents.size - filenames.size, nil)
    elsif contents.size < filenames.size
      log.warn 'Size of filename list is bigger than that of contents list!'
    end
  end

  urls_filenames = get_content_url(contents, filenames, size, add_postfix: add_postfix)
  if urls_filenames.empty?
    log.warn 'No contents to be transferred.'
    return []
  end
  log.info "#{urls_filenames.size} contents to be transferred."
  transferred = transfer_contents_sub(urls_filenames, dir)
  if transferred.size == urls_filenames.size
    log.info 'All transfer completed.'
  else
    log.info "Some files are failed to transfer (#{transferred.size}/#{urls_filenames.size})."
  end
  transferred
end

Private Instance Methods

get_content_list_sub(source, type: nil, target: 'all', view: nil, sort: nil, count: nil) click to toggle source
# File lib/sony_camera_remote_api.rb, line 1100
def get_content_list_sub(source, type: nil, target: 'all', view: nil, sort: nil, count: nil)
  max_count = getContentCount([{'uri' => source, 'type' => type, 'view' => view}]).result[0]['count']
  count = count ? [max_count, count].min : max_count
  contents = []
  (0..((count - 1) / 100)).each do |i|
    start = i * 100
    cnt = start + 100 < count ? 100 : count - start
    contents += getContentList([{'uri' => source, 'stIdx' => start, 'cnt' => cnt, 'type' => type, 'view' => view, 'sort' => sort}]).result[0]
    # pp contents
  end
  contents
end
get_content_url(contents, filenames, sizes, add_postfix: true) click to toggle source
# File lib/sony_camera_remote_api.rb, line 1138
def get_content_url(contents, filenames, sizes, add_postfix: true)
  urls_filenames = []
  contents.zip(filenames).product(sizes).map { |e| e.flatten }.each do |content, filename, size|
    next unless content

    filename ||= content['content']['original'][0]['fileName']
    base = File.basename filename, '.*'
    postfix = add_postfix ? "_#{size}" : ''
    case size
      when 'original'
        raise StandardError if content['content']['original'].size > 1 # FIXME: When do we come here???
        url = content['content']['original'][0]['url']
        ext =
            case content['contentKind']
              when 'still'
                '.JPG'
              when 'movie_mp4'
                '.MP4'
              when 'movie_xavcs'
                '.MP4'
            end
        filename = "#{base}#{ext}"
      when 'large'
        url = content['content']['largeUrl']
        filename = "#{base}#{postfix}.JPG"
      when 'small'
        url = content['content']['smallUrl']
        filename = "#{base}#{postfix}.JPG"
      when 'thumbnail'
        url = content['content']['thumbnailUrl']
        filename = "#{base}#{postfix}.JPG"
    end
    if url.empty?
      log.error "Skipping empty URL for file: #{filename}, size: #{size}"
    else
      urls_filenames << [url, filename]
    end
  end
  urls_filenames
end
get_transfer_size() click to toggle source

Use postview size parameter to determine the image size to get

# File lib/sony_camera_remote_api.rb, line 918
def get_transfer_size
  return 'original' unless support? :getPostviewImageSize
  postview_size = getPostviewImageSize.result[0]
  case postview_size
    when 'Original'
      'original'
    when '2M'
      'large'
  end
end
init_liveview(size: nil) click to toggle source

Initialize and start liveview

# File lib/sony_camera_remote_api.rb, line 1077
def init_liveview(size: nil)
  # Enable liveview frame information if available
  rsp = setLiveviewFrameInfo!([{'frameInfo' => true}])
  if rsp && rsp.result
    frame_info_enabled = true
  else
    frame_info_enabled = false
  end

  if size
    # need to stop liveview when the liveview size is changed
    stopLiveview
    current, available = getAvailableLiveviewSize.result
    unless available.include?(size)
      raise IllegalArgument, new, "The value '#{size}' is not available for parameter 'LiveviewSize'. current: #{current}, available: #{available}"
    end
    [ startLiveviewWithSize([size]).result[0], frame_info_enabled ]
  else
    [ startLiveview.result[0], frame_info_enabled ]
  end
end
transfer_contents_sub(urls_filenames, dir) click to toggle source
# File lib/sony_camera_remote_api.rb, line 1114
def transfer_contents_sub(urls_filenames, dir)
  FileUtils.mkdir_p dir if dir
  transferred = []
  urls_filenames.each do |url, filename|
    next unless url
    filepath = dir ? File.join(dir, filename) : filename
    log.debug url
    log.info "Transferring #{filepath}..."
    time = Benchmark.realtime do
      @retrying.reconnect_and_retry(hook: method(:change_function_to_transfer)) do
        open(filepath, 'wb') do |file|
          @http.get_content(url) do |chunk|
            file.write chunk
          end
        end
      end
    end
    log.info "Transferred #{filepath}. (#{format('%.2f', time)} sec)"
    transferred << filepath
  end
  transferred
end
transfer_in_burst_mode(url, prefix: nil, dir: nil) click to toggle source

In burst shooting mode, the last one in 10 images is the only one we can get by postview URI. So we must change function to 'Transfer Contents', then get the newest 10 contents. The problem is that getContentList API can sort the results by timestamp but not by file number. For example, assume that you have 2 cameras A and B, and the time setting of the A is ahead of B. If you capture stills by A and then act burst shooting by B with the same SD card, you will get stills captured by A, because the 'newest' contents are determined by timestamp.

# File lib/sony_camera_remote_api.rb, line 936
def transfer_in_burst_mode(url, prefix: nil, dir: nil)
  transfer_size = get_transfer_size
  change_function_to_transfer
  # As of now, burst shooting mode always capture 10 still images.
  contents = get_content_list type: 'still', sort: 'descending', count: 10
  if prefix.present?
    filenames = generate_sequencial_filenames prefix, 'JPG', num: 10
  else
    filenames = contents.map { |c| c['content']['original'][0]['fileName'] }
  end
  transferred = transfer_contents contents, filenames, size: transfer_size, dir: dir, add_postfix: false
  change_function_to_shoot 'still', 'Burst'
  transferred
end
transfer_interval_stills(num_shots, prefix: nil, dir: nil) click to toggle source
# File lib/sony_camera_remote_api.rb, line 961
def transfer_interval_stills(num_shots, prefix: nil, dir: nil)
  transfer_size = get_transfer_size
  change_function_to_transfer
  contents = get_content_list type: 'still', sort: 'descending', count: num_shots
  if prefix
    filenames = generate_sequencial_filenames prefix, 'JPG', num: contents.size
    transferred = transfer_contents contents, filenames, dir: dir, add_postfix: false
  else
    transferred = transfer_contents contents, size: transfer_size, dir: dir, add_postfix: false
  end
  change_function_to_shoot 'intervalstill'
  transferred
end
transfer_postview(url, filename, dir: nil) click to toggle source

Transfer a postview

# File lib/sony_camera_remote_api.rb, line 893
def transfer_postview(url, filename, dir: nil)
  filepath = dir ? File.join(dir, filename) : filename
  log.info "Transferring #{filepath}..."
  FileUtils.mkdir_p dir if dir
  result = true
  time = Benchmark.realtime do
    result = @retrying.reconnect_and_give_up do
      open(filepath, 'wb') do |file|
        @http.get_content(url) do |chunk|
          file.write chunk
        end
      end
      true
    end
  end
  if result
    log.info "Transferred #{filepath}. (#{format('%.2f', time)} sec)"
    filepath
  else
    log.info "Failed to transfer #{filepath}. (#{format('%.2f', time)} sec)"
    nil
  end
end
transfer_recorded_movie(filename: nil, dir: nil) click to toggle source
# File lib/sony_camera_remote_api.rb, line 952
def transfer_recorded_movie(filename: nil, dir: nil)
  change_function_to_transfer
  content = get_content_list type: ['movie_mp4', 'movie_xavcs'], sort: 'descending', count: 1
  transferred = transfer_contents content, filename, dir: dir
  change_function_to_shoot 'movie'
  transferred[0] if !transferred.empty?
end
zoom_by_1shot(current, relative) click to toggle source

1shot zoom

# File lib/sony_camera_remote_api.rb, line 1038
def zoom_by_1shot(current, relative)
  # Return if relative is lesser than threshold
  return [current, relative] if relative.abs < SHORT_ZOOM_THRESHOLD

  absolute = current + relative
  log.debug "  Short zoom start: #{current} -> #{absolute}"

  diff = relative
  while true
    if diff > 0
      log.debug '    in'
      actZoom ['in', '1shot']
    elsif diff < 0
      log.debug '    out'
      actZoom ['out', '1shot']
    else
      break
    end
    pos = wait_event(polling: true) { |r| r[2]['zoomPosition'] }[2]['zoomPosition']
    diff = absolute - pos
    break if diff.abs < SHORT_ZOOM_THRESHOLD
  end

  # Wait for the lense stops completely
  final = current
  loop do
    begin
      final = wait_event(timeout: SHORT_ZOOM_FINISH_TIMEOUT) { |r| r[2]['zoomPosition'] != final }[2]['zoomPosition']
    rescue EventTimeoutError => e
      break
    end
  end

  log.debug "  Short zoom finished: #{current} -> #{final} (target: #{absolute})"
  [final, absolute - final]
end
zoom_by_long_push(current, relative) click to toggle source

Long push zoom is tend to go through the desired potision, so LONG_ZOOM_THRESHOLD is a important parameter.

# File lib/sony_camera_remote_api.rb, line 996
def zoom_by_long_push(current, relative)
  # Return if relative is lesser than threshold
  return [current, relative] if relative.abs < LONG_ZOOM_THRESHOLD

  absolute = current + relative
  log.debug "  Long zoom start: #{current} -> #{absolute}"
  case
    when relative > 0
      target = absolute - LONG_ZOOM_THRESHOLD
      dir = 'in'
      condition = ->(r) { r[2]['zoomPosition'] > target }
    when relative < 0
      target = absolute + LONG_ZOOM_THRESHOLD
      dir = 'out'
      condition = ->(r) { r[2]['zoomPosition'] < target }
    else
      return [current, relative]
  end
  log.debug "    stopping line: #{target}"

  actZoom [dir, 'start']
  wait_event(polling: true, &condition)
  actZoom [dir, 'stop']

  # Wait for the lense stops completely
  final = current
  loop do
    begin
      final = wait_event(timeout: LONG_ZOOM_FINISH_TIMEOUT) { |r| r[2]['zoomPosition'] != final }[2]['zoomPosition']
    rescue EventTimeoutError => e
      break
    end
  end

  log.debug "  Long zoom finished: #{current} -> #{final} (target: #{absolute})"
  [final, absolute - final]
end
zoom_until_end(absolute) click to toggle source

Zoom until wide-end or tele-end.

# File lib/sony_camera_remote_api.rb, line 977
def zoom_until_end(absolute)
  case
    when absolute == 100
      actZoom ['in', 'start']
      wait_event(polling: true) { |r| r[2]['zoomPosition'] == absolute }
      actZoom ['in', 'stop']
    when absolute == 0
      actZoom ['out', 'start']
      wait_event(polling: true) { |r| r[2]['zoomPosition'] == absolute }
      actZoom ['out', 'stop']
  end
  absolute
end