sora-sdk 2025.3.0.dev8__tar.gz → 2025.4.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.
Potentially problematic release.
This version of sora-sdk might be problematic. Click here for more details.
- {sora_sdk-2025.3.0.dev8/src/sora_sdk.egg-info → sora_sdk-2025.4.0.dev1}/PKG-INFO +2 -2
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/README.md +1 -1
- sora_sdk-2025.4.0.dev1/VERSION +1 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/buildbase.py +292 -3
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/pyproject.toml +2 -3
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1/src/sora_sdk.egg-info}/PKG-INFO +2 -2
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/SOURCES.txt +1 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_amd_amf.py +72 -4
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_intel_vpl.py +101 -152
- sora_sdk-2025.4.0.dev1/tests/test_key_frame_request.py +75 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_nvidia_video_codec_sdk.py +65 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_sendonly_recvonly.py +10 -0
- sora_sdk-2025.3.0.dev8/VERSION +0 -1
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/LICENSE +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/MANIFEST.in +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/pypath.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/run.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/setup.cfg +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/setup.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/__init__.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/py.typed +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/sora_sdk_ext.pyi +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/dependency_links.txt +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/top_level.txt +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_apple_video_toolbox.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_authz.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_authz_simulcast.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_ca_cert.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_capability.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_degradation_preference.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_encoded_transform.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_messaging.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_messaging_header.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_openh264.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_openh264_simulcast.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_opus.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_re_offer_re_answer_sdp.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling_message.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_signaling_notify.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_simulcast.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_sora_disconnect.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_type_disconnect.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_type_switched.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/tests/test_vad.py +0 -0
- {sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.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: 2025.
|
|
3
|
+
Version: 2025.4.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>
|
|
@@ -108,7 +108,7 @@ PyPI 経由ではインストールできません。
|
|
|
108
108
|
|
|
109
109
|
## システム条件
|
|
110
110
|
|
|
111
|
-
- WebRTC SFU Sora 2024.
|
|
111
|
+
- WebRTC SFU Sora 2024.2.0 以降
|
|
112
112
|
- Python 3.11 以上
|
|
113
113
|
|
|
114
114
|
## Python サポートポリシー
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2025.4.0.dev1
|
|
@@ -302,7 +302,12 @@ def _extractzip(z: zipfile.ZipFile, path: str):
|
|
|
302
302
|
def extract(file: str, output_dir: str, output_dirname: str, filetype: Optional[str] = None):
|
|
303
303
|
path = os.path.join(output_dir, output_dirname)
|
|
304
304
|
logging.info(f"Extract {file} to {path}")
|
|
305
|
-
if
|
|
305
|
+
if (
|
|
306
|
+
filetype == "gzip"
|
|
307
|
+
or file.endswith(".tar.gz")
|
|
308
|
+
or filetype == "lzma"
|
|
309
|
+
or file.endswith(".tar.xz")
|
|
310
|
+
):
|
|
306
311
|
rm_rf(path)
|
|
307
312
|
with tarfile.open(file) as t:
|
|
308
313
|
dir = is_single_dir_tar(t)
|
|
@@ -378,6 +383,7 @@ def git_clone_shallow(url, hash, dir, submodule=False):
|
|
|
378
383
|
|
|
379
384
|
|
|
380
385
|
def apply_patch(patch, dir, depth):
|
|
386
|
+
patch = os.path.abspath(patch)
|
|
381
387
|
with cd(dir):
|
|
382
388
|
logging.info(f"patch -p{depth} < {patch}")
|
|
383
389
|
if platform.system() == "Windows":
|
|
@@ -800,6 +806,7 @@ def build_and_install_boost(
|
|
|
800
806
|
)
|
|
801
807
|
elif target_os == "android":
|
|
802
808
|
# Android の場合、android-ndk を使ってビルドする
|
|
809
|
+
# ただし cxx が指定されてた場合はそちらを優先する
|
|
803
810
|
with open("project-config.jam", "w", encoding="utf-8") as f:
|
|
804
811
|
bin = os.path.join(
|
|
805
812
|
android_ndk, "toolchains", "llvm", "prebuilt", android_build_platform, "bin"
|
|
@@ -814,7 +821,7 @@ def build_and_install_boost(
|
|
|
814
821
|
f.write(
|
|
815
822
|
f"using clang \
|
|
816
823
|
: android \
|
|
817
|
-
: {escape(os.path.join(bin, 'clang++'))} \
|
|
824
|
+
: {escape(cxx if len(cxx) != 0 else os.path.join(bin, 'clang++'))} \
|
|
818
825
|
--target=aarch64-none-linux-android{native_api_level} \
|
|
819
826
|
--sysroot={escape(sysroot)} \
|
|
820
827
|
: <archiver>{escape(os.path.join(bin, 'llvm-ar'))} \
|
|
@@ -932,7 +939,7 @@ def build_sora(
|
|
|
932
939
|
]
|
|
933
940
|
|
|
934
941
|
with cd(local_sora_cpp_sdk_dir):
|
|
935
|
-
cmd(["python3", "run.py", platform, *local_sora_cpp_sdk_args])
|
|
942
|
+
cmd(["python3", "run.py", "build", platform, *local_sora_cpp_sdk_args])
|
|
936
943
|
|
|
937
944
|
|
|
938
945
|
class SoraInfo(NamedTuple):
|
|
@@ -1226,6 +1233,93 @@ def install_sdl2(
|
|
|
1226
1233
|
cmd(["cmake", "--install", ".", "--config", configuration])
|
|
1227
1234
|
|
|
1228
1235
|
|
|
1236
|
+
@versioned
|
|
1237
|
+
def install_sdl3(
|
|
1238
|
+
version, source_dir, build_dir, install_dir, debug: bool, platform: str, cmake_args: List[str]
|
|
1239
|
+
):
|
|
1240
|
+
url = f"https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL3-{version}.tar.gz"
|
|
1241
|
+
path = download(url, source_dir)
|
|
1242
|
+
sdl3_source_dir = os.path.join(source_dir, "sdl3")
|
|
1243
|
+
sdl3_build_dir = os.path.join(build_dir, "sdl3")
|
|
1244
|
+
sdl3_install_dir = os.path.join(install_dir, "sdl3")
|
|
1245
|
+
rm_rf(sdl3_source_dir)
|
|
1246
|
+
rm_rf(sdl3_build_dir)
|
|
1247
|
+
rm_rf(sdl3_install_dir)
|
|
1248
|
+
extract(path, source_dir, "sdl3")
|
|
1249
|
+
|
|
1250
|
+
mkdir_p(sdl3_build_dir)
|
|
1251
|
+
with cd(sdl3_build_dir):
|
|
1252
|
+
configuration = "Debug" if debug else "Release"
|
|
1253
|
+
cmake_args = cmake_args[:]
|
|
1254
|
+
cmake_args += [
|
|
1255
|
+
sdl3_source_dir,
|
|
1256
|
+
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
1257
|
+
f"-DCMAKE_INSTALL_PREFIX={cmake_path(sdl3_install_dir)}",
|
|
1258
|
+
"-DBUILD_SHARED_LIBS=OFF",
|
|
1259
|
+
"-DSDL_STATIC=ON",
|
|
1260
|
+
"-DSDL_SHARED=OFF",
|
|
1261
|
+
]
|
|
1262
|
+
if platform == "windows":
|
|
1263
|
+
cmake_args += [
|
|
1264
|
+
f"-DCMAKE_MSVC_RUNTIME_LIBRARY={'MultiThreaded' if not debug else 'MultiThreadedDebug'}",
|
|
1265
|
+
"-DSDL_AUDIO=OFF",
|
|
1266
|
+
"-DSDL_JOYSTICK=OFF",
|
|
1267
|
+
"-DSDL_HAPTIC=OFF",
|
|
1268
|
+
# GitHub Actions 上で gameinput.h が存在しないのに
|
|
1269
|
+
# なぜか check_c_source_compiles() が成功してしまうので
|
|
1270
|
+
# HAVE_GAMEINPUT_H=0 で強制的に無効化する
|
|
1271
|
+
"-DHAVE_GAMEINPUT_H=0",
|
|
1272
|
+
]
|
|
1273
|
+
elif platform == "macos":
|
|
1274
|
+
# どの環境でも同じようにインストールされるようにするため全部 ON/OFF を明示的に指定する
|
|
1275
|
+
cmake_args += [
|
|
1276
|
+
"-DSDL_AUDIO=OFF",
|
|
1277
|
+
"-DSDL_VIDEO=ON",
|
|
1278
|
+
"-DSDL_RENDER=ON",
|
|
1279
|
+
"-DSDL_HAPTIC=ON",
|
|
1280
|
+
"-DSDL_POWER=ON",
|
|
1281
|
+
"-DSDL_JOYSTICK=ON",
|
|
1282
|
+
"-DSDL_SENSOR=ON",
|
|
1283
|
+
"-DSDL_OPENGL=ON",
|
|
1284
|
+
"-DSDL_OPENGLES=ON",
|
|
1285
|
+
"-DSDL_METAL=ON",
|
|
1286
|
+
"-DSDL_VULKAN=OFF",
|
|
1287
|
+
]
|
|
1288
|
+
elif platform == "linux":
|
|
1289
|
+
# どの環境でも同じようにインストールされるようにするため全部 ON/OFF を明示的に指定する
|
|
1290
|
+
cmake_args += [
|
|
1291
|
+
"-DSDL_AUDIO=OFF",
|
|
1292
|
+
"-DSDL_VIDEO=ON",
|
|
1293
|
+
"-DSDL_RENDER=ON",
|
|
1294
|
+
"-DSDL_HAPTIC=ON",
|
|
1295
|
+
"-DSDL_POWER=ON",
|
|
1296
|
+
"-DSDL_JOYSTICK=ON",
|
|
1297
|
+
"-DSDL_SENSOR=ON",
|
|
1298
|
+
"-DSDL_OPENGL=ON",
|
|
1299
|
+
"-DSDL_OPENGLES=ON",
|
|
1300
|
+
"-DSDL_X11=ON",
|
|
1301
|
+
"-DSDL_X11_SHARED=OFF",
|
|
1302
|
+
"-DSDL_X11_XCURSOR=OFF",
|
|
1303
|
+
"-DSDL_X11_XDBE=OFF",
|
|
1304
|
+
"-DSDL_X11_XFIXES=OFF",
|
|
1305
|
+
"-DSDL_X11_XINPUT=OFF",
|
|
1306
|
+
"-DSDL_X11_XRANDR=OFF",
|
|
1307
|
+
"-DSDL_X11_XSCRNSAVER=OFF",
|
|
1308
|
+
"-DSDL_X11_XSHAPE=OFF",
|
|
1309
|
+
"-DSDL_X11_XSYNC=OFF",
|
|
1310
|
+
"-DSDL_WAYLAND=OFF",
|
|
1311
|
+
"-DSDL_VULKAN=OFF",
|
|
1312
|
+
"-DSDL_KMSDRM=OFF",
|
|
1313
|
+
"-DSDL_RPI=OFF",
|
|
1314
|
+
]
|
|
1315
|
+
cmd(["cmake"] + cmake_args)
|
|
1316
|
+
|
|
1317
|
+
cmd(
|
|
1318
|
+
["cmake", "--build", ".", "--config", configuration, f"-j{multiprocessing.cpu_count()}"]
|
|
1319
|
+
)
|
|
1320
|
+
cmd(["cmake", "--install", ".", "--config", configuration])
|
|
1321
|
+
|
|
1322
|
+
|
|
1229
1323
|
@versioned
|
|
1230
1324
|
def install_cli11(version, install_dir):
|
|
1231
1325
|
cli11_install_dir = os.path.join(install_dir, "cli11")
|
|
@@ -1307,6 +1401,33 @@ def install_vpl(version, configuration, source_dir, build_dir, install_dir, cmak
|
|
|
1307
1401
|
cmd(["cmake", "--install", ".", "--config", configuration])
|
|
1308
1402
|
|
|
1309
1403
|
|
|
1404
|
+
@versioned
|
|
1405
|
+
def install_blend2d_official(
|
|
1406
|
+
version,
|
|
1407
|
+
configuration,
|
|
1408
|
+
source_dir,
|
|
1409
|
+
build_dir,
|
|
1410
|
+
install_dir,
|
|
1411
|
+
ios,
|
|
1412
|
+
cmake_args,
|
|
1413
|
+
):
|
|
1414
|
+
rm_rf(os.path.join(source_dir, "blend2d"))
|
|
1415
|
+
rm_rf(os.path.join(build_dir, "blend2d"))
|
|
1416
|
+
rm_rf(os.path.join(install_dir, "blend2d"))
|
|
1417
|
+
|
|
1418
|
+
url = f"https://blend2d.com/download/blend2d-{version}.tar.gz"
|
|
1419
|
+
path = download(url, source_dir)
|
|
1420
|
+
extract(path, source_dir, "blend2d")
|
|
1421
|
+
_build_blend2d(
|
|
1422
|
+
configuration=configuration,
|
|
1423
|
+
source_dir=source_dir,
|
|
1424
|
+
build_dir=build_dir,
|
|
1425
|
+
install_dir=install_dir,
|
|
1426
|
+
ios=ios,
|
|
1427
|
+
cmake_args=cmake_args,
|
|
1428
|
+
)
|
|
1429
|
+
|
|
1430
|
+
|
|
1310
1431
|
@versioned
|
|
1311
1432
|
def install_blend2d(
|
|
1312
1433
|
version,
|
|
@@ -1332,7 +1453,17 @@ def install_blend2d(
|
|
|
1332
1453
|
asmjit_version,
|
|
1333
1454
|
os.path.join(source_dir, "blend2d", "3rdparty", "asmjit"),
|
|
1334
1455
|
)
|
|
1456
|
+
_build_blend2d(
|
|
1457
|
+
configuration=configuration,
|
|
1458
|
+
source_dir=source_dir,
|
|
1459
|
+
build_dir=build_dir,
|
|
1460
|
+
install_dir=install_dir,
|
|
1461
|
+
ios=ios,
|
|
1462
|
+
cmake_args=cmake_args,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1335
1465
|
|
|
1466
|
+
def _build_blend2d(configuration, source_dir, build_dir, install_dir, ios, cmake_args):
|
|
1336
1467
|
mkdir_p(os.path.join(build_dir, "blend2d"))
|
|
1337
1468
|
with cd(os.path.join(build_dir, "blend2d")):
|
|
1338
1469
|
cmd(
|
|
@@ -1747,6 +1878,7 @@ def install_opus(
|
|
|
1747
1878
|
"cmake",
|
|
1748
1879
|
f"-DCMAKE_INSTALL_PREFIX={cmake_path(opus_install_dir)}",
|
|
1749
1880
|
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
1881
|
+
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
|
|
1750
1882
|
"-DOPUS_BUILD_SHARED_LIBRARY=OFF",
|
|
1751
1883
|
"-DOPUS_BUILD_TESTING=OFF",
|
|
1752
1884
|
"-DOPUS_BUILD_PROGRAMS=OFF",
|
|
@@ -1803,6 +1935,163 @@ def install_amf(version, install_dir):
|
|
|
1803
1935
|
)
|
|
1804
1936
|
|
|
1805
1937
|
|
|
1938
|
+
@versioned
|
|
1939
|
+
def install_mbedtls(version, source_dir, build_dir, install_dir, debug, cmake_args):
|
|
1940
|
+
rm_rf(os.path.join(source_dir, "mbedtls"))
|
|
1941
|
+
rm_rf(os.path.join(build_dir, "mbedtls"))
|
|
1942
|
+
rm_rf(os.path.join(install_dir, "mbedtls"))
|
|
1943
|
+
git_clone_shallow(
|
|
1944
|
+
"https://github.com/Mbed-TLS/mbedtls.git",
|
|
1945
|
+
version,
|
|
1946
|
+
os.path.join(source_dir, "mbedtls"),
|
|
1947
|
+
)
|
|
1948
|
+
with cd(os.path.join(source_dir, "mbedtls")):
|
|
1949
|
+
configuration = "Debug" if debug else "Release"
|
|
1950
|
+
cmd(["python3", "scripts/config.py", "set", "MBEDTLS_SSL_DTLS_SRTP"])
|
|
1951
|
+
cmd(
|
|
1952
|
+
[
|
|
1953
|
+
"cmake",
|
|
1954
|
+
f"-DCMAKE_INSTALL_PREFIX={cmake_path(os.path.join(install_dir, 'mbedtls'))}",
|
|
1955
|
+
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
1956
|
+
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
|
|
1957
|
+
"-B",
|
|
1958
|
+
os.path.join(build_dir, "mbedtls"),
|
|
1959
|
+
]
|
|
1960
|
+
+ cmake_args
|
|
1961
|
+
)
|
|
1962
|
+
cmd(
|
|
1963
|
+
[
|
|
1964
|
+
"cmake",
|
|
1965
|
+
"--build",
|
|
1966
|
+
os.path.join(build_dir, "mbedtls"),
|
|
1967
|
+
f"-j{multiprocessing.cpu_count()}",
|
|
1968
|
+
"--config",
|
|
1969
|
+
"Release",
|
|
1970
|
+
]
|
|
1971
|
+
)
|
|
1972
|
+
cmd(["cmake", "--install", os.path.join(build_dir, "mbedtls")])
|
|
1973
|
+
|
|
1974
|
+
|
|
1975
|
+
@versioned
|
|
1976
|
+
def install_libjpeg_turbo(version, source_dir, build_dir, install_dir, configuration, cmake_args):
|
|
1977
|
+
libjpeg_source_dir = os.path.join(source_dir, "libjpeg-turbo")
|
|
1978
|
+
libjpeg_build_dir = os.path.join(build_dir, "libjpeg-turbo")
|
|
1979
|
+
libjpeg_install_dir = os.path.join(install_dir, "libjpeg-turbo")
|
|
1980
|
+
rm_rf(libjpeg_source_dir)
|
|
1981
|
+
rm_rf(libjpeg_build_dir)
|
|
1982
|
+
rm_rf(libjpeg_install_dir)
|
|
1983
|
+
git_clone_shallow(
|
|
1984
|
+
"https://github.com/libjpeg-turbo/libjpeg-turbo.git",
|
|
1985
|
+
version,
|
|
1986
|
+
libjpeg_source_dir,
|
|
1987
|
+
)
|
|
1988
|
+
mkdir_p(libjpeg_build_dir)
|
|
1989
|
+
with cd(libjpeg_build_dir):
|
|
1990
|
+
cmd(
|
|
1991
|
+
[
|
|
1992
|
+
"cmake",
|
|
1993
|
+
libjpeg_source_dir,
|
|
1994
|
+
f"-DCMAKE_INSTALL_PREFIX={libjpeg_install_dir}",
|
|
1995
|
+
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
1996
|
+
"-DENABLE_SHARED=FALSE",
|
|
1997
|
+
"-DENABLE_STATIC=TRUE",
|
|
1998
|
+
"-DCMAKE_BUILD_TYPE=Release",
|
|
1999
|
+
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
|
|
2000
|
+
]
|
|
2001
|
+
+ cmake_args
|
|
2002
|
+
)
|
|
2003
|
+
cmd(
|
|
2004
|
+
[
|
|
2005
|
+
"cmake",
|
|
2006
|
+
"--build",
|
|
2007
|
+
libjpeg_build_dir,
|
|
2008
|
+
f"-j{multiprocessing.cpu_count()}",
|
|
2009
|
+
"--config",
|
|
2010
|
+
"Release",
|
|
2011
|
+
]
|
|
2012
|
+
)
|
|
2013
|
+
cmd(["cmake", "--install", libjpeg_build_dir])
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
@versioned
|
|
2017
|
+
def install_libyuv(
|
|
2018
|
+
version, source_dir, build_dir, install_dir, libjpeg_turbo_dir, configuration, cmake_args
|
|
2019
|
+
):
|
|
2020
|
+
libyuv_source_dir = os.path.join(source_dir, "libyuv")
|
|
2021
|
+
libyuv_build_dir = os.path.join(build_dir, "libyuv")
|
|
2022
|
+
libyuv_install_dir = os.path.join(install_dir, "libyuv")
|
|
2023
|
+
rm_rf(libyuv_source_dir)
|
|
2024
|
+
rm_rf(libyuv_build_dir)
|
|
2025
|
+
rm_rf(libyuv_install_dir)
|
|
2026
|
+
git_clone_shallow(
|
|
2027
|
+
"https://chromium.googlesource.com/libyuv/libyuv",
|
|
2028
|
+
version,
|
|
2029
|
+
libyuv_source_dir,
|
|
2030
|
+
)
|
|
2031
|
+
mkdir_p(libyuv_build_dir)
|
|
2032
|
+
with cd(libyuv_build_dir):
|
|
2033
|
+
cmd(
|
|
2034
|
+
[
|
|
2035
|
+
"cmake",
|
|
2036
|
+
libyuv_source_dir,
|
|
2037
|
+
f"-DCMAKE_INSTALL_PREFIX={cmake_path(libyuv_install_dir)}",
|
|
2038
|
+
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
2039
|
+
f"-DCMAKE_PREFIX_PATH={cmake_path(libjpeg_turbo_dir)}",
|
|
2040
|
+
*cmake_args,
|
|
2041
|
+
]
|
|
2042
|
+
)
|
|
2043
|
+
cmd(
|
|
2044
|
+
[
|
|
2045
|
+
"cmake",
|
|
2046
|
+
"--build",
|
|
2047
|
+
libyuv_build_dir,
|
|
2048
|
+
f"-j{multiprocessing.cpu_count()}",
|
|
2049
|
+
"--config",
|
|
2050
|
+
"Release",
|
|
2051
|
+
]
|
|
2052
|
+
)
|
|
2053
|
+
cmd(["cmake", "--install", libyuv_build_dir])
|
|
2054
|
+
|
|
2055
|
+
|
|
2056
|
+
@versioned
|
|
2057
|
+
def install_aom(version, source_dir, build_dir, install_dir, configuration, cmake_args):
|
|
2058
|
+
aom_source_dir = os.path.join(source_dir, "aom")
|
|
2059
|
+
aom_build_dir = os.path.join(build_dir, "aom")
|
|
2060
|
+
aom_install_dir = os.path.join(install_dir, "aom")
|
|
2061
|
+
rm_rf(aom_source_dir)
|
|
2062
|
+
rm_rf(aom_build_dir)
|
|
2063
|
+
rm_rf(aom_install_dir)
|
|
2064
|
+
git_clone_shallow(
|
|
2065
|
+
"https://aomedia.googlesource.com/aom",
|
|
2066
|
+
version,
|
|
2067
|
+
aom_source_dir,
|
|
2068
|
+
)
|
|
2069
|
+
with cd(aom_source_dir):
|
|
2070
|
+
cmd(
|
|
2071
|
+
[
|
|
2072
|
+
"cmake",
|
|
2073
|
+
"-B",
|
|
2074
|
+
aom_build_dir,
|
|
2075
|
+
f"-DCMAKE_INSTALL_PREFIX={cmake_path(aom_install_dir)}",
|
|
2076
|
+
f"-DCMAKE_BUILD_TYPE={configuration}",
|
|
2077
|
+
"-DENABLE_DOCS=OFF",
|
|
2078
|
+
"-DENABLE_TESTS=OFF",
|
|
2079
|
+
*cmake_args,
|
|
2080
|
+
]
|
|
2081
|
+
)
|
|
2082
|
+
cmd(
|
|
2083
|
+
[
|
|
2084
|
+
"cmake",
|
|
2085
|
+
"--build",
|
|
2086
|
+
aom_build_dir,
|
|
2087
|
+
f"-j{multiprocessing.cpu_count()}",
|
|
2088
|
+
"--config",
|
|
2089
|
+
"Release",
|
|
2090
|
+
]
|
|
2091
|
+
)
|
|
2092
|
+
cmd(["cmake", "--install", aom_build_dir])
|
|
2093
|
+
|
|
2094
|
+
|
|
1806
2095
|
class PlatformTarget(object):
|
|
1807
2096
|
def __init__(self, os, osver, arch, extra=None):
|
|
1808
2097
|
self.os = os
|
|
@@ -26,9 +26,9 @@ build-backend = "setuptools.build_meta"
|
|
|
26
26
|
[tool.uv]
|
|
27
27
|
python-preference = "only-managed"
|
|
28
28
|
dev-dependencies = [
|
|
29
|
-
"nanobind==2.
|
|
29
|
+
"nanobind==2.8.0",
|
|
30
30
|
"setuptools==80.9",
|
|
31
|
-
"build==1.
|
|
31
|
+
"build==1.3.0",
|
|
32
32
|
"wheel==0.45.1",
|
|
33
33
|
"numpy",
|
|
34
34
|
"httpx",
|
|
@@ -37,7 +37,6 @@ dev-dependencies = [
|
|
|
37
37
|
"pyjwt",
|
|
38
38
|
"pytest-repeat",
|
|
39
39
|
"ty",
|
|
40
|
-
"pydantic-settings",
|
|
41
40
|
]
|
|
42
41
|
|
|
43
42
|
[tool.ruff]
|
{sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk/sora_sdk_ext.cpython-311-darwin.so
RENAMED
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sora_sdk
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.4.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>
|
|
@@ -108,7 +108,7 @@ PyPI 経由ではインストールできません。
|
|
|
108
108
|
|
|
109
109
|
## システム条件
|
|
110
110
|
|
|
111
|
-
- WebRTC SFU Sora 2024.
|
|
111
|
+
- WebRTC SFU Sora 2024.2.0 以降
|
|
112
112
|
- Python 3.11 以上
|
|
113
113
|
|
|
114
114
|
## Python サポートポリシー
|
|
@@ -24,6 +24,7 @@ tests/test_capability.py
|
|
|
24
24
|
tests/test_degradation_preference.py
|
|
25
25
|
tests/test_encoded_transform.py
|
|
26
26
|
tests/test_intel_vpl.py
|
|
27
|
+
tests/test_key_frame_request.py
|
|
27
28
|
tests/test_messaging.py
|
|
28
29
|
tests/test_messaging_header.py
|
|
29
30
|
tests/test_nvidia_video_codec_sdk.py
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import time
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from api import request_key_frame_api
|
|
5
6
|
from client import (
|
|
6
7
|
SoraClient,
|
|
7
8
|
SoraRole,
|
|
@@ -60,8 +61,70 @@ def test_amd_amf_available(settings):
|
|
|
60
61
|
@pytest.mark.parametrize(
|
|
61
62
|
"video_codec_type",
|
|
62
63
|
[
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
"AV1",
|
|
65
|
+
"H264",
|
|
66
|
+
"H265",
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
def test_amd_amf_key_frame_request(settings, video_codec_type):
|
|
70
|
+
sendonly = SoraClient(
|
|
71
|
+
settings,
|
|
72
|
+
SoraRole.SENDONLY,
|
|
73
|
+
audio=False,
|
|
74
|
+
video=True,
|
|
75
|
+
video_codec_type=video_codec_type,
|
|
76
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
77
|
+
codecs=[
|
|
78
|
+
SoraVideoCodecPreference.Codec(
|
|
79
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
80
|
+
encoder=SoraVideoCodecImplementation.AMD_AMF,
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
sendonly.connect(fake_video=True)
|
|
86
|
+
|
|
87
|
+
time.sleep(3)
|
|
88
|
+
|
|
89
|
+
assert sendonly.connection_id is not None
|
|
90
|
+
|
|
91
|
+
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
92
|
+
api_count = 3
|
|
93
|
+
for _ in range(api_count):
|
|
94
|
+
response = request_key_frame_api(
|
|
95
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
96
|
+
)
|
|
97
|
+
assert response.status_code == 200
|
|
98
|
+
time.sleep(3)
|
|
99
|
+
|
|
100
|
+
# 統計を取得する
|
|
101
|
+
sendonly_stats = sendonly.get_stats()
|
|
102
|
+
|
|
103
|
+
sendonly.disconnect()
|
|
104
|
+
|
|
105
|
+
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
106
|
+
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
107
|
+
|
|
108
|
+
print("video_codec_type:", video_codec_type)
|
|
109
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
110
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
111
|
+
print(
|
|
112
|
+
"keyFramesEncoded >= pliCount * 0.7:",
|
|
113
|
+
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
117
|
+
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
118
|
+
|
|
119
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
120
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.mark.skipif(os.environ.get("AMD_AMF") is None, reason="AMD AMF でのみ実行する")
|
|
124
|
+
@pytest.mark.parametrize(
|
|
125
|
+
"video_codec_type",
|
|
126
|
+
[
|
|
127
|
+
"AV1",
|
|
65
128
|
"H264",
|
|
66
129
|
"H265",
|
|
67
130
|
],
|
|
@@ -89,6 +152,8 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
|
|
|
89
152
|
)
|
|
90
153
|
sendonly.connect(fake_video=True)
|
|
91
154
|
|
|
155
|
+
time.sleep(5)
|
|
156
|
+
|
|
92
157
|
recvonly = SoraClient(
|
|
93
158
|
settings,
|
|
94
159
|
SoraRole.RECVONLY,
|
|
@@ -123,7 +188,6 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
|
|
|
123
188
|
|
|
124
189
|
# codec が無かったら StopIteration 例外が上がる
|
|
125
190
|
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
126
|
-
# H.264/H.265 が採用されているかどうか確認する
|
|
127
191
|
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
128
192
|
|
|
129
193
|
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
@@ -131,10 +195,13 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
|
|
|
131
195
|
assert outbound_rtp_stats["encoderImplementation"] == "AMF"
|
|
132
196
|
assert outbound_rtp_stats["bytesSent"] > 0
|
|
133
197
|
assert outbound_rtp_stats["packetsSent"] > 0
|
|
198
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > 0
|
|
199
|
+
assert outbound_rtp_stats["pliCount"] > 0
|
|
200
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
201
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.5
|
|
134
202
|
|
|
135
203
|
# codec が無かったら StopIteration 例外が上がる
|
|
136
204
|
recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
|
|
137
|
-
# H.264/H.265 が採用されているかどうか確認する
|
|
138
205
|
assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
139
206
|
|
|
140
207
|
# inbound-rtp が無かったら StopIteration 例外が上がる
|
|
@@ -142,6 +209,7 @@ def test_amd_amf_sendonly_recvonly(settings, video_codec_type):
|
|
|
142
209
|
assert inbound_rtp_stats["decoderImplementation"] == "AMF"
|
|
143
210
|
assert inbound_rtp_stats["bytesReceived"] > 0
|
|
144
211
|
assert inbound_rtp_stats["packetsReceived"] > 0
|
|
212
|
+
assert inbound_rtp_stats["keyFramesDecoded"] > 0
|
|
145
213
|
|
|
146
214
|
|
|
147
215
|
@pytest.mark.skipif(os.environ.get("AMD_AMF") is None, reason="AMD AMF でのみ実行する")
|
|
@@ -3,6 +3,7 @@ import platform
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
|
+
from api import request_key_frame_api
|
|
6
7
|
from client import (
|
|
7
8
|
SoraClient,
|
|
8
9
|
SoraRole,
|
|
@@ -43,8 +44,8 @@ def test_intel_vpl_available():
|
|
|
43
44
|
case SoraVideoCodecType.VP9:
|
|
44
45
|
assert c.decoder is True
|
|
45
46
|
# VPL 的に VP9 は利用できるが、
|
|
46
|
-
# Sora Python SDK では VPL VP9 Encoder
|
|
47
|
-
assert c.encoder is
|
|
47
|
+
# Sora Python SDK では VPL VP9 Encoder が正常に動作しない
|
|
48
|
+
assert c.encoder is True
|
|
48
49
|
case SoraVideoCodecType.AV1:
|
|
49
50
|
# チップによって対応指定ないものがあるので判断しない
|
|
50
51
|
# assert c.decoder is True
|
|
@@ -60,6 +61,69 @@ def test_intel_vpl_available():
|
|
|
60
61
|
pytest.fail(f"未実装の codec_type: {c.type}")
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
@pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
|
|
65
|
+
@pytest.mark.parametrize(
|
|
66
|
+
"video_codec_type",
|
|
67
|
+
[
|
|
68
|
+
"VP9",
|
|
69
|
+
"AV1",
|
|
70
|
+
"H264",
|
|
71
|
+
"H265",
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
75
|
+
sendonly = SoraClient(
|
|
76
|
+
settings,
|
|
77
|
+
SoraRole.SENDONLY,
|
|
78
|
+
audio=False,
|
|
79
|
+
video=True,
|
|
80
|
+
video_codec_type=video_codec_type,
|
|
81
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
82
|
+
codecs=[
|
|
83
|
+
SoraVideoCodecPreference.Codec(
|
|
84
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
85
|
+
encoder=SoraVideoCodecImplementation.INTEL_VPL,
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
sendonly.connect(fake_video=True)
|
|
91
|
+
|
|
92
|
+
time.sleep(3)
|
|
93
|
+
|
|
94
|
+
assert sendonly.connection_id is not None
|
|
95
|
+
|
|
96
|
+
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
97
|
+
api_count = 3
|
|
98
|
+
for _ in range(api_count):
|
|
99
|
+
response = request_key_frame_api(
|
|
100
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
101
|
+
)
|
|
102
|
+
assert response.status_code == 200
|
|
103
|
+
time.sleep(3)
|
|
104
|
+
|
|
105
|
+
# 統計を取得する
|
|
106
|
+
sendonly_stats = sendonly.get_stats()
|
|
107
|
+
|
|
108
|
+
sendonly.disconnect()
|
|
109
|
+
|
|
110
|
+
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
111
|
+
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
112
|
+
|
|
113
|
+
# 3 回以上
|
|
114
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
115
|
+
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
116
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
117
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
118
|
+
|
|
119
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
120
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
121
|
+
print(
|
|
122
|
+
"keyFramesEncoded >= pliCount * 0.7:",
|
|
123
|
+
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
63
127
|
@pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
|
|
64
128
|
@pytest.mark.parametrize(
|
|
65
129
|
(
|
|
@@ -73,14 +137,17 @@ def test_intel_vpl_available():
|
|
|
73
137
|
# FIXME: AV1 では、解像度が一定数より低くなる場合、エラーになるのでコメントアウトしている
|
|
74
138
|
[
|
|
75
139
|
# 1080p
|
|
140
|
+
("VP9", "libvpl", 1920, 1080, 3),
|
|
76
141
|
("AV1", "libvpl", 1920, 1080, 3),
|
|
77
142
|
("H264", "libvpl", 1920, 1080, 3),
|
|
78
143
|
("H265", "libvpl", 1920, 1080, 3),
|
|
79
144
|
# 720p
|
|
145
|
+
("VP9", "libvpl", 1280, 720, 3),
|
|
80
146
|
("AV1", "libvpl", 1280, 720, 3),
|
|
81
147
|
("H264", "libvpl", 1280, 720, 3),
|
|
82
148
|
("H265", "libvpl", 1280, 720, 3),
|
|
83
149
|
# 540p
|
|
150
|
+
("VP9", "libvpl", 960, 540, 3),
|
|
84
151
|
("AV1", "libvpl", 960, 540, 3),
|
|
85
152
|
("H264", "libvpl", 960, 540, 3),
|
|
86
153
|
("H265", "libvpl", 960, 540, 3),
|
|
@@ -139,7 +206,7 @@ def test_intel_vpl_simulcast(
|
|
|
139
206
|
)
|
|
140
207
|
sendonly.connect(fake_video=True)
|
|
141
208
|
|
|
142
|
-
time.sleep(
|
|
209
|
+
time.sleep(3)
|
|
143
210
|
|
|
144
211
|
sendonly_stats = sendonly.get_stats()
|
|
145
212
|
|
|
@@ -218,6 +285,7 @@ def test_intel_vpl_simulcast(
|
|
|
218
285
|
@pytest.mark.parametrize(
|
|
219
286
|
"video_codec_type",
|
|
220
287
|
[
|
|
288
|
+
"VP9",
|
|
221
289
|
"AV1",
|
|
222
290
|
"H264",
|
|
223
291
|
"H265",
|
|
@@ -246,6 +314,8 @@ def test_intel_vpl_sendonly_recvonly(settings, video_codec_type):
|
|
|
246
314
|
)
|
|
247
315
|
sendonly.connect(fake_video=True)
|
|
248
316
|
|
|
317
|
+
time.sleep(3)
|
|
318
|
+
|
|
249
319
|
recvonly = SoraClient(
|
|
250
320
|
settings,
|
|
251
321
|
SoraRole.RECVONLY,
|
|
@@ -260,7 +330,7 @@ def test_intel_vpl_sendonly_recvonly(settings, video_codec_type):
|
|
|
260
330
|
)
|
|
261
331
|
recvonly.connect()
|
|
262
332
|
|
|
263
|
-
time.sleep(
|
|
333
|
+
time.sleep(3)
|
|
264
334
|
|
|
265
335
|
sendonly_stats = sendonly.get_stats()
|
|
266
336
|
recvonly_stats = recvonly.get_stats()
|
|
@@ -346,7 +416,7 @@ def test_intel_vpl_av1_mini_resolution(
|
|
|
346
416
|
)
|
|
347
417
|
sendonly.connect(fake_video=True)
|
|
348
418
|
|
|
349
|
-
time.sleep(
|
|
419
|
+
time.sleep(3)
|
|
350
420
|
|
|
351
421
|
assert sendonly.connect_message is not None
|
|
352
422
|
assert sendonly.connect_message["channel_id"] == settings.channel_id
|
|
@@ -384,165 +454,39 @@ def test_intel_vpl_av1_mini_resolution(
|
|
|
384
454
|
assert outbound_rtp_stats["packetsSent"] > 0
|
|
385
455
|
|
|
386
456
|
|
|
387
|
-
|
|
388
|
-
def test_intel_vpl_decoding_av1(settings):
|
|
389
|
-
"""
|
|
390
|
-
N100 などは AV1 のデコーディングに対応している
|
|
391
|
-
"""
|
|
392
|
-
sendonly = SoraClient(
|
|
393
|
-
settings,
|
|
394
|
-
SoraRole.SENDONLY,
|
|
395
|
-
audio=False,
|
|
396
|
-
video=True,
|
|
397
|
-
video_codec_type="AV1",
|
|
398
|
-
video_codec_preference=SoraVideoCodecPreference(
|
|
399
|
-
codecs=[
|
|
400
|
-
SoraVideoCodecPreference.Codec(
|
|
401
|
-
type=SoraVideoCodecType.AV1,
|
|
402
|
-
encoder=SoraVideoCodecImplementation.INTERNAL,
|
|
403
|
-
),
|
|
404
|
-
]
|
|
405
|
-
),
|
|
406
|
-
)
|
|
407
|
-
sendonly.connect(fake_video=True)
|
|
408
|
-
|
|
409
|
-
recvonly = SoraClient(
|
|
410
|
-
settings,
|
|
411
|
-
SoraRole.RECVONLY,
|
|
412
|
-
video_codec_preference=SoraVideoCodecPreference(
|
|
413
|
-
codecs=[
|
|
414
|
-
SoraVideoCodecPreference.Codec(
|
|
415
|
-
type=SoraVideoCodecType.AV1,
|
|
416
|
-
decoder=SoraVideoCodecImplementation.INTEL_VPL,
|
|
417
|
-
),
|
|
418
|
-
]
|
|
419
|
-
),
|
|
420
|
-
)
|
|
421
|
-
recvonly.connect()
|
|
422
|
-
|
|
423
|
-
time.sleep(5)
|
|
424
|
-
|
|
425
|
-
sendonly_stats = sendonly.get_stats()
|
|
426
|
-
recvonly_stats = recvonly.get_stats()
|
|
427
|
-
|
|
428
|
-
sendonly.disconnect()
|
|
429
|
-
recvonly.disconnect()
|
|
430
|
-
|
|
431
|
-
# offer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
432
|
-
assert sendonly.offer_message is not None
|
|
433
|
-
assert "sdp" in sendonly.offer_message
|
|
434
|
-
assert "AV1" in sendonly.offer_message["sdp"]
|
|
435
|
-
|
|
436
|
-
# answer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
437
|
-
assert sendonly.answer_message is not None
|
|
438
|
-
assert "sdp" in sendonly.answer_message
|
|
439
|
-
assert "AV1" in sendonly.answer_message["sdp"]
|
|
440
|
-
|
|
441
|
-
# codec が無かったら StopIteration 例外が上がる
|
|
442
|
-
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
443
|
-
assert sendonly_codec_stats["mimeType"] == "video/AV1"
|
|
444
|
-
|
|
445
|
-
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
446
|
-
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
447
|
-
assert outbound_rtp_stats["encoderImplementation"] == "libaom"
|
|
448
|
-
assert outbound_rtp_stats["bytesSent"] > 0
|
|
449
|
-
assert outbound_rtp_stats["packetsSent"] > 0
|
|
450
|
-
|
|
451
|
-
# codec が無かったら StopIteration 例外が上がる
|
|
452
|
-
recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
|
|
453
|
-
assert recvonly_codec_stats["mimeType"] == "video/AV1"
|
|
454
|
-
|
|
455
|
-
# inbound-rtp が無かったら StopIteration 例外が上がる
|
|
456
|
-
inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
|
|
457
|
-
assert inbound_rtp_stats["decoderImplementation"] == "libvpl"
|
|
458
|
-
assert inbound_rtp_stats["bytesReceived"] > 0
|
|
459
|
-
assert inbound_rtp_stats["packetsReceived"] > 0
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
## VP9
|
|
457
|
+
## VPL Decode
|
|
463
458
|
|
|
464
459
|
|
|
465
460
|
@pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
|
|
466
|
-
@pytest.mark.xfail(
|
|
467
|
-
strict=True, reason="VP9 は C++ SDK の Intel VPL で対応できていないのでテストが失敗する"
|
|
468
|
-
)
|
|
469
461
|
@pytest.mark.parametrize(
|
|
470
462
|
(
|
|
471
463
|
"video_codec_type",
|
|
472
|
-
"
|
|
464
|
+
"encoder_implementation",
|
|
465
|
+
"decoder_implementation",
|
|
473
466
|
),
|
|
474
467
|
[
|
|
475
|
-
("VP9", "libvpl"),
|
|
468
|
+
("VP9", "libvpx", "libvpl"),
|
|
469
|
+
("AV1", "libaom", "libvpl"),
|
|
476
470
|
],
|
|
477
471
|
)
|
|
478
|
-
def
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
SoraRole.SENDONLY,
|
|
482
|
-
audio=False,
|
|
483
|
-
video=True,
|
|
484
|
-
video_codec_type=video_codec_type,
|
|
485
|
-
video_codec_preference=SoraVideoCodecPreference(
|
|
486
|
-
codecs=[
|
|
487
|
-
SoraVideoCodecPreference.Codec(
|
|
488
|
-
type=SoraVideoCodecType.VP9,
|
|
489
|
-
encoder=SoraVideoCodecImplementation.INTEL_VPL,
|
|
490
|
-
),
|
|
491
|
-
]
|
|
492
|
-
),
|
|
493
|
-
)
|
|
494
|
-
sendonly.connect(fake_video=True)
|
|
495
|
-
|
|
496
|
-
time.sleep(5)
|
|
497
|
-
|
|
498
|
-
assert sendonly.connect_message is not None
|
|
499
|
-
assert sendonly.connect_message["channel_id"] == settings.channel_id
|
|
500
|
-
assert "video" in sendonly.connect_message
|
|
501
|
-
assert sendonly.connect_message["video"]["codec_type"] == video_codec_type
|
|
502
|
-
|
|
503
|
-
# offer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
504
|
-
assert sendonly.offer_message is not None
|
|
505
|
-
assert "sdp" in sendonly.offer_message
|
|
506
|
-
assert video_codec_type in sendonly.offer_message["sdp"]
|
|
507
|
-
|
|
508
|
-
# answer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
509
|
-
assert sendonly.answer_message is not None
|
|
510
|
-
assert "sdp" in sendonly.answer_message
|
|
511
|
-
assert video_codec_type in sendonly.answer_message["sdp"]
|
|
512
|
-
|
|
513
|
-
sendonly_stats = sendonly.get_stats()
|
|
514
|
-
|
|
515
|
-
sendonly.disconnect()
|
|
516
|
-
|
|
517
|
-
# codec が無かったら StopIteration 例外が上がる
|
|
518
|
-
codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
519
|
-
# VP9 が採用されているかどうか確認する
|
|
520
|
-
assert codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
521
|
-
|
|
522
|
-
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
523
|
-
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
524
|
-
# ここで libvpx になって失敗する
|
|
525
|
-
assert outbound_rtp_stats["encoderImplementation"] == expected_implementation
|
|
526
|
-
assert outbound_rtp_stats["bytesSent"] > 0
|
|
527
|
-
assert outbound_rtp_stats["packetsSent"] > 0
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
@pytest.mark.skipif(os.environ.get("INTEL_VPL") is None, reason="Intel VPL でのみ実行する")
|
|
531
|
-
def test_intel_vpl_decoding_vp9(settings):
|
|
472
|
+
def test_intel_vpl_decode(
|
|
473
|
+
settings, video_codec_type, encoder_implementation, decoder_implementation
|
|
474
|
+
):
|
|
532
475
|
"""
|
|
533
|
-
|
|
476
|
+
* N100 などは AV1 のデコーディングに対応している
|
|
477
|
+
* VPL VP9 はデコーダーは利用できるので、そのテスト
|
|
534
478
|
"""
|
|
535
479
|
sendonly = SoraClient(
|
|
536
480
|
settings,
|
|
537
481
|
SoraRole.SENDONLY,
|
|
538
482
|
audio=False,
|
|
539
483
|
video=True,
|
|
540
|
-
video_codec_type=
|
|
484
|
+
video_codec_type=video_codec_type,
|
|
541
485
|
video_codec_preference=SoraVideoCodecPreference(
|
|
542
486
|
codecs=[
|
|
543
487
|
SoraVideoCodecPreference.Codec(
|
|
544
|
-
type=
|
|
545
|
-
#
|
|
488
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
489
|
+
# エンコーダーはソフトウェアを利用する
|
|
546
490
|
encoder=SoraVideoCodecImplementation.INTERNAL,
|
|
547
491
|
),
|
|
548
492
|
]
|
|
@@ -550,13 +494,15 @@ def test_intel_vpl_decoding_vp9(settings):
|
|
|
550
494
|
)
|
|
551
495
|
sendonly.connect(fake_video=True)
|
|
552
496
|
|
|
497
|
+
time.sleep(3)
|
|
498
|
+
|
|
553
499
|
recvonly = SoraClient(
|
|
554
500
|
settings,
|
|
555
501
|
SoraRole.RECVONLY,
|
|
556
502
|
video_codec_preference=SoraVideoCodecPreference(
|
|
557
503
|
codecs=[
|
|
558
504
|
SoraVideoCodecPreference.Codec(
|
|
559
|
-
type=
|
|
505
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
560
506
|
decoder=SoraVideoCodecImplementation.INTEL_VPL,
|
|
561
507
|
),
|
|
562
508
|
]
|
|
@@ -564,7 +510,7 @@ def test_intel_vpl_decoding_vp9(settings):
|
|
|
564
510
|
)
|
|
565
511
|
recvonly.connect()
|
|
566
512
|
|
|
567
|
-
time.sleep(
|
|
513
|
+
time.sleep(3)
|
|
568
514
|
|
|
569
515
|
sendonly_stats = sendonly.get_stats()
|
|
570
516
|
recvonly_stats = recvonly.get_stats()
|
|
@@ -575,31 +521,34 @@ def test_intel_vpl_decoding_vp9(settings):
|
|
|
575
521
|
# offer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
576
522
|
assert sendonly.offer_message is not None
|
|
577
523
|
assert "sdp" in sendonly.offer_message
|
|
578
|
-
assert
|
|
524
|
+
assert video_codec_type in sendonly.offer_message["sdp"]
|
|
579
525
|
|
|
580
526
|
# answer の sdp に video_codec_type が含まれているかどうかを確認している
|
|
581
527
|
assert sendonly.answer_message is not None
|
|
582
528
|
assert "sdp" in sendonly.answer_message
|
|
583
|
-
assert
|
|
529
|
+
assert video_codec_type in sendonly.answer_message["sdp"]
|
|
584
530
|
|
|
585
531
|
# codec が無かったら StopIteration 例外が上がる
|
|
586
532
|
sendonly_codec_stats = next(s for s in sendonly_stats if s.get("type") == "codec")
|
|
587
533
|
# VP9 が採用されているかどうか確認する
|
|
588
|
-
assert sendonly_codec_stats["mimeType"] == "video/
|
|
534
|
+
assert sendonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
589
535
|
|
|
590
536
|
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
591
537
|
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
592
|
-
assert outbound_rtp_stats["encoderImplementation"] ==
|
|
538
|
+
assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
|
|
593
539
|
assert outbound_rtp_stats["bytesSent"] > 0
|
|
594
540
|
assert outbound_rtp_stats["packetsSent"] > 0
|
|
541
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > 0
|
|
542
|
+
assert outbound_rtp_stats["pliCount"] > 0
|
|
595
543
|
|
|
596
544
|
# codec が無かったら StopIteration 例外が上がる
|
|
597
545
|
recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
|
|
598
546
|
# VP9 が採用されているかどうか確認する
|
|
599
|
-
assert recvonly_codec_stats["mimeType"] == "video/
|
|
547
|
+
assert recvonly_codec_stats["mimeType"] == f"video/{video_codec_type}"
|
|
600
548
|
|
|
601
549
|
# inbound-rtp が無かったら StopIteration 例外が上がる
|
|
602
550
|
inbound_rtp_stats = next(s for s in recvonly_stats if s.get("type") == "inbound-rtp")
|
|
603
|
-
assert inbound_rtp_stats["decoderImplementation"] ==
|
|
551
|
+
assert inbound_rtp_stats["decoderImplementation"] == decoder_implementation
|
|
604
552
|
assert inbound_rtp_stats["bytesReceived"] > 0
|
|
605
553
|
assert inbound_rtp_stats["packetsReceived"] > 0
|
|
554
|
+
assert inbound_rtp_stats["keyFramesDecoded"] > 0
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from api import request_key_frame_api
|
|
5
|
+
from client import (
|
|
6
|
+
SoraClient,
|
|
7
|
+
SoraRole,
|
|
8
|
+
codec_type_string_to_codec_type,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from sora_sdk import (
|
|
12
|
+
SoraVideoCodecImplementation,
|
|
13
|
+
SoraVideoCodecPreference,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"video_codec_type",
|
|
19
|
+
[
|
|
20
|
+
"VP9",
|
|
21
|
+
"VP8",
|
|
22
|
+
"AV1",
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_key_frame_request(settings, video_codec_type):
|
|
26
|
+
sendonly = SoraClient(
|
|
27
|
+
settings,
|
|
28
|
+
SoraRole.SENDONLY,
|
|
29
|
+
audio=False,
|
|
30
|
+
video=True,
|
|
31
|
+
video_codec_type=video_codec_type,
|
|
32
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
33
|
+
codecs=[
|
|
34
|
+
SoraVideoCodecPreference.Codec(
|
|
35
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
36
|
+
encoder=SoraVideoCodecImplementation.INTERNAL,
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
sendonly.connect(fake_video=True)
|
|
42
|
+
|
|
43
|
+
time.sleep(3)
|
|
44
|
+
|
|
45
|
+
assert sendonly.connection_id is not None
|
|
46
|
+
|
|
47
|
+
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
48
|
+
api_count = 3
|
|
49
|
+
for _ in range(api_count):
|
|
50
|
+
response = request_key_frame_api(
|
|
51
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
52
|
+
)
|
|
53
|
+
assert response.status_code == 200
|
|
54
|
+
time.sleep(3)
|
|
55
|
+
|
|
56
|
+
# 統計を取得する
|
|
57
|
+
sendonly_stats = sendonly.get_stats()
|
|
58
|
+
|
|
59
|
+
sendonly.disconnect()
|
|
60
|
+
|
|
61
|
+
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
62
|
+
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
63
|
+
|
|
64
|
+
# 3 回以上
|
|
65
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
66
|
+
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
67
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
68
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
69
|
+
|
|
70
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
71
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
72
|
+
print(
|
|
73
|
+
"keyFramesEncoded >= pliCount * 0.7:",
|
|
74
|
+
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
75
|
+
)
|
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import time
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from api import request_key_frame_api
|
|
5
6
|
from client import (
|
|
6
7
|
SoraClient,
|
|
7
8
|
SoraRole,
|
|
@@ -56,6 +57,70 @@ def test_nvidia_video_codec_sdk_available():
|
|
|
56
57
|
pytest.fail(f"未実装の codec_type: {c.type}")
|
|
57
58
|
|
|
58
59
|
|
|
60
|
+
@pytest.mark.skipif(
|
|
61
|
+
os.environ.get("NVIDIA_VIDEO_CODEC_SDK") is None, reason="NVIDIA Video Codec SDK でのみ実行する"
|
|
62
|
+
)
|
|
63
|
+
@pytest.mark.parametrize(
|
|
64
|
+
"video_codec_type",
|
|
65
|
+
[
|
|
66
|
+
"AV1",
|
|
67
|
+
"H264",
|
|
68
|
+
"H265",
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
def test_intel_vpl_key_frame_request(settings, video_codec_type):
|
|
72
|
+
sendonly = SoraClient(
|
|
73
|
+
settings,
|
|
74
|
+
SoraRole.SENDONLY,
|
|
75
|
+
audio=False,
|
|
76
|
+
video=True,
|
|
77
|
+
video_codec_type=video_codec_type,
|
|
78
|
+
video_codec_preference=SoraVideoCodecPreference(
|
|
79
|
+
codecs=[
|
|
80
|
+
SoraVideoCodecPreference.Codec(
|
|
81
|
+
type=codec_type_string_to_codec_type(video_codec_type),
|
|
82
|
+
encoder=SoraVideoCodecImplementation.NVIDIA_VIDEO_CODEC_SDK,
|
|
83
|
+
),
|
|
84
|
+
]
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
sendonly.connect(fake_video=True)
|
|
88
|
+
|
|
89
|
+
time.sleep(3)
|
|
90
|
+
|
|
91
|
+
assert sendonly.connection_id is not None
|
|
92
|
+
|
|
93
|
+
# キーフレーム要求 API を 3 秒間隔で 3 回呼び出す
|
|
94
|
+
api_count = 3
|
|
95
|
+
for _ in range(api_count):
|
|
96
|
+
response = request_key_frame_api(
|
|
97
|
+
settings.api_url, sendonly.channel_id, sendonly.connection_id
|
|
98
|
+
)
|
|
99
|
+
assert response.status_code == 200
|
|
100
|
+
time.sleep(3)
|
|
101
|
+
|
|
102
|
+
# 統計を取得する
|
|
103
|
+
sendonly_stats = sendonly.get_stats()
|
|
104
|
+
|
|
105
|
+
sendonly.disconnect()
|
|
106
|
+
|
|
107
|
+
# outbound-rtp が無かったら StopIteration 例外が上がる
|
|
108
|
+
outbound_rtp_stats = next(s for s in sendonly_stats if s.get("type") == "outbound-rtp")
|
|
109
|
+
|
|
110
|
+
# 3 回以上
|
|
111
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > api_count
|
|
112
|
+
assert outbound_rtp_stats["pliCount"] >= api_count
|
|
113
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
114
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
115
|
+
|
|
116
|
+
# PLI カウントの 50% 以上がキーフレームとしてエンコードされることを確認
|
|
117
|
+
assert outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7
|
|
118
|
+
print(
|
|
119
|
+
"keyFramesEncoded >= pliCount * 0.7:",
|
|
120
|
+
outbound_rtp_stats["keyFramesEncoded"] >= outbound_rtp_stats["pliCount"] * 0.7,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
59
124
|
@pytest.mark.skipif(
|
|
60
125
|
os.environ.get("NVIDIA_VIDEO_CODEC_SDK") is None, reason="NVIDIA Video Codec SDK でのみ実行する"
|
|
61
126
|
)
|
|
@@ -68,6 +68,8 @@ def test_sendonly_recvonly_video(
|
|
|
68
68
|
)
|
|
69
69
|
sendonly.connect(fake_video=True)
|
|
70
70
|
|
|
71
|
+
time.sleep(5)
|
|
72
|
+
|
|
71
73
|
recvonly = SoraClient(
|
|
72
74
|
settings,
|
|
73
75
|
SoraRole.RECVONLY,
|
|
@@ -91,6 +93,11 @@ def test_sendonly_recvonly_video(
|
|
|
91
93
|
assert outbound_rtp_stats["encoderImplementation"] == encoder_implementation
|
|
92
94
|
assert outbound_rtp_stats["bytesSent"] > 0
|
|
93
95
|
assert outbound_rtp_stats["packetsSent"] > 0
|
|
96
|
+
assert outbound_rtp_stats["keyFramesEncoded"] > 0
|
|
97
|
+
assert outbound_rtp_stats["pliCount"] > 0
|
|
98
|
+
|
|
99
|
+
print("keyFramesEncoded:", outbound_rtp_stats["keyFramesEncoded"])
|
|
100
|
+
print("pliCount:", outbound_rtp_stats["pliCount"])
|
|
94
101
|
|
|
95
102
|
# codec が無かったら StopIteration 例外が上がる
|
|
96
103
|
recvonly_codec_stats = next(s for s in recvonly_stats if s.get("type") == "codec")
|
|
@@ -101,3 +108,6 @@ def test_sendonly_recvonly_video(
|
|
|
101
108
|
assert inbound_rtp_stats["decoderImplementation"] == decoder_implementation
|
|
102
109
|
assert inbound_rtp_stats["bytesReceived"] > 0
|
|
103
110
|
assert inbound_rtp_stats["packetsReceived"] > 0
|
|
111
|
+
assert inbound_rtp_stats["keyFramesDecoded"] > 0
|
|
112
|
+
|
|
113
|
+
print("keyFrameDecoded:", inbound_rtp_stats["keyFramesDecoded"])
|
sora_sdk-2025.3.0.dev8/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2025.3.0.dev8
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sora_sdk-2025.3.0.dev8 → sora_sdk-2025.4.0.dev1}/src/sora_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|