HAP-python 4.9.2__py3-none-any.whl → 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyhap/camera.py CHANGED
@@ -22,205 +22,209 @@ import ipaddress
22
22
  import logging
23
23
  import os
24
24
  import struct
25
+ import sys
25
26
  from uuid import UUID
26
27
 
27
- import async_timeout
28
-
29
28
  from pyhap import RESOURCE_DIR, tlv
30
29
  from pyhap.accessory import Accessory
31
30
  from pyhap.const import CATEGORY_CAMERA
32
31
  from pyhap.util import byte_bool, to_base64_str
33
32
 
33
+ if sys.version_info >= (3, 11):
34
+ from asyncio import timeout as async_timeout
35
+ else:
36
+ from async_timeout import timeout as async_timeout
37
+
34
38
  SETUP_TYPES = {
35
- 'SESSION_ID': b'\x01',
36
- 'STATUS': b'\x02',
37
- 'ADDRESS': b'\x03',
38
- 'VIDEO_SRTP_PARAM': b'\x04',
39
- 'AUDIO_SRTP_PARAM': b'\x05',
40
- 'VIDEO_SSRC': b'\x06',
41
- 'AUDIO_SSRC': b'\x07'
39
+ "SESSION_ID": b"\x01",
40
+ "STATUS": b"\x02",
41
+ "ADDRESS": b"\x03",
42
+ "VIDEO_SRTP_PARAM": b"\x04",
43
+ "AUDIO_SRTP_PARAM": b"\x05",
44
+ "VIDEO_SSRC": b"\x06",
45
+ "AUDIO_SSRC": b"\x07",
42
46
  }
43
47
 
44
48
 
45
49
  SETUP_STATUS = {
46
- 'SUCCESS': b'\x00',
47
- 'BUSY': b'\x01',
48
- 'ERROR': b'\x02'
50
+ "SUCCESS": b"\x00",
51
+ "BUSY": b"\x01",
52
+ "ERROR": b"\x02",
49
53
  }
50
54
 
51
55
 
52
56
  SETUP_IPV = {
53
- 'IPV4': b'\x00',
54
- 'IPV6': b'\x01'
57
+ "IPV4": b"\x00",
58
+ "IPV6": b"\x01",
55
59
  }
56
60
 
57
61
 
58
62
  SETUP_ADDR_INFO = {
59
- 'ADDRESS_VER': b'\x01',
60
- 'ADDRESS': b'\x02',
61
- 'VIDEO_RTP_PORT': b'\x03',
62
- 'AUDIO_RTP_PORT': b'\x04'
63
+ "ADDRESS_VER": b"\x01",
64
+ "ADDRESS": b"\x02",
65
+ "VIDEO_RTP_PORT": b"\x03",
66
+ "AUDIO_RTP_PORT": b"\x04",
63
67
  }
64
68
 
65
69
 
66
70
  SETUP_SRTP_PARAM = {
67
- 'CRYPTO': b'\x01',
68
- 'MASTER_KEY': b'\x02',
69
- 'MASTER_SALT': b'\x03'
71
+ "CRYPTO": b"\x01",
72
+ "MASTER_KEY": b"\x02",
73
+ "MASTER_SALT": b"\x03",
70
74
  }
71
75
 
72
76
 
73
77
  STREAMING_STATUS = {
74
- 'AVAILABLE': b'\x00',
75
- 'STREAMING': b'\x01',
76
- 'BUSY': b'\x02'
78
+ "AVAILABLE": b"\x00",
79
+ "STREAMING": b"\x01",
80
+ "BUSY": b"\x02",
77
81
  }
78
82
 
79
83
 
80
84
  RTP_CONFIG_TYPES = {
81
- 'CRYPTO': b'\x02'
85
+ "CRYPTO": b"\x02",
82
86
  }
83
87
 
84
88
 
85
89
  SRTP_CRYPTO_SUITES = {
86
- 'AES_CM_128_HMAC_SHA1_80': b'\x00',
87
- 'AES_CM_256_HMAC_SHA1_80': b'\x01',
88
- 'NONE': b'\x02'
90
+ "AES_CM_128_HMAC_SHA1_80": b"\x00",
91
+ "AES_CM_256_HMAC_SHA1_80": b"\x01",
92
+ "NONE": b"\x02",
89
93
  }
90
94
 
91
95
 
92
96
  VIDEO_TYPES = {
93
- 'CODEC': b'\x01',
94
- 'CODEC_PARAM': b'\x02',
95
- 'ATTRIBUTES': b'\x03',
96
- 'RTP_PARAM': b'\x04'
97
+ "CODEC": b"\x01",
98
+ "CODEC_PARAM": b"\x02",
99
+ "ATTRIBUTES": b"\x03",
100
+ "RTP_PARAM": b"\x04",
97
101
  }
98
102
 
99
103
 
100
104
  VIDEO_CODEC_TYPES = {
101
- 'H264': b'\x00'
105
+ "H264": b"\x00",
102
106
  }
103
107
 
104
108
 
105
109
  VIDEO_CODEC_PARAM_TYPES = {
106
- 'PROFILE_ID': b'\x01',
107
- 'LEVEL': b'\x02',
108
- 'PACKETIZATION_MODE': b'\x03',
109
- 'CVO_ENABLED': b'\x04',
110
- 'CVO_ID': b'\x05'
110
+ "PROFILE_ID": b"\x01",
111
+ "LEVEL": b"\x02",
112
+ "PACKETIZATION_MODE": b"\x03",
113
+ "CVO_ENABLED": b"\x04",
114
+ "CVO_ID": b"\x05",
111
115
  }
112
116
 
113
117
 
114
118
  VIDEO_CODEC_PARAM_CVO_TYPES = {
115
- 'UNSUPPORTED': b'\x01',
116
- 'SUPPORTED': b'\x02'
119
+ "UNSUPPORTED": b"\x01",
120
+ "SUPPORTED": b"\x02",
117
121
  }
118
122
 
119
123
 
120
124
  VIDEO_CODEC_PARAM_PROFILE_ID_TYPES = {
121
- 'BASELINE': b'\x00',
122
- 'MAIN': b'\x01',
123
- 'HIGH': b'\x02'
125
+ "BASELINE": b"\x00",
126
+ "MAIN": b"\x01",
127
+ "HIGH": b"\x02",
124
128
  }
125
129
 
126
130
 
127
131
  VIDEO_CODEC_PARAM_LEVEL_TYPES = {
128
- 'TYPE3_1': b'\x00',
129
- 'TYPE3_2': b'\x01',
130
- 'TYPE4_0': b'\x02'
132
+ "TYPE3_1": b"\x00",
133
+ "TYPE3_2": b"\x01",
134
+ "TYPE4_0": b"\x02",
131
135
  }
132
136
 
133
137
 
134
138
  VIDEO_CODEC_PARAM_PACKETIZATION_MODE_TYPES = {
135
- 'NON_INTERLEAVED': b'\x00'
139
+ "NON_INTERLEAVED": b"\x00",
136
140
  }
