sora-sdk 2026.2.0.dev0__tar.gz → 2026.2.0.dev1__tar.gz

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.
Files changed (57) hide show
  1. {sora_sdk-2026.2.0.dev0/src/sora_sdk.egg-info → sora_sdk-2026.2.0.dev1}/PKG-INFO +1 -1
  2. sora_sdk-2026.2.0.dev1/VERSION +1 -0
  3. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk/sora_sdk_ext.cpython-312-darwin.so +0 -0
  4. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk/sora_sdk_ext.pyi +1 -1
  5. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1/src/sora_sdk.egg-info}/PKG-INFO +1 -1
  6. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
  7. sora_sdk-2026.2.0.dev1/tests/test_field_trials.py +199 -0
  8. sora_sdk-2026.2.0.dev0/VERSION +0 -1
  9. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/LICENSE +0 -0
  10. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/MANIFEST.in +0 -0
  11. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/README.md +0 -0
  12. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/buildbase.py +0 -0
  13. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/pypath.py +0 -0
  14. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/pyproject.toml +0 -0
  15. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/run.py +0 -0
  16. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/setup.cfg +0 -0
  17. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/setup.py +0 -0
  18. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk/__init__.py +0 -0
  19. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk/py.typed +0 -0
  20. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
  21. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/src/sora_sdk.egg-info/top_level.txt +0 -0
  22. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_amd_amf.py +0 -0
  23. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_apple_video_toolbox.py +0 -0
  24. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_audio_sink_callbacks.py +0 -0
  25. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_audio_sink_read_gil.py +0 -0
  26. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_audio_source_invalid_params.py +0 -0
  27. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_authz.py +0 -0
  28. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_authz_simulcast.py +0 -0
  29. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_ca_cert.py +0 -0
  30. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_capability.py +0 -0
  31. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_convert_json_value_int64.py +0 -0
  32. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_degradation_preference.py +0 -0
  33. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_disconnect_timeout.py +0 -0
  34. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_encoded_transform.py +0 -0
  35. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_intel_vpl.py +0 -0
  36. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_key_frame_request.py +0 -0
  37. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_messaging.py +0 -0
  38. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_messaging_header.py +0 -0
  39. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_ndarray_parent_reference.py +0 -0
  40. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_nvidia_video_codec.py +0 -0
  41. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_openh264.py +0 -0
  42. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_openh264_simulcast.py +0 -0
  43. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_opus.py +0 -0
  44. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_raspberry_pi.py +0 -0
  45. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_re_offer_re_answer_sdp.py +0 -0
  46. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_rtc_log_refcount.py +0 -0
  47. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_send_data_channel_get_stats_after_disconnect.py +0 -0
  48. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_sendonly_recvonly.py +0 -0
  49. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_signaling.py +0 -0
  50. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_signaling_message.py +0 -0
  51. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_signaling_notify.py +0 -0
  52. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_simulcast.py +0 -0
  53. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_sora_disconnect.py +0 -0
  54. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_type_disconnect.py +0 -0
  55. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_type_switched.py +0 -0
  56. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_vad.py +0 -0
  57. {sora_sdk-2026.2.0.dev0 → sora_sdk-2026.2.0.dev1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2026.2.0.dev0
3
+ Version: 2026.2.0.dev1
4
4
  Summary: WebRTC SFU Sora Python SDK
5
5
  Home-page: https://github.com/shiguredo/sora-python-sdk
6
6
  Author-email: "Shiguredo Inc." <contact+pypi@shiguredo.jp>
@@ -0,0 +1 @@
1
+ 2026.2.0.dev1
@@ -492,7 +492,7 @@ class SoraVideoCodecPreference:
492
492
  def create_video_codec_preference_from_implementation(arg0: SoraVideoCodecCapability, arg1: SoraVideoCodecImplementation, /) -> SoraVideoCodecPreference: ...
493
493
 
494
494
  class Sora:
495
- def __init__(self, openh264: str | None = None, video_codec_preference: SoraVideoCodecPreference | None = None, force_i420_conversion: bool | None = None) -> None: ...
495
+ def __init__(self, openh264: str | None = None, video_codec_preference: SoraVideoCodecPreference | None = None, force_i420_conversion: bool | None = None, field_trials: str | None = None) -> None: ...
496
496
 
497
497
  def create_connection(self, signaling_urls: list[str], role: str, channel_id: str, client_id: Optional[str] = None, bundle_id: Optional[str] = None, metadata: Optional[dict] = None, signaling_notify_metadata: Optional[dict] = None, audio_source: Optional[SoraTrackInterface] = None, video_source: Optional[SoraTrackInterface] = None, audio_frame_transformer: Optional[SoraAudioFrameTransformer] = None, video_frame_transformer: Optional[SoraVideoFrameTransformer] = None, audio: Optional[bool] = None, video: Optional[bool] = None, audio_codec_type: Optional[str] = None, video_codec_type: Optional[str] = None, video_bit_rate: Optional[int] = None, audio_bit_rate: Optional[int] = None, video_vp9_params: Optional[dict] = None, video_av1_params: Optional[dict] = None, video_h264_params: Optional[dict] = None, video_h265_params: Optional[dict] = None, audio_opus_params: Optional[dict] = None, simulcast: Optional[bool] = None, spotlight: Optional[bool] = None, spotlight_number: Optional[int] = None, simulcast_rid: Optional[str] = None, simulcast_request_rid: Optional[str] = None, spotlight_focus_rid: Optional[str] = None, spotlight_unfocus_rid: Optional[str] = None, forwarding_filter: Optional[dict] = None, forwarding_filters: Optional[list[dict]] = None, data_channels: Optional[list[dict]] = None, data_channel_signaling: Optional[bool] = None, ignore_disconnect_websocket: Optional[bool] = None, data_channel_signaling_timeout: Optional[int] = None, disconnect_wait_timeout: Optional[int] = None, websocket_close_timeout: Optional[int] = None, websocket_connection_timeout: Optional[int] = None, audio_streaming_language_code: Optional[str] = None, insecure: Optional[bool] = None, client_cert: Optional[bytes] = None, client_key: Optional[bytes] = None, ca_cert: Optional[bytes] = None, proxy_url: Optional[str] = None, proxy_username: Optional[str] = None, proxy_password: Optional[str] = None, proxy_agent: Optional[str] = None, degradation_preference: Optional[SoraDegradationPreference] = None, user_agent: Optional[str] = None) -> SoraConnection: ...
498
498
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sora_sdk
3
- Version: 2026.2.0.dev0
3
+ Version: 2026.2.0.dev1
4
4
  Summary: WebRTC SFU Sora Python SDK
5
5
  Home-page: https://github.com/shiguredo/sora-python-sdk
6
6
  Author-email: "Shiguredo Inc." <contact+pypi@shiguredo.jp>
@@ -28,6 +28,7 @@ tests/test_convert_json_value_int64.py
28
28
  tests/test_degradation_preference.py
29
29
  tests/test_disconnect_timeout.py
30
30
  tests/test_encoded_transform.py
31
+ tests/test_field_trials.py
31
32
  tests/test_intel_vpl.py
32
33
  tests/test_key_frame_request.py
33
34
  tests/test_messaging.py
@@ -0,0 +1,199 @@
1
+ """
2
+ WebRTC-Video-PerSsrcKeyframes フィールドトライアルに関する検証をまとめる。
3
+
4
+ 指定した文字列は sora-cpp-sdk の SoraClientContextConfig::field_trials を経由して
5
+ libwebrtc の Environment に設定される。不正な文字列 (例: Enabled の後に "/" が無いもの)
6
+ の場合は SoraClientContext::Create が nullptr を返すため RuntimeError になる。
7
+ """
8
+
9
+ import time
10
+
11
+ import pytest
12
+ from api import request_key_frame_api
13
+ from client import SoraClient, SoraRole
14
+ from simulcast import default_video_bit_rate
15
+
16
+ from sora_sdk import Sora
17
+
18
+
19
+ def get_active_video_stats_by_rid(client: SoraClient) -> dict[str, dict]:
20
+ """
21
+ 有効な映像レイヤの outbound-rtp 統計を rid ごとにまとめて返す。
22
+
23
+ 統計の値は型が一定しない JSON のオブジェクトのため、値の型は指定しない。
24
+ bytesSent が 0 のレイヤは有効になっていないため対象外にする。
25
+ """
26
+ return {
27
+ s["rid"]: s
28
+ for s in client.get_stats()
29
+ if s.get("type") == "outbound-rtp"
30
+ and s.get("kind") == "video"
31
+ and s.get("bytesSent", 0) > 0
32
+ }
33
+
34
+
35
+ def get_key_frames_encoded(stats_by_rid: dict[str, dict]) -> dict[str, int]:
36
+ """rid ごとの outbound-rtp 統計から keyFramesEncoded を取り出す。"""
37
+ return {rid: stats["keyFramesEncoded"] for rid, stats in stats_by_rid.items()}
38
+
39
+
40
+ def connect_sendonly(settings, field_trials: str | None) -> SoraClient:
41
+ """
42
+ VP8 / サイマルキャストの SoraClient を生成して接続する。
43
+
44
+ VP8 を指定し、3 レイヤが有効になるビットレートを明示する。レイヤの有効化には
45
+ 時間がかかるため、固定で 10 秒待ってから返す (条件待ちはしていない)。
46
+ """
47
+ sendonly = SoraClient(
48
+ settings,
49
+ SoraRole.SENDONLY,
50
+ simulcast=True,
51
+ audio=False,
52
+ video=True,
53
+ video_codec_type="VP8",
54
+ video_bit_rate=default_video_bit_rate("VP8", 1280, 720),
55
+ video_width=1280,
56
+ video_height=720,
57
+ field_trials=field_trials,
58
+ )
59
+ sendonly.connect(fake_video=True)
60
+
61
+ # サイマルキャストの全レイヤが送信を開始するまで固定で 10 秒待つ
62
+ time.sleep(10)
63
+
64
+ return sendonly
65
+
66
+
67
+ def test_field_trials(settings):
68
+ """
69
+ WebRTC-Video-PerSsrcKeyframes を指定した Sora でキーフレーム要求が動作すること。
70
+
71
+ 前提:
72
+ - 不正なフィールドトライアル文字列では SoraClientContext の生成に失敗する
73
+ - サイマルキャストの全レイヤを有効にするためビットレートを明示する
74
+
75
+ 期待:
76
+ - 不正な文字列では RuntimeError になる
77
+ - キーフレーム要求 API の呼び出しで有効な全レイヤのキーフレーム数が増える
78
+ """
79
+ # 文字列の末尾に "/" が無い場合は不正なフィールドトライアル文字列として扱われる
80
+ with pytest.raises(RuntimeError):
81
+ Sora(
82
+ openh264=settings.openh264_path,
83
+ field_trials="WebRTC-Video-PerSsrcKeyframes/Enabled",
84
+ )
85
+
86
+ sendonly = connect_sendonly(settings, "WebRTC-Video-PerSsrcKeyframes/Enabled/")
87
+
88
+ # connection_id は None になり得るため、API 呼び出しに使う前に絞り込む
89
+ assert sendonly.connection_id is not None
90
+
91
+ before = get_key_frames_encoded(get_active_video_stats_by_rid(sendonly))
92
+ print("キーフレーム要求前のキーフレーム数:", before)
93
+
94
+ response = request_key_frame_api(settings.api_url, sendonly.channel_id, sendonly.connection_id)
95
+
96
+ time.sleep(5)
97
+
98
+ after = get_key_frames_encoded(get_active_video_stats_by_rid(sendonly))
99
+ print("キーフレーム要求後のキーフレーム数:", after)
100
+
101
+ sendonly.disconnect()
102
+
103
+ # 接続を閉じてから確認する。ここで失敗しても接続が残らない
104
+ assert response.status_code == 200, response.text
105
+
106
+ # サイマルキャストの全レイヤが有効になっていること
107
+ assert len(after) == 3
108
+ # キーフレーム要求により有効な全レイヤのキーフレーム数が増えていること
109
+ for rid, count in before.items():
110
+ assert after.get(rid, 0) > count, f"rid={rid} のキーフレーム数が増えていない"
111
+
112
+
113
+ @pytest.mark.parametrize(
114
+ ("field_trials", "increased_rids"),
115
+ [
116
+ # フィールドトライアル有効時は PLI を受けた rid のレイヤだけがキーフレームを生成する
117
+ ("WebRTC-Video-PerSsrcKeyframes/Enabled/", {"r2"}),
118
+ # フィールドトライアル無効時はどの rid への PLI でも全レイヤがキーフレームを生成する
119
+ (None, {"r0", "r1", "r2"}),
120
+ ],
121
+ ids=["field_trials_enabled", "field_trials_disabled"],
122
+ )
123
+ def test_per_ssrc_keyframes(settings, field_trials, increased_rids):
124
+ """
125
+ WebRTC-Video-PerSsrcKeyframes の有無で rid を指定したキーフレーム要求の結果が変わること。
126
+
127
+ 前提:
128
+ - VP8 / 3 レイヤのサイマルキャストではレイヤごとに独立した libvpx エンコーダが使われる
129
+ (encoderImplementation が "SimulcastEncoderAdapter (libvpx, libvpx, libvpx)" になる)
130
+ - 単一の simulcast 対応エンコーダになる構成では、libvpx がどのレイヤへの要求でも
131
+ 全レイヤをキーフレームにするため、このテストの期待は成立しない
132
+ - RequestKeyFrame API の rid 指定は Sora 2026.2.0-canary.15 以降で利用できる
133
+
134
+ 期待:
135
+ - フィールドトライアル有効: rid=r2 のキーフレーム要求で r2 のみキーフレーム数が増える
136
+ - フィールドトライアル無効: r0 / r1 / r2 すべてのキーフレーム数が増える
137
+
138
+ 前提が崩れた場合:
139
+ - 要求前に 3 レイヤが有効でない場合は失敗させる
140
+ - 品質制限がかかっている場合と、増加しないことを判定するレイヤがフレームを生成して
141
+ いない場合は skip する
142
+ """
143
+ sendonly = connect_sendonly(settings, field_trials)
144
+
145
+ # connection_id は None になり得るため、API 呼び出しに使う前に絞り込む
146
+ assert sendonly.connection_id is not None
147
+
148
+ stats_before = get_active_video_stats_by_rid(sendonly)
149
+ key_frames_before = get_key_frames_encoded(stats_before)
150
+ print("キーフレーム要求前のキーフレーム数:", key_frames_before)
151
+
152
+ # rid を指定すると、その rid の SSRC にのみ PLI が送られる
153
+ response = request_key_frame_api(
154
+ settings.api_url, sendonly.channel_id, sendonly.connection_id, rid="r2"
155
+ )
156
+
157
+ # Sora はキーフレームを受け取るまで 1 秒間隔で PLI を再送するため、再送が終わるまで待つ
158
+ time.sleep(5)
159
+
160
+ stats_after = get_active_video_stats_by_rid(sendonly)
161
+ key_frames_after = get_key_frames_encoded(stats_after)
162
+ print("キーフレーム要求後のキーフレーム数:", key_frames_after)
163
+
164
+ sendonly.disconnect()
165
+
166
+ # 接続を閉じてから確認する。ここで失敗しても接続が残らない
167
+ assert response.status_code == 200, response.text
168
+
169
+ # 要求前に 3 レイヤすべてが有効になっていること。ここはテストの前提なので失敗させる
170
+ assert len(stats_before) == 3, (
171
+ f"有効な映像レイヤが 3 つではない: rids={sorted(stats_before)}, field_trials={field_trials!r}"
172
+ )
173
+
174
+ # 品質制限がかかっている場合もレイヤが無効化されうるので、安定したテストができないものとして
175
+ # tests/test_simulcast.py と同じく skip する
176
+ for stats in (*stats_before.values(), *stats_after.values()):
177
+ if stats["qualityLimitationReason"] != "none":
178
+ pytest.skip(
179
+ f"rid={stats['rid']} の qualityLimitationReason: {stats['qualityLimitationReason']}"
180
+ )
181
+
182
+ # 増加しないことを判定するレイヤが実際にフレームを生成していることを確認する。
183
+ # bytesSent と framesEncoded は累積値のため、止まっているレイヤも統計からは消えず、
184
+ # キーフレーム数が変化しないことだけを見ると判定が空虚になる
185
+ for rid in sorted(set(stats_before) - increased_rids):
186
+ if stats_after[rid]["framesEncoded"] == stats_before[rid]["framesEncoded"]:
187
+ pytest.skip(f"rid={rid} のレイヤがキーフレーム要求の間にフレームを生成していない")
188
+
189
+ for rid, count in key_frames_before.items():
190
+ if rid in increased_rids:
191
+ assert key_frames_after[rid] > count, (
192
+ f"rid={rid} のキーフレーム数が増えていない: "
193
+ f"before={count}, after={key_frames_after[rid]}, field_trials={field_trials!r}"
194
+ )
195
+ else:
196
+ assert key_frames_after[rid] == count, (
197
+ f"rid={rid} のキーフレーム数が増えている: "
198
+ f"before={count}, after={key_frames_after[rid]}, field_trials={field_trials!r}"
199
+ )
@@ -1 +0,0 @@
1
- 2026.2.0.dev0