diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5d414091..4bc1c934 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,13 +6,40 @@ on: paths-ignore: - "doc/**" - "**.md" - - "**.yml" # 毎日日本時間(JST)の14:00(UTCの5:00)に実行します。 # ただし、週末は除きます。 schedule: - cron: "0 5 * * 1-5" jobs: + # まず pyi を生成する + # クロスコンパイル環境だと pyi が生成できないので、 + # 1箇所で pyi を生成してアーティファクトにアップロードして、 + # それを各ビルドで利用する形にする。 + build_pyi: + runs-on: ubuntu-22.04 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - run: | + sudo apt-get -y install libva-dev libdrm-dev + - uses: eifinger/setup-rye@v3 + with: + version: 'latest' + - run: echo "$HOME/.rye/shims" >> $GITHUB_PATH + - run: rye pin cpython@3.12 + - run: rye sync + - name: Generate pyi + run: | + rye run python run.py + mkdir sora_sdk/ + cp src/sora_sdk/py.typed sora_sdk/ + cp src/sora_sdk/sora_sdk_ext.pyi sora_sdk/ + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: sora_sdk + path: sora_sdk/ build_linux_macos: strategy: fail-fast: false @@ -46,10 +73,18 @@ jobs: python_version: "3.11" - platform: { name: ubuntu-20.04_armv8_jetson } python_version: "3.12" + needs: [build_pyi] runs-on: ${{ matrix.platform.runs_on }} timeout-minutes: 60 steps: - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: sora_sdk + path: sora_sdk/ + - run: | + cp sora_sdk/py.typed src/sora_sdk/py.typed + cp sora_sdk/sora_sdk_ext.pyi src/sora_sdk/sora_sdk_ext.pyi - run: | sudo apt-get update sudo apt-get -y install libva-dev libdrm-dev @@ -112,6 +147,7 @@ jobs: path: "dist/" build_windows: + needs: [build_pyi] runs-on: windows-2022 timeout-minutes: 60 strategy: @@ -129,6 +165,13 @@ jobs: with: python-version: "${{ matrix.python_version }}" cache: "pip" + - uses: actions/download-artifact@v4 + with: + name: sora_sdk + path: sora_sdk/ + - run: | + cp sora_sdk/py.typed src/sora_sdk/py.typed + cp sora_sdk/sora_sdk_ext.pyi src/sora_sdk/sora_sdk_ext.pyi - run: pip install -r requirements-dev.lock - run: python run.py - run: python -m build diff --git a/.gitignore b/.gitignore index 568cd3ac..a1e34b44 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ build src/sora_sdk/*.so src/sora_sdk/*.dll src/sora_sdk/*.pyd +src/sora_sdk/*.pyi +src/sora_sdk/py.typed # パッケージ /dist diff --git a/CHANGES.md b/CHANGES.md index b9f09445..099bdc6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,8 @@ - @melpon - [UPDATE] Sora C++ SDK のバージョンを `2024.6.1` に上げる - @voluntas +- [ADD] sora_sdk に型を付ける + - @melpon - [FIX] SoraAudioSink.read が timeout を無視して失敗を返すケースがあったので修正する - @enm10k - [FIX] SoraAudioSink.read が timeout を無視するケースがある問題を修正した結果、 read の実行タイミングによってはクラッシュするようになったので修正する diff --git a/CMakeLists.txt b/CMakeLists.txt index 609f31d0..6c8cfd4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,17 @@ nanobind_add_module( src/sora_video_source.cpp ) +if (SORA_GEN_PYI) + nanobind_add_stub( + sora_sdk_ext_stub + MODULE sora_sdk_ext + OUTPUT sora_sdk_ext.pyi + PYTHON_PATH $ + DEPENDS sora_sdk_ext + MARKER_FILE py.typed + ) +endif() + set_target_properties(sora_sdk_ext PROPERTIES CXX_STANDARD 20 C_STANDARD 20) set_target_properties(sora_sdk_ext PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -132,3 +143,6 @@ endif() target_link_libraries(sora_sdk_ext PRIVATE Sora::sora) install(TARGETS sora_sdk_ext LIBRARY DESTINATION .) +if (SORA_GEN_PYI) + install(FILES py.typed sora_sdk_ext.pyi DESTINATION ".") +endif() diff --git a/pyproject.toml b/pyproject.toml index f387dda9..d36a32a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ dev-dependencies = [ "auditwheel~=6.0.0", "pytest>=8.2", "ruff>=0.4", + "typing-extensions>=4.12.2", ] [tool.ruff] diff --git a/requirements-dev.lock b/requirements-dev.lock index a7f399a9..33c373c3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -34,6 +34,7 @@ setuptools==70.0.0 tomli==2.0.1 # via build # via pytest +typing-extensions==4.12.2 wheel==0.43.0 zipp==3.19.0 # via importlib-metadata diff --git a/run.py b/run.py index 11814d10..faa58c10 100644 --- a/run.py +++ b/run.py @@ -754,6 +754,10 @@ def main(): "-DNB_SUFFIX=.cpython-38-aarch64-linux-gnu.so", ] + # Windows 以外の、クロスコンパイルでない環境では pyi ファイルを生成する + if target_platform.os != "windows" and build_platform.package_name == target_platform.package_name: + cmake_args.append("-DSORA_GEN_PYI=ON") + sora_src_dir = os.path.join("src", "sora_sdk") sora_build_dir = os.path.join(build_dir, "sora_sdk") if target_platform.os == "windows": @@ -779,6 +783,10 @@ def main(): shutil.copyfile( os.path.join(sora_build_target_dir, file), os.path.join(sora_src_dir, file) ) + if file in ("sora_sdk_ext.pyi", "py.typed"): + shutil.copyfile( + os.path.join(sora_build_target_dir, file), os.path.join(sora_src_dir, file) + ) if __name__ == "__main__": diff --git a/src/sora_sdk_ext.cpp b/src/sora_sdk_ext.cpp index d2ac1e71..afd4dcff 100644 --- a/src/sora_sdk_ext.cpp +++ b/src/sora_sdk_ext.cpp @@ -319,7 +319,50 @@ NB_MODULE(sora_sdk_ext, m) { "insecure"_a = nb::none(), "client_cert"_a = nb::none(), "client_key"_a = nb::none(), "proxy_url"_a = nb::none(), "proxy_username"_a = nb::none(), "proxy_password"_a = nb::none(), - "proxy_agent"_a = nb::none()) + "proxy_agent"_a = nb::none(), + nb::sig("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: 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, " + "simulcast: Optional[bool] = None, " + "spotlight: Optional[bool] = None, " + "spotlight_number: Optional[int] = None, " + "simulcast_rid: Optional[str] = None, " + "spotlight_focus_rid: Optional[str] = None, " + "spotlight_unfocus_rid: Optional[str] = None, " + "forwarding_filter: Optional[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[str] = None, " + "client_key: Optional[str] = None, " + "proxy_url: Optional[str] = None, " + "proxy_username: Optional[str] = None, " + "proxy_password: Optional[str] = None, " + "proxy_agent: Optional[str] = None" + ") -> SoraConnection")) .def("create_audio_source", &Sora::CreateAudioSource, "channels"_a, "sample_rate"_a) .def("create_video_source", &Sora::CreateVideoSource);