137
141
 
138
142
 
139
143
  VIDEO_ATTRIBUTES_TYPES = {
140
- 'IMAGE_WIDTH': b'\x01',
141
- 'IMAGE_HEIGHT': b'\x02',
142
- 'FRAME_RATE': b'\x03'
144
+ "IMAGE_WIDTH": b"\x01",
145
+ "IMAGE_HEIGHT": b"\x02",
146
+ "FRAME_RATE": b"\x03",
143
147
  }
144
148
 
145
149
 
146
- SUPPORTED_VIDEO_CONFIG_TAG = b'\x01'
150
+ SUPPORTED_VIDEO_CONFIG_TAG = b"\x01"
147
151
 
148
152
 
149
153
  SELECTED_STREAM_CONFIGURATION_TYPES = {
150
- 'SESSION': b'\x01',
151
- 'VIDEO': b'\x02',
152
- 'AUDIO': b'\x03'
154
+ "SESSION": b"\x01",
155
+ "VIDEO": b"\x02",
156
+ "AUDIO": b"\x03",
153
157
  }
154
158
 
155
159
 
156
160
  RTP_PARAM_TYPES = {
157
- 'PAYLOAD_TYPE': b'\x01',
158
- 'SYNCHRONIZATION_SOURCE': b'\x02',
159
- 'MAX_BIT_RATE': b'\x03',
160
- 'RTCP_SEND_INTERVAL': b'\x04',
161
- 'MAX_MTU': b'\x05',
162
- 'COMFORT_NOISE_PAYLOAD_TYPE': b'\x06'
161
+ "PAYLOAD_TYPE": b"\x01",
162
+ "SYNCHRONIZATION_SOURCE": b"\x02",
163
+ "MAX_BIT_RATE": b"\x03",
164
+ "RTCP_SEND_INTERVAL": b"\x04",
165
+ "MAX_MTU": b"\x05",
166
+ "COMFORT_NOISE_PAYLOAD_TYPE": b"\x06",
163
167
  }
164
168
 
165
169
 
166
170
  AUDIO_TYPES = {
167
- 'CODEC': b'\x01',
168
- 'CODEC_PARAM': b'\x02',
169
- 'RTP_PARAM': b'\x03',
170
- 'COMFORT_NOISE': b'\x04'
171
+ "CODEC": b"\x01",
172
+ "CODEC_PARAM": b"\x02",
173
+ "RTP_PARAM": b"\x03",
174
+ "COMFORT_NOISE": b"\x04",
171
175
  }
172
176
 
173
177
 
174
178
  AUDIO_CODEC_TYPES = {
175
- 'PCMU': b'\x00',
176
- 'PCMA': b'\x01',
177
- 'AACELD': b'\x02',
178
- 'OPUS': b'\x03'
179
+ "PCMU": b"\x00",
180
+ "PCMA": b"\x01",
181
+ "AACELD": b"\x02",
182
+ "OPUS": b"\x03",
179
183
  }
180
184
 
181
185
 
182
186
  AUDIO_CODEC_PARAM_TYPES = {
183
- 'CHANNEL': b'\x01',
184
- 'BIT_RATE': b'\x02',
185
- 'SAMPLE_RATE': b'\x03',
186
- 'PACKET_TIME': b'\x04'
187
+ "CHANNEL": b"\x01",
188
+ "BIT_RATE": b"\x02",
189
+ "SAMPLE_RATE": b"\x03",
190
+ "PACKET_TIME": b"\x04",
187
191
  }
188
192
 
189
193
 
190
194
  AUDIO_CODEC_PARAM_BIT_RATE_TYPES = {
191
- 'VARIABLE': b'\x00',
192
- 'CONSTANT': b'\x01'
195
+ "VARIABLE": b"\x00",
196
+ "CONSTANT": b"\x01",
193
197
  }
194
198
 
195
199
 
196
200
  AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES = {
197
- 'KHZ_8': b'\x00',
198
- 'KHZ_16': b'\x01',
199
- 'KHZ_24': b'\x02'
201
+ "KHZ_8": b"\x00",
202
+ "KHZ_16": b"\x01",
203
+ "KHZ_24": b"\x02",
200
204
  }
201
205
 
202
206
 
203
- SUPPORTED_AUDIO_CODECS_TAG = b'\x01'
204
- SUPPORTED_COMFORT_NOISE_TAG = b'\x02'
205
- SUPPORTED_AUDIO_CONFIG_TAG = b'\x02'
206
- SET_CONFIG_REQUEST_TAG = b'\x02'
207
- SESSION_ID = b'\x01'
207
+ SUPPORTED_AUDIO_CODECS_TAG = b"\x01"
208
+ SUPPORTED_COMFORT_NOISE_TAG = b"\x02"
209
+ SUPPORTED_AUDIO_CONFIG_TAG = b"\x02"
210
+ SET_CONFIG_REQUEST_TAG = b"\x02"
211
+ SESSION_ID = b"\x01"
208
212
 
209
213
 
210
- NO_SRTP = b'\x01\x01\x02\x02\x00\x03\x00'
211
- '''Configuration value for no SRTP.'''
214
+ NO_SRTP = b"\x01\x01\x02\x02\x00\x03\x00"
215
+ """Configuration value for no SRTP."""
212
216
 
213
217
 
214
218
  FFMPEG_CMD = (
215
- 'ffmpeg -re -f avfoundation -framerate {fps} -i 0:0 -threads 0 '
216
- '-vcodec libx264 -an -pix_fmt yuv420p -r {fps} -f rawvideo -tune zerolatency '
217
- '-vf scale={width}:{height} -b:v {v_max_bitrate}k -bufsize {v_max_bitrate}k '
218
- '-payload_type 99 -ssrc {v_ssrc} -f rtp '
219
- '-srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params {v_srtp_key} '
220
- 'srtp://{address}:{v_port}?rtcpport={v_port}&'
221
- 'localrtcpport={v_port}&pkt_size=1378'
219
+ "ffmpeg -re -f avfoundation -framerate {fps} -i 0:0 -threads 0 "
220
+ "-vcodec libx264 -an -pix_fmt yuv420p -r {fps} -f rawvideo -tune zerolatency "
221
+ "-vf scale={width}:{height} -b:v {v_max_bitrate}k -bufsize {v_max_bitrate}k "
222
+ "-payload_type 99 -ssrc {v_ssrc} -f rtp "
223
+ "-srtp_out_suite AES_CM_128_HMAC_SHA1_80 -srtp_out_params {v_srtp_key} "
224
+ "srtp://{address}:{v_port}?rtcpport={v_port}&"
225
+ "localrtcpport={v_port}&pkt_size=1378"
222
226
  )
223
- '''Template for the ffmpeg command.'''
227
+ """Template for the ffmpeg command."""
224
228
 
225
229
  logger = logging.getLogger(__name__)
226
230
 
@@ -242,10 +246,10 @@ class Camera(Accessory):
242
246
  :type support_srtp: bool
