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
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, #output_to

Constructor Details

#initialize(endpoints: nil, reconnect_by: nil, log_file: $stdout, 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:

  • endpoints (Hash) (defaults to: nil)

    Endpoint URLs. if not given, SSDP search is executed.

  • reconnect_by (Proc) (defaults to: nil)

    Hook method called when Wi-Fi is disconnected.

  • log_file (String, IO, Array<String, IO>) (defaults to: $stdout)

    file name or stream to output log.

  • finalize (Boolean) (defaults to: false)

    If true, stopRecMode API is called in the destructor. As far as I know, we don’t have any trouble even if we never call stopRecMode.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/sony_camera_remote_api.rb', line 44

def initialize(endpoints: nil, reconnect_by: nil, log_file: $stdout, finalize: false)
  output_to log_file if log_file.present?
  @endpoints = endpoints || ssdp_search
  @reconnect_by = 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.



29
30
31
# File 'lib/sony_camera_remote_api.rb', line 29

def endpoints
  @endpoints
end

Class Method Details

.finalize(this) ⇒ Object

Destructor



63
64
65
66
67
68
# File 'lib/sony_camera_remote_api.rb', line 63

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

Instance Method Details

#act_focus(force: false) ⇒ Boolean

Act focus, which is the same as half-pressing shutter button. If already focued, this method does nothing unless ‘force’ parameter specified as true.

Examples:

# Try to focus on and succeeded.
act_focus     #=> true

Parameters:

  • force (Boolean) (defaults to: false)

    Re-forcus if the camera has already focused.

Returns:

  • (Boolean)

    true if focus succeeded, false if failed.



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/sony_camera_remote_api.rb', line 359

def act_focus(force: false)
  return false unless support? :actHalfPressShutter
  return true  unless needs_focus?(force: force)
  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, force: false) ⇒ Boolean

Note:

Tracking focus and Touch focus is exclusive function. Tracking focus is disabled automatically by calling this method.

Act touch focus, by which we can specify the focus position. The focus position is expressed by percentage to the origin of coordinates, which is upper left of liveview images. If already focued, this method does nothing unless ‘force’ parameter specified as true.

Examples:

# Try to focus on bottom-left position and succeeded with 'Wide' type focus.
act_touch_focus(10, 90)     #=> 'Wide'
# Try to focus on upper-right position but failed.
act_touch_focus(90, 10)     #=> nil

Parameters:

  • x (Fixnum)

    Percentage of X-axis position.

  • y (Fixnum)

    Percentage of Y-axis position.

  • force (Boolean) (defaults to: false)

    Re-forcus if the camera has already focused.

Returns:

  • (Boolean)

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

See Also:

  • AF position parameter in API reference


391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/sony_camera_remote_api.rb', line 391

def act_touch_focus(x, y, force: false)
  return false unless support? :setTouchAFPosition
  return true  unless needs_focus?(force: force)
  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, force: false) ⇒ Boolean

Act trackig 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. If already focued, this method does nothing unless ‘force’ parameter specified as true.

Examples:

# Act tracking focus from the center position, and succeeded to start tracking.
act_tracking_focus(50, 50)    #=> true

Parameters:

  • x (Fixnum)

    Percentage of X-axis position.

  • y (Fixnum)

    Percentage of Y-axis position.

  • force (Boolean) (defaults to: false)

    Re-forcus if the camera has already focused.

Returns:

  • (Boolean)

    true if focus succeeded, false if failed.



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/sony_camera_remote_api.rb', line 420

def act_tracking_focus(x, y, force: false)
  return false unless support_group? :TrackingFocus
  return true  unless needs_focus?(force: force)
  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>

Act 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:

act_zoom(absolute: 0)     # zoom out to the wide-end
act_zoom(absolute: 100)   # zoom in to the tele-end
act_zoom(relative: -50)   # zoom out by -50 from the current position

Parameters:

  • absolute (Fixnum) (defaults to: nil)

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

  • relative (Fixnum) (defaults to: nil)

    Relative percecntage to current position of the lense.

Returns:

  • (Array<Fixnum>)

    Array of initial zoom position and current zoom position.



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/sony_camera_remote_api.rb', line 314

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 all type of focuses (half press, touch focus, tracking focus).



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/sony_camera_remote_api.rb', line 449

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(focus: true, 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:

# Capture single still image and save it as 'a.jpg' to 'image' directory
change_function_to_shoot('still', 'Single')
capture_still
capture_still(filename: 'a.jpg', dir: 'image')

# Capture 10 images by burst shooting mode and save them as 'TEST_0.jpg', ... 'TEST_9.jpg'.
change_function_to_shoot('still', 'Burst')
capture_still
capture_still(prefix: 'TEST')

Parameters:

  • focus (Boolean) (defaults to: true)

    Flag to focus on before capturing.

  • transfer (Boolean) (defaults to: true)

    Flag to transfer the postview image.

  • filename (String) (defaults to: nil)

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

  • prefix (String) (defaults to: nil)

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

  • dir (String) (defaults to: nil)

    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.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/sony_camera_remote_api.rb', line 123

def capture_still(focus: true, transfer: true, filename: nil, prefix: nil, dir: nil)
  act_focus if focus
  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

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)

    Continous shooting mode (only available when shoot mode is ‘still’)

See Also:

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


77
78
79
80
81
82
83
84
# File 'lib/sony_camera_remote_api.rb', line 77

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



94
95
96
# File 'lib/sony_camera_remote_api.rb', line 94

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:

Typical usage:

change_function_to_transfer
contents = get_content_list(type: still, count: 10)     # get 10 newest still contents
delete_contents(contents)                               # delete them

Parameters:

  • contents (Array<Hash>)

    array of content hashes, which can be obtained by get_content_list



737
738
739
740
741
742
743
744
745
746
747
# File 'lib/sony_camera_remote_api.rb', line 737

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.



441
442
443
444
# File 'lib/sony_camera_remote_api.rb', line 441

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:

Typical usage:

change_function_to_transfer

# Get all contents in the storage
get_content_list
# Get still contents captured on 2016/8/1
get_content_list(type: 'still', date: '20160801')
# Get 3 oldest XAVC-S movie contents
get_content_list(type: 'movie_xavcs', sort: 'ascending', count: 3)

Parameters:

  • type (String, Array<String>) (defaults to: nil)

    Same as ‘type’ request parameter of getContentList API.

  • date (Boolean) (defaults to: nil)

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

  • sort (String) (defaults to: 'descending')

    Same as ‘sort’ request parameter of getContentList API.

  • count (Fixnum) (defaults to: nil)

    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.


585
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
# File 'lib/sony_camera_remote_api.rb', line 585

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, cnt = get_date_list.find { |d| d[0]['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_counts = get_date_list type: type, sort: sort
      contents = []
      if count.present?
        dates_counts.each do |date, cnt_of_date|
          num = [cnt_of_date, 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_counts.each do |date, cnt_of_date|
          contents += get_content_list_sub date['uri'], type: type, view: 'date', sort: sort, count: cnt_of_date
        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< Array<String, Fixnum> >

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:

Typical usage:

change_function_to_transfer
# Get all dates and the number of contents of the date
get_date_list
# Get 5 newest dates that contains at least one MP4 and XAVC-S movie content.
get_date_list(type: ['movie_mp4','movie_xavcs'], date_count: 5)
# Get all dates that contains at least 10 still contents.
get_date_list(type: 'still', content_count: 10)

Parameters:

  • type (String, Array<String>) (defaults to: nil)

    Same as ‘type’ request parameter of getContentList API

  • sort (String) (defaults to: 'descending')

    Same as ‘sort’ request parameter of getContentList API

  • date_count (Fixnum) (defaults to: nil)

    Number of dates to get.

  • content_count (Fixnum) (defaults to: nil)

    Number of contents to get

Returns:

  • (Array< Array<String, Fixnum> >)

    Array of pairs of a date in format of ‘YYYYMMDD’ and a number of contents of the date.



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
# File 'lib/sony_camera_remote_api.rb', line 644

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.
    filtered_dates << [d, cnt] if cnt > 0
    # Break if contents count exceeds.
    break if content_count and filtered_dates.map { |d, c| c }.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(focus: true) ⇒ void

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:

Do continuous shooting and transfer:

change_function_to_shoot('still', 'Continuous')
start_continuous_shooting
...
stop_continuous_shooting(transfer: true)

Parameters:

  • focus (Boolean) (defaults to: true)

    Flag to focus on before capturing.



161
162
163
164
165
166
167
# File 'lib/sony_camera_remote_api.rb', line 161

def start_continuous_shooting(focus: true)
  act_focus if focus
  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:

Do interval still recording:

change_function_to_shoot('intervalstill')
start_interval_recording
...
stop_interval_recording(transfer: true)


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

def start_interval_recording
  act_focus
  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.

Parameters:

  • size (String) (defaults to: nil)

    The liveview size.

  • time (Fixnum) (defaults to: nil)

    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



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/sony_camera_remote_api.rb', line 478

def start_liveview_thread(size: nil, time: nil)
  liveview_url = 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}"
                  block_time = Benchmark.realtime do
                    yield(LiveviewImage.new(obj), frame_info)
                  end
                  log.info "block time     : #{format('%.2f', block_time*1000)} ms."
                  count += 1
                when 0x02
                  # When payload is liveview frame information
                  log.info "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:

Typical usage:

change_function_to_shoot('looprec')
start_loop_recording
...
stop_loop_recording(transfer: true)


280
281
282
283
284
285
# File 'lib/sony_camera_remote_api.rb', line 280

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:

Record movie and transfer:

change_function_to_shoot('movie')
start_movie_recording
...
stop_movie_recording(transfer: true)


213
214
215
216
217
218
# File 'lib/sony_camera_remote_api.rb', line 213

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) (defaults to: false)

    Flag to transfer the captured images.

  • prefix (String) (defaults to: nil)

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

  • dir (String) (defaults to: nil)

    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.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/sony_camera_remote_api.rb', line 176

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) (defaults to: false)

    Flag to transfer still images

  • prefix (String) (defaults to: nil)

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

  • dir (String) (defaults to: nil)

    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.



260
261
262
263
264
265
266
267
268
# File 'lib/sony_camera_remote_api.rb', line 260

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) (defaults to: false)

    Flag to transfer the recorded movie file

  • filename (String) (defaults to: nil)

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

  • dir (String) (defaults to: nil)

    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.



294
295
296
297
298
299
300
301
# File 'lib/sony_camera_remote_api.rb', line 294

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) (defaults to: false)

    Flag to transfer the recorded movie file.

  • filename (String) (defaults to: nil)

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

  • dir (String) (defaults to: nil)

    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.



227
228
229
230
231
232
233
234
# File 'lib/sony_camera_remote_api.rb', line 227

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

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

Note:

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

Transfer content(s) from the camera storage.

Examples:

Typical usage:

change_function_to_transfer
contents = get_content_list(type: 'still', count: 10)     # get 10 newest still contents
transfer_contents(contents)                             # transfer them

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) (defaults to: 'original')

    Content size. available values are ‘original’, ‘large’, ‘small’, ‘thumbnail’.



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
# File 'lib/sony_camera_remote_api.rb', line 684

def transfer_contents(contents, filenames=[], dir: nil, size: 'original')
  if SIZE_LIST.exclude?(size)
    log.error "#{size} is invalid for size option!"
    return nil
  end

  contents = [contents].compact unless contents.is_a? Array
  filenames = [filenames].compact unless filenames.is_a? Array
  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 = contents.zip(filenames).map do |content, filename|
    next unless content
    url =
        case size
          when 'original'
            raise StandardError if content['content']['original'].size > 1 # FIXME: When do we come here???
            content['content']['original'][0]['url']
          when 'large'
            content['content']['largeUrl']
          when 'small'
            content['content']['smallUrl']
          when 'thumbnail'
            content['content']['thumbnailUrl']
        end
    filename ||= content['content']['original'][0]['fileName']
    [url, filename]
  end

  log.info "#{contents.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