Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c38ef745a3 | ||
|
|
bee1c7726a | ||
|
|
14ad11db98 | ||
|
|
ca42eeb386 | ||
|
|
ab7e2797a9 | ||
|
|
b230e6829a | ||
|
|
3dbe5c4ddd | ||
|
|
63d9650c55 | ||
|
|
1cd207180f | ||
|
|
87e40a2afa |
@@ -30,14 +30,40 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Cache cargo registry
|
- name: Cache cargo + build artifacts
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
key: ${{ runner.os }}-${{ matrix.arch }}-cargo-${{ hashFiles('Cargo.lock') }}
|
key: ${{ runner.os }}-${{ matrix.arch }}-cargo-${{ hashFiles('Cargo.lock') }}
|
||||||
|
# Reuse a prior build's compiled deps (pipewire-sys/bindgen is slow,
|
||||||
|
# especially on the aarch64 host) even when Cargo.lock changed.
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.arch }}-cargo-
|
||||||
|
|
||||||
|
- name: Ensure audio build deps (libpipewire + clang for native capture)
|
||||||
|
run: |
|
||||||
|
if command -v apt-get >/dev/null; then
|
||||||
|
# Ephemeral container runner: safe to install into it.
|
||||||
|
SUDO=""; command -v sudo >/dev/null && SUDO=sudo
|
||||||
|
$SUDO apt-get update
|
||||||
|
$SUDO apt-get install -y libpipewire-0.3-dev libspa-0.2-dev clang libclang-dev pkg-config
|
||||||
|
else
|
||||||
|
# Bare/host runner: never mutate it - require deps preinstalled.
|
||||||
|
missing=""
|
||||||
|
pkg-config --exists libpipewire-0.3 2>/dev/null || missing="$missing libpipewire(+dev headers)"
|
||||||
|
command -v clang >/dev/null || missing="$missing clang"
|
||||||
|
if [ -n "$missing" ]; then
|
||||||
|
echo "::error::bare runner missing build deps:$missing — install once on the host; CI does not modify bare runners"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "bare runner already has libpipewire + clang"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --release --target ${{ matrix.target }}
|
run: cargo build --release --locked --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: |
|
run: |
|
||||||
@@ -53,7 +79,7 @@ jobs:
|
|||||||
- name: Create release
|
- name: Create release
|
||||||
run: |
|
run: |
|
||||||
curl -sX POST \
|
curl -sX POST \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \
|
||||||
-d "{\"tag_name\":\"${{ gitea.ref_name }}\",\"name\":\"${{ gitea.ref_name }}\"}" \
|
-d "{\"tag_name\":\"${{ gitea.ref_name }}\",\"name\":\"${{ gitea.ref_name }}\"}" \
|
||||||
@@ -62,23 +88,23 @@ jobs:
|
|||||||
- name: Upload assets
|
- name: Upload assets
|
||||||
run: |
|
run: |
|
||||||
RELEASE_ID=$(curl -s \
|
RELEASE_ID=$(curl -s \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/tags/${{ gitea.ref_name }}" \
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/tags/${{ gitea.ref_name }}" \
|
||||||
| jq -r '.id')
|
| jq -r '.id')
|
||||||
|
|
||||||
for FILE in "${{ env.TARBALL }}" "${{ env.TARBALL }}.sha256"; do
|
for FILE in "${{ env.TARBALL }}" "${{ env.TARBALL }}.sha256"; do
|
||||||
EXISTING=$(curl -s \
|
EXISTING=$(curl -s \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \
|
||||||
| jq -r ".[] | select(.name == \"${FILE}\") | .id")
|
| jq -r ".[] | select(.name == \"${FILE}\") | .id")
|
||||||
for AID in $EXISTING; do
|
for AID in $EXISTING; do
|
||||||
curl -sX DELETE \
|
curl -sX DELETE \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets/${AID}"
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets/${AID}"
|
||||||
done
|
done
|
||||||
|
|
||||||
curl -sX POST \
|
curl -sX POST \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
-H "Content-Type: application/octet-stream" \
|
-H "Content-Type: application/octet-stream" \
|
||||||
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets?name=${FILE}" \
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets?name=${FILE}" \
|
||||||
--data-binary "@${FILE}" --fail-with-body
|
--data-binary "@${FILE}" --fail-with-body
|
||||||
@@ -102,6 +128,35 @@ jobs:
|
|||||||
sed -i "s/sha256sums_x86_64=('.*')/sha256sums_x86_64=('${SUM_X86}')/" aur/PKGBUILD
|
sed -i "s/sha256sums_x86_64=('.*')/sha256sums_x86_64=('${SUM_X86}')/" aur/PKGBUILD
|
||||||
sed -i "s/sha256sums_aarch64=('.*')/sha256sums_aarch64=('${SUM_AARCH}')/" aur/PKGBUILD
|
sed -i "s/sha256sums_aarch64=('.*')/sha256sums_aarch64=('${SUM_AARCH}')/" aur/PKGBUILD
|
||||||
|
|
||||||
|
# Also publish the built packages to this instance's Arch registry
|
||||||
|
# (docs.gitea.com/usage/packages/arch). The PKGBUILD only repacks the
|
||||||
|
# release tarballs, so CARCH can produce both architectures from this
|
||||||
|
# one host. Consumers: see the infrastructure README.
|
||||||
|
# Best-effort mirror to the instance Arch registry. The ephemeral
|
||||||
|
# GITHUB_TOKEN is not accepted as a package-write credential, so this
|
||||||
|
# uses a dedicated REGISTRY_TOKEN secret (a write:package token for bl);
|
||||||
|
# if it is unset the step is skipped, and continue-on-error keeps a
|
||||||
|
# registry hiccup from failing the release or the AUR push.
|
||||||
|
- name: Publish to the Arch package registry
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${{ secrets.REGISTRY_TOKEN }}" ]; then
|
||||||
|
echo "::warning::REGISTRY_TOKEN not set — skipping Arch registry publish"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
cd aur
|
||||||
|
# --packagelist gives the exact filename (honours the runner's
|
||||||
|
# PKGEXT); !strip (in the PKGBUILD) lets the foreign-arch binary be
|
||||||
|
# packaged on this host without stripping.
|
||||||
|
for carch in aarch64 x86_64; do
|
||||||
|
pkgfile=$(CARCH="$carch" makepkg --packagelist | tail -1)
|
||||||
|
CARCH="$carch" makepkg -f --nodeps --noconfirm --skipinteg
|
||||||
|
curl --fail-with-body --user "bl:${{ secrets.REGISTRY_TOKEN }}" \
|
||||||
|
--upload-file "$pkgfile" \
|
||||||
|
"${{ gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/arch/uhhm"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Push to AUR
|
- name: Push to AUR
|
||||||
env:
|
env:
|
||||||
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
||||||
|
|||||||
Generated
+403
-7
@@ -2,6 +2,25 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotate-snippets"
|
||||||
|
version = "0.11.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
@@ -57,6 +76,25 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.72.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||||
|
dependencies = [
|
||||||
|
"annotate-snippets",
|
||||||
|
"bitflags",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex 1.3.0",
|
||||||
|
"syn 2.0.87",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.13.0"
|
version = "2.13.0"
|
||||||
@@ -69,12 +107,52 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex 2.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom 7.1.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-expr"
|
||||||
|
version = "0.20.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.21"
|
version = "4.5.21"
|
||||||
@@ -106,7 +184,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.87",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -130,6 +208,12 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie-factory"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
@@ -176,7 +260,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn",
|
"syn 2.0.87",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -188,6 +272,18 @@ dependencies = [
|
|||||||
"litrs",
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -195,27 +291,101 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.186"
|
version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libspa"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "882f7427e7989dcc9d388b7f05c4630390a1d7696f9ffa469cd4a7a48f0b4c40"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cc",
|
||||||
|
"cookie-factory",
|
||||||
|
"libc",
|
||||||
|
"libspa-sys",
|
||||||
|
"nom 8.0.0",
|
||||||
|
"rustix",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libspa-sys"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b6e17bdaf63ed0d5e4144022624032b41fd9733112e8c74ac26fc9bf1291924"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -243,6 +413,18 @@ version = "0.4.33"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -255,6 +437,25 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-complex"
|
name = "num-complex"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -305,6 +506,37 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pipewire"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde71084c4e25959d68f1ea54daa75e5ecdb338e5caf0b5510143b79baa32d5c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"libspa",
|
||||||
|
"libspa-sys",
|
||||||
|
"pipewire-sys",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pipewire-sys"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce653f53e63e5b93853218092ee9a8906a5d082c92f3f1db26316955dd63ce0"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"libspa-sys",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "primal-check"
|
name = "primal-check"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -316,9 +548,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.89"
|
version = "1.0.107"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -341,6 +573,41 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -375,7 +642,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -390,6 +657,47 @@ version = "1.0.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
@@ -429,11 +737,12 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spectrust"
|
name = "spectrust"
|
||||||
version = "0.2.6"
|
version = "0.2.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"pipewire",
|
||||||
"rustfft",
|
"rustfft",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -460,6 +769,75 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-deps"
|
||||||
|
version = "7.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-expr",
|
||||||
|
"heck",
|
||||||
|
"pkg-config",
|
||||||
|
"toml",
|
||||||
|
"version-compare",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "1.1.6+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde_core",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "1.1.1+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.1.3+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.1.2+spec-1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "transpose"
|
name = "transpose"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -482,12 +860,24 @@ version = "1.13.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version-compare"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -609,3 +999,9 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "spectrust"
|
name = "spectrust"
|
||||||
version = "0.2.6"
|
version = "0.2.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@@ -12,3 +12,4 @@ byteorder = "1.5.0"
|
|||||||
clap = { version = "4.5.21", features = ["derive"] }
|
clap = { version = "4.5.21", features = ["derive"] }
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
rustfft = "6.2.0"
|
rustfft = "6.2.0"
|
||||||
|
pipewire = "0.10"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ A terminal audio spectrum analyzer written in Rust. Visualizes PCM audio in real
|
|||||||
|
|
||||||
**Arch Linux (AUR)**
|
**Arch Linux (AUR)**
|
||||||
```
|
```
|
||||||
yay -S spectrust
|
paru -S spectrust
|
||||||
```
|
```
|
||||||
|
|
||||||
**From source**
|
**From source**
|
||||||
|
|||||||
+6
-5
@@ -1,16 +1,17 @@
|
|||||||
# Maintainer: Bendik Aagaard Lynghaug <bendik.lynghaug@gmail.com>
|
# Maintainer: Bendik Aagaard Lynghaug <bendik.lynghaug@gmail.com>
|
||||||
pkgname=spectrust
|
pkgname=spectrust
|
||||||
pkgver=0.2.6
|
pkgver=0.2.9
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Terminal audio spectrum analyzer"
|
pkgdesc="Terminal audio spectrum analyzer"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
url="https://prosjekt.klingenbergbygg.no/bl/spectrust"
|
options=('!strip')
|
||||||
|
url="https://project.uhhm.no/bl/spectrust"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
depends=()
|
depends=('libpipewire')
|
||||||
provides=('spectrust')
|
provides=('spectrust')
|
||||||
conflicts=('spectrust-git' 'spectrust-bin')
|
conflicts=('spectrust-git' 'spectrust-bin')
|
||||||
source_x86_64=("spectrust-v${pkgver}-x86_64.tar.gz::https://prosjekt.klingenbergbygg.no/bl/spectrust/releases/download/v${pkgver}/spectrust-v${pkgver}-x86_64.tar.gz")
|
source_x86_64=("spectrust-v${pkgver}-x86_64.tar.gz::https://project.uhhm.no/bl/spectrust/releases/download/v${pkgver}/spectrust-v${pkgver}-x86_64.tar.gz")
|
||||||
source_aarch64=("spectrust-v${pkgver}-aarch64.tar.gz::https://prosjekt.klingenbergbygg.no/bl/spectrust/releases/download/v${pkgver}/spectrust-v${pkgver}-aarch64.tar.gz")
|
source_aarch64=("spectrust-v${pkgver}-aarch64.tar.gz::https://project.uhhm.no/bl/spectrust/releases/download/v${pkgver}/spectrust-v${pkgver}-aarch64.tar.gz")
|
||||||
sha256sums_x86_64=('SKIP')
|
sha256sums_x86_64=('SKIP')
|
||||||
sha256sums_aarch64=('SKIP')
|
sha256sums_aarch64=('SKIP')
|
||||||
|
|
||||||
|
|||||||
+16
-29
@@ -17,7 +17,8 @@ use std::sync::mpsc;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
|
mod pipewire_capture;
|
||||||
|
|
||||||
const EMPTY_CELL: Cell = Cell { character: ' ', filled: false };
|
const EMPTY_CELL: Cell = Cell { character: ' ', filled: false };
|
||||||
const BLOCK_CELL: Cell = Cell { character: '█', filled: true };
|
const BLOCK_CELL: Cell = Cell { character: '█', filled: true };
|
||||||
@@ -121,7 +122,7 @@ fn handle_terminal_resize(
|
|||||||
long_about = "Spectrust is a PCM spectrum analyzer that visualizes audio in your terminal.\n\
|
long_about = "Spectrust is a PCM spectrum analyzer that visualizes audio in your terminal.\n\
|
||||||
It can read from stdin, a file, or capture directly from PipeWire.\n\n\
|
It can read from stdin, a file, or capture directly from PipeWire.\n\n\
|
||||||
Examples:\n\
|
Examples:\n\
|
||||||
- Direct PipeWire capture: spectrust -p\n\
|
- Direct PipeWire capture (native, low-latency): spectrust -p\n\
|
||||||
- From audio file: spectrust -i audio.pcm\n\
|
- From audio file: spectrust -i audio.pcm\n\
|
||||||
- From stdin: pw-record --raw - | spectrust\n\n\
|
- From stdin: pw-record --raw - | spectrust\n\n\
|
||||||
Runtime controls:\n\
|
Runtime controls:\n\
|
||||||
@@ -133,9 +134,12 @@ struct Cli {
|
|||||||
#[arg(short, long, help = "Target frames per second", default_value_t = 60)]
|
#[arg(short, long, help = "Target frames per second", default_value_t = 60)]
|
||||||
fps: u16,
|
fps: u16,
|
||||||
|
|
||||||
#[arg(short, long, help = "Use PipeWire to capture audio directly")]
|
#[arg(short, long, help = "Capture audio directly via native low-latency PipeWire (default sink monitor)")]
|
||||||
pipewire: bool,
|
pipewire: bool,
|
||||||
|
|
||||||
|
#[arg(long, help = "PipeWire capture quantum in frames — lower is lower latency (more CPU/xrun risk)", default_value_t = 256)]
|
||||||
|
latency: u32,
|
||||||
|
|
||||||
#[arg(short, long, help = "Input file (reads from stdin if not provided)")]
|
#[arg(short, long, help = "Input file (reads from stdin if not provided)")]
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
|
|
||||||
@@ -184,41 +188,23 @@ fn main() {
|
|||||||
|
|
||||||
let _ = out.flush();
|
let _ = out.flush();
|
||||||
|
|
||||||
let reader: Box<dyn Read + Send + 'static> = if cli.pipewire {
|
let (tx, rx) = mpsc::channel::<Vec<f32>>();
|
||||||
let spawn_result = Command::new("pw-record")
|
|
||||||
.args([
|
if cli.pipewire {
|
||||||
"--format=s16",
|
// Native low-latency capture: pushes mono f32 straight into `tx`.
|
||||||
&format!("--rate={}", cli.rate),
|
if let Err(e) = pipewire_capture::spawn(tx, cli.rate, cli.channels, cli.latency) {
|
||||||
&format!("--channels={}", cli.channels),
|
|
||||||
"--raw",
|
|
||||||
"-",
|
|
||||||
])
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn();
|
|
||||||
match spawn_result {
|
|
||||||
Ok(mut process) => {
|
|
||||||
Box::new(process.stdout.take().expect("failed to capture pw-record stdout"))
|
|
||||||
}
|
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
|
||||||
let _ = execute!(out, LeaveAlternateScreen);
|
let _ = execute!(out, LeaveAlternateScreen);
|
||||||
let _ = disable_raw_mode();
|
let _ = disable_raw_mode();
|
||||||
eprintln!("error: pw-record not found — install PipeWire or pipe audio via stdin.");
|
eprintln!("error: {e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
} else {
|
||||||
let _ = execute!(out, LeaveAlternateScreen);
|
let reader: Box<dyn Read + Send + 'static> = if let Some(filename) = &cli.input {
|
||||||
let _ = disable_raw_mode();
|
|
||||||
eprintln!("error: failed to start pw-record: {e}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(filename) = &cli.input {
|
|
||||||
Box::new(File::open(filename).expect("failed to open input file"))
|
Box::new(File::open(filename).expect("failed to open input file"))
|
||||||
} else {
|
} else {
|
||||||
Box::new(std::io::stdin())
|
Box::new(std::io::stdin())
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel::<Vec<f32>>();
|
|
||||||
let _audio_thread = thread::spawn(move || {
|
let _audio_thread = thread::spawn(move || {
|
||||||
let mut reader = reader;
|
let mut reader = reader;
|
||||||
let mut input_buffer = vec![0u8; samples_per_frame * num_channels * (bit_depth / 8)];
|
let mut input_buffer = vec![0u8; samples_per_frame * num_channels * (bit_depth / 8)];
|
||||||
@@ -255,6 +241,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let max_width: u16 = 1000;
|
let max_width: u16 = 1000;
|
||||||
let max_height: u16 = 1000;
|
let max_height: u16 = 1000;
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
//! Native, low-latency PipeWire capture.
|
||||||
|
//!
|
||||||
|
//! Instead of shelling out to `pw-record` (an extra process, its own
|
||||||
|
//! buffering, and an s16 round-trip), this connects a real capture stream to
|
||||||
|
//! the graph, negotiates F32 at a small quantum, and pushes mono f32 samples
|
||||||
|
//! straight into the render channel from the stream's realtime `process`
|
||||||
|
//! callback. By default it captures the monitor of the default sink (what you
|
||||||
|
//! hear); `latency` is a frames/rate hint the server honours by shrinking the
|
||||||
|
//! quantum. Rate and channel count follow the native graph format.
|
||||||
|
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use pipewire as pw;
|
||||||
|
use pw::spa;
|
||||||
|
use pw::{properties::properties, spa::pod::Pod};
|
||||||
|
use spa::param::audio::AudioInfoRaw;
|
||||||
|
use spa::param::format::{MediaSubtype, MediaType};
|
||||||
|
use spa::param::format_utils;
|
||||||
|
|
||||||
|
/// Start capturing on a dedicated thread. Returns once the stream is connected
|
||||||
|
/// (or with an error describing why it could not start), so the caller can
|
||||||
|
/// report cleanly before taking over the terminal.
|
||||||
|
pub fn spawn(tx: Sender<Vec<f32>>, rate: u32, channels: u8, latency: u32) -> Result<(), String> {
|
||||||
|
let (ready_tx, ready_rx) = std::sync::mpsc::channel::<Result<(), String>>();
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("pipewire-capture".into())
|
||||||
|
.spawn(move || {
|
||||||
|
if let Err(e) = run(tx, rate, channels, latency, ready_tx.clone()) {
|
||||||
|
let _ = ready_tx.send(Err(e));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| format!("failed to spawn capture thread: {e}"))?;
|
||||||
|
ready_rx
|
||||||
|
.recv()
|
||||||
|
.map_err(|_| "pipewire capture thread exited before connecting".to_string())?
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
tx: Sender<Vec<f32>>,
|
||||||
|
rate: u32,
|
||||||
|
_channels: u8,
|
||||||
|
latency: u32,
|
||||||
|
ready: Sender<Result<(), String>>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
pw::init();
|
||||||
|
|
||||||
|
let mainloop = pw::main_loop::MainLoopRc::new(None).map_err(|e| format!("mainloop: {e}"))?;
|
||||||
|
let context =
|
||||||
|
pw::context::ContextRc::new(&mainloop, None).map_err(|e| format!("context: {e}"))?;
|
||||||
|
let core = context
|
||||||
|
.connect_rc(None)
|
||||||
|
.map_err(|e| format!("connect to PipeWire (is it running?): {e}"))?;
|
||||||
|
|
||||||
|
let props = properties! {
|
||||||
|
*pw::keys::MEDIA_TYPE => "Audio",
|
||||||
|
*pw::keys::MEDIA_CATEGORY => "Capture",
|
||||||
|
*pw::keys::MEDIA_ROLE => "Music",
|
||||||
|
// Capture the monitor of the default sink - visualize what's playing.
|
||||||
|
*pw::keys::STREAM_CAPTURE_SINK => "true",
|
||||||
|
// Ask the server for a small quantum: lower = lower latency.
|
||||||
|
*pw::keys::NODE_LATENCY => format!("{latency}/{rate}"),
|
||||||
|
*pw::keys::NODE_NAME => "spectrust",
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = pw::stream::StreamBox::new(&core, "spectrust", props)
|
||||||
|
.map_err(|e| format!("stream: {e}"))?;
|
||||||
|
|
||||||
|
// Channel count is learned from the negotiated format (param_changed) and
|
||||||
|
// read back in the realtime process callback; 0 until negotiated.
|
||||||
|
let _listener = stream
|
||||||
|
.add_local_listener_with_user_data(AudioInfoRaw::default())
|
||||||
|
.param_changed(|_, info, id, param| {
|
||||||
|
let Some(param) = param else { return };
|
||||||
|
if id != pw::spa::param::ParamType::Format.as_raw() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok((mt, ms)) = format_utils::parse_format(param) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if mt != MediaType::Audio || ms != MediaSubtype::Raw {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = info.parse(param);
|
||||||
|
})
|
||||||
|
.process(move |stream, info| {
|
||||||
|
let Some(mut buffer) = stream.dequeue_buffer() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ch = info.channels() as usize;
|
||||||
|
if ch == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let datas = buffer.datas_mut();
|
||||||
|
let Some(data) = datas.first_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let n_bytes = data.chunk().size() as usize;
|
||||||
|
let Some(bytes) = data.data() else { return };
|
||||||
|
let bytes = &bytes[..n_bytes.min(bytes.len())];
|
||||||
|
let frame_bytes = ch * 4;
|
||||||
|
let frames = bytes.len() / frame_bytes;
|
||||||
|
if frames == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut mono = Vec::with_capacity(frames);
|
||||||
|
for f in 0..frames {
|
||||||
|
let base = f * frame_bytes;
|
||||||
|
let mut sum = 0.0f32;
|
||||||
|
for c in 0..ch {
|
||||||
|
let o = base + c * 4;
|
||||||
|
sum += f32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]);
|
||||||
|
}
|
||||||
|
mono.push(sum / ch as f32);
|
||||||
|
}
|
||||||
|
let _ = tx.send(mono);
|
||||||
|
})
|
||||||
|
.register()
|
||||||
|
.map_err(|e| format!("listener: {e}"))?;
|
||||||
|
|
||||||
|
// EnumFormat pod: force F32; leave rate/channels empty to accept the
|
||||||
|
// native graph format (the server converts as needed).
|
||||||
|
let mut audio_info = AudioInfoRaw::new();
|
||||||
|
audio_info.set_format(spa::param::audio::AudioFormat::F32LE);
|
||||||
|
let obj = spa::pod::Object {
|
||||||
|
type_: spa::utils::SpaTypes::ObjectParamFormat.as_raw(),
|
||||||
|
id: spa::param::ParamType::EnumFormat.as_raw(),
|
||||||
|
properties: audio_info.into(),
|
||||||
|
};
|
||||||
|
let values: Vec<u8> = spa::pod::serialize::PodSerializer::serialize(
|
||||||
|
std::io::Cursor::new(Vec::new()),
|
||||||
|
&spa::pod::Value::Object(obj),
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("pod serialize: {e}"))?
|
||||||
|
.0
|
||||||
|
.into_inner();
|
||||||
|
let mut params = [Pod::from_bytes(&values).ok_or("invalid format pod")?];
|
||||||
|
|
||||||
|
stream
|
||||||
|
.connect(
|
||||||
|
spa::utils::Direction::Input,
|
||||||
|
None,
|
||||||
|
pw::stream::StreamFlags::AUTOCONNECT
|
||||||
|
| pw::stream::StreamFlags::MAP_BUFFERS
|
||||||
|
| pw::stream::StreamFlags::RT_PROCESS,
|
||||||
|
&mut params,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("connect stream: {e}"))?;
|
||||||
|
|
||||||
|
let _ = ready.send(Ok(()));
|
||||||
|
mainloop.run();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user