243
247
  """
244
248
  if support_srtp:
245
- crypto = SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80']
249
+ crypto = SRTP_CRYPTO_SUITES["AES_CM_128_HMAC_SHA1_80"]
246
250
  else:
247
- crypto = SRTP_CRYPTO_SUITES['NONE']
248
- return tlv.encode(RTP_CONFIG_TYPES['CRYPTO'], crypto, to_base64=True)
251
+ crypto = SRTP_CRYPTO_SUITES["NONE"]
252
+ return tlv.encode(RTP_CONFIG_TYPES["CRYPTO"], crypto, to_base64=True)
249
253
 
250
254
  @staticmethod
251
255
  def get_supported_video_stream_config(video_params):
@@ -259,31 +263,41 @@ class Camera(Accessory):
259
263
  :type video_params: dict
260
264
  """
261
265
  codec_params_tlv = tlv.encode(
262
- VIDEO_CODEC_PARAM_TYPES['PACKETIZATION_MODE'],
263
- VIDEO_CODEC_PARAM_PACKETIZATION_MODE_TYPES['NON_INTERLEAVED'])
266
+ VIDEO_CODEC_PARAM_TYPES["PACKETIZATION_MODE"],
267
+ VIDEO_CODEC_PARAM_PACKETIZATION_MODE_TYPES["NON_INTERLEAVED"],
268
+ )
264
269
 
265
- codec_params = video_params['codec']
266
- for profile in codec_params['profiles']:
267
- codec_params_tlv += \
268
- tlv.encode(VIDEO_CODEC_PARAM_TYPES['PROFILE_ID'], profile)
270
+ codec_params = video_params["codec"]
271
+ for profile in codec_params["profiles"]:
272
+ codec_params_tlv += tlv.encode(
273
+ VIDEO_CODEC_PARAM_TYPES["PROFILE_ID"], profile
274
+ )
269
275
 
270
- for level in codec_params['levels']:
271
- codec_params_tlv += \
272
- tlv.encode(VIDEO_CODEC_PARAM_TYPES['LEVEL'], level)
276
+ for level in codec_params["levels"]:
277
+ codec_params_tlv += tlv.encode(VIDEO_CODEC_PARAM_TYPES["LEVEL"], level)
273
278
 
274
- attr_tlv = b''
275
- for resolution in video_params['resolutions']:
279
+ attr_tlv = b""
280
+ for resolution in video_params["resolutions"]:
276
281
  res_tlv = tlv.encode(
277
- VIDEO_ATTRIBUTES_TYPES['IMAGE_WIDTH'], struct.pack('<H', resolution[0]),
278
- VIDEO_ATTRIBUTES_TYPES['IMAGE_HEIGHT'], struct.pack('<H', resolution[1]),
279
- VIDEO_ATTRIBUTES_TYPES['FRAME_RATE'], struct.pack('<H', resolution[2]))
280
- attr_tlv += tlv.encode(VIDEO_TYPES['ATTRIBUTES'], res_tlv)
282
+ VIDEO_ATTRIBUTES_TYPES["IMAGE_WIDTH"],
283
+ struct.pack("<H", resolution[0]),
284
+ VIDEO_ATTRIBUTES_TYPES["IMAGE_HEIGHT"],
285
+ struct.pack("<H", resolution[1]),
286
+ VIDEO_ATTRIBUTES_TYPES["FRAME_RATE"],
287
+ struct.pack("<H", resolution[2]),
288
+ )
289
+ attr_tlv += tlv.encode(VIDEO_TYPES["ATTRIBUTES"], res_tlv)
281
290
 
282
- config_tlv = tlv.encode(VIDEO_TYPES['CODEC'], VIDEO_CODEC_TYPES['H264'],
283
- VIDEO_TYPES['CODEC_PARAM'], codec_params_tlv)
291
+ config_tlv = tlv.encode(
292
+ VIDEO_TYPES["CODEC"],
293
+ VIDEO_CODEC_TYPES["H264"],
294
+ VIDEO_TYPES["CODEC_PARAM"],
295
+ codec_params_tlv,
296
+ )
284
297
 
285
- return tlv.encode(SUPPORTED_VIDEO_CONFIG_TAG, config_tlv + attr_tlv,
286
- to_base64=True)
298
+ return tlv.encode(
299
+ SUPPORTED_VIDEO_CONFIG_TAG, config_tlv + attr_tlv, to_base64=True
300
+ )
287
301
 
288
302
  @staticmethod
289
303
  def get_supported_audio_stream_config(audio_params):
@@ -299,60 +313,71 @@ class Camera(Accessory):
299
313
  :type audio_params: dict
