kohakuclip 1.0.0__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 (63) hide show
  1. kohakuclip-1.0.0/.github/workflows/auto-patch-release.yml +79 -0
  2. kohakuclip-1.0.0/.github/workflows/ci.yml +124 -0
  3. kohakuclip-1.0.0/.github/workflows/nightly.yml +96 -0
  4. kohakuclip-1.0.0/.github/workflows/release.yml +214 -0
  5. kohakuclip-1.0.0/.gitignore +251 -0
  6. kohakuclip-1.0.0/Cargo.lock +846 -0
  7. kohakuclip-1.0.0/Cargo.toml +39 -0
  8. kohakuclip-1.0.0/Change.md +34 -0
  9. kohakuclip-1.0.0/LICENSE +201 -0
  10. kohakuclip-1.0.0/PKG-INFO +253 -0
  11. kohakuclip-1.0.0/README.md +229 -0
  12. kohakuclip-1.0.0/benchmarks/bench.py +141 -0
  13. kohakuclip-1.0.0/benchmarks/gpu_check.py +163 -0
  14. kohakuclip-1.0.0/benchmarks/leak_check.py +99 -0
  15. kohakuclip-1.0.0/benchmarks/loader_bench.py +76 -0
  16. kohakuclip-1.0.0/examples/lightning_module.py +72 -0
  17. kohakuclip-1.0.0/examples/torch_loop.py +96 -0
  18. kohakuclip-1.0.0/pyproject.toml +46 -0
  19. kohakuclip-1.0.0/rustfmt.toml +3 -0
  20. kohakuclip-1.0.0/scripts/ci/release_notes.py +63 -0
  21. kohakuclip-1.0.0/scripts/ci/version.py +99 -0
  22. kohakuclip-1.0.0/scripts/ci/wheel_smoke.py +40 -0
  23. kohakuclip-1.0.0/src/kclip_rs/decode/decoder.rs +73 -0
  24. kohakuclip-1.0.0/src/kclip_rs/decode/emit.rs +101 -0
  25. kohakuclip-1.0.0/src/kclip_rs/decode/mod.rs +148 -0
  26. kohakuclip-1.0.0/src/kclip_rs/decode/profile.rs +37 -0
  27. kohakuclip-1.0.0/src/kclip_rs/image/color.rs +79 -0
  28. kohakuclip-1.0.0/src/kclip_rs/image/mod.rs +51 -0
  29. kohakuclip-1.0.0/src/kclip_rs/image/resize.rs +233 -0
  30. kohakuclip-1.0.0/src/kclip_rs/image/simd.rs +284 -0
  31. kohakuclip-1.0.0/src/kclip_rs/lib.rs +17 -0
  32. kohakuclip-1.0.0/src/kclip_rs/mp4/config.rs +59 -0
  33. kohakuclip-1.0.0/src/kclip_rs/mp4/faststart.rs +72 -0
  34. kohakuclip-1.0.0/src/kclip_rs/mp4/mod.rs +70 -0
  35. kohakuclip-1.0.0/src/kclip_rs/mp4/moov.rs +193 -0
  36. kohakuclip-1.0.0/src/kclip_rs/python/mod.rs +14 -0
  37. kohakuclip-1.0.0/src/kclip_rs/python/reader.rs +458 -0
  38. kohakuclip-1.0.0/src/kclip_rs/python/writer.rs +59 -0
  39. kohakuclip-1.0.0/src/kclip_rs/read/mod.rs +5 -0
  40. kohakuclip-1.0.0/src/kclip_rs/read/plan.rs +198 -0
  41. kohakuclip-1.0.0/src/kclip_rs/read/pool.rs +91 -0
  42. kohakuclip-1.0.0/src/kclip_rs/read/sample.rs +31 -0
  43. kohakuclip-1.0.0/src/kclip_rs/storage/folder.rs +33 -0
  44. kohakuclip-1.0.0/src/kclip_rs/storage/index.rs +173 -0
  45. kohakuclip-1.0.0/src/kclip_rs/storage/mod.rs +96 -0
  46. kohakuclip-1.0.0/src/kclip_rs/storage/shard.rs +147 -0
  47. kohakuclip-1.0.0/src/kclip_rs/storage/tar.rs +59 -0
  48. kohakuclip-1.0.0/src/kclip_rs/storage/zip.rs +230 -0
  49. kohakuclip-1.0.0/src/kclip_rs/write/av1.rs +294 -0
  50. kohakuclip-1.0.0/src/kclip_rs/write/encode.rs +264 -0
  51. kohakuclip-1.0.0/src/kclip_rs/write/memio.rs +70 -0
  52. kohakuclip-1.0.0/src/kclip_rs/write/mod.rs +89 -0
  53. kohakuclip-1.0.0/src/kclip_rs/write/pack.rs +70 -0
  54. kohakuclip-1.0.0/src/kohakuclip/__init__.py +20 -0
  55. kohakuclip-1.0.0/src/kohakuclip/_core.pyi +82 -0
  56. kohakuclip-1.0.0/src/kohakuclip/gpu.py +152 -0
  57. kohakuclip-1.0.0/src/kohakuclip/py.typed +0 -0
  58. kohakuclip-1.0.0/src/kohakuclip/sampling.py +19 -0
  59. kohakuclip-1.0.0/src/kohakuclip/torch.py +266 -0
  60. kohakuclip-1.0.0/src/kohakuclip/writer.py +313 -0
  61. kohakuclip-1.0.0/tests/__init__.py +0 -0
  62. kohakuclip-1.0.0/tests/test_reader.py +297 -0
  63. kohakuclip-1.0.0/tests/test_torch.py +101 -0
@@ -0,0 +1,79 @@
1
+ name: Auto Patch Release
2
+
3
+ on:
4
+ schedule:
5
+ # 05:00 UTC weekly, Monday
6
+ - cron: "0 5 * * 1"
7
+ workflow_dispatch:
8
+ inputs:
9
+ dry_run:
10
+ description: "Evaluate and report, do not tag"
11
+ required: false
12
+ default: true
13
+ type: boolean
14
+ min_commits:
15
+ description: "Commits on main since the last tag before a patch is cut"
16
+ required: false
17
+ default: "3"
18
+
19
+ permissions:
20
+ contents: write
21
+ actions: write
22
+
23
+ jobs:
24
+ patch:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ ref: main
30
+ fetch-depth: 0
31
+ token: ${{ secrets.GITHUB_TOKEN }}
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.13"
35
+
36
+ - name: Decide whether a patch is due
37
+ id: decide
38
+ shell: bash
39
+ run: |
40
+ current=$(python scripts/ci/version.py read)
41
+ last=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || echo "")
42
+ span=${last:+${last}..HEAD}
43
+ count=$(git rev-list --count --no-merges ${span:-HEAD})
44
+ min="${{ inputs.min_commits || 3 }}"
45
+ echo "current=${current} last=${last:-none} commits=${count} min=${min}"
46
+ if [ "${count}" -ge "${min}" ] && [ "v${current}" = "${last:-}" ]; then
47
+ echo "release=true" >> "$GITHUB_OUTPUT"
48
+ else
49
+ # Either nothing has landed, or pyproject is already ahead of the
50
+ # last tag — which means a human is mid-release; stay out of it.
51
+ echo "release=false" >> "$GITHUB_OUTPUT"
52
+ fi
53
+ echo "next=$(python scripts/ci/version.py bump patch)" >> "$GITHUB_OUTPUT"
54
+
55
+ - name: Report only
56
+ if: steps.decide.outputs.release != 'true' || inputs.dry_run
57
+ run: |
58
+ echo "release=${{ steps.decide.outputs.release }}"
59
+ echo "would cut ${{ steps.decide.outputs.next }}"
60
+
61
+ - name: Stamp, tag and push
62
+ if: steps.decide.outputs.release == 'true' && !inputs.dry_run
63
+ shell: bash
64
+ run: |
65
+ next="${{ steps.decide.outputs.next }}"
66
+ python scripts/ci/version.py write "${next}"
67
+ git config user.name "github-actions[bot]"
68
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
69
+ git commit -am "bump to version ${next}"
70
+ git tag "v${next}"
71
+ git push origin main --follow-tags
72
+
73
+ # The tag push above cannot start another workflow (GITHUB_TOKEN pushes
74
+ # do not trigger `on: push`), so Release is dispatched explicitly.
75
+ - name: Dispatch the release workflow
76
+ if: steps.decide.outputs.release == 'true' && !inputs.dry_run
77
+ env:
78
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79
+ run: gh workflow run release.yml -f ref="v${{ steps.decide.outputs.next }}"
@@ -0,0 +1,124 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ # Formatting and the breakage-only ruff set (pyproject pins which rules block); rustfmt.
15
+ lint:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.13"
22
+ - run: pip install black ruff
23
+ - run: black --check src/ tests/ benchmarks/ examples/ scripts/
24
+ - run: ruff check src/ tests/ benchmarks/ examples/ scripts/
25
+ - uses: dtolnay/rust-toolchain@stable
26
+ with:
27
+ components: rustfmt
28
+ - run: cargo fmt --check
29
+
30
+ # Builds against conda-forge's FFmpeg (the GPL build: the tests encode H.264 with x264 too),
31
+ # then clippy, the Rust unit tests and the end-to-end suite on CPU torch.
32
+ test:
33
+ runs-on: ubuntu-latest
34
+ strategy:
35
+ fail-fast: false
36
+ matrix:
37
+ python-version: ["3.13", "3.14"]
38
+ defaults:
39
+ run:
40
+ shell: bash -el {0}
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ - uses: mamba-org/setup-micromamba@v2
44
+ with:
45
+ environment-name: kohakuclip
46
+ create-args: >-
47
+ python=${{ matrix.python-version }}
48
+ ffmpeg=8.1.*=gpl*
49
+ libclang=18
50
+ clang=18
51
+ cache-environment: true
52
+ - uses: dtolnay/rust-toolchain@stable
53
+ with:
54
+ components: clippy
55
+ - name: Build environment
56
+ run: |
57
+ {
58
+ echo "FFMPEG_INCLUDE_DIR=$CONDA_PREFIX/include"
59
+ echo "FFMPEG_LIBS_DIR=$CONDA_PREFIX/lib"
60
+ echo "FFMPEG_LINK_MODE=dynamic"
61
+ echo "LIBCLANG_PATH=$CONDA_PREFIX/lib"
62
+ echo "BINDGEN_EXTRA_CLANG_ARGS=-I$CONDA_PREFIX/lib/clang/18/include -I/usr/include/x86_64-linux-gnu -I/usr/include"
63
+ echo "RUSTFLAGS=-C link-arg=-Wl,-rpath,$CONDA_PREFIX/lib"
64
+ } >> "$GITHUB_ENV"
65
+ - run: cargo clippy --release --all-targets -- -D warnings
66
+ - run: cargo test --release --lib
67
+ - name: Install
68
+ run: |
69
+ pip install maturin
70
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
71
+ pip install av numpy pytest
72
+ maturin develop --release
73
+ - run: pytest tests -v --tb=short
74
+
75
+ # The release build, end to end: a manylinux_2_35 wheel with FFmpeg bundled, installed
76
+ # into a clean venv with no FFmpeg around, then used to encode, pack and read a shard.
77
+ package-check:
78
+ runs-on: ubuntu-22.04
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ - uses: actions/setup-python@v5
82
+ with:
83
+ python-version: "3.13"
84
+ - uses: mamba-org/setup-micromamba@v2
85
+ with:
86
+ environment-name: ffmpeg
87
+ # the same packages as the test job's environment (whose bindings build), with the
88
+ # LGPL FFmpeg
89
+ create-args: >-
90
+ python=3.13
91
+ ffmpeg=8.1.*=lgpl*
92
+ libclang=18
93
+ clang=18
94
+ - uses: dtolnay/rust-toolchain@stable
95
+ # Steps after this one run outside the activated environment: CLANG_PATH keeps the
96
+ # bindings on the environment's clang (the runner's own clang has other headers).
97
+ - name: Build environment (the LGPL FFmpeg, clang 18 for the bindings)
98
+ shell: bash -el {0}
99
+ run: |
100
+ {
101
+ echo "FFMPEG_INCLUDE_DIR=$CONDA_PREFIX/include"
102
+ echo "FFMPEG_LIBS_DIR=$CONDA_PREFIX/lib"
103
+ echo "FFMPEG_LINK_MODE=dynamic"
104
+ echo "LIBCLANG_PATH=$CONDA_PREFIX/lib"
105
+ echo "CLANG_PATH=$CONDA_PREFIX/bin/clang"
106
+ echo "BINDGEN_EXTRA_CLANG_ARGS=-I$CONDA_PREFIX/lib/clang/18/include -I/usr/include/x86_64-linux-gnu -I/usr/include"
107
+ echo "RUSTFLAGS=-Ctarget-cpu=x86-64-v3 -Clink-arg=-Wl,--as-needed -Clink-arg=-Wl,-rpath,$CONDA_PREFIX/lib"
108
+ } >> "$GITHUB_ENV"
109
+ # conda-forge's FFmpeg dependencies (ICU, shaderc) need GLIBCXX 3.4.30: the wheels are
110
+ # manylinux_2_35 (glibc 2.35, e.g. Ubuntu 22.04+), built on this Ubuntu 22.04 runner
111
+ # and repaired by maturin, which bundles FFmpeg and its libraries into the wheel
112
+ # (found through the extension's rpath; LD_LIBRARY_PATH would hand the env's
113
+ # libpython to every Python on the runner).
114
+ - name: Build wheels
115
+ run: |
116
+ pip install "maturin[patchelf]"
117
+ maturin build --release --strip \
118
+ --compatibility manylinux_2_35 -i python3.13 -o dist
119
+ - name: Install the wheel into a clean venv
120
+ run: |
121
+ python -m venv .venv-wheel
122
+ . .venv-wheel/bin/activate
123
+ pip install dist/*.whl
124
+ python scripts/ci/wheel_smoke.py
@@ -0,0 +1,96 @@
1
+ name: Nightly
2
+
3
+ on:
4
+ schedule:
5
+ # 03:00 UTC, once a day. This is the only automatic trigger: nothing about
6
+ # a nightly is per-push, so a commit landing at noon waits for tomorrow.
7
+ - cron: "0 3 * * *"
8
+ workflow_dispatch:
9
+ inputs:
10
+ force:
11
+ description: "Build even when the gate says there is nothing new"
12
+ required: false
13
+ default: false
14
+ type: boolean
15
+
16
+ # Serialised, and never cancelled: a second run has to see the tags the first
17
+ # one pushed, or both would decide the same commit is unbuilt.
18
+ concurrency:
19
+ group: nightly
20
+ cancel-in-progress: false
21
+
22
+ permissions:
23
+ contents: read
24
+ actions: write
25
+
26
+ jobs:
27
+ # Decides only. Release is the sole workflow that builds or uploads, so PyPI
28
+ # needs exactly one trusted publisher.
29
+ dispatch:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ with:
34
+ ref: main
35
+ fetch-depth: 0
36
+ # Release cuts its tag through `gh release create`, so the tags are
37
+ # the record of what has been published and the gate below reads
38
+ # them; a shallow clone without them would rebuild every night.
39
+ fetch-tags: true
40
+ - uses: actions/setup-python@v5
41
+ with:
42
+ python-version: "3.13"
43
+
44
+ - name: Decide whether a nightly is due
45
+ id: gate
46
+ shell: bash
47
+ run: |
48
+ head=$(git rev-parse HEAD)
49
+ today=$(date -u +%Y%m%d)
50
+
51
+ # Every release, stable or nightly, leaves a v* tag on the commit it
52
+ # was cut from. A tag already sitting on HEAD means main has not
53
+ # moved since the last one, and a rebuild would only differ by its
54
+ # timestamp — a duplicate under a new PyPI filename.
55
+ published=$(git tag --points-at HEAD --list 'v*' | head -n1)
56
+
57
+ # Second belt: the nightly version carries the UTC date it was cut
58
+ # on, so an extra dispatch or a re-run cannot cut a second nightly
59
+ # for the same day even if main did move.
60
+ already_today=$(git tag --list "v*.dev${today}*" | head -n1)
61
+
62
+ if [ "${{ inputs.force }}" = "true" ]; then
63
+ reason="forced"
64
+ build=true
65
+ elif [ -n "${published}" ]; then
66
+ reason="${head} is already released as ${published}"
67
+ build=false
68
+ elif [ -n "${already_today}" ]; then
69
+ reason="${already_today} was already cut today"
70
+ build=false
71
+ else
72
+ reason="main is at ${head}, unreleased"
73
+ build=true
74
+ fi
75
+
76
+ echo "build=${build}" >> "$GITHUB_OUTPUT"
77
+ echo "${reason}" | tee -a "$GITHUB_STEP_SUMMARY"
78
+
79
+ # Computed after the gate so the stamp names the moment of the build.
80
+ - name: Compute the nightly version
81
+ id: compute
82
+ if: steps.gate.outputs.build == 'true'
83
+ shell: bash
84
+ run: |
85
+ version=$(python scripts/ci/version.py nightly)
86
+ echo "version=${version}" >> "$GITHUB_OUTPUT"
87
+ echo "nightly ${version}" | tee -a "$GITHUB_STEP_SUMMARY"
88
+
89
+ - name: Dispatch the release workflow
90
+ if: steps.gate.outputs.build == 'true'
91
+ env:
92
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
93
+ run: |
94
+ gh workflow run release.yml \
95
+ -f ref=main \
96
+ -f version="${{ steps.compute.outputs.version }}"
@@ -0,0 +1,214 @@
1
+ name: Release
2
+
3
+ on:
4
+ # Cutting a release in the GitHub UI, or pushing the tag, both publish.
5
+ release:
6
+ types: [published]
7
+ push:
8
+ tags: ["v*"]
9
+ workflow_dispatch:
10
+ inputs:
11
+ ref:
12
+ description: "Tag, branch or sha to release"
13
+ required: false
14
+ default: ""
15
+ version:
16
+ description: "Stamp this version before building (nightlies pass X.Y.Z.devN)"
17
+ required: false
18
+ default: ""
19
+
20
+ permissions:
21
+ contents: write
22
+ id-token: write
23
+
24
+ # The only workflow that builds or uploads, so PyPI needs one trusted publisher:
25
+ # this file (release.yml), environment `pypi`.
26
+ jobs:
27
+ version:
28
+ name: Resolve version
29
+ runs-on: ubuntu-latest
30
+ outputs:
31
+ version: ${{ steps.version.outputs.version }}
32
+ prerelease: ${{ steps.version.outputs.prerelease }}
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ with:
36
+ ref: ${{ inputs.ref || github.ref }}
37
+ - uses: actions/setup-python@v5
38
+ with:
39
+ python-version: "3.13"
40
+ - id: version
41
+ shell: bash
42
+ run: |
43
+ if [ -n "${{ inputs.version }}" ]; then
44
+ python scripts/ci/version.py write "${{ inputs.version }}"
45
+ fi
46
+ version=$(python scripts/ci/version.py read)
47
+ case "${version}" in
48
+ *dev*|*a*|*b*|*rc*) pre=true ;;
49
+ *) pre=false ;;
50
+ esac
51
+ {
52
+ echo "version=${version}"
53
+ echo "prerelease=${pre}"
54
+ } >> "$GITHUB_OUTPUT"
55
+ echo "releasing ${version} (prerelease=${pre})"
56
+
57
+ # A tag that disagrees with pyproject is a mis-cut release: stop before anything is
58
+ # uploaded, since a PyPI filename cannot be reused.
59
+ - name: Tag must match pyproject
60
+ if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release'
61
+ shell: bash
62
+ run: |
63
+ tag="${GITHUB_REF_NAME#v}"
64
+ test "${tag}" = "${{ steps.version.outputs.version }}" \
65
+ || { echo "tag ${tag} != pyproject ${{ steps.version.outputs.version }}"; exit 1; }
66
+
67
+ # manylinux wheels with an LGPL FFmpeg (libdav1d, SVT-AV1, aom, openh264) bundled by
68
+ # auditwheel: `pip install kohakuclip` needs nothing else. Source builds against your own
69
+ # FFmpeg go through the sdist (see README, Build).
70
+ # manylinux_2_35 wheels with an LGPL FFmpeg (libdav1d, SVT-AV1, aom, openh264) bundled:
71
+ # `pip install kohakuclip` needs nothing else. Source builds against your own FFmpeg go
72
+ # through the sdist (see README, Install and build).
73
+ wheels:
74
+ name: Wheels
75
+ needs: [version]
76
+ runs-on: ubuntu-22.04
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ with:
80
+ ref: ${{ inputs.ref || github.ref }}
81
+ - uses: actions/setup-python@v5
82
+ with:
83
+ python-version: |
84
+ 3.14
85
+ 3.13
86
+ - name: Stamp the version
87
+ if: inputs.version != ''
88
+ run: python scripts/ci/version.py write "${{ inputs.version }}"
89
+ - uses: mamba-org/setup-micromamba@v2
90
+ with:
91
+ environment-name: ffmpeg
92
+ # the same packages as the test job's environment (whose bindings build), with the
93
+ # LGPL FFmpeg
94
+ create-args: >-
95
+ python=3.13
96
+ ffmpeg=8.1.*=lgpl*
97
+ libclang=18
98
+ clang=18
99
+ - uses: dtolnay/rust-toolchain@stable
100
+ # Steps after this one run outside the activated environment: CLANG_PATH keeps the
101
+ # bindings on the environment's clang (the runner's own clang has other headers).
102
+ - name: Build environment (the LGPL FFmpeg, clang 18 for the bindings)
103
+ shell: bash -el {0}
104
+ run: |
105
+ {
106
+ echo "FFMPEG_INCLUDE_DIR=$CONDA_PREFIX/include"
107
+ echo "FFMPEG_LIBS_DIR=$CONDA_PREFIX/lib"
108
+ echo "FFMPEG_LINK_MODE=dynamic"
109
+ echo "LIBCLANG_PATH=$CONDA_PREFIX/lib"
110
+ echo "CLANG_PATH=$CONDA_PREFIX/bin/clang"
111
+ echo "BINDGEN_EXTRA_CLANG_ARGS=-I$CONDA_PREFIX/lib/clang/18/include -I/usr/include/x86_64-linux-gnu -I/usr/include"
112
+ echo "RUSTFLAGS=-Ctarget-cpu=x86-64-v3 -Clink-arg=-Wl,--as-needed -Clink-arg=-Wl,-rpath,$CONDA_PREFIX/lib"
113
+ } >> "$GITHUB_ENV"
114
+ # conda-forge's FFmpeg dependencies (ICU, shaderc) need GLIBCXX 3.4.30: the wheels are
115
+ # manylinux_2_35 (glibc 2.35, e.g. Ubuntu 22.04+), built on this Ubuntu 22.04 runner
116
+ # and repaired by maturin, which bundles FFmpeg and its libraries into the wheel
117
+ # (found through the extension's rpath; LD_LIBRARY_PATH would hand the env's
118
+ # libpython to every Python on the runner).
119
+ - name: Build wheels
120
+ run: |
121
+ pip install "maturin[patchelf]"
122
+ maturin build --release --strip \
123
+ --compatibility manylinux_2_35 -i python3.13 -i python3.14 -o dist
124
+ - name: Smoke test the wheel in a clean venv
125
+ run: |
126
+ python3.13 -m venv .venv-wheel
127
+ . .venv-wheel/bin/activate
128
+ pip install dist/*cp313*.whl
129
+ python scripts/ci/wheel_smoke.py
130
+ - uses: actions/upload-artifact@v4
131
+ with:
132
+ name: wheels
133
+ path: dist/*.whl
134
+ if-no-files-found: error
135
+ retention-days: 7
136
+
137
+ sdist:
138
+ name: Source distribution
139
+ needs: [version]
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - uses: actions/checkout@v4
143
+ with:
144
+ ref: ${{ inputs.ref || github.ref }}
145
+ - uses: actions/setup-python@v5
146
+ with:
147
+ python-version: "3.13"
148
+ - name: Stamp the version
149
+ if: inputs.version != ''
150
+ run: python scripts/ci/version.py write "${{ inputs.version }}"
151
+ - uses: PyO3/maturin-action@v1
152
+ with:
153
+ command: sdist
154
+ args: -o dist
155
+ - run: pip install twine && twine check dist/*
156
+ - uses: actions/upload-artifact@v4
157
+ with:
158
+ name: sdist
159
+ path: dist/*.tar.gz
160
+ if-no-files-found: error
161
+ retention-days: 7
162
+
163
+ publish-pypi:
164
+ name: Publish distributions to PyPI
165
+ needs: [wheels, sdist]
166
+ runs-on: ubuntu-latest
167
+ environment:
168
+ name: pypi
169
+ url: https://pypi.org/p/kohakuclip
170
+ permissions:
171
+ id-token: write
172
+ steps:
173
+ - uses: actions/download-artifact@v4
174
+ with:
175
+ path: dist/
176
+ merge-multiple: true
177
+ - uses: pypa/gh-action-pypi-publish@release/v1
178
+
179
+ publish-github:
180
+ name: Publish the GitHub release
181
+ needs: [version, publish-pypi]
182
+ runs-on: ubuntu-latest
183
+ steps:
184
+ - uses: actions/checkout@v4
185
+ with:
186
+ ref: ${{ inputs.ref || github.ref }}
187
+ fetch-depth: 0
188
+ - uses: actions/setup-python@v5
189
+ with:
190
+ python-version: "3.13"
191
+ - uses: actions/download-artifact@v4
192
+ with:
193
+ path: dist/
194
+ merge-multiple: true
195
+ - name: Create or update the release
196
+ env:
197
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
198
+ shell: bash
199
+ run: |
200
+ version="${{ needs.version.outputs.version }}"
201
+ tag="v${version}"
202
+ flag=""
203
+ if [ "${{ needs.version.outputs.prerelease }}" = "true" ]; then
204
+ flag="--prerelease"
205
+ fi
206
+ notes=$(python scripts/ci/release_notes.py --version "${version}")
207
+ if ! gh release view "${tag}" >/dev/null 2>&1; then
208
+ gh release create "${tag}" --title "KohakuClip ${version}" \
209
+ --notes "${notes}" ${flag} --target "$(git rev-parse HEAD)"
210
+ else
211
+ gh release edit "${tag}" --title "KohakuClip ${version}" \
212
+ --notes "${notes}" ${flag}
213
+ fi
214
+ for f in dist/*; do gh release upload "${tag}" "$f" --clobber; done