Class: SonyCameraRemoteAPI::Camera

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Logging, SSDP, Utils
Defined in:
lib/sony_camera_remote_api.rb

Overview

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

Constant Summary collapse

CONT_SHOOT_SAVING_TIME =

Timeout for saving images captured by continous shooting.

25
TRACKING_FOCUS_TIMEOUT =

Timeout for focusing by tracking focus.

4
TRANSFER_SIZE_LIST =

Predefined transfer sizes

%w(original large small thumbnail).freeze

Constants included from SSDP

SSDP::SSDP_RETRY_INTERVAL, SSDP::SSDP_SEARCH_RETRY, SSDP::SSDP_SEARCH_TARGET

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

generate_sequencial_filenames, get_next_file_number, partial_and_unique_match, print_array_in_columns

Methods included from SSDP

#parse_device_description, #ssdp_search

Methods included from Logging

configure_logger_for, #log, log_file, logger_for, #set_level, #set_output

Constructor Details

#initialize(shelf = nil, reconnect_by: nil, log_file: $stdout, log_level: Logger::INFO, finalize: false) ⇒ Camera

Note:

It is good idea to save endpoint URLs by each cameras somewhere to omit SSDP search.

Creates a new Camera object.

Parameters:

  • shelf (Shelf) (defaults to: nil)

    Shelf class object that is used for connection.

  • reconnect_by (Proc)

    Hook method to reconnect to the camera, which is called when Wi-Fi is disconnected. Not necessary if shelf is given.

  • log_file (String, IO, Array<String, IO>)

    File name or stream to output log.

  • finalize (Boolean)

    If true, stopRecMode API is called in the destructor. As far as I know, we have no problem even if we never call stopRecMode.


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/sony_camera_remote_api.rb', line 47

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
  @cli = HTTPClient.new
  @cli.connect_timeout  = @cli.send_timeout = @cli.receive_timeout = 30

  # 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

Instance Attribute Details

#endpointsObject (readonly)

Returns the value of attribute endpoints


31
32
33
# File 'lib/sony_camera_remote_api.rb', line 31

def endpoints
  @endpoints
end

Class Method Details

.finalize(this) ⇒ Object

Destructor: this calls stopRecMode API to finish shooting.


75
76
77
78
79
80
# File 'lib/sony_camera_remote_api.rb', line 75

def self.finalize(this)
  proc do
    this.stopRecMode!
    this.log.info 'Finished remote shooting function.'
  end
end

Instance Method Details

#act_focusBoolean

Note:

You have to set shooting mode to 'still' before calling this method.

Do focus, which is the same as half-pressing the shutter button.

Examples:

# 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

Returns:

  • (Boolean)

    true if focus succeeded, false if failed.


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/sony_camera_remote_api.rb', line 395

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) ⇒ Boolean

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.

Do touch focus, by which we can specify the focus position.

Examples:

# 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

Parameters:

  • x (Fixnum)

    Percentage of X-axis position.

  • y (Fixnum)

    Percentage of Y-axis position.

Returns:

  • (Boolean)

    AFType ('Touch' or 'Wide') if focus succeeded. nil if failed.

See Also:

  • AF position parameter in API reference

444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/sony_camera_remote_api.rb', line 444

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) ⇒ Boolean

Note:

You have to set shooting mode to 'still' before calling this method.

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.

Examples:

# 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

Parameters:

  • x (Fixnum)

    Percentage of X-axis position.

  • y (Fixnum)

    Percentage of Y-axis position.

Returns:

  • (Boolean)

    true if focus succeeded, false if failed.


494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/sony_camera_remote_api.rb', line 494

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) ⇒ Array<Fixnum>

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.

Examples:

# 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

Parameters:

  • absolute (Fixnum)

    Absolute position of the lense. 0 is the Wide-end and 100 is the Tele-end.

  • relative (Fixnum)

    Relative percecntage to current position of the lense.

Returns:

  • (Array<Fixnum>)

    Array of initial zoom position and current zoom position.


343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/sony_camera_remote_api.rb', line 343

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_focusvoid

This method returns an undefined value.

Cancel focus If camera has been focused.

Examples:

# 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

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/sony_camera_remote_api.rb', line 545

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) ⇒ String, ...

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

Capture still image(s) and transfer them to local storage.

Examples:

# 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'

Parameters:

  • transfer (Boolean)

    Flag to transfer the postview image.

  • filename (String)

    Name of image file to be transferred. If not given, original name is used. Only available in Single/MotionShot shooting mode.

  • prefix (String)

    Prefix of sequential image files to be transferred. If not given, original name is used. Only available in Burst shooting mode.

  • dir (String)

    Directory where image file is saved. If not given, current directory is used.

Returns:

  • (String, Array<String>, nil)

    Filename of the transferred image(s). If 'transfer' is false, returns nil.


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/sony_camera_remote_api.rb', line 136

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) ⇒ void

TODO:

make mode argument nullable

This method returns an undefined value.

Change camera function to 'Remote Shooting' and then set shooting mode.

Parameters:

  • mode (String)

    Shoot mode

  • cont (String) (defaults to: nil)

    Continuous shooting mode (only available when shoot mode is 'still')

See Also:

  • mode parameters' in API reference
  • shooting mode parameter' in API reference

90
91
92
93
94
95
96
97
# File 'lib/sony_camera_remote_api.rb', line 90

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_transfervoid

This method returns an undefined value.

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

  • get_content_list

  • get_date_list

  • transfer_contents

  • delete_contents


107
108
109
# File 'lib/sony_camera_remote_api.rb', line 107

def change_function_to_transfer
  set_parameter :CameraFunction, 'Contents Transfer'
end

#delete_contents(contents) ⇒ Object

Note:

You have to set camera function to 'Contents Transfer' before calling this method.

Delete content(s) of camera storage.

Examples:

# 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)

Parameters:

  • contents (Array<Hash>)

    array of content hashes, which can be obtained by get_content_list


868
869
870
871
872
873
874
875
876
877
878
# File 'lib/sony_camera_remote_api.rb', line 868

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?Boolean

Return whether the camera has focused or not.

Returns:

  • (Boolean)

    true if focused, false otherwise.

See Also:


517
518
519
520
# File 'lib/sony_camera_remote_api.rb', line 517

def focused?
  result = getEvent(false).result
  result[35] && result[35]['focusStatus'] == 'Focused'
end

#get_content_list(type: nil, date: nil, sort: 'descending', count: nil) ⇒ Array<Hash>

Note:

You have to set camera function to 'Contents Transfer' before calling this method.

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.

Examples:

# 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)

Parameters:

  • type (String, Array<String>)

    Same as 'type' request parameter of getContentList API.

  • date (Boolean)

    Date in format of 'YYYYMMDD' used in date-view. If not specified, flat-view is used.

  • sort (String)

    Same as 'sort' request parameter of getContentList API.

  • count (Fixnum)

    Number of contents to get. Unlike the one of request parameter of getContentList API, you can specify over 100.

Returns:

  • (Array<Hash>)

    Content informations

See Also:

  • API in the API reference.

709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
# File 'lib/sony_camera_remote_api.rb', line 709

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) ⇒ Array<Hash>

Note:

You have to set camera function to 'Contents Transfer' before calling this method.

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.

Examples:

# 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

Parameters:

  • type (String, Array<String>)

    Same as 'type' request parameter of getContentList API

  • sort (String)

    Same as 'sort' request parameter of getContentList API

  • date_count (Fixnum)

    Number of dates to get.

  • content_count (Fixnum)

    Number of contents to get

Returns:

  • (Array<Hash>)

    An array of dates in format of 'YYYYMMdd' and an array of number of contents of the associated date.


780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
# File 'lib/sony_camera_remote_api.rb', line 780

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_shootingvoid

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'.

This method returns an undefined value.

Start continuous shooting. To stop shooting, call stop_continuous_shooting method.

Examples:

# 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)

176
177
178
179
180
181
# File 'lib/sony_camera_remote_api.rb', line 176

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_recordingvoid

Note:

You have to set shooting mode to 'intervalstill' before calling this method.

This method returns an undefined value.

Start interval still recording (a.k.a Timelapse). To stop recording, call stop_interval_recording method.

Examples:

# 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

268
269
270
271
272
273
# File 'lib/sony_camera_remote_api.rb', line 268

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) {|LiveviewImage, LiveviewFrameInformation| ... } ⇒ Thread

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.

Examples:

# 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

Parameters:

  • size (String)

    The liveview size.

  • time (Fixnum)

    Time in seconds until finishing liveview streaming.

Yields:

Yield Parameters:

  • liveview (LiveviewImage)

    image of each frame.

  • liveview (LiveviewFrameInformation)

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

Returns:

  • (Thread)

    liveview downloading thread object


586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# File 'lib/sony_camera_remote_api.rb', line 586

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
        reconnect_and_retry_forever do
          # Retrieve streaming data
          @cli.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
    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_recordingvoid

Note:

You have to set shooting mode to 'looprec' before calling this method.

This method returns an undefined value.

Start loop recording. To stop recording, call stop_loop_recording method.

Examples:

# 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

306
307
308
309
310
311
# File 'lib/sony_camera_remote_api.rb', line 306

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_recordingvoid

Note:

You have to set shooting mode to 'movie' before calling this method.

This method returns an undefined value.

Start movie recording. To stop recording, call stop_movie_recording method.

Examples:

# 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)

231
232
233
234
235
236
# File 'lib/sony_camera_remote_api.rb', line 231

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) ⇒ Array<String>?

Note:

'transfer' flag is set false as default, because transfer time is prone to be much longer.

Stop continuous shooting and transfers all still images.

Parameters:

  • transfer (Boolean)

    Flag to transfer the captured images.

  • prefix (String)

    Prefix of of sequencial image files to be transferred. If not given, original name is used.

  • dir (String)

    Directory where image file is saved. If not given, current directory is used.

Returns:

  • (Array<String>, nil)

    List of filenames of the transferred images. If 'transfer' is false, returns nil.


190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/sony_camera_remote_api.rb', line 190

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) ⇒ Array<String>?

Note:

'transfer' flag is set false as default, because transfer time is prone to be much longer.

Stop interval still recording and transfers all still images.

Parameters:

  • transfer (Boolean)

    Flag to transfer still images

  • prefix (String)

    Prefix of sequencial image files to be transferred. If not given, original name is used.

  • dir (String)

    Directory where image file is saved. If not given, current directory is used.

Returns:

  • (Array<String>, nil)

    List of filenames of the transferred images. If 'transfer' is false, returns nil.


282
283
284
285
286
287
288
289
290
# File 'lib/sony_camera_remote_api.rb', line 282

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) ⇒ String?

Note:

'transfer' flag is set false as default, because transfer time is prone to be much longer.

Stop loop recording and transfers the movie file.

Parameters:

  • transfer (Boolean)

    Flag to transfer the recorded movie file

  • filename (String)

    Name of the movie file to be transferred. If not given, original name is used.

  • dir (String)

    Directory where image file is saved. If not given, current directory is used.

Returns:

  • (String, nil)

    Filename of the transferred movie. If 'transfer' is false, returns nil.


320
321
322
323
324
325
326
327
# File 'lib/sony_camera_remote_api.rb', line 320

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) ⇒ String?

Note:

'transfer' flag is set false as default, because transfer time is prone to be much longer.

Stop movie recording and transfers the movie file.

Parameters:

  • transfer (Boolean)

    Flag to transfer the recorded movie file.

  • filename (String)

    Name of the movie file to be transferred. If not given, original name is used.

  • dir (String)

    Directory where image file is saved. If not given, current directory is used.

Returns:

  • (String, nil)

    Filename of the transferred movie. If 'transfer' is false, returns nil.


245
246
247
248
249
250
251
252
# File 'lib/sony_camera_remote_api.rb', line 245

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?Boolean

Return whether the camera is tracking an object for tracking focus.

Returns:

  • (Boolean)

    true if focused, false otherwise.

See Also:


526
527
528
529
# File 'lib/sony_camera_remote_api.rb', line 526

def tracking?
  result = getEvent(false).result
  result[54] && result[54]['trackingFocusStatus'] == 'Tracking'
end

#transfer_contents(contents, filenames = [], dir: nil, size: 'original') ⇒ Object

TODO:

If 'contents' is directory (date), get all contents of the directory.

Note:

You have to set camera function to 'Contents Transfer' before calling this method.

Transfer content(s) from the camera storage.

Parameters:

  • contents (Array<Hash>)

    Array of content information, which can be obtained by get_content_list

  • filenames (Array<String>) (defaults to: [])

    Array of filename strings

  • size (String)

    Content size. available values are 'original', 'large', 'small', 'thumbnail'.

See Also:


822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
# File 'lib/sony_camera_remote_api.rb', line 822

def transfer_contents(contents, filenames=[], dir: nil, size: 'original')
  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)
  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