300
314
  """
301
315
  has_supported_codec = False
302
- configs = b''
303
- for codec_param in audio_params['codecs']:
304
- param_type = codec_param['type']
305
- if param_type == 'OPUS':
316
+ configs = b""
317
+ for codec_param in audio_params["codecs"]:
318
+ param_type = codec_param["type"]
319
+ if param_type == "OPUS":
306
320
  has_supported_codec = True
307
- codec = AUDIO_CODEC_TYPES['OPUS']
308
- bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE']
309
- elif param_type == 'AAC-eld':
321
+ codec = AUDIO_CODEC_TYPES["OPUS"]
322
+ bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES["VARIABLE"]
323
+ elif param_type == "AAC-eld":
310
324
  has_supported_codec = True
311
- codec = AUDIO_CODEC_TYPES['AACELD']
312
- bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE']
325
+ codec = AUDIO_CODEC_TYPES["AACELD"]
326
+ bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES["VARIABLE"]
313
327
  else:
314
- logger.warning('Unsupported codec %s', param_type)
328
+ logger.warning("Unsupported codec %s", param_type)
315
329
  continue
316
330
 
317
- param_samplerate = codec_param['samplerate']
331
+ param_samplerate = codec_param["samplerate"]
318
332
  if param_samplerate == 8:
319
- samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_8']
333
+ samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES["KHZ_8"]
320
334
  elif param_samplerate == 16:
321
- samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_16']
335
+ samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES["KHZ_16"]
322
336
  elif param_samplerate == 24:
323
- samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_24']
337
+ samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES["KHZ_24"]
324
338
  else:
325
- logger.warning('Unsupported sample rate %s', param_samplerate)
339
+ logger.warning("Unsupported sample rate %s", param_samplerate)
326
340
  continue
327
341
 
328
- param_tlv = tlv.encode(AUDIO_CODEC_PARAM_TYPES['CHANNEL'], b'\x01',
329
- AUDIO_CODEC_PARAM_TYPES['BIT_RATE'], bitrate,
330
- AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE'], samplerate)
331
- config_tlv = tlv.encode(AUDIO_TYPES['CODEC'], codec,
332
- AUDIO_TYPES['CODEC_PARAM'], param_tlv)
342
+ param_tlv = tlv.encode(
343
+ AUDIO_CODEC_PARAM_TYPES["CHANNEL"],
344
+ b"\x01",
345
+ AUDIO_CODEC_PARAM_TYPES["BIT_RATE"],
346
+ bitrate,
347
+ AUDIO_CODEC_PARAM_TYPES["SAMPLE_RATE"],
348
+ samplerate,
349
+ )
350
+ config_tlv = tlv.encode(
351
+ AUDIO_TYPES["CODEC"], codec, AUDIO_TYPES["CODEC_PARAM"], param_tlv
352
+ )
333
353
  configs += tlv.encode(SUPPORTED_AUDIO_CODECS_TAG, config_tlv)
334
354
 
335
355
  if not has_supported_codec:
336
- logger.warning('Client does not support any audio codec that iOS supports.')
356
+ logger.warning("Client does not support any audio codec that iOS supports.")
337
357
 
338
- codec = AUDIO_CODEC_TYPES['OPUS']
339
- bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES['VARIABLE']
340
- samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES['KHZ_24']
358
+ codec = AUDIO_CODEC_TYPES["OPUS"]
359
+ bitrate = AUDIO_CODEC_PARAM_BIT_RATE_TYPES["VARIABLE"]
360
+ samplerate = AUDIO_CODEC_PARAM_SAMPLE_RATE_TYPES["KHZ_24"]
341
361
 
342
362
  param_tlv = tlv.encode(
343
- AUDIO_CODEC_PARAM_TYPES['CHANNEL'], b'\x01',
344
- AUDIO_CODEC_PARAM_TYPES['BIT_RATE'], bitrate,
345
- AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE'], samplerate)
363
+ AUDIO_CODEC_PARAM_TYPES["CHANNEL"],
364
+ b"\x01",
365
+ AUDIO_CODEC_PARAM_TYPES["BIT_RATE"],
366
+ bitrate,
367
+ AUDIO_CODEC_PARAM_TYPES["SAMPLE_RATE"],
368
+ samplerate,
369
+ )
346
370
 
347
- config_tlv = tlv.encode(AUDIO_TYPES['CODEC'], codec,
348
- AUDIO_TYPES['CODEC_PARAM'], param_tlv)
371
+ config_tlv = tlv.encode(
372
+ AUDIO_TYPES["CODEC"], codec, AUDIO_TYPES["CODEC_PARAM"], param_tlv
373
+ )
349
374
 
350
375
  configs = tlv.encode(SUPPORTED_AUDIO_CODECS_TAG, config_tlv)
351
376
 
352
- comfort_noise = byte_bool(
353
- audio_params.get('comfort_noise', False))
377
+ comfort_noise = byte_bool(audio_params.get("comfort_noise", False))
354
378
  audio_config = to_base64_str(
355
- configs + tlv.encode(SUPPORTED_COMFORT_NOISE_TAG, comfort_noise))
379
+ configs + tlv.encode(SUPPORTED_COMFORT_NOISE_TAG, comfort_noise)
380
+ )
356
381
  return audio_config
357
382
 
358
383
  def __init__(self, options, *args, **kwargs):
@@ -409,20 +434,20 @@ class Camera(Accessory):
409
434
 
410
435
  :type options: ``dict``
411
436
  """
412
- self.has_srtp = options.get('srtp', False)
413
- self.start_stream_cmd = options.get('start_stream_cmd', FFMPEG_CMD)
437
+ self.has_srtp = options.get("srtp", False)
438
+ self.start_stream_cmd = options.get("start_stream_cmd", FFMPEG_CMD)
414
439
 
415
- self.stream_address = options['address']
440
+ self.stream_address = options["address"]
416
441
  try:
417
442
  ipaddress.IPv4Address(self.stream_address)
418
- self.stream_address_isv6 = b'\x00'
443
+ self.stream_address_isv6 = b"\x00"
419
444
  except ValueError:
420
- self.stream_address_isv6 = b'\x01'
445
+ self.stream_address_isv6 = b"\x01"
421
446
  self.sessions = {}
422
447
 
423
448
  super().__init__(*args, **kwargs)
424
449
 
425
- self.add_preload_service('Microphone')
450
+ self.add_preload_service("Microphone")
426
451
  self._streaming_status = []
427
452
  self._management = []
428
453
  self._setup_stream_management(options)
@@ -441,7 +466,9 @@ class Camera(Accessory):
441
466
 
442
467
  def _create_stream_management(self, stream_idx, options):
443
468
  """Create a stream management service."""
444
- management = self.add_preload_service("CameraRTPStreamManagement", unique_id=stream_idx)
469
+ management = self.add_preload_service(
470
+ "CameraRTPStreamManagement", unique_id=stream_idx
471
+ )
445
472
  management.configure_char(
446
473
  "StreamingStatus",
447
474
  getter_callback=lambda: self._get_streaming_status(stream_idx),
@@ -484,105 +511,129 @@ class Camera(Accessory):
484
511
  started.
485
512
  :type reconfigure: bool
486
513
  """
487
- video_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES['VIDEO'])
488
- audio_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES['AUDIO'])
514
+ video_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES["VIDEO"])
515
+ audio_tlv = objs.get(SELECTED_STREAM_CONFIGURATION_TYPES["AUDIO"])
489
516
 
490
517
  opts = {}
491
518
 
492
519
  if video_tlv:
493
520
  video_objs = tlv.decode(video_tlv)
494
521
 
495
- video_codec_params = video_objs.get(VIDEO_TYPES['CODEC_PARAM'])
522
+ video_codec_params = video_objs.get(VIDEO_TYPES["CODEC_PARAM"])
496
523
  if video_codec_params:
497
524
  video_codec_param_objs = tlv.decode(video_codec_params)
498
- opts['v_profile_id'] = \
499
- video_codec_param_objs[VIDEO_CODEC_PARAM_TYPES['PROFILE_ID']]
500
- opts['v_level'] = \
501
- video_codec_param_objs[VIDEO_CODEC_PARAM_TYPES['LEVEL']]
502
-
503
- video_attrs = video_objs.get(VIDEO_TYPES['ATTRIBUTES'])
525
+ opts["v_profile_id"] = video_codec_param_objs[
526
+ VIDEO_CODEC_PARAM_TYPES["PROFILE_ID"]
527
+ ]
528
+ opts["v_level"] = video_codec_param_objs[
529
+ VIDEO_CODEC_PARAM_TYPES["LEVEL"]
530
+ ]
531
+
532
+ video_attrs = video_objs.get(VIDEO_TYPES["ATTRIBUTES"])
504
533
  if video_attrs:
505
534
  video_attr_objs = tlv.decode(video_attrs)
506
- opts['width'] = struct.unpack('<H',
507
- video_attr_objs[VIDEO_ATTRIBUTES_TYPES['IMAGE_WIDTH']])[0]
508
- opts['height'] = struct.unpack('<H',
509
- video_attr_objs[VIDEO_ATTRIBUTES_TYPES['IMAGE_HEIGHT']])[0]
510
- opts['fps'] = struct.unpack('<B',
511
- video_attr_objs[VIDEO_ATTRIBUTES_TYPES['FRAME_RATE']])[0]
512
-
513
- video_rtp_param = video_objs.get(VIDEO_TYPES['RTP_PARAM'])
535
+ opts["width"] = struct.unpack(
536
+ "<H", video_attr_objs[VIDEO_ATTRIBUTES_TYPES["IMAGE_WIDTH"]]
537
+ )[0]
538
+ opts["height"] = struct.unpack(
539
+ "<H", video_attr_objs[VIDEO_ATTRIBUTES_TYPES["IMAGE_HEIGHT"]]
540
+ )[0]
541
+ opts["fps"] = struct.unpack(
542
+ "<B", video_attr_objs[VIDEO_ATTRIBUTES_TYPES["FRAME_RATE"]]
543
+ )[0]
544
+
545
+ video_rtp_param = video_objs.get(VIDEO_TYPES["RTP_PARAM"])
514
546
  if video_rtp_param:
515
547
  video_rtp_param_objs = tlv.decode(video_rtp_param)
516
- if RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE'] in video_rtp_param_objs:
517
- opts['v_ssrc'] = struct.unpack('<I',
548
+ if RTP_PARAM_TYPES["SYNCHRONIZATION_SOURCE"] in video_rtp_param_objs:
549
+ opts["v_ssrc"] = struct.unpack(
550
+ "<I",
518
551
  video_rtp_param_objs.get(
519
- RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE']))[0]
520
- if RTP_PARAM_TYPES['PAYLOAD_TYPE'] in video_rtp_param_objs:
521
- opts['v_payload_type'] = \
522
- video_rtp_param_objs.get(RTP_PARAM_TYPES['PAYLOAD_TYPE'])
523
- if RTP_PARAM_TYPES['MAX_BIT_RATE'] in video_rtp_param_objs:
524
- opts['v_max_bitrate'] = struct.unpack('<H',
525
- video_rtp_param_objs.get(RTP_PARAM_TYPES['MAX_BIT_RATE']))[0]
526
- if RTP_PARAM_TYPES['RTCP_SEND_INTERVAL'] in video_rtp_param_objs:
527
- opts['v_rtcp_interval'] = struct.unpack('<f',
528
- video_rtp_param_objs.get(RTP_PARAM_TYPES['RTCP_SEND_INTERVAL']))[0]
529
- if RTP_PARAM_TYPES['MAX_MTU'] in video_rtp_param_objs:
530
- opts['v_max_mtu'] = video_rtp_param_objs.get(RTP_PARAM_TYPES['MAX_MTU'])
552
+ RTP_PARAM_TYPES["SYNCHRONIZATION_SOURCE"]
553
+ ),
554
+ )[0]
555
+ if RTP_PARAM_TYPES["PAYLOAD_TYPE"] in video_rtp_param_objs:
556
+ opts["v_payload_type"] = video_rtp_param_objs.get(
557
+ RTP_PARAM_TYPES["PAYLOAD_TYPE"]
558
+ )
559
+ if RTP_PARAM_TYPES["MAX_BIT_RATE"] in video_rtp_param_objs:
560
+ opts["v_max_bitrate"] = struct.unpack(
561
+ "<H", video_rtp_param_objs.get(RTP_PARAM_TYPES["MAX_BIT_RATE"])
562
+ )[0]
563
+ if RTP_PARAM_TYPES["RTCP_SEND_INTERVAL"] in video_rtp_param_objs:
564
+ opts["v_rtcp_interval"] = struct.unpack(
565
+ "<f",
566
+ video_rtp_param_objs.get(RTP_PARAM_TYPES["RTCP_SEND_INTERVAL"]),
567
+ )[0]
568
+ if RTP_PARAM_TYPES["MAX_MTU"] in video_rtp_param_objs:
569
+ opts["v_max_mtu"] = video_rtp_param_objs.get(
570
+ RTP_PARAM_TYPES["MAX_MTU"]
571
+ )
531
572
 
532
573
  if audio_tlv:
533
574
  audio_objs = tlv.decode(audio_tlv)
534
575
 
535
- opts['a_codec'] = audio_objs[AUDIO_TYPES['CODEC']]
536
- audio_codec_param_objs = tlv.decode(
537
- audio_objs[AUDIO_TYPES['CODEC_PARAM']])
538
- audio_rtp_param_objs = tlv.decode(
539
- audio_objs[AUDIO_TYPES['RTP_PARAM']])
540
- opts['a_comfort_noise'] = audio_objs[AUDIO_TYPES['COMFORT_NOISE']]
541
-
542
- opts['a_channel'] = \
543
- audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['CHANNEL']][0]
544
- opts['a_bitrate'] = struct.unpack('?',
545
- audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['BIT_RATE']])[0]
546
- opts['a_sample_rate'] = 8 * (
547
- 1 + audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['SAMPLE_RATE']][0])
548
- opts['a_packet_time'] = struct.unpack('<B',
549
- audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES['PACKET_TIME']])[0]
550
-
551
- opts['a_ssrc'] = struct.unpack('<I',
552
- audio_rtp_param_objs[RTP_PARAM_TYPES['SYNCHRONIZATION_SOURCE']])[0]
553
- opts['a_payload_type'] = audio_rtp_param_objs[RTP_PARAM_TYPES['PAYLOAD_TYPE']]
554
- opts['a_max_bitrate'] = struct.unpack('<H',
555
- audio_rtp_param_objs[RTP_PARAM_TYPES['MAX_BIT_RATE']])[0]
556
- opts['a_rtcp_interval'] = struct.unpack('<f',
557
- audio_rtp_param_objs[RTP_PARAM_TYPES['RTCP_SEND_INTERVAL']])[0]
558
- opts['a_comfort_payload_type'] = \
559
- audio_rtp_param_objs[RTP_PARAM_TYPES['COMFORT_NOISE_PAYLOAD_TYPE']]
560
-
561
- session_objs = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES['SESSION']])
562
- session_id = UUID(bytes=session_objs[SETUP_TYPES['SESSION_ID']])
576
+ opts["a_codec"] = audio_objs[AUDIO_TYPES["CODEC"]]
577
+ audio_codec_param_objs = tlv.decode(audio_objs[AUDIO_TYPES["CODEC_PARAM"]])
578
+ audio_rtp_param_objs = tlv.decode(audio_objs[AUDIO_TYPES["RTP_PARAM"]])
579
+ opts["a_comfort_noise"] = audio_objs[AUDIO_TYPES["COMFORT_NOISE"]]
580
+
581
+ opts["a_channel"] = audio_codec_param_objs[
582
+ AUDIO_CODEC_PARAM_TYPES["CHANNEL"]
583
+ ][0]
584
+ opts["a_bitrate"] = struct.unpack(
585
+ "?", audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES["BIT_RATE"]]
586
+ )[0]
587
+ opts["a_sample_rate"] = 8 * (
588
+ 1 + audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES["SAMPLE_RATE"]][0]
589
+ )
590
+ opts["a_packet_time"] = struct.unpack(
591
+ "<B", audio_codec_param_objs[AUDIO_CODEC_PARAM_TYPES["PACKET_TIME"]]
592
+ )[0]
593
+
594
+ opts["a_ssrc"] = struct.unpack(
595
+ "<I", audio_rtp_param_objs[RTP_PARAM_TYPES["SYNCHRONIZATION_SOURCE"]]
596
+ )[0]
597
+ opts["a_payload_type"] = audio_rtp_param_objs[
598
+ RTP_PARAM_TYPES["PAYLOAD_TYPE"]
599
+ ]
600
+ opts["a_max_bitrate"] = struct.unpack(
601
+ "<H", audio_rtp_param_objs[RTP_PARAM_TYPES["MAX_BIT_RATE"]]
602
+ )[0]
603
+ opts["a_rtcp_interval"] = struct.unpack(
604
+ "<f", audio_rtp_param_objs[RTP_PARAM_TYPES["RTCP_SEND_INTERVAL"]]
605
+ )[0]
606
+ opts["a_comfort_payload_type"] = audio_rtp_param_objs[
607
+ RTP_PARAM_TYPES["COMFORT_NOISE_PAYLOAD_TYPE"]
608
+ ]
609
+
610
+ session_objs = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES["SESSION"]])
611
+ session_id = UUID(bytes=session_objs[SETUP_TYPES["SESSION_ID"]])
563
612
  session_info = self.sessions[session_id]
564
- stream_idx = session_info['stream_idx']
613
+ stream_idx = session_info["stream_idx"]
565
614
 
566
615
  opts.update(session_info)
567
- success = await self.reconfigure_stream(session_info, opts) if reconfigure \
616
+ success = (
617
+ await self.reconfigure_stream(session_info, opts)
618
+ if reconfigure
568
619
  else await self.start_stream(session_info, opts)
620
+ )
569
621
 
570
622
  if success:
571
- self._streaming_status[stream_idx] = STREAMING_STATUS['STREAMING']
623
+ self._streaming_status[stream_idx] = STREAMING_STATUS["STREAMING"]
572
624
  else:
573
625
  logger.error(
574
- '[%s] Failed to start/reconfigure stream, deleting session.',
575
- session_id
626
+ "[%s] Failed to start/reconfigure stream, deleting session.", session_id
576
627
  )
577
628
  del self.sessions[session_id]
578
- self._streaming_status[stream_idx] = STREAMING_STATUS['AVAILABLE']
629
+ self._streaming_status[stream_idx] = STREAMING_STATUS["AVAILABLE"]
579
630
 
580
631
  def _get_streaming_status(self, stream_idx):
581
632
  """Get the streaming status in TLV format.
582
633
 
583
634
  Called when iOS reads the StreaminStatus ``Characteristic``.
584
635
  """
585
- return tlv.encode(b'\x01', self._streaming_status[stream_idx], to_base64=True)
636
+ return tlv.encode(b"\x01", self._streaming_status[stream_idx], to_base64=True)
586
637
 
587
638
  async def _stop_stream(self, objs):
588
639
  """Stop the stream for the specified session.
@@ -592,23 +643,23 @@ class Camera(Accessory):
592
643
  :param objs: TLV-decoded SelectedRTPStreamConfiguration value.
593
644
  :param objs: ``dict``
594
645
  """
595
- session_objs = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES['SESSION']])
596
- session_id = UUID(bytes=session_objs[SETUP_TYPES['SESSION_ID']])
646
+ session_objs = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES["SESSION"]])
647
+ session_id = UUID(bytes=session_objs[SETUP_TYPES["SESSION_ID"]])
597
648
 
598
649
  session_info = self.sessions.get(session_id)
599
650
  if not session_info:
600
651
  logger.error(
601
- 'Requested to stop stream for session %s, but no '
602
- 'such session was found',
603
- session_id
652
+ "Requested to stop stream for session %s, but no "
653
+ "such session was found",
654
+ session_id,
604
655
  )
605
656
  return
606
657
 
607
- stream_idx = session_info['stream_idx']
658
+ stream_idx = session_info["stream_idx"]
608
659
  await self.stop_stream(session_info)
609
660
  del self.sessions[session_id]
610
661
 
611
- self._streaming_status[stream_idx] = STREAMING_STATUS['AVAILABLE']
662
+ self._streaming_status[stream_idx] = STREAMING_STATUS["AVAILABLE"]
612
663
 
613
664
  def set_selected_stream_configuration(self, value):
614
665
  """Set the selected stream configuration.
@@ -621,17 +672,17 @@ class Camera(Accessory):
621
672
  :param value: base64-encoded selected configuration in TLV format
622
673
  :type value: ``str``
623
674
  """
624
- logger.debug('set_selected_stream_config - value - %s', value)
675
+ logger.debug("set_selected_stream_config - value - %s", value)
625
676
 
626
677
  objs = tlv.decode(value, from_base64=True)
627
- if SELECTED_STREAM_CONFIGURATION_TYPES['SESSION'] not in objs:
628
- logger.error('Bad request to set selected stream configuration.')
678
+ if SELECTED_STREAM_CONFIGURATION_TYPES["SESSION"] not in objs:
679
+ logger.error("Bad request to set selected stream configuration.")
629
680
  return
630
681
 
631
- session = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES['SESSION']])
682
+ session = tlv.decode(objs[SELECTED_STREAM_CONFIGURATION_TYPES["SESSION"]])
632
683
 
633
- request_type = session[b'\x02'][0]
634
- logger.debug('Set stream config request: %d', request_type)
684
+ request_type = session[b"\x02"][0]
685
+ logger.debug("Set stream config request: %d", request_type)
635
686
  if request_type == 1:
636
687
  job = functools.partial(self._start_stream, reconfigure=False)
637
688
  elif request_type == 0:
@@ -639,7 +690,7 @@ class Camera(Accessory):
639
690
  elif request_type == 4:
640
691
  job = functools.partial(self._start_stream, reconfigure=True)
641
692
  else:
642
- logger.error('Unknown request type %d', request_type)
693
+ logger.error("Unknown request type %d", request_type)
643
694
  return
644
695
 
645
696
  self.driver.add_job(job, objs)
@@ -662,58 +713,73 @@ class Camera(Accessory):
662
713
  stream_idx = 0
663
714
 
664
715
  objs = tlv.decode(value, from_base64=True)
665
- session_id = UUID(bytes=objs[SETUP_TYPES['SESSION_ID']])
716
+ session_id = UUID(bytes=objs[SETUP_TYPES["SESSION_ID"]])
666
717
 
667
718
  # Extract address info
668
- address_tlv = objs[SETUP_TYPES['ADDRESS']]
719
+ address_tlv = objs[SETUP_TYPES["ADDRESS"]]
669
720
  address_info_objs = tlv.decode(address_tlv)
670
- is_ipv6 = struct.unpack('?',
671
- address_info_objs[SETUP_ADDR_INFO['ADDRESS_VER']])[0]
672
- address = address_info_objs[SETUP_ADDR_INFO['ADDRESS']].decode('utf8')
721
+ is_ipv6 = struct.unpack("?", address_info_objs[SETUP_ADDR_INFO["ADDRESS_VER"]])[
722
+ 0
723
+ ]
724
+ address = address_info_objs[SETUP_ADDR_INFO["ADDRESS"]].decode("utf8")
673
725
  target_video_port = struct.unpack(
674
- '<H', address_info_objs[SETUP_ADDR_INFO['VIDEO_RTP_PORT']])[0]
726
+ "<H", address_info_objs[SETUP_ADDR_INFO["VIDEO_RTP_PORT"]]
727
+ )[0]
675
728
  target_audio_port = struct.unpack(
676
- '<H', address_info_objs[SETUP_ADDR_INFO['AUDIO_RTP_PORT']])[0]
729
+ "<H", address_info_objs[SETUP_ADDR_INFO["AUDIO_RTP_PORT"]]
730
+ )[0]
677
731
 
678
732
  # Video SRTP Params
679
- video_srtp_tlv = objs[SETUP_TYPES['VIDEO_SRTP_PARAM']]
733
+ video_srtp_tlv = objs[SETUP_TYPES["VIDEO_SRTP_PARAM"]]
680
734
  video_info_objs = tlv.decode(video_srtp_tlv)
681
- video_crypto_suite = video_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0]
682
- video_master_key = video_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']]
683
- video_master_salt = video_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']]
735
+ video_crypto_suite = video_info_objs[SETUP_SRTP_PARAM["CRYPTO"]][0]
736
+ video_master_key = video_info_objs[SETUP_SRTP_PARAM["MASTER_KEY"]]
737
+ video_master_salt = video_info_objs[SETUP_SRTP_PARAM["MASTER_SALT"]]
684
738
 
685
739
  # Audio SRTP Params
686
- audio_srtp_tlv = objs[SETUP_TYPES['AUDIO_SRTP_PARAM']]
740
+ audio_srtp_tlv = objs[SETUP_TYPES["AUDIO_SRTP_PARAM"]]
687
741
  audio_info_objs = tlv.decode(audio_srtp_tlv)
688
- audio_crypto_suite = audio_info_objs[SETUP_SRTP_PARAM['CRYPTO']][0]
689
- audio_master_key = audio_info_objs[SETUP_SRTP_PARAM['MASTER_KEY']]
690
- audio_master_salt = audio_info_objs[SETUP_SRTP_PARAM['MASTER_SALT']]
742
+ audio_crypto_suite = audio_info_objs[SETUP_SRTP_PARAM["CRYPTO"]][0]
743
+ audio_master_key = audio_info_objs[SETUP_SRTP_PARAM["MASTER_KEY"]]
744
+ audio_master_salt = audio_info_objs[SETUP_SRTP_PARAM["MASTER_SALT"]]
691
745
 
692
746
  logger.debug(
693
- 'Received endpoint configuration:'
694
- '\nsession_id: %s\naddress: %s\nis_ipv6: %s'
695
- '\ntarget_video_port: %s\ntarget_audio_port: %s'
696
- '\nvideo_crypto_suite: %s\nvideo_srtp: %s'
697
- '\naudio_crypto_suite: %s\naudio_srtp: %s',
698
- session_id, address, is_ipv6, target_video_port, target_audio_port,
747
+ "Received endpoint configuration:"
748
+ "\nsession_id: %s\naddress: %s\nis_ipv6: %s"
749
+ "\ntarget_video_port: %s\ntarget_audio_port: %s"
750
+ "\nvideo_crypto_suite: %s\nvideo_srtp: %s"
751
+ "\naudio_crypto_suite: %s\naudio_srtp: %s",
752
+ session_id,
753
+ address,
754
+ is_ipv6,
755
+ target_video_port,
756
+ target_audio_port,
699
757
  video_crypto_suite,
700
758
  to_base64_str(video_master_key + video_master_salt),
701
759
  audio_crypto_suite,
702
- to_base64_str(audio_master_key + audio_master_salt)
760
+ to_base64_str(audio_master_key + audio_master_salt),
703
761
  )
704
762
 
705
763
  # Configure the SetupEndpoints response
706
764
 
707
765
  if self.has_srtp:
708
766
  video_srtp_tlv = tlv.encode(
709
- SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'],
710
- SETUP_SRTP_PARAM['MASTER_KEY'], video_master_key,
711
- SETUP_SRTP_PARAM['MASTER_SALT'], video_master_salt)
767
+ SETUP_SRTP_PARAM["CRYPTO"],
768
+ SRTP_CRYPTO_SUITES["AES_CM_128_HMAC_SHA1_80"],
769
+ SETUP_SRTP_PARAM["MASTER_KEY"],
770
+ video_master_key,
771
+ SETUP_SRTP_PARAM["MASTER_SALT"],
772
+ video_master_salt,
773
+ )
712
774
 
713
775
  audio_srtp_tlv = tlv.encode(
714
- SETUP_SRTP_PARAM['CRYPTO'], SRTP_CRYPTO_SUITES['AES_CM_128_HMAC_SHA1_80'],
715
- SETUP_SRTP_PARAM['MASTER_KEY'], audio_master_key,
716
- SETUP_SRTP_PARAM['MASTER_SALT'], audio_master_salt)
776
+ SETUP_SRTP_PARAM["CRYPTO"],
777
+ SRTP_CRYPTO_SUITES["AES_CM_128_HMAC_SHA1_80"],
778
+ SETUP_SRTP_PARAM["MASTER_KEY"],
779
+ audio_master_key,
780
+ SETUP_SRTP_PARAM["MASTER_SALT"],
781
+ audio_master_salt,
782
+ )
717
783
  else:
718
784
  video_srtp_tlv = NO_SRTP
719
785
  audio_srtp_tlv = NO_SRTP
@@ -722,39 +788,55 @@ class Camera(Accessory):
722
788
  audio_ssrc = int.from_bytes(os.urandom(3), byteorder="big")
723
789
 
724
790
  res_address_tlv = tlv.encode(
725
- SETUP_ADDR_INFO['ADDRESS_VER'], self.stream_address_isv6,
726
- SETUP_ADDR_INFO['ADDRESS'], self.stream_address.encode('utf-8'),
727
- SETUP_ADDR_INFO['VIDEO_RTP_PORT'], struct.pack('<H', target_video_port),
728
- SETUP_ADDR_INFO['AUDIO_RTP_PORT'], struct.pack('<H', target_audio_port))
791
+ SETUP_ADDR_INFO["ADDRESS_VER"],
792
+ self.stream_address_isv6,
793
+ SETUP_ADDR_INFO["ADDRESS"],
794
+ self.stream_address.encode("utf-8"),
795
+ SETUP_ADDR_INFO["VIDEO_RTP_PORT"],
796
+ struct.pack("<H", target_video_port),
797
+ SETUP_ADDR_INFO["AUDIO_RTP_PORT"],
798
+ struct.pack("<H", target_audio_port),
799
+ )
729
800
 
730
801
  response_tlv = tlv.encode(
731
- SETUP_TYPES['SESSION_ID'], session_id.bytes,
732
- SETUP_TYPES['STATUS'], SETUP_STATUS['SUCCESS'],
733
- SETUP_TYPES['ADDRESS'], res_address_tlv,
734
- SETUP_TYPES['VIDEO_SRTP_PARAM'], video_srtp_tlv,
735
- SETUP_TYPES['AUDIO_SRTP_PARAM'], audio_srtp_tlv,
736
- SETUP_TYPES['VIDEO_SSRC'], struct.pack('<I', video_ssrc),
737
- SETUP_TYPES['AUDIO_SSRC'], struct.pack('<I', audio_ssrc),
738
- to_base64=True)
802
+ SETUP_TYPES["SESSION_ID"],
803
+ session_id.bytes,
804
+ SETUP_TYPES["STATUS"],
805
+ SETUP_STATUS["SUCCESS"],
806
+ SETUP_TYPES["ADDRESS"],
807
+ res_address_tlv,
808
+ SETUP_TYPES["VIDEO_SRTP_PARAM"],
809
+ video_srtp_tlv,
810
+ SETUP_TYPES["AUDIO_SRTP_PARAM"],
811
+ audio_srtp_tlv,
812
+ SETUP_TYPES["VIDEO_SSRC"],
813
+ struct.pack("<I", video_ssrc),
814
+ SETUP_TYPES["AUDIO_SSRC"],
815
+ struct.pack("<I", audio_ssrc),
816
+ to_base64=True,
817
+ )
739
818
 
740
819
  self.sessions[session_id] = {
741
- 'id': session_id,
742
- 'stream_idx': stream_idx,
743
- 'address': address,
744
- 'v_port': target_video_port,
745
- 'v_srtp_key': to_base64_str(video_master_key + video_master_salt),
746
- 'v_ssrc': video_ssrc,
747
- 'a_port': target_audio_port,
748
- 'a_srtp_key': to_base64_str(audio_master_key + audio_master_salt),
749
- 'a_ssrc': audio_ssrc
820
+ "id": session_id,
821
+ "stream_idx": stream_idx,
822
+ "address": address,
823
+ "v_port": target_video_port,
824
+ "v_srtp_key": to_base64_str(video_master_key + video_master_salt),
825
+ "v_ssrc": video_ssrc,
826
+ "a_port": target_audio_port,
827
+ "a_srtp_key": to_base64_str(audio_master_key + audio_master_salt),
828
+ "a_ssrc": audio_ssrc,
750
829
  }
751
830
 
752
- self._management[stream_idx].get_characteristic('SetupEndpoints').set_value(response_tlv)
831
+ self._management[stream_idx].get_characteristic("SetupEndpoints").set_value(
832
+ response_tlv
833
+ )
753
834
 
754
835
  async def stop(self):
755
836
  """Stop all streaming sessions."""
756
- await asyncio.gather(*(
757
- self.stop_stream(session_info) for session_info in self.sessions.values()))
837
+ await asyncio.gather(
838
+ *(self.stop_stream(session_info) for session_info in self.sessions.values())
839
+ )
758
840
 
759
841
  # ### For client extensions ###
760
842
 
@@ -816,28 +898,28 @@ class Camera(Accessory):
816
898
  :rtype: ``bool``
817
899
  """
818
900
  logger.debug(
819
- '[%s] Starting stream with the following parameters: %s',
820
- session_info['id'],
821
- stream_config
901
+ "[%s] Starting stream with the following parameters: %s",
902
+ session_info["id"],
903
+ stream_config,
822
904
  )
823
905
 
824
906
  cmd = self.start_stream_cmd.format(**stream_config).split()
825
- logger.debug('Executing start stream command: "%s"', ' '.join(cmd))
907
+ logger.debug('Executing start stream command: "%s"', " ".join(cmd))
826
908
  try:
827
- process = await asyncio.create_subprocess_exec(*cmd,
828
- stdout=asyncio.subprocess.DEVNULL,
829
- stderr=asyncio.subprocess.PIPE,
830
- limit=1024)
909
+ process = await asyncio.create_subprocess_exec(
910
+ *cmd,
911
+ stdout=asyncio.subprocess.DEVNULL,
912
+ stderr=asyncio.subprocess.PIPE,
913
+ limit=1024,
914
+ )
831
915
  except Exception as e: # pylint: disable=broad-except
832
- logger.error('Failed to start streaming process because of error: %s', e)
916
+ logger.error("Failed to start streaming process because of error: %s", e)
833
917
  return False
834
918
 
835
- session_info['process'] = process
919
+ session_info["process"] = process
836
920
 
837
921
  logger.info(
838
- '[%s] Started stream process - PID %d',
839
- session_info['id'],
840
- process.pid
922
+ "[%s] Started stream process - PID %d", session_info["id"], process.pid
841
923
  )
842
924
 
843
925
  return True
@@ -853,25 +935,25 @@ class Camera(Accessory):
853
935
  - id - The session ID.
854
936
  :type session_info: ``dict``
855
937
  """
856
- session_id = session_info['id']
857
- ffmpeg_process = session_info.get('process')
938
+ session_id = session_info["id"]
939
+ ffmpeg_process = session_info.get("process")
858
940
  if ffmpeg_process:
859
- logger.info('[%s] Stopping stream.', session_id)
941
+ logger.info("[%s] Stopping stream.", session_id)
860
942
  try:
861
943
  ffmpeg_process.terminate()
862
- async with async_timeout.timeout(2.0):
944
+ async with async_timeout(2.0):
863
945
  _, stderr = await ffmpeg_process.communicate()
864
- logger.debug('Stream command stderr: %s', stderr)
946
+ logger.debug("Stream command stderr: %s", stderr)
865
947
  except asyncio.TimeoutError:
866
948
  logger.error(
867
- 'Timeout while waiting for the stream process '
868
- 'to terminate. Trying with kill.'
949
+ "Timeout while waiting for the stream process "
950
+ "to terminate. Trying with kill."
869
951
  )
870
952
  ffmpeg_process.kill()
871
953
  await ffmpeg_process.wait()
872
- logger.debug('Stream process stopped.')
954
+ logger.debug("Stream process stopped.")
873
955
  else:
874
- logger.warning('No process for session ID %s', session_id)
956
+ logger.warning("No process for session ID %s", session_id)
875
957
 
876
958
  async def reconfigure_stream(self, session_info, stream_config):
877
959
  """Reconfigure the stream so that it uses the given ``stream_config``.
@@ -894,5 +976,5 @@ class Camera(Accessory):
894
976
  :param image_size: ``dict`` describing the requested image size. Contains the
895
977
  keys "image-width" and "image-height"
896
978
  """
897
- with open(os.path.join(RESOURCE_DIR, 'snapshot.jpg'), 'rb') as fp:
979
+ with open(os.path.join(RESOURCE_DIR, "snapshot.jpg"), "rb") as fp:
898
980
  return fp.read()