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
|
## 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
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -179,13 +266,10 @@ uvx ruff check . # lint
|
|||||||
|
|
||||||
Each catalogue is a self-contained provider module under
|
Each catalogue is a self-contained provider module under
|
||||||
`beefy_player/providers/`; `base.py` documents the interface. `tools/ansi_art.py`
|
`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
|
converts a PNG logo into half-block ANSI art. On NixOS, `nix develop` (or the
|
||||||
FHS shell for the same `uv sync` / `uv run` workflow.
|
older `shell.nix`) provides the same `uv sync` / `uv run` workflow.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
WTFPL — Do What The Fuck You Want To Public License, Version 2. See
|
WTFPL — Do What The Fuck You Want To Public License, Version 2.
|
||||||
[LICENSE](LICENSE). SomaFM and its channel names are trademarks of
|
See [LICENSE](LICENSE).
|
||||||
SomaFM.com, LLC. tilderadio belongs to the tildeverse community.
|
|
||||||
|
|
||||||
Terminal image rendering uses [Pillow](https://python-pillow.org/).
|
|
||||||
@@ -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 <beefcarver@tilde.club>
|
||||||
|
|
||||||
|
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://aur@aur.archlinux.org/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."""
|
"""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 "python@3.14"
|
||||||
|
|
||||||
|
# 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]
|
[project]
|
||||||
name = "beefy-player"
|
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)"
|
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"
|
readme = "README.md"
|
||||||
license = "WTFPL"
|
license = "WTFPL"
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from beefy_player.player import Player
|
from beefy_player.player import Player
|
||||||
|
|
||||||
FAKE_MPV = '''#!/usr/bin/env python3
|
FAKE_MPV = f"#!{sys.executable}\n" + '''import json, os, socket, sys, time
|
||||||
import json, os, socket, sys, time
|
|
||||||
|
|
||||||
sock_path = None
|
sock_path = None
|
||||||
volume = 70
|
volume = 70
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -8,8 +9,7 @@ import numpy as np
|
|||||||
from beefy_player import spectrum as spectrum_module
|
from beefy_player import spectrum as spectrum_module
|
||||||
from beefy_player.spectrum import Frame, SpectrumEngine
|
from beefy_player.spectrum import Frame, SpectrumEngine
|
||||||
|
|
||||||
PCM_SCRIPT = """#!/usr/bin/env python3
|
PCM_SCRIPT = f"#!{sys.executable}\n" + """import math, struct, sys, time
|
||||||
import math, struct, sys, time
|
|
||||||
sr = 44100
|
sr = 44100
|
||||||
samples = [int(0.6 * 32767 * math.sin(2 * math.pi * 1000 * i / sr)) for i in range(4096)]
|
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)
|
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:
|
def test_dead_monitor_falls_back_instead_of_flat_bars(tmp_path) -> None:
|
||||||
script = tmp_path / "silent-parec"
|
script = tmp_path / "silent-parec"
|
||||||
script.write_text(
|
script.write_text(
|
||||||
"#!/usr/bin/env python3\n"
|
f"#!{sys.executable}\n"
|
||||||
"import sys\n"
|
"import sys\n"
|
||||||
"block = b'\\0' * 4096\n"
|
"block = b'\\0' * 4096\n"
|
||||||
"for _ in range(1000):\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.
|
# replaced by the drifting stream tap) for the rest of the session.
|
||||||
script = tmp_path / "parec"
|
script = tmp_path / "parec"
|
||||||
script.write_text(
|
script.write_text(
|
||||||
"#!/usr/bin/env python3\n"
|
f"#!{sys.executable}\n"
|
||||||
"import math, struct, sys, time\n"
|
"import math, struct, sys, time\n"
|
||||||
"sr = 44100\n"
|
"sr = 44100\n"
|
||||||
"n = 2048\n"
|
"n = 2048\n"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "beefy-player"
|
name = "beefy-player"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "dbus-next", marker = "sys_platform == 'linux'" },
|
{ name = "dbus-next", marker = "sys_platform == 'linux'" },
|
||||||
|
|||||||
Reference in new issue
Block a user