Compare commits
10
Commits
e0e1ad5df3
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6684ddbbd | ||
|
|
d06a94f388 | ||
|
|
1f686ce7ba | ||
|
|
cbcdfae8bf | ||
|
|
ffd67fc90d | ||
|
|
941c744638 | ||
|
|
2753a7b6ba | ||
|
|
dc6ed00abe | ||
|
|
12792b1265 | ||
|
|
4346f75eac |
No files matched your search
@@ -46,6 +46,93 @@ as you type. Press `p` to switch catalogue without restarting.
|
||||
|
||||
## Installation
|
||||
|
||||
### Nix
|
||||
|
||||
The flake builds the package, runs the test suite and wraps everything the app
|
||||
calls at runtime (`mpv`, `ffmpeg`, and PulseAudio's `parec`/`pactl` — PipeWire
|
||||
speaks the same protocol):
|
||||
|
||||
```bash
|
||||
nix run git+https://tildeforge.dev/beefcarver/beefy-player # try it
|
||||
nix profile install git+https://tildeforge.dev/beefcarver/beefy-player # install it
|
||||
```
|
||||
|
||||
In a NixOS configuration:
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs.beefy-player.url = "git+https://tildeforge.dev/beefcarver/beefy-player";
|
||||
|
||||
outputs = { nixpkgs, beefy-player, ... }: {
|
||||
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||
modules = [
|
||||
beefy-player.nixosModules.default
|
||||
{
|
||||
programs.beefy-player.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Or, for a single user with Home Manager:
|
||||
|
||||
```nix
|
||||
{
|
||||
inputs.beefy-player.url = "git+https://tildeforge.dev/beefcarver/beefy-player";
|
||||
|
||||
outputs = { home-manager, beefy-player, ... }: {
|
||||
homeConfigurations.me = home-manager.lib.homeManagerConfiguration {
|
||||
modules = [
|
||||
beefy-player.homeModules.default
|
||||
{
|
||||
programs.beefy-player.enable = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Both modules add one option, `programs.beefy-player.package`, so a pinned or
|
||||
overridden build can be substituted for the flake's own. The raw package
|
||||
(`packages.${system}.default`) and an overlay (`overlays.default`) are exported
|
||||
too. From a checkout, `nix develop` gives you `uv`, `ruff` and the audio tools
|
||||
for the usual `uv sync` / `uv run pytest` workflow; `nix build` builds and
|
||||
tests the package.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
The `aur/PKGBUILD` here is AUR-ready, but the package is not published in the
|
||||
AUR: the AUR has closed new sign-ups, so there is no account to push it to.
|
||||
Build and install it from a checkout instead:
|
||||
|
||||
```bash
|
||||
git clone https://tildeforge.dev/beefcarver/beefy-player
|
||||
cd beefy-player/aur
|
||||
makepkg -si # needs base-devel; builds, runs the test suite, installs
|
||||
```
|
||||
|
||||
Once the AUR accepts new accounts again, the package can be published and
|
||||
`paru -S beefy-player` / `yay -S beefy-player` will work; until then `makepkg`
|
||||
is the only route. The `aur/PKGBUILD` is tracked and kept in sync with every
|
||||
release either way.
|
||||
|
||||
The Python side is pulled in automatically. For audio, the output-synced
|
||||
spectrum and image passthrough through tmux, install the optional tools too:
|
||||
|
||||
```bash
|
||||
sudo pacman -S mpv ffmpeg libpulse tmux
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
uv run beefy-player
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
@@ -179,13 +266,10 @@ uvx ruff check . # lint
|
||||
|
||||
Each catalogue is a self-contained provider module under
|
||||
`beefy_player/providers/`; `base.py` documents the interface. `tools/ansi_art.py`
|
||||
converts a PNG logo into half-block ANSI art. On NixOS, `shell.nix` provides an
|
||||
FHS shell for the same `uv sync` / `uv run` workflow.
|
||||
converts a PNG logo into half-block ANSI art. On NixOS, `nix develop` (or the
|
||||
older `shell.nix`) provides the same `uv sync` / `uv run` workflow.
|
||||
|
||||
## Licence
|
||||
|
||||
WTFPL — Do What The Fuck You Want To Public License, Version 2. See
|
||||
[LICENSE](LICENSE). SomaFM and its channel names are trademarks of
|
||||
SomaFM.com, LLC. tilderadio belongs to the tildeverse community.
|
||||
|
||||
Terminal image rendering uses [Pillow](https://python-pillow.org/).
|
||||
WTFPL — Do What The Fuck You Want To Public License, Version 2.
|
||||
See [LICENSE](LICENSE).
|
||||
@@ -0,0 +1,28 @@
|
||||
pkgbase = beefy-player
|
||||
pkgdesc = Flashy terminal player for internet radio with a live spectrum analyser
|
||||
pkgver = 1.2.2
|
||||
pkgrel = 1
|
||||
url = https://tildeforge.dev/beefcarver/beefy-player
|
||||
arch = any
|
||||
license = WTFPL
|
||||
depends = python
|
||||
depends = python-dbus-next
|
||||
depends = python-httpx
|
||||
depends = python-numpy
|
||||
depends = python-pillow
|
||||
depends = python-rich
|
||||
depends = python-textual
|
||||
makedepends = python-build
|
||||
makedepends = python-hatchling
|
||||
makedepends = python-installer
|
||||
makedepends = python-wheel
|
||||
checkdepends = python-pytest
|
||||
checkdepends = python-pytest-asyncio
|
||||
optdepends = ffmpeg: decode the stream for the spectrum analyser, and ffplay as the audio fallback
|
||||
optdepends = libpulse: capture the output monitor so the analyser follows what you hear (parec/pactl)
|
||||
optdepends = mpv: preferred audio backend
|
||||
optdepends = tmux: detect the outer terminal for kitty graphics passthrough inside tmux
|
||||
source = beefy-player-1.2.2.tar.gz::https://tildeforge.dev/beefcarver/beefy-player/archive/941c7446384bf346e40d1438a7ce67939d9615df.tar.gz
|
||||
sha256sums = 8ac568676ef5ef8345441c28e4f8f5d61fb06d5225e566ac578acae18672c778
|
||||
|
||||
pkgname = beefy-player
|
||||
@@ -0,0 +1,59 @@
|
||||
# Maintainer: beefy-player contributors <[email protected]>
|
||||
|
||||
pkgname=beefy-player
|
||||
pkgver=1.2.2
|
||||
pkgrel=1
|
||||
pkgdesc="Flashy terminal player for internet radio with a live spectrum analyser"
|
||||
arch=('any')
|
||||
url="https://tildeforge.dev/beefcarver/beefy-player"
|
||||
license=('WTFPL')
|
||||
depends=(
|
||||
'python'
|
||||
'python-dbus-next'
|
||||
'python-httpx'
|
||||
'python-numpy'
|
||||
'python-pillow'
|
||||
'python-rich'
|
||||
'python-textual'
|
||||
)
|
||||
makedepends=(
|
||||
'python-build'
|
||||
'python-hatchling'
|
||||
'python-installer'
|
||||
'python-wheel'
|
||||
)
|
||||
checkdepends=(
|
||||
'python-pytest'
|
||||
'python-pytest-asyncio'
|
||||
)
|
||||
optdepends=(
|
||||
'ffmpeg: decode the stream for the spectrum analyser, and ffplay as the audio fallback'
|
||||
'libpulse: capture the output monitor so the analyser follows what you hear (parec/pactl)'
|
||||
'mpv: preferred audio backend'
|
||||
'tmux: detect the outer terminal for kitty graphics passthrough inside tmux'
|
||||
)
|
||||
|
||||
# Upstream does not tag releases yet, so the source is the archive of the
|
||||
# commit that carries this version. Once a `v$pkgver` tag exists, switch to
|
||||
#
|
||||
# source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||
#
|
||||
_commit=941c7446384bf346e40d1438a7ce67939d9615df
|
||||
source=("$pkgname-$pkgver.tar.gz::$url/archive/$_commit.tar.gz")
|
||||
sha256sums=('8ac568676ef5ef8345441c28e4f8f5d61fb06d5225e566ac578acae18672c778')
|
||||
|
||||
build() {
|
||||
cd "$pkgname"
|
||||
python -m build --wheel --no-isolation
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "$pkgname"
|
||||
# The suite is offline: catalogues are fixtures, mpv/parec are faked.
|
||||
python -m pytest -q
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$pkgname"
|
||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
# AUR packaging for beefy-player
|
||||
|
||||
An AUR repository holds exactly two tracked files, `PKGBUILD` and `.SRCINFO`.
|
||||
This directory is the staging area for both; the rest of the project is
|
||||
irrelevant to the AUR.
|
||||
|
||||
## Build it locally
|
||||
|
||||
```bash
|
||||
cd aur
|
||||
makepkg -si # build, run the test suite, install
|
||||
```
|
||||
|
||||
`makepkg -o && makepkg -e` skips the download/checksum phase, and
|
||||
`extra-x86_64-build` (devtools) builds in a clean chroot. The package builds
|
||||
with `base-devel` plus the `makedepends`/`checkdepends` listed in the
|
||||
`PKGBUILD`; every dependency comes from the official repositories, none from
|
||||
the AUR.
|
||||
|
||||
## Publish to the AUR
|
||||
|
||||
```bash
|
||||
git clone ssh://[email protected]/beefy-player.git /tmp/aur-beefy-player
|
||||
cp PKGBUILD .SRCINFO /tmp/aur-beefy-player/
|
||||
cd /tmp/aur-beefy-player
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "Initial import: beefy-player 1.2.2-1"
|
||||
git push
|
||||
```
|
||||
|
||||
## Releasing a new version
|
||||
|
||||
1. Set `pkgver` in `PKGBUILD` and `pyproject.toml`, and reset `pkgrel=1`.
|
||||
2. Point `_commit` at the commit carrying that version:
|
||||
|
||||
```bash
|
||||
git rev-parse HEAD # or the tag, once one exists
|
||||
```
|
||||
|
||||
3. Refresh the checksum:
|
||||
|
||||
```bash
|
||||
curl -fsSL "https://tildeforge.dev/beefcarver/beefy-player/archive/$_commit.tar.gz" | sha256sum
|
||||
```
|
||||
|
||||
4. Regenerate the metadata (on Arch, or inside a chroot):
|
||||
|
||||
```bash
|
||||
makepkg --printsrcinfo > .SRCINFO
|
||||
```
|
||||
|
||||
5. Push to the AUR with the conventional commit message:
|
||||
|
||||
```bash
|
||||
git commit -am "upgpkg: beefy-player <pkgver>-<pkgrel>"
|
||||
```
|
||||
|
||||
## Why the source is a commit archive
|
||||
|
||||
Upstream publishes no tags or releases, so the checksum in `sha256sums`
|
||||
belongs to the archive of a specific commit rather than of a `v$pkgver` tag.
|
||||
Forge archives of a commit are deterministic, so the checksum stays stable.
|
||||
Once `v$pkgver` is tagged, replace the `source` line with
|
||||
|
||||
```bash
|
||||
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||
```
|
||||
|
||||
## Packaging notes
|
||||
|
||||
- The wheel is built with `hatchling`; `theme.tcss` is force-included by
|
||||
`pyproject.toml`, so it ends up in `site-packages/beefy_player/`.
|
||||
- `check()` runs the real test suite. It is offline by design (station
|
||||
catalogues are fixtures and `mpv`/`parec` are faked), which makes it safe in
|
||||
a build chroot with no network.
|
||||
- `mpv`, `ffmpeg`, `libpulse` (`parec`/`pactl`) and `tmux` are `optdepends`,
|
||||
not `depends`: the player degrades gracefully without them and says so in
|
||||
the UI. Users who want sound need `mpv` (or `ffmpeg` for `ffplay`) and, for
|
||||
the output-synced spectrum, `libpulse`.
|
||||
- `license=('WTFPL')` is an SPDX identifier shipped by Arch's `licenses`
|
||||
package.
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Beefy Player — a flashy terminal player for internet radio."""
|
||||
|
||||
__version__ = "1.2.1"
|
||||
__version__ = "1.2.2"
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1793726008,
|
||||
"narHash": "sha256-jQp1wKvbwWdLBASTXkSi/VYmDJ5AbBi1WvcMt9HWJC8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1d060086d60cb76a40cf37109f979d88a5bd6396",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
{
|
||||
description = "beefy-player — a flashy terminal player for internet radio";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs }:
|
||||
let
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
||||
|
||||
# Things the app shells out to at runtime: mpv for audio, ffmpeg for the
|
||||
# spectrum capture, and parec/pactl from PulseAudio for the monitor source
|
||||
# (PipeWire speaks the same protocol).
|
||||
runtimeDeps =
|
||||
pkgs:
|
||||
with pkgs;
|
||||
[
|
||||
mpv
|
||||
ffmpeg
|
||||
]
|
||||
++ lib.optionals stdenv.hostPlatform.isLinux [ pulseaudio ];
|
||||
|
||||
beefy-player =
|
||||
pkgs:
|
||||
let
|
||||
py = pkgs.python3Packages;
|
||||
in
|
||||
py.buildPythonApplication {
|
||||
pname = "beefy-player";
|
||||
version = "1.2.2";
|
||||
pyproject = true;
|
||||
src = self;
|
||||
|
||||
build-system = [ py.hatchling ];
|
||||
|
||||
dependencies =
|
||||
with py;
|
||||
[
|
||||
textual
|
||||
rich
|
||||
httpx
|
||||
numpy
|
||||
pillow
|
||||
]
|
||||
++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ dbus-next ];
|
||||
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
nativeCheckInputs = with py; [
|
||||
pytestCheckHook
|
||||
pytest-asyncio
|
||||
];
|
||||
|
||||
# The suite is offline (catalogues are cached fixtures).
|
||||
doCheck = true;
|
||||
|
||||
pythonImportsCheck = [ "beefy_player" ];
|
||||
|
||||
makeWrapperArgs = [
|
||||
"--prefix"
|
||||
"PATH"
|
||||
":"
|
||||
(pkgs.lib.makeBinPath (runtimeDeps pkgs))
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Flashy terminal player for internet radio, with a live spectrum analyser";
|
||||
homepage = "https://tildeforge.dev/beefcarver/beefy-player";
|
||||
license = pkgs.lib.licenses.wtfpl;
|
||||
mainProgram = "beefy-player";
|
||||
platforms = pkgs.lib.platforms.unix;
|
||||
};
|
||||
};
|
||||
# One module body, two install targets: a NixOS system adds the package
|
||||
# to environment.systemPackages, Home Manager to home.packages.
|
||||
mkModule =
|
||||
install:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.programs.beefy-player;
|
||||
in
|
||||
{
|
||||
options.programs.beefy-player = {
|
||||
enable = lib.mkEnableOption "beefy-player, a terminal radio player";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
defaultText = lib.literalMD "the `beefy-player` package from this flake";
|
||||
description = "The beefy-player package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable (lib.setAttrByPath install [ cfg.package ]);
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
pkgs:
|
||||
let
|
||||
pkg = beefy-player pkgs;
|
||||
in
|
||||
{
|
||||
default = pkg;
|
||||
beefy-player = pkg;
|
||||
}
|
||||
);
|
||||
|
||||
apps = forAllSystems (pkgs: {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${beefy-player pkgs}/bin/beefy-player";
|
||||
meta.description = "Flashy terminal player for internet radio";
|
||||
};
|
||||
});
|
||||
|
||||
checks = forAllSystems (pkgs: {
|
||||
beefy-player = beefy-player pkgs;
|
||||
});
|
||||
|
||||
devShells = forAllSystems (
|
||||
pkgs:
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
in
|
||||
{
|
||||
# No FHS needed here: `nix develop` puts libstdc++ on LD_LIBRARY_PATH
|
||||
# so uv's manylinux wheels load, unlike the plain `nix-shell` case in
|
||||
# shell.nix.
|
||||
default = pkgs.mkShell {
|
||||
packages = runtimeDeps pkgs ++ [
|
||||
pkgs.python3
|
||||
pkgs.uv
|
||||
pkgs.ruff
|
||||
pkgs.git
|
||||
pkgs.cacert
|
||||
];
|
||||
|
||||
env = {
|
||||
UV_PYTHON = "${pkgs.python3}/bin/python3";
|
||||
UV_PYTHON_DOWNLOADS = "never";
|
||||
UV_PROJECT_ENVIRONMENT = "${toString ./.}/.venv";
|
||||
UV_LINK_MODE = "copy";
|
||||
SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
};
|
||||
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.zlib
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "beefy-player dev shell — uv sync, uv run pytest, uv run beefy-player"
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
nixosModules.default = mkModule [
|
||||
"environment"
|
||||
"systemPackages"
|
||||
];
|
||||
nixosModules.beefy-player = self.nixosModules.default;
|
||||
|
||||
homeModules.default = mkModule [
|
||||
"home"
|
||||
"packages"
|
||||
];
|
||||
homeModules.beefy-player = self.homeModules.default;
|
||||
|
||||
formatter = forAllSystems (pkgs: pkgs.nixfmt);
|
||||
|
||||
overlays.default = final: _prev: {
|
||||
beefy-player = beefy-player final;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
class BeefyPlayer < Formula
|
||||
include Language::Python::Virtualenv
|
||||
|
||||
desc "Terminal player for internet radio with a live spectrum analyser"
|
||||
homepage "https://tildeforge.dev/beefcarver/beefy-player"
|
||||
url "https://tildeforge.dev/beefcarver/beefy-player/releases/download/v1.2.2/beefy-player-1.2.2.tar.gz"
|
||||
sha256 "0000000000000000000000000000000000000000000000000000000000000000" # TODO: release asset
|
||||
license "WTFPL"
|
||||
head "https://tildeforge.dev/beefcarver/beefy-player.git", branch: "main"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
# numpy and pillow are large native builds. Depending on Homebrew's own
|
||||
# formulae (which have bottles) keeps them out of the app's virtualenv build;
|
||||
# the venv sees them through system site-packages.
|
||||
depends_on "numpy"
|
||||
depends_on "pillow"
|
||||
|
||||
# Runtime tools the app shells out to: mpv for audio, ffmpeg for the stream
|
||||
# decode fallback of the spectrum analyser. Both are optional at runtime --
|
||||
# the UI degrades gracefully -- but a radio player without them is not much fun.
|
||||
depends_on "ffmpeg"
|
||||
depends_on "mpv"
|
||||
|
||||
# Tells `brew update-python-resources` what to resolve: numpy/pillow come from
|
||||
# the formulae above, and dbus-next is marker-gated to Linux upstream so it
|
||||
# has to be requested explicitly for the Linux side of the tap.
|
||||
pypi_packages exclude_packages: "numpy,pillow", extra_packages: "dbus-next"
|
||||
|
||||
def install
|
||||
# system_site_packages is the default, so the excluded numpy/pillow from the
|
||||
# formulae are importable from the virtualenv.
|
||||
virtualenv_install_with_resources
|
||||
end
|
||||
|
||||
def caveats
|
||||
<<~EOS
|
||||
The spectrum analyser follows what you hear. On macOS 14.2+ grant your
|
||||
terminal permission under System Settings > Privacy & Security > System
|
||||
Audio Recording, otherwise it falls back to decoding the stream.
|
||||
|
||||
Inside tmux, install `tmux` for image passthrough.
|
||||
EOS
|
||||
end
|
||||
|
||||
test do
|
||||
assert_match version.to_s, shell_output("#{bin}/beefy-player --version")
|
||||
# --offline keeps the test hermetic: cached or bundled stations only.
|
||||
assert_match(/^# /, shell_output("#{bin}/beefy-player --offline --list"))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,120 @@
|
||||
# Homebrew packaging for beefy-player
|
||||
|
||||
A Homebrew **tap** is just a second git repository that Homebrew clones next to
|
||||
homebrew/core. There is no central submission, no review and no binary host:
|
||||
`brew` reads the Ruby formula from the tap, downloads the pinned source tarball
|
||||
and installs it on the user's machine.
|
||||
|
||||
This directory is the staging area for the tap repository, which lives at
|
||||
**https://tildeforge.dev/beefcarver/homebrew-tap** (repo name `homebrew-tap`, so
|
||||
the tap name is `beefcarver/tap`).
|
||||
|
||||
Because Homebrew's one-argument `brew tap` assumes GitHub, a Forgejo tap has to
|
||||
be tapped with its URL, and Homebrew 6+ also wants an explicit trust.
|
||||
|
||||
## Install (user side)
|
||||
|
||||
```bash
|
||||
brew tap beefcarver/tap https://tildeforge.dev/beefcarver/homebrew-tap
|
||||
brew trust --formula beefcarver/tap/beefy-player
|
||||
brew install beefcarver/tap/beefy-player
|
||||
```
|
||||
|
||||
`brew update` refreshes the tap from then on; `brew upgrade` picks up new
|
||||
versions like any core formula. If the tap is mirrored to GitHub later,
|
||||
`brew install beefcarver/tap/beefy-player` becomes a one-liner, because that
|
||||
form auto-taps from GitHub and trusts only the formula it installs.
|
||||
|
||||
## Publishing the tap (maintainer side)
|
||||
|
||||
### 1. One tag per release, with a release asset
|
||||
|
||||
The formula pins a stable URL plus a SHA-256, so it needs an immutable tarball.
|
||||
Do **not** point it at Forgejo's `/archive/v$VERSION.tar.gz` URL: those archives
|
||||
are generated on demand and are not guaranteed byte-stable. Attach a tarball to
|
||||
the release instead (Forgejo serves it at `/releases/download/<tag>/<file>`):
|
||||
|
||||
```bash
|
||||
VERSION=$(uv run python -c 'import beefy_player; print(beefy_player.__version__)')
|
||||
git archive --format=tar.gz --prefix="beefy-player-$VERSION/" \
|
||||
-o "beefy-player-$VERSION.tar.gz" "v$VERSION"
|
||||
sha256sum "beefy-player-$VERSION.tar.gz"
|
||||
```
|
||||
|
||||
Create the tag on the mirror (`git push tildeforge "v$VERSION"`), upload the
|
||||
tarball to the `v$VERSION` release, then paste the URL and checksum into
|
||||
`Formula/b/beefy-player.rb`.
|
||||
|
||||
### 2. Resolve the Python resources
|
||||
|
||||
Every Python dependency (and its recursive dependencies) that is not provided by
|
||||
another formula must be declared as a `resource` so Homebrew installs with
|
||||
dependency resolution disabled. Never write these by hand:
|
||||
|
||||
```bash
|
||||
brew tap beefcarver/tap /path/to/homebrew-tap # local clone, or the URL
|
||||
brew update-python-resources --print-only beefcarver/tap/beefy-player
|
||||
brew update-python-resources beefcarver/tap/beefy-player
|
||||
```
|
||||
|
||||
`pypi_packages exclude_packages: "numpy,pillow"` keeps the two native builds out
|
||||
of the resource list — they come from the `numpy`/`pillow` formulae, which ship
|
||||
bottles, and the virtualenv picks them up through system site-packages.
|
||||
`extra_packages: "dbus-next"` makes sure the Linux-only dependency is recorded
|
||||
even though the resolver runs on macOS.
|
||||
|
||||
In a third-party tap, `--ignore-errors` records anything that cannot be resolved
|
||||
and leaves a `RESOURCE-ERROR` comment; the official taps do not allow that, a
|
||||
personal one does.
|
||||
|
||||
### 3. Build, test and audit locally
|
||||
|
||||
```bash
|
||||
brew install --build-from-source beefcarver/tap/beefy-player
|
||||
brew test beefcarver/tap/beefy-player
|
||||
brew audit --strict beefcarver/tap/beefy-player
|
||||
brew style beefcarver/tap/beefy-player
|
||||
```
|
||||
|
||||
The formula's `test do` block runs `--version` and `--offline --list`, so it
|
||||
needs no network and no audio device.
|
||||
|
||||
### 4. Push
|
||||
|
||||
```bash
|
||||
git add Formula/b/beefy-player.rb && git commit -m 'beefy-player 1.2.2' && git push
|
||||
```
|
||||
|
||||
## Version bumps
|
||||
|
||||
For each release: tag, upload the tarball, update `url`/`sha256`, rerun
|
||||
`brew update-python-resources` (only needed when the dependency set or pins
|
||||
changed), install from source, test, push. `brew bump-formula-pr` only works
|
||||
against GitHub-hosted taps, so on Forgejo the bump is a `sed` plus a re-test.
|
||||
|
||||
Optional `livecheck` support, so `brew livecheck --tap beefcarver/tap` can spot
|
||||
new tags:
|
||||
|
||||
```ruby
|
||||
livecheck do
|
||||
url "https://tildeforge.dev/beefcarver/beefy-player/tags"
|
||||
regex(/^v?(\d+(?:\.\d+)+)$/i)
|
||||
end
|
||||
```
|
||||
|
||||
## Bottles (optional)
|
||||
|
||||
Without a `bottle do` block users build from source — for this app that means
|
||||
filling a virtualenv with pure-Python resources while `numpy`, `pillow`, `mpv`
|
||||
and `ffmpeg` arrive as bottles, so it is quick. Prebuilt bottles need Homebrew's
|
||||
GitHub Actions workflows, which cannot run on Forgejo; skip them unless the tap
|
||||
is also mirrored to GitHub.
|
||||
|
||||
## Why a tap and not homebrew-core
|
||||
|
||||
homebrew-core requires a tagged, immutable source URL and enough notability to
|
||||
be accepted at all (roughly 75+ stars / 30+ forks, or coverage that makes a
|
||||
project notable). A tap has none of those constraints, which is why upstream
|
||||
taps are the normal route for a project this size. The formula here follows
|
||||
core's conventions, so it could be submitted there later unchanged if the
|
||||
project qualifies.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "beefy-player"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
description = "A flashy terminal player for internet radio — live spectrum analyser, MPRIS / macOS Now Playing support, song history and pluggable station catalogues (SomaFM, Radio Paradise, dublab, Rinse FM and more)"
|
||||
readme = "README.md"
|
||||
license = "WTFPL"
|
||||
|
||||
@@ -2,13 +2,13 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from beefy_player.player import Player
|
||||
|
||||
FAKE_MPV = '''#!/usr/bin/env python3
|
||||
import json, os, socket, sys, time
|
||||
FAKE_MPV = f"#!{sys.executable}\n" + '''import json, os, socket, sys, time
|
||||
|
||||
sock_path = None
|
||||
volume = 70
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
@@ -8,8 +9,7 @@ import numpy as np
|
||||
from beefy_player import spectrum as spectrum_module
|
||||
from beefy_player.spectrum import Frame, SpectrumEngine
|
||||
|
||||
PCM_SCRIPT = """#!/usr/bin/env python3
|
||||
import math, struct, sys, time
|
||||
PCM_SCRIPT = f"#!{sys.executable}\n" + """import math, struct, sys, time
|
||||
sr = 44100
|
||||
samples = [int(0.6 * 32767 * math.sin(2 * math.pi * 1000 * i / sr)) for i in range(4096)]
|
||||
chunk = struct.pack("<%dh" % len(samples), *samples)
|
||||
@@ -303,7 +303,7 @@ def test_tap_audio_is_downmixed_and_retuned() -> None:
|
||||
def test_dead_monitor_falls_back_instead_of_flat_bars(tmp_path) -> None:
|
||||
script = tmp_path / "silent-parec"
|
||||
script.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
f"#!{sys.executable}\n"
|
||||
"import sys\n"
|
||||
"block = b'\\0' * 4096\n"
|
||||
"for _ in range(1000):\n"
|
||||
@@ -333,7 +333,7 @@ def test_monitor_survives_silence_after_audio(tmp_path, monkeypatch) -> None:
|
||||
# replaced by the drifting stream tap) for the rest of the session.
|
||||
script = tmp_path / "parec"
|
||||
script.write_text(
|
||||
"#!/usr/bin/env python3\n"
|
||||
f"#!{sys.executable}\n"
|
||||
"import math, struct, sys, time\n"
|
||||
"sr = 44100\n"
|
||||
"n = 2048\n"
|
||||
|
||||
Reference in new issue
Block a user