Compare commits

...

105 commits

Author SHA1 Message Date
github-actions[bot]
6975589341 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.905687%2Brev-1327e798cb055f96f92685df444e9a2c326ab5ed/019bb874-9b65-73ec-9dd5-8f14598e59e0/source.tar.gz' (2026-01-12)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.906484%2Brev-1cd347bf3355fce6c64ab37d3967b4a2cb4b878c/019bfb68-fb8e-7f55-bb2a-5bee98516c95/source.tar.gz' (2026-01-25)
2026-01-29 14:19:01 +00:00
dependabot[bot]
5df0f028e2 chore(deps): bump DeterminateSystems/update-flake-lock from 27 to 28
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 27 to 28.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v27...v28)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-version: '28'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 08:26:45 +00:00
dependabot[bot]
8393e053b9 chore(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 08:26:31 +00:00
Martin Wimpress
7a63368963
docs(readme): remove uncertain RTMPS support statement
The statement about untested RTMPS support was speculative and
potentially confusing to users. Removed as it has now been verified as
working.

Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:22:11 +00:00
Martin Wimpress
da76e1c219
chore: symlink Dockerfile to Containerfile
Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:09:33 +00:00
Martin Wimpress
2bda8192f1
docs(agents): add AGENTS.md for AI agent context
Provides comprehensive project documentation including:
- Overview of Stream Sprout RTMP restreaming tool
- Tech stack and build instructions
- Code style and linting requirements
- Project structure and configuration details
- Commit guidelines and security considerations

Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:06:33 +00:00
github-actions[bot]
a811fe527e flake.lock: Update
Flake lock file updates:

• Updated input 'flake-schemas':
    'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz' (2024-07-26)
  → 'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.2.0/019a4a84-544d-7c59-b26d-e334e320c932/source.tar.gz' (2025-10-27)
• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808080%2Brev-ddae11e58c0c345bf66efbddbf2192ed0e58f896/01989f5e-b09d-7b09-9699-5d522e6f12ce/source.tar.gz' (2025-08-11)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.905687%2Brev-1327e798cb055f96f92685df444e9a2c326ab5ed/019bb874-9b65-73ec-9dd5-8f14598e59e0/source.tar.gz' (2026-01-12)
2026-01-22 15:59:17 +00:00
dependabot[bot]
c417dc1b10 chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:59:00 +00:00
dependabot[bot]
b3be5c43d9 chore(deps): bump DeterminateSystems/nix-installer-action from 19 to 21
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 19 to 21.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v19...v21)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '21'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:45 +00:00
dependabot[bot]
90eb31a5e5 chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:27 +00:00
dependabot[bot]
f76da1c62b chore(deps): bump anchore/scan-action from 6 to 7
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 6 to 7.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/scan-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:12 +00:00
dependabot[bot]
5d864aacb8 chore(deps): bump amannn/action-semantic-pull-request from 5 to 6
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5 to 6.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5...v6)

---
updated-dependencies:
- dependency-name: amannn/action-semantic-pull-request
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:57:46 +00:00
Martin Wimpress
382dff7a48
chore: remove coreutils from container dependencies
Removes the coreutils package from the Alpine container dependencies.
2025-08-26 23:51:25 +01:00
Martin Wimpress
bd1676efa6
chore: bump version from 0.1.5 to 0.1.6
Update version number in the stream-sprout script for a new release
2025-08-26 23:35:43 +01:00
dependabot[bot]
a79d451d0c chore(deps): bump DeterminateSystems/magic-nix-cache-action from 8 to 13
Bumps [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action) from 8 to 13.
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/v8...v13)

---
updated-dependencies:
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-version: '13'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:29:51 +01:00
Martin Wimpress
c470ca46e4 refactor(dockerfile): switch from custom ffmpeg to jellyfin-ffmpeg
- Replace custom ffmpeg image with alpine base and jellyfin-ffmpeg package
- Add symlinks for ffmpeg and ffprobe to standard locations
- Set USER directive to run as nobody for improved security
2025-08-26 23:27:57 +01:00
dependabot[bot]
c156db1f64 chore(deps): bump DeterminateSystems/flake-checker-action from 9 to 12
Bumps [DeterminateSystems/flake-checker-action](https://github.com/determinatesystems/flake-checker-action) from 9 to 12.
- [Release notes](https://github.com/determinatesystems/flake-checker-action/releases)
- [Commits](https://github.com/determinatesystems/flake-checker-action/compare/v9...v12)

---
updated-dependencies:
- dependency-name: DeterminateSystems/flake-checker-action
  dependency-version: '12'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:27:12 +01:00
dependabot[bot]
7c57494674 chore(deps): bump DeterminateSystems/nix-installer-action from 16 to 19
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 16 to 19.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v16...v19)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '19'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:54 +01:00
dependabot[bot]
57a1f800d2 chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:36 +01:00
dependabot[bot]
9fbbde4d6c chore(deps): bump DeterminateSystems/update-flake-lock from 24 to 27
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 24 to 27.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v24...v27)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-version: '27'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:18 +01:00
dependabot[bot]
1d7e3e8247 chore(deps): bump anchore/scan-action from 5 to 6
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 5 to 6.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:47:39 +01:00
dependabot[bot]
84a1e43137 chore(deps): bump DeterminateSystems/nix-installer-action from 15 to 16
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 15 to 16.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v15...v16)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:47:23 +01:00
github-actions[bot]
48c4943d72 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.636163%2Brev-cd3e8833d70618c4eea8df06f95b364b016d4950/0192cd43-85cd-7ff3-b9be-a3f7995e917d/source.tar.gz?narHash=sha256-knnVBGfTCZlQgxY1SgH0vn2OyehH9ykfF8geZgS95bk%3D' (2024-10-26)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808080%2Brev-ddae11e58c0c345bf66efbddbf2192ed0e58f896/01989f5e-b09d-7b09-9699-5d522e6f12ce/source.tar.gz?narHash=sha256-3sWA5WJybUE16kIMZ3%2BuxcxKZY/JRR4DFBqLdSLBo7w%3D' (2025-08-11)
2025-08-18 09:47:08 +01:00
dependabot[bot]
1a19e85d94 chore(deps): bump DeterminateSystems/nix-installer-action from 14 to 15
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 14 to 15.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v14...v15)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:23:19 +00:00
dependabot[bot]
3f91c0f573 chore(deps): bump anchore/scan-action from 4 to 5
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 4 to 5.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:17:00 +00:00
github-actions[bot]
e5a0db3a8f flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.635490%2Brev-f65141456289e81ea0d5a05af8898333cab5c53d/019237db-783b-7330-a22e-7d60c20ce855/source.tar.gz?narHash=sha256-pojbL/qteElw/nIXlN8kmHn/w6PQbEHr7Iz%2BWOXs0EM%3D' (2024-09-27)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.636163%2Brev-cd3e8833d70618c4eea8df06f95b364b016d4950/0192cd43-85cd-7ff3-b9be-a3f7995e917d/source.tar.gz?narHash=sha256-knnVBGfTCZlQgxY1SgH0vn2OyehH9ykfF8geZgS95bk%3D' (2024-10-26)
2024-11-12 10:16:48 +00:00
dependabot[bot]
6ec390f406 chore(deps): bump DeterminateSystems/magic-nix-cache-action from 7 to 8
Bumps [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action) from 7 to 8.
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/v7...v8)

---
updated-dependencies:
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:16:34 +00:00
dependabot[bot]
0bb875c287 chore(deps): bump DeterminateSystems/flake-checker-action from 8 to 9
Bumps [DeterminateSystems/flake-checker-action](https://github.com/determinatesystems/flake-checker-action) from 8 to 9.
- [Release notes](https://github.com/determinatesystems/flake-checker-action/releases)
- [Commits](https://github.com/determinatesystems/flake-checker-action/compare/v8...v9)

---
updated-dependencies:
- dependency-name: DeterminateSystems/flake-checker-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:21:39 +01:00
dependabot[bot]
901586e4bf chore(deps): bump DeterminateSystems/update-flake-lock from 23 to 24
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 23 to 24.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v23...v24)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:21:18 +01:00
github-actions[bot]
ed5d5d136b flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.634418%2Brev-2527da1ef492c495d5391f3bcf9c1dd9f4514e32/019193c7-3325-7c5c-9d46-f2d05135ea41/source.tar.gz?narHash=sha256-XROVLf9ti4rrNCFLr%2BDmXRZtPjCQTW4cYy59owTEmxk%3D' (2024-08-24)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.635490%2Brev-f65141456289e81ea0d5a05af8898333cab5c53d/019237db-783b-7330-a22e-7d60c20ce855/source.tar.gz?narHash=sha256-pojbL/qteElw/nIXlN8kmHn/w6PQbEHr7Iz%2BWOXs0EM%3D' (2024-09-27)
2024-09-30 17:21:08 +01:00
dependabot[bot]
5aa579111e chore(deps): bump DeterminateSystems/nix-installer-action from 13 to 14
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 13 to 14.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v13...v14)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:20:47 +01:00
Dale Visser
39c182ecf7 docs: Fix README link 2024-09-07 17:47:44 +01:00
github-actions[bot]
6984d04f7a flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633516%2Brev-8c50662509100d53229d4be607f1a3a31157fa12/0190f691-c019-7d99-b723-4b2dd6dfd38f/source.tar.gz?narHash=sha256-2ShmEaFi0kJVOEEu5gmlykN5dwjWYWYUJmlRTvZQRpU%3D' (2024-07-27)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.634418%2Brev-2527da1ef492c495d5391f3bcf9c1dd9f4514e32/019193c7-3325-7c5c-9d46-f2d05135ea41/source.tar.gz?narHash=sha256-XROVLf9ti4rrNCFLr%2BDmXRZtPjCQTW4cYy59owTEmxk%3D' (2024-08-24)
2024-09-07 17:47:20 +01:00
dependabot[bot]
1cb4c8ced3 chore(deps): bump anchore/scan-action from 3 to 4
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 3 to 4.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:58 +01:00
dependabot[bot]
a9ed96eaea chore(deps): bump docker/build-push-action from 4 to 6
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:32 +01:00
dependabot[bot]
43d6b9ad88 chore(deps): bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:11 +01:00
Alan Pope
8e3b4dc089 chore: remove armhf snap build
The armhf snap hasn't been published, and I doubt anyone would use it if it were. It also blocks other architectures and revisions from being reviewed as it fails review in the store. Other architectures do not fail.

```
Found files with executable stack. This adds PROT_EXEC to mmap(2) during mediation which may cause security denials. Either adjust your program to not require an executable stack, strip it with 'execstack --clear-execstack ...' or remove the affected file from your snap. Affected files: usr/lib/arm-linux-gnueabihf/libx264.so.164 functional-snap-v2_execstack 
```
2024-08-02 15:36:32 +01:00
dependabot[bot]
f1b552c2bd
chore(deps): bump actions/upload-artifact from 2 to 4 (#38)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-30 10:37:31 +01:00
Alan Pope
9de404f4b2
feat: Add SBOM generation and vulnerability scanning in workflows (#39)
* feat: generate container sbom during release

* No need to publish separately, it's automatic

* feat: Add regular vulnerability scanning

* syntax

* specify container file

* vital missing step

* Display grype output in the log in table format
2024-07-30 10:37:08 +01:00
github-actions[bot]
84b36880cb flake.lock: Update
Flake lock file updates:

• Updated input 'flake-schemas':
    'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.3/0190b841-54d3-7b7a-8550-24942bc38caf/source.tar.gz?narHash=sha256-c2AZH9cOnSpPXV8Lwy19/I8EgW7G%2BE%2BZh6YQBZZwzxI%3D' (2024-07-15)
  → 'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz?narHash=sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw%3D' (2024-07-26)
• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633334%2Brev-63d37ccd2d178d54e7fb691d7ec76000740ea24a/0190d847-0241-7628-8ab0-d49f442300f4/source.tar.gz?narHash=sha256-7cCC8%2BTdq1%2B3OPyc3%2BgVo9dzUNkNIQfwSDJ2HSi2u3o%3D' (2024-07-21)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633516%2Brev-8c50662509100d53229d4be607f1a3a31157fa12/0190f691-c019-7d99-b723-4b2dd6dfd38f/source.tar.gz?narHash=sha256-2ShmEaFi0kJVOEEu5gmlykN5dwjWYWYUJmlRTvZQRpU%3D' (2024-07-27)
2024-07-29 11:36:42 +01:00
Martin Wimpress
198c807a07
fix: check the version of bash is new enough 2024-07-26 07:31:47 +01:00
Martin Wimpress
5514c3da26 fix: actually wrap the runtime requirement in the nix package 2024-07-26 07:22:29 +01:00
Martin Wimpress
a11f8f57b0 feat: add show_info() 2024-07-25 14:28:03 +01:00
Martin Wimpress
544822aaa9 ci: run test builds if ci jobs change 2024-07-25 14:21:23 +01:00
Martin Wimpress
4495463a74 ci: use the actual stream-sprout version for tagging container releases 2024-07-25 14:21:23 +01:00
Martin Wimpress
46d611bbda docs: update bug report template 2024-07-25 10:36:07 +01:00
Martin Wimpress
3ae05ef362 style: remove quotes from example yaml 2024-07-25 10:36:07 +01:00
Martin Wimpress
e25f977afd docs: add warnings about exposing Stream Sprout on the public internet 2024-07-25 10:04:27 +01:00
Martin Wimpress
c78953780a feat: expand ~ to $HOME in the yaml parser 2024-07-25 09:00:48 +01:00
Martin Wimpress
132b240133 ci: tag containers with alpine so I can namespace future variants 2024-07-25 08:55:16 +01:00
Martin Wimpress
7b548aa7df refactor: give the snap artefact a more descriptive name 2024-07-25 08:25:03 +01:00
Martin Wimpress
6f89206695 fix: get current version from stream-sprout VERSION 2024-07-25 08:25:03 +01:00
Martin Wimpress
86352205af refactor: move test-snap-build alongside the other test build jobs 2024-07-25 08:25:03 +01:00
Martin Wimpress
9830543ac5 docs: minor updates for clarity 2024-07-25 08:25:03 +01:00
Martin Wimpress
f20bde1521 docs: add snap to install instructions 2024-07-25 08:25:03 +01:00
Martin Wimpress
cb86dfc5f4 chore: white space clean up 2024-07-25 08:25:03 +01:00
Martin Wimpress
ec278996b9 refactor: change awk to mawk in snapcraft.yaml; conform to Ubuntu defaults
Ubuntu ships mawk by default, Stage mawk in the snap so the behaviour between the .deb and snap is consistent.
2024-07-25 08:25:03 +01:00
Martin Wimpress
1cc2100527 docs: update versions in README 2024-07-25 08:25:03 +01:00
Martin Wimpress
5801fecb3d fix: remove incorrect query parameters from server URL 2024-07-25 08:25:03 +01:00
Martin Wimpress
1f5231c6e9 fix: get the complete video codec information in stream_details() 2024-07-25 08:25:03 +01:00
Martin Wimpress
cd6a8185c6 fix: avoid injection of substitution commands when parsing yaml 2024-07-25 01:39:10 +01:00
Martin Wimpress
f3b1271813 docs: update documentation to explain the new server config 2024-07-24 23:14:45 +01:00
Martin Wimpress
830edfabab refactor: add get_server_url() to validate server configuration 2024-07-24 23:14:45 +01:00
Martin Wimpress
f0262fbd38 refactor: update example yaml for new server configuration 2024-07-24 23:14:45 +01:00
Alan Pope
0eba5601d7
feat: Add snap support (#23)
* WIP: snapcraft config

* chore: tidy up snap workflow

* fix: update ld_library_path

* fix the version of the snap

This uses a combination of most recent git tag and short rev.

* Add git as a build package

Required because we have a dump plugin and a nill plugin which pull in next to nothing. Making it hard to do a version stamp without the git command
2024-07-24 19:23:58 +01:00
Martin Wimpress
a79438f0d2 fix: do not build STREAM_TEE inside a subshell. fixes #25
Iterate over the variables already exposed by parse_yaml and avoid populating the global variable STREAM_TEE inside a subshell because when a subshell exits the variables are reset.
2024-07-24 18:06:43 +01:00
Martin Wimpress
4928a51bd1 ci: add build/publish container image 2024-07-24 02:03:58 +01:00
Martin Wimpress
dda746f103 ci: add build container image 2024-07-24 02:03:58 +01:00
Martin Wimpress
4e32f890bc docs: update README with details about using the container 2024-07-24 02:03:58 +01:00
Martin Wimpress
f0916a091b feat: add Containerfile 2024-07-24 02:03:58 +01:00
Martin Wimpress
7998737c0e fix: also actually parse the config file 2024-07-24 02:03:58 +01:00
Martin Wimpress
8ca09f94b5
chore: bump version to 0.1.5 2024-07-23 23:20:23 +01:00
Martin Wimpress
6d14c84e72 docs: add details about --config 2024-07-23 23:11:19 +01:00
Martin Wimpress
dbc72f288c feat: add command line option to specify a custom config file
- Close #20
2024-07-23 23:11:19 +01:00
Martin Wimpress
5061ae32be fix: actually parse the configuration file 2024-07-23 23:11:19 +01:00
Martin Wimpress
080092e136
docs: add demo 2024-07-23 19:23:09 +01:00
Martin Wimpress
b2904c7481 feat: show stream audio and video details when a new stream is detected 2024-07-23 18:27:28 +01:00
Martin Wimpress
c4148c12a1 style: brand the log file 2024-07-23 18:27:28 +01:00
Martin Wimpress
cc928c2565 feat: trap more signals 2024-07-23 18:27:28 +01:00
Martin Wimpress
18c13219c0 feat: add a banner 2024-07-23 18:27:28 +01:00
Martin Wimpress
6e5098bf9f chore: update packaging and docs to remove procps 2024-07-23 16:35:45 +01:00
Martin Wimpress
71ed0d7c26 style: tidy up the status messages 2024-07-23 16:35:45 +01:00
Martin Wimpress
63c7aa39d7 fix: prevent server keys concatenating on restarts 2024-07-23 16:35:45 +01:00
Martin Wimpress
66b39fda90 refactor: just check for ffmpeg being available 2024-07-23 16:35:45 +01:00
Martin Wimpress
f81ff23d52 refactor: replace the use of procps utilities with kill 2024-07-23 16:35:45 +01:00
Martin Wimpress
d580129636 fix: don't echo control characters 2024-07-23 16:35:45 +01:00
Martin Wimpress
f8b3c9c794 feat: background ffmpeg and monitor what it is doing 2024-07-23 16:35:45 +01:00
Martin Wimpress
e42d6f9d39
chore: bump version to 0.1.4 2024-07-23 13:03:56 +01:00
Martin Wimpress
c523d9bae1 ci: resolve some dh warnings 2024-07-23 12:32:49 +01:00
Martin Wimpress
8844ba12be ci: drop -dev version suffix 2024-07-23 12:32:49 +01:00
Martin Wimpress
824a7e724b docs: improve yaml examples 2024-07-23 12:32:49 +01:00
Martin Wimpress
5f8f70bced refactor: update packaging to not depend on yq 2024-07-23 12:32:49 +01:00
Martin Wimpress
04db65ce11 docs: remove all references to yq 2024-07-23 12:32:49 +01:00
Martin Wimpress
aae7ce3afd refactor(yaml): drop all use of yq from stream-sprout 2024-07-23 12:32:49 +01:00
Martin Wimpress
db233cde72 feat(yaml): remove end of line comments
https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script#comment119813940_21189044
2024-07-23 12:32:49 +01:00
Martin Wimpress
a3d4e11ee5 feat(yaml): strip leading and trailing whitespace from the vlaue
https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script#comment51310697_21189044
2024-07-23 12:32:49 +01:00
Martin Wimpress
ed4b0ad746 refactor(yaml): simplify how fs is initialised
https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script#comment131463136_21189044
2024-07-23 12:32:49 +01:00
Martin Wimpress
a77c910db2 fix(yaml): make parse_yaml() shellcheck compliant 2024-07-23 12:32:49 +01:00
Martin Wimpress
67925af986 feat(yaml): add pure bash yaml parser
https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script
2024-07-23 12:32:49 +01:00
Martin Wimpress
d1650f6d19
chore: bump version to 0.1.3-dev 2024-07-23 02:05:26 +01:00
Martin Wimpress
7c2373358b
docs: correct yq installation notes for macOS 2024-07-23 02:04:50 +01:00
Martin Wimpress
a1beb178f1
chore: bump version to 0.1.2 2024-07-23 01:52:31 +01:00
Martin Wimpress
11d61aa534 fix: update yq use to be compatible with github.com/kislyuk/yq 2024-07-23 01:51:27 +01:00
Martin Wimpress
dc44f33c81 chore: update debian/copyright with the correct license 2024-07-23 00:43:17 +01:00
Martin Wimpress
2774e28e91 fix: correct URLs to the project 2024-07-23 00:43:17 +01:00
24 changed files with 890 additions and 206 deletions

View file

@ -21,21 +21,25 @@ Steps to reproduce the behavior:
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Stream Sprout output** **Stream Sprout output and logs**
Run `stream-sprout` and include the output of the failure below: Run `stream-sprout` and include the output, along with **redacted logs** (*remove your IP address and keys*), and wrap it in the collapsible markdown section below.
<details> <details>
<summary>Stream Sprout output</summary> <summary>Stream Sprout output</summary>
```text ```text
stream-sprout output here stream-sprout output from the time of the error here
```
<summary>Stream Sprout logs</summary>
```text
stream-sprout logs from the time of the error here
``` ```
</details> </details>
**System information** **System information**
- OS: [e.g. Ubuntu 20.04] Run `stream-sprout --info` and include the output here.
- stream-sprout version: [e.g. 0.1.0]
- FFmpeg version: [e.g. 4.2.4]
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.

BIN
.github/demo.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

@ -13,9 +13,9 @@ jobs:
name: Flake Checker name: Flake Checker
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: DeterminateSystems/nix-installer-action@v13 - uses: DeterminateSystems/nix-installer-action@v21
- uses: DeterminateSystems/magic-nix-cache-action@v7 - uses: DeterminateSystems/magic-nix-cache-action@v13
- uses: DeterminateSystems/flake-checker-action@v8 - uses: DeterminateSystems/flake-checker-action@v12

View file

@ -10,11 +10,11 @@ jobs:
name: Flake Lock Updater name: Flake Lock Updater
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: DeterminateSystems/nix-installer-action@v13 - uses: DeterminateSystems/nix-installer-action@v21
- uses: DeterminateSystems/magic-nix-cache-action@v7 - uses: DeterminateSystems/magic-nix-cache-action@v13
- uses: DeterminateSystems/update-flake-lock@v23 - uses: DeterminateSystems/update-flake-lock@v28
with: with:
pr-title: "chore: update flake.lock" pr-title: "chore: update flake.lock"

View file

@ -15,7 +15,7 @@ jobs:
name: Validate pull request title name: Validate pull request title
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: amannn/action-semantic-pull-request@v5 - uses: amannn/action-semantic-pull-request@v6
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:

View file

@ -10,7 +10,7 @@ jobs:
name: Shellcheck name: Shellcheck
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
with: with:

View file

@ -16,7 +16,7 @@ jobs:
name: "Check versions ⚖️" name: "Check versions ⚖️"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: "Compare App and Git versions 🟰" - name: "Compare App and Git versions 🟰"
@ -37,8 +37,11 @@ jobs:
name: "Build Release 👨‍🔧" name: "Build Release 👨‍🔧"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: "Build .deb 🍥" - name: "Build .deb 🍥"
env:
DEBFULLNAME: "Martin Wimpress"
DEBEMAIL: "code@wimpress.io"
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install debhelper devscripts sudo apt-get -y install debhelper devscripts
@ -66,7 +69,7 @@ jobs:
id-token: "write" id-token: "write"
contents: "read" contents: "read"
steps: steps:
- uses: "actions/checkout@v4" - uses: "actions/checkout@v6"
with: with:
ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
- uses: "DeterminateSystems/nix-installer-action@main" - uses: "DeterminateSystems/nix-installer-action@main"
@ -76,3 +79,45 @@ jobs:
visibility: "public" visibility: "public"
name: "wimpysworld/stream-sprout" name: "wimpysworld/stream-sprout"
tag: "${{ inputs.tag }}" tag: "${{ inputs.tag }}"
publish-container:
needs: [version-check]
name: "Publish Container 🐋"
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Container Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get stream-sprout version 🔢
id: get_version
run: |
STREAM_SPROUT_VER=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)
echo "STREAM_SPROUT_VER=$STREAM_SPROUT_VER" >> $GITHUB_ENV
- name: "Build Container 🐋"
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ env.STREAM_SPROUT_VER }}-alpine
ghcr.io/${{ github.repository }}:${{ github.sha }}-alpine
platforms: linux/amd64, linux/arm64
- name: "Generate SBOM"
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}:latest-alpine
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
- name: Logout from Container Registry
run: docker logout ghcr.io

35
.github/workflows/scan-container.yaml vendored Normal file
View file

@ -0,0 +1,35 @@
name: "Vulnerability 🐞 scan 🔍 container"
on:
schedule:
- cron: "0 10 * * 2"
workflow_dispatch:
jobs:
vulnerability-scan:
name: "Build and scan"
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: build local container
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
tags: localbuild/testimage:latest
push: false
load: true
- name: Scan image
uses: anchore/scan-action@v7
with:
image: "localbuild/testimage:latest"
output-format: table
- name: Inspect action report
run: cat ${{ steps.scan.outputs.table }}

View file

@ -5,18 +5,24 @@ on:
branches: branches:
- main - main
paths: paths:
- .github/workflows/*.yml
- stream-sprout - stream-sprout
- debian/** - debian/**
- flake.nix - flake.nix
- package.nix - package.nix
- Containerfile
- snap/snapcraft.yaml
push: push:
branches: branches:
- main - main
paths: paths:
- .github/workflows/*.yml
- stream-sprout - stream-sprout
- debian/** - debian/**
- flake.nix - flake.nix
- package.nix - package.nix
- Containerfile
- snap/snapcraft.yaml
workflow_dispatch: workflow_dispatch:
# TODO: arm64 runner # TODO: arm64 runner
@ -27,9 +33,11 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: "Checkout 🥡" - name: "Checkout 🥡"
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: "Build & Test .deb 🍥" - name: "Build & Test .deb 🍥"
env: env:
DEBFULLNAME: "Martin Wimpress"
DEBEMAIL: "code@wimpress.io"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
sudo apt-get -y update sudo apt-get -y update
@ -47,13 +55,69 @@ jobs:
contents: "read" contents: "read"
steps: steps:
- name: "Checkout 🥡" - name: "Checkout 🥡"
uses: "actions/checkout@v4" uses: "actions/checkout@v6"
- name: "Install Nix ❄️" - name: "Install Nix ❄️"
uses: "DeterminateSystems/nix-installer-action@v13" uses: "DeterminateSystems/nix-installer-action@v21"
- name: "Enable Magic Nix Cache 🪄" - name: "Enable Magic Nix Cache 🪄"
uses: "DeterminateSystems/magic-nix-cache-action@v7" uses: "DeterminateSystems/magic-nix-cache-action@v13"
- name: "Build & Test .nix ❄️" - name: "Build & Test .nix ❄️"
run: | run: |
nix build .#stream-sprout nix build .#stream-sprout
tree ./result tree ./result
test-container-build:
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Container Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get stream-sprout version 🔢
id: get_version
run: |
STREAM_SPROUT_VER=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)
echo "STREAM_SPROUT_VER=$STREAM_SPROUT_VER" >> $GITHUB_ENV
- name: "Build Container 🐋"
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
push: false
tags: |
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ env.STREAM_SPROUT_VER }}-alpine
ghcr.io/${{ github.repository }}:${{ github.sha }}-alpine
platforms: linux/amd64, linux/arm64
- name: Logout from Container Registry
run: docker logout ghcr.io
test-snap-build:
runs-on: ubuntu-24.04
steps:
- name: Checkout 🥡
uses: actions/checkout@v6
- name: Build snap 🐊
uses: snapcore/action-build@v1
id: snapcraft
- name: Show log 🪵
if: ${{ failure() }}
run: |
cat /home/runner/.local/state/snapcraft/log/snapcraft*.log
- name: Review snap 🕵️
uses: diddlesnaps/snapcraft-review-action@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
isClassic: false
- name: Upload artifacts ⤴️
uses: actions/upload-artifact@v6
with:
name: stream-sprout-snap
path: ${{ steps.snapcraft.outputs.snap}}

105
AGENTS.md Normal file
View file

@ -0,0 +1,105 @@
# AGENTS.md
## Overview
Stream Sprout is a bash-based RTMP restreaming tool that forwards a single video source (from OBS Studio or similar) to multiple destinations like Twitch, YouTube, Owncast, and Peertube simultaneously. It uses FFmpeg's tee muxer to copy streams without transcoding.
## Tech Stack
- **Language:** Bash 5.0+ (single script: `stream-sprout`)
- **Runtime dependency:** FFmpeg (RTMP server and restreaming)
- **Configuration:** YAML parsed via awk/sed
- **Packaging:** Nix flake, Debian .deb, Snap, Docker/Podman
## Build and Run Commands
```bash
# Run directly (requires ffmpeg, bash 5.0+, awk, grep, sed)
./stream-sprout --config stream-sprout.yaml
# Show version and FFmpeg info
./stream-sprout --version
# Show system info (useful for bug reports)
./stream-sprout --info
# Nix build
nix build
# Enter development shell with all dependencies
nix develop
# Docker build and run
docker build -t stream-sprout .
docker run -p 1935:1935 -it -v $PWD:/data stream-sprout --config /data/stream-sprout.yaml
```
## Linting
ShellCheck is enforced via CI on all pull requests.
```bash
# Run locally before committing
shellcheck stream-sprout
```
The script includes `# shellcheck disable=SC2154` for variables set dynamically via `eval` from YAML parsing.
## Code Style
- Bash scripts use `#!/usr/bin/env bash`
- Functions use `function name() {}` syntax
- Use `local` for function-scoped variables
- Use `readonly` for constants
- Validation with informative error messages using Unicode icons and ANSI colours
- Version is tracked in the script: `readonly VERSION="x.y.z"`
## Project Structure
```
stream-sprout # Main bash script (single file)
stream-sprout.yaml # Local config (gitignored)
stream-sprout.yaml.example # Example configuration
package.nix # Nix package definition
devshell.nix # Nix development shell
flake.nix # Nix flake
Dockerfile # Alpine-based container
```
## Configuration
YAML config with two main sections:
- `server:` - RTMP server settings (ip, port, app, key, archive options)
- `services:` - Destination services (each with enabled, rtmp_server, key)
Config search order: `./stream-sprout.yaml`, `$XDG_CONFIG_HOME/stream-sprout.yaml`, `/etc/stream-sprout.yaml`
## PR and Commit Guidelines
- **Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/)**
- PR titles are validated against Conventional Commits format
- Single-commit PRs must have matching PR title and commit message
- ShellCheck must pass with no warnings
Common prefixes: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`
## Version Updates
When changing version:
1. Update `VERSION` in `stream-sprout` script
2. The Nix package extracts version automatically from the script
## Constraints
- Requires bash 5.0 or newer
- FFmpeg must be available on PATH
- RTMP only (no RTMPS support currently)
- FFmpeg does not enforce stream keys (documented security limitation)
## Security Considerations
- Stream keys are stored in plain text in YAML config
- FFmpeg accepts any RTMP stream on the configured port regardless of app/key path
- Do not expose the RTMP port to untrusted networks without additional protection (VPN, firewall, SSH tunnel)

View file

@ -2,9 +2,9 @@
We welcome contributions to Stream Sprout. We welcome contributions to Stream Sprout.
- Help other Quickemu users by answering questions in the [Quickemu Discussions](https://github.com/quickemu-project/quickemu/discussions) 🛟 - Improve the documentation in [this README](https://github.com/wimpysworld/stream-sprout/edit/master/README.md) 📖
- Improve the documentation in [this README](https://github.com/wimpys-world/stream-sprout/edit/master/README.md) 📖 - File bug reports and feature requests in the [Issues](https://github.com/wimpysworld/stream-sprout/issues) 📁
- File bug reports and feature requests in the [Issues](https://github.com/wimpys-world/stream-sprout/issues) 📁 - Submit [Pull requests](https://github.com/wimpysworld/stream-sprout/pulls) to fix bugs 🐞 or add new features ✨
- Submit [Pull requests](https://github.com/wimpys-world/stream-sprout/pulls) to fix bugs 🐞 or add new features ✨
- Commit messages must [conform to the Conventional Commits specification](https://www.conventionalcommits.org/). - Commit messages must [conform to the Conventional Commits specification](https://www.conventionalcommits.org/).
- Tell other people about Stream Sprout 📣
- [Sponsor the project](https://github.com/sponsors/flexiondotorg) 💖 - [Sponsor the project](https://github.com/sponsors/flexiondotorg) 💖

17
Containerfile Normal file
View file

@ -0,0 +1,17 @@
FROM alpine:latest
RUN apk add --no-cache --update \
bash \
jellyfin-ffmpeg \
gawk \
grep \
sed
RUN ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/local/bin/ffmpeg && \
ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/local/bin/ffprobe
COPY --chown=nobody:nobody --chmod=755 stream-sprout /usr/bin/stream-sprout
EXPOSE 1935
USER nobody
ENTRYPOINT [ "stream-sprout" ]

1
Dockerfile Symbolic link
View file

@ -0,0 +1 @@
Containerfile

155
README.md
View file

@ -19,9 +19,13 @@
Stream Sprout 🌱 is a simple, self-contained, and easy-to-use solution for streaming to multiple destinations such as Twitch, YouTube, [Owncast](https://owncast.online/) and [Peertube](https://joinpeertube.org/) 📡 Stream Sprout 🌱 is a simple, self-contained, and easy-to-use solution for streaming to multiple destinations such as Twitch, YouTube, [Owncast](https://owncast.online/) and [Peertube](https://joinpeertube.org/) 📡
It uses [FFmpeg](https://ffmpeg.org/) to receive the video stream from OBS Studio (or anything that can publish a RTMP stream) and then restreams it to multiple destinations; providing similar functionality as services like Restream.io and Livepush.io but without the need to pay 💸 for a third-party service or run something like nginx with the [RTMP module](https://github.com/arut/nginx-rtmp-module). <div align="center">
<img src=".github/demo.gif" alt="Stream Sprout" width="505" height="415"/>
</div>
Stream Sprout is configured with a simple YAML file and designed to be run on the same computer as your [OBS Studio](https://obsproject.com/) instance (it can be run remotely too) and does not require root privileges. It uses [FFmpeg](https://ffmpeg.org/) to receive the video stream from OBS Studio (or any encoder that can produce RTMP) and then restreams it to multiple destinations. This provides similar functionality as services like Restream.io and Livepush.io but without the need to pay 💸 for a third-party service or run something like nginx with the [RTMP module](https://github.com/arut/nginx-rtmp-module).
Stream Sprout is configured with a simple YAML file and designed to be run on the same computer as your [OBS Studio](https://obsproject.com/) instance (it can be run remotely, [**with appropriate security measures**](#-ffmpeg-rtmp-server-accepts-any-rtmp-stream-on-the-listening-port-), and does not require root privileges.
There is no transcoding or processing of the video stream 🎞️ There is no transcoding or processing of the video stream 🎞️
The stream is received and then restreamed to the destinations you configure without modification. The stream is received and then restreamed to the destinations you configure without modification.
@ -30,12 +34,12 @@ Optionally you can also archive the stream to disk 💾
While the restreaming process is lightweight, **your bandwidth requirements will increase with each destination you add.** 📈 While the restreaming process is lightweight, **your bandwidth requirements will increase with each destination you add.** 📈
Ensure you have sufficient bandwidth to support the number of destinations you intend to stream to ⤴️ Ensure you have sufficient bandwidth to support the number of destinations you intend to stream to ⤴️
Stream Sprout is developed on Linux 🐧 and should work on macOS 🍏 or any other platform that supports `bash`, `ffmpeg` and `yq` 👍️ Stream Sprout is developed on Linux 🐧 and should work on macOS 🍏 or any other platform that supports `bash` and `ffmpeg` 👍️
## Get Started ## Get Started
- [Install](#installation) Stream Sprout 🧑‍💻 - [Install](#installation) Stream Sprout 🧑‍💻
- [Configure](#configuration) Stream Sprout 🧑‍💻 - [Configure](#configure-stream-sprout) Stream Sprout 🧑‍💻
- [Configure](#configure-obs-studio) OBS Studio 🎛️ - [Configure](#configure-obs-studio) OBS Studio 🎛️
- Start `stream-sprout` ⌨️ - Start `stream-sprout` ⌨️
- Click the *Start Streaming* button in OBS Studio 🖱️ - Click the *Start Streaming* button in OBS Studio 🖱️
@ -47,17 +51,15 @@ Stream Sprout is developed on Linux 🐧 and should work on macOS 🍏 or any ot
### Debian ### Debian
Stream Sprout depends on `yq` and that is available in bullseye-backports, bookworm, trixie and newer.
- Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️ - Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️
- Install it with `apt-get install ./stream-sprout_0.1.0-1_all.deb`. - Install it with `apt-get install ./stream-sprout_0.1.5-1_all.deb`.
### macOS ### macOS
Install the Stream Sprout requirements using `brew`: Install the Stream Sprout requirements using `brew`:
```shell ```shell
brew install bash ffmpeg procps yq brew install bash ffmpeg
``` ```
Now clone the project: Now clone the project:
@ -76,18 +78,70 @@ See the flake on FlakeHub for more details:
- <https://flakehub.com/flake/wimpysworld/stream-sprout> - <https://flakehub.com/flake/wimpysworld/stream-sprout>
### Ubuntu 24.04 and newer ### Snap
Stream Sprout depends on `yq` and that has been available in Ubuntu since 23.10. [![stream-sprout](https://snapcraft.io/stream-sprout/badge.svg)](https://snapcraft.io/stream-sprout)
For Linux distributions that support snap packages, Stream Sprout is available from the Snap Store 🛍️
```shell
sudo snap install stream-sprout
```
### Ubuntu
- Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️ - Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️
- Install it with `apt-get install ./stream-sprout_0.1.0-1_all.deb`. - Install it with `apt-get install ./stream-sprout_0.1.5-1_all.deb`.
For Ubuntu versions earlier than 24.04 you can [install Stream Sprout from source](#from-source). ### Docker & Podman
#### Pull the container
The Stream Sprout container image is available from the GitHub Container Registry for amd64 and arm64.
To pull the latest container image:
```shell
docker pull ghcr.io/wimpysworld/stream-sprout:latest-alpine
```
Or if you want a specific version:
```shell
docker pull ghcr.io/wimpysworld/stream-sprout:0.1.5-alpine
```
#### Run the container
The `stream-sprout.yaml` configuration file will be on the host computer so you need mount a volume to access it from the container.
If you have already pulled the container image, you can run Stream Sprout with:
```shell
docker run -p 1935:1935 -it -v $PWD:/data stream-sprout --config /data/stream-sprout.yaml
```
If you have not pulled or built the container image, you can run Stream Sprout with:
```shell
docker run -p 1935:1935 -it -v $PWD:/data ghcr.io/wimpysworld/stream-sprout:alpine-latest --config /data/stream-sprout.yaml
```
- The `-p 1935:1935` part will expose the RTMP server port `1935` on the host computer.
- If you have configured Stream Sprout to use a different port, you should change the port number here too.
- The `-it` options will run the container in interactive mode.
- The `-v $PWD:/data` part will mount your current directory `$PWD` as `/data` within the container, allowing you to access your files using the `/data` path.
#### Build the container
Build the Stream Sprout container image:
```shell
docker build -t stream-sprout .
```
### From source ### From source
You need to have [FFmpeg](https://ffmpeg.org/) and [yq](https://github.com/kislyuk/yq) installed on your system. You need to have [FFmpeg](https://ffmpeg.org/) on your system.
```bash ```bash
git clone https://github.com/wimpysworld/stream-sprout.git git clone https://github.com/wimpysworld/stream-sprout.git
@ -98,31 +152,52 @@ cd stream-sprout
Copy the [example Stream Sprout configuration](https://github.com/wimpysworld/stream-sprout/blob/main/stream-sprout.yaml.example) and edit it to suit your needs 📝 Copy the [example Stream Sprout configuration](https://github.com/wimpysworld/stream-sprout/blob/main/stream-sprout.yaml.example) and edit it to suit your needs 📝
Stream Sprout will look for a configuration file in the following locations, in this order: You can specify the configuration file to use with the `--config <path>` option.
If you don't specify a configuration file, Stream Sprout will look for a configuration file in the following locations, in this order:
- Current working directory `./stream-sprout.yaml` - Current working directory `./stream-sprout.yaml`
- XDG configuration directory `$XDG_CONFIG_HOME/stream-sprout.yaml` (*Linux*) or `~/.config/stream-sprout.yaml` (*macOS*) - XDG configuration directory `$XDG_CONFIG_HOME/stream-sprout.yaml` (*Linux*) or `~/.config/stream-sprout.yaml` (*macOS*)
- `/etc/stream-sprout.yaml` - `/etc/stream-sprout.yaml`
### Server ### Server
The `server:` section is used to configure the RTMP server that Stream Sprout creates; it must be an RTMP URL.
The default port for RTMP is `1935`, but you can use any port you like.
If you remotely host Stream Sprout, you should use an IP address in the `url:` that accessible by your computer that runs OBS Studio and also set `key:` to a secure value to prevent unauthorized access.
Running `uuidgen` will generate a suitable value.
If `archive_stream:` is `true` Stream Sprout will archive the stream to disk in the directory specified by `archive_path:`.
If `archive_path:` is not accessible, Stream Sprout will fallback to using the current working directory.
Here's an example configuration for the Stream Sprout `server:` section. Here's an example configuration for the Stream Sprout `server:` section.
```yaml ```yaml
server: server:
url: "rtmp://127.0.0.1:1935" ip: 127.0.0.1
key: "<insert your own key here>" port: 1935
app: sprout
key: create your key with uuidgen here
archive_stream: false archive_stream: false
archive_path: "${HOME}/Streams" archive_path: ~/Streams
``` ```
The `server:` section is used to configure the RTMP server that Stream Sprout creates.
- The default `ip` address is `127.0.0.1`. Use `0.0.0.0` to allow connections to any network interface.
- If you remotely host Stream Sprout, use an IP address that is accessible by your computer that runs OBS Studio.
- The default `port` for RTMP is `1935`, but you can use any port between `1024` and `65535`.
- The default `app` name is `sprout`, but you can use any name you like.
- Set `key:` to a secure value to prevent unauthorized access. Running `uuidgen` will generate a suitable value.
The IP address, port, app name and key are composed to create the RTMP URL that you will use in OBS Studio.
For example, `rtmp://ip:port/app/key`.
### 🚨 FFMPEG WILL ACCEPT ANY RTMP STREAM ON THE CORRECT PORT 🚨
**FFmpeg does not currently enforce `app` or `key` paths for its incoming RTMP server.**
**Regardless of the `app` or `key` you set in the Stream Sprout YAML FFmpeg will accept *any* incoming stream on the correct `port`**
⚠️ Do not expose the Stream Sprout RTMP server to the public internet without additional security measures ⚠️
- Consider using a VPN or SSH tunnel to secure the connection 🔐
- Or firewall the RTMP port to only allow connections from trusted IP addresses 🔥🧱
- See the [Limitations section](#limitations) section below for more information.
#### Archive streams
If `archive_stream:` is `true` Stream Sprout will archive the stream to disk in the directory specified by `archive_path:`.
If `archive_path:` is not accessible, Stream Sprout will fallback to using the current working directory.
### Services ### Services
`services:` are arbitrarily named. `services:` are arbitrarily named.
@ -146,7 +221,7 @@ services:
twitch: twitch:
enabled: true enabled: true
rtmp_server: "rtmp://live.twitch.tv/app/" rtmp_server: "rtmp://live.twitch.tv/app/"
key: "<your_stream_key>" key: "your_twitch_stream_key"
``` ```
#### Ingest servers #### Ingest servers
@ -172,7 +247,7 @@ services:
youtube: youtube:
enabled: true enabled: true
rtmp_server: "rtmp://a.rtmp.youtube.com/live2/" rtmp_server: "rtmp://a.rtmp.youtube.com/live2/"
key: "<your_stream_key>" key: "your_youtube_stream_key"
``` ```
## Configure OBS Studio ## Configure OBS Studio
@ -181,24 +256,32 @@ services:
- Go to `Settings` > `Stream` - Go to `Settings` > `Stream`
- Select `Custom` from the `Service` dropdown - Select `Custom` from the `Service` dropdown
- Copy the server `url:` from your Stream Sprout configuration to the `Server` field: - Copy the server `url:` from your Stream Sprout configuration to the `Server` field:
- `rtmp://127.0.0.1:1935` (*default*) - `rtmp://127.0.0.1:1935/sprout` (*default*)
- Copy the `key:` (if you specified one) from your Stream Sprout configuration to the `Stream Key` field - Copy the `key:` (if you specified one) from your Stream Sprout configuration to the `Stream Key` field
![OBS Studio Stream Settings](.github/obs-settings.png) ![OBS Studio Stream Settings](.github/obs-settings.png)
## Limitations ## Limitations
- Stream Sprout does not support secure RTMP (RTMPS) at this time. - Protecting the Stream Sprout RTMP server with a key does not work
- *At least I don't think it does, but I haven't fully tested it.* - FFmpeg does not currently support enforcing RTMP stream app paths or keys
- https://www.reddit.com/r/ffmpeg/comments/s4keuu/enforce_rtmp_stream_keys_and_strict_paths/
- https://patchwork.ffmpeg.org/project/ffmpeg/patch/20190925185708.70924-1-unique.will.martin@gmail.com/
```
[rtmp @ 0x2ca9be80] Unexpected stream STREAMBOMB, expecting c5b559b2-589d-4925-a28e-20d1954fd6c5
Last message repeated 1 times
```
- Each destination you add will increase your bandwidth requirements. - Each destination you add will increase your bandwidth requirements.
## References ## References
These are some of the references used to create this project: These are some of the references used to create this project:
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites - https://trac.ffmpeg.org/wiki/EncodingForStreamingSites
- https://ffmpeg.org/ffmpeg-protocols.html#rtmp - https://ffmpeg.org/ffmpeg-protocols.html#rtmp
- https://ffmpeg.org/ffmpeg-formats.html#flv - https://ffmpeg.org/ffmpeg-formats.html#flv
- https://ffmpeg.org/ffmpeg-formats.html#tee-1 - https://ffmpeg.org/ffmpeg-formats.html#tee-1
- https://obsproject.com/forum/resources/obs-studio-stream-to-multiple-platforms-or-channels-at-once.932/ - https://obsproject.com/forum/resources/obs-studio-stream-to-multiple-platforms-or-channels-at-once.932/
- https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg - https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
- https://dev.to/ajeetraina/run-ffmpeg-within-a-docker-container-a-step-by-step-guide-c0l
- https://github.com/jrottenberg/ffmpeg

View file

@ -11,7 +11,7 @@ Here are the versions of Stream Sprout currently being supported with security u
## Reporting a Vulnerability ## Reporting a Vulnerability
If you discover a vulnerability then [file an issue](https://github.com/wimpys-world/stream-sprout/issues/new) and click *Report a vulnerability*. If you discover a vulnerability then [file an issue](https://github.com/wimpysworld/stream-sprout/issues/new) and click *Report a vulnerability*.
- Stream Sprout is a spare-time hobby project. - Stream Sprout is a spare-time hobby project.
- We do not have SLAs for responding to security issues. - We do not have SLAs for responding to security issues.

30
debian/control vendored
View file

@ -5,9 +5,9 @@ Maintainer: Martin Wimpress <code@wimpress.io>
Build-Depends: Build-Depends:
debhelper-compat (= 12), debhelper-compat (= 12),
Standards-Version: 4.5.1 Standards-Version: 4.5.1
Homepage: https://github.com/wimpys-world/stream-sprout Homepage: https://github.com/wimpysworld/stream-sprout
Vcs-Browser: https://github.com/wimpys-world/stream-sprout Vcs-Browser: https://github.com/wimpysworld/stream-sprout
Vcs-Git: https://github.com/wimpys-world/stream-sprout.git Vcs-Git: https://github.com/wimpysworld/stream-sprout.git
Rules-Requires-Root: no Rules-Requires-Root: no
Package: stream-sprout Package: stream-sprout
@ -15,11 +15,25 @@ Architecture: all
Depends: Depends:
coreutils, coreutils,
ffmpeg, ffmpeg,
procps, grep,
yq, mawk,
sed,
${misc:Depends}, ${misc:Depends},
${shlibs:Depends}, ${shlibs:Depends},
Description: Restream a video source to multiple destinations such as Twitch, YouTube, and Owncast. Description: Restream a video source to multiple destinations such as Twitch, YouTube, Owncast and Peertube.
Stream Sprout uses FFmpeg to re-stream a video source to multiple destinations Stream Sprout is a simple, self-contained, and easy-to-use solution for
such as Twitch, YouTube, and Owncast. streaming to multiple destinations such as Twitch, YouTube, Owncast and Peertube
. .
It uses FFmpeg to receive the video stream from OBS Studio (or anything that
can publish a RTMP stream) and then restreams it to multiple destinations;
providing similar functionality as services like Restream.io and Livepush.io
but without the need to pay for a third-party service or run something like
nginx with the RTMP module.
.
Stream Sprout is configured with a simple YAML file and designed to be run on
the same computer as your OBS Studio instance (it can be run remotely too) and
does not require root privileges.
.
There is no transcoding or processing of the video stream. The stream is
received and then restreamed to the destinations you configure without
modification. Optionally you can also archive the stream to disk.

57
debian/copyright vendored
View file

@ -1,45 +1,40 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: stream-sprout Upstream-Name: stream-sprout
Upstream-Contact: Martin Wimpress <code@wimpress.io> Upstream-Contact: Martin Wimpress <code@wimpress.io>
Source: https://github.com/wimpys-world/stream-sprout Source: https://github.com/wimpysworld/stream-sprout
Files: * Files: *
Copyright: 2024 Martin Wimpress <code@wimpress.io> Copyright: 2024 Martin Wimpress <code@wimpress.io>
License: APACHE-2.0 License: Apache-2.0
Permission is hereby granted, free of charge, to any person obtaining a copy Licensed under the Apache License, Version 2.0 (the "License");
of this software and associated documentation files (the "Software"), to deal you may not use this file except in compliance with the License.
in the Software without restriction, including without limitation the rights You may obtain a copy of the License at
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
. .
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. http://www.apache.org/licenses/LICENSE-2.0
. .
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR Unless required by applicable law or agreed to in writing, software
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, distributed under the License is distributed on an "AS IS" BASIS,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER See the License for the specific language governing permissions and
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, limitations under the License.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE .
SOFTWARE. On Debian systems, the complete text of the Apache License, Version 2
can be found in "/usr/share/common-licenses/Apache-2.0".
# If you want to use GPL v2 or later for the /debian/* files use
# the following clauses, or change it to suit. Delete these two lines
Files: debian/* Files: debian/*
Copyright: 2024 Martin Wimpress <code@wimpress.io> Copyright: 2024 Martin Wimpress <code@wimpress.io>
License: GPL-2+ License: Apache-2.0
This package is free software; you can redistribute it and/or modify Licensed under the Apache License, Version 2.0 (the "License");
it under the terms of the GNU General Public License as published by you may not use this file except in compliance with the License.
the Free Software Foundation; either version 2 of the License, or You may obtain a copy of the License at
(at your option) any later version.
. .
This package is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
. .
You should have received a copy of the GNU General Public License Unless required by applicable law or agreed to in writing, software
along with this program. If not, see <https://www.gnu.org/licenses/> distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
. .
On Debian systems, the complete text of the GNU General On Debian systems, the complete text of the Apache License, Version 2
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". can be found in "/usr/share/common-licenses/Apache-2.0".

View file

@ -5,9 +5,11 @@
}: }:
mkShell { mkShell {
packages = with pkgs; ([ packages = with pkgs; ([
coreutils-full
ffmpeg-headless ffmpeg-headless
procps gawk
yq gnugrep
gnused
]); ]);
shellHook = '' shellHook = ''

20
flake.lock generated
View file

@ -2,12 +2,12 @@
"nodes": { "nodes": {
"flake-schemas": { "flake-schemas": {
"locked": { "locked": {
"lastModified": 1721078157, "lastModified": 1761577921,
"narHash": "sha256-c2AZH9cOnSpPXV8Lwy19/I8EgW7G+E+Zh6YQBZZwzxI=", "narHash": "sha256-eK3/xbUOrxp9fFlei09XNjqcdiHXxndzrTXp7jFpOk8=",
"rev": "29e53dd33b1a38f235ef073e768c62821cb6146e", "rev": "47849c7625e223d36766968cc6dc23ba0e135922",
"revCount": 66, "revCount": 107,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.3/0190b841-54d3-7b7a-8550-24942bc38caf/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.2.0/019a4a84-544d-7c59-b26d-e334e320c932/source.tar.gz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@ -16,12 +16,12 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1721548954, "lastModified": 1769318308,
"narHash": "sha256-7cCC8+Tdq1+3OPyc3+gVo9dzUNkNIQfwSDJ2HSi2u3o=", "narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=",
"rev": "63d37ccd2d178d54e7fb691d7ec76000740ea24a", "rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c",
"revCount": 633334, "revCount": 906484,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633334%2Brev-63d37ccd2d178d54e7fb691d7ec76000740ea24a/0190d847-0241-7628-8ab0-d49f442300f4/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.906484%2Brev-1cd347bf3355fce6c64ab37d3967b4a2cb4b878c/019bfb68-fb8e-7f55-bb2a-5bee98516c95/source.tar.gz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",

View file

@ -2,15 +2,20 @@
, installShellFiles , installShellFiles
, makeWrapper , makeWrapper
, stdenv , stdenv
, coreutils-full
, ffmpeg-headless , ffmpeg-headless
, gawk
, gnugrep
, gnused
, procps , procps
, yq
}: }:
let let
runtimePaths = [ runtimePaths = [
coreutils-full
ffmpeg-headless ffmpeg-headless
procps gawk
yq gnugrep
gnused
]; ];
versionMatches = versionMatches =
builtins.match '' builtins.match ''
@ -29,12 +34,14 @@ stdenv.mkDerivation rec {
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
install -Dm755 -t "$out/bin" stream-sprout install -Dm755 -t "$out/bin" stream-sprout
wrapProgram $out/bin/stream-sprout \
--prefix PATH : "${lib.makeBinPath runtimePaths}"
runHook postInstall runHook postInstall
''; '';
meta = { meta = {
description = "Re-stream a video source to multiple destinations such as Twitch, YouTube, and Owncast."; description = "Re-stream a video source to multiple destinations such as Twitch, YouTube, and Owncast.";
homepage = "https://github.com/wimpys-world/stream-sprout"; homepage = "https://github.com/wimpysworld/stream-sprout";
mainProgram = "stream-sprout"; mainProgram = "stream-sprout";
license = lib.licenses.asl20; license = lib.licenses.asl20;
maintainers = with lib.maintainers; [ flexiondotorg ]; maintainers = with lib.maintainers; [ flexiondotorg ];

53
snap/snapcraft.yaml Normal file
View file

@ -0,0 +1,53 @@
name: stream-sprout
base: core24
adopt-info: stream-sprout
summary: Restream video to multiple destinations
description: |
Restream a video source to multiple destinations such as Twitch, YouTube,
and Owncast
grade: stable
confinement: strict
platforms:
amd64:
build-on: [ amd64 ]
build-for: [ amd64 ]
arm64:
build-on: [ arm64 ]
build-for: [arm64 ]
parts:
stream-sprout:
after: [ deps ]
plugin: dump
source: .
build-packages:
- git
override-pull: |
craftctl default
craftctl set version=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)-$(git rev-parse --short HEAD)
prime:
- stream-sprout
- stream-sprout.yaml.example
- LICENSE
- SECURITY.md
deps:
plugin: nil
stage-packages:
- ffmpeg
- sed
- mawk
- grep
apps:
stream-sprout:
command: stream-sprout
environment:
LD_LIBRARY_PATH: $SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/pulseaudio:$SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/lapack
plugs:
- home
- removable-media
- network-bind
- network

View file

@ -1,51 +1,144 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# shellcheck disable=SC2154
# Disable echo of control characters like ^C
stty -echoctl
readonly STREAM_SPROUT_YAML="stream-sprout.yaml" readonly STREAM_SPROUT_YAML="stream-sprout.yaml"
readonly VERSION="0.1.1" readonly VERSION="0.1.6"
function ctrl_c() { function cleanup() {
echo " - Trapped: CTRL-C" echo -e " \e[31m\U26D4\e[0m Control-C"
pkill ffmpeg sleep 0.25
if kill -0 "${FFMPEG_PID}" 2>/dev/null; then
echo -e " \e[31m\U1F480\e[0m FFmpeg process (${FFMPEG_PID}) has been terminated"
kill "${FFMPEG_PID}"
else
echo -e " \e[31m\U23F9\e[0m FFmpeg process (${FFMPEG_PID}) has ended"
fi
rename_archive rename_archive
exit exit
} }
function get_archive_path() { # Function to display help
local ARCHIVE_PATH="" function show_help() {
ARCHIVE_PATH=$(yq e ".server.archive_path" "${STREAM_SPROUT_CONFIG}") echo "Restream a video source to multiple destinations such as Twitch, YouTube, Owncast and Peertube."
# Expand any environment variables in the path echo ""
ARCHIVE_PATH=$(eval echo "${ARCHIVE_PATH}") echo "Usage: $(basename "${0}") [options]"
if [ -z "${ARCHIVE_PATH}" ]; then echo ""
echo "./" echo "Options:"
echo " --config <path> Specify a custom config file path."
echo " --info Show system information; useful when filing bug reports."
echo " --version Show version information."
echo " --help Display this help message."
}
function show_info() {
local CONTAINER_ENV
local CONTAINER_RUNTIME
local CONTAINER_RUNTIMES=("docker" "lxc" "podman")
local OS_KERNEL
local PRETTY_NAME
OS_KERNEL=$(uname -s)
if [ "${OS_KERNEL}" == "Darwin" ]; then
# Get macOS product name and version using swvers
if [ -x "$(command -v sw_vers)" ]; then
PRETTY_NAME="$(sw_vers -productName) $(sw_vers -productVersion)"
else
PRETTY_NAME="macOS"
fi
elif [ -e /etc/os-release ]; then
PRETTY_NAME=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
else else
mkdir -p "${ARCHIVE_PATH}" 2> /dev/null PRETTY_NAME="Unknown OS"
echo "${ARCHIVE_PATH}"
fi fi
echo -e "Operating System : ${PRETTY_NAME}"
# Check for container environment
if [ "${OS_KERNEL}" == "Linux" ]; then
if [ -n "${SNAP}" ]; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="snapd"
else
for runtime in "${CONTAINER_RUNTIMES[@]}"; do
if grep -qa ":/${runtime}/" /proc/1/cgroup; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="${runtime}"
break
else
CONTAINER_ENV="No"
CONTAINER_RUNTIME="Unknown"
fi
done
fi
echo -e "Containerized : ${CONTAINER_ENV}"
if [ "${CONTAINER_ENV,,}" == "yes" ]; then
echo -e "Container Runtime: ${CONTAINER_RUNTIME}"
fi
fi
echo -e "Stream Sprout : ${VERSION}"
echo -e "awk : $(awk --version | head -n 1)"
echo -e "bash : $(bash --version | head -n 1)"
echo -e "ffmpeg : $(ffmpeg -version | head -n 1)"
}
function show_version() {
echo -e "\e[92mStream Sprout\e[0m ${VERSION} using FFmpeg ${FFMPEG_VER}"
}
# https://stackoverflow.com/questions/5014632/how-can-i-parse-a-yaml-file-from-a-linux-shell-script
function parse_yaml() {
local prefix="${2}"
local s=""
local w=""
local fs=""
s='[[:space:]]*'
w='[a-zA-Z0-9_]*'
fs=$'\034'
sed -ne "s|^\(${s}\):|\1|" \
-e 's|`||g;s|\$||g;' \
-e "s|~|${HOME}|g;" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}[\"']\(.*\)[\"']$s\$|\1${fs}\2${fs}\3|p" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "${1}" |
awk -F"${fs}" '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
gsub(/^[ \t]+/, "", $3); gsub(/[ \t]+$/, "", $3);
gsub(/\s*#.*$/, "", $3);
printf("%s%s%s=\"%s\"\n", "'"${prefix}"'",vn, $2, $3);
}
}'
} }
function rename_archive() { function rename_archive() {
local STAMP="" local STAMP=""
# If there is a stream file, then rename it to the current date and time # If there is a stream file, then rename it to the current date and time
if [ -e "${ARCHIVE_PATH}/${ARCHIVE_TEMP}" ]; then if [ -e "${sprout_server_archive_path}/${sprout_server_archive_temp}" ]; then
STAMP=$(date +%Y%m%d_%H%M%S) STAMP=$(date +%Y%m%d_%H%M%S)
echo " - Rename: ${ARCHIVE_PATH}/${ARCHIVE_TEMP} to ${ARCHIVE_PATH}/stream-sprout-${STAMP}.mkv" echo -e " \U1F500 ${sprout_server_archive_path}/${sprout_server_archive_temp}"
mv "${ARCHIVE_PATH}/${ARCHIVE_TEMP}" "${ARCHIVE_PATH}/stream-sprout-${STAMP}.mkv" echo -e " \U21AA ${sprout_server_archive_path}/stream-sprout-${STAMP}.mkv"
mv "${sprout_server_archive_path}/${sprout_server_archive_temp}" "${sprout_server_archive_path}/stream-sprout-${STAMP}.mkv"
fi fi
} }
function add_archive() { function add_archive() {
local ARCHIVE_ENABLED="" sprout_server_archive_temp="stream-temp-$(date +%s%N).mkv"
ARCHIVE_PATH="$(get_archive_path)"
ARCHIVE_TEMP="stream-temp-$(date +%s%N).mkv"
# Check if recording is enabled in the YAML configuration # Check if recording is enabled in the YAML configuration
ARCHIVE_ENABLED=$(yq e ".server.archive_stream" "${STREAM_SPROUT_CONFIG}") if [[ "${sprout_server_archive_stream,,}" == "true" || "${sprout_server_archive_stream}" == "1" ]]; then
if [[ "${ARCHIVE_ENABLED,,}" == "true" || "${ARCHIVE_ENABLED}" == "1" ]]; then if [ -z "${sprout_server_archive_path}" ]; then
echo " - Archive: ${ARCHIVE_PATH}/${ARCHIVE_TEMP}" sprout_server_archive_path="$(dirname "${PWD}")"
else
mkdir -p "${sprout_server_archive_path}" 2>/dev/null
fi
echo -e " \e[34m\U1F4BE\e[0m ${sprout_server_archive_path}/${sprout_server_archive_temp}"
if [ -n "${STREAM_TEE}" ]; then if [ -n "${STREAM_TEE}" ]; then
STREAM_TEE+="|" STREAM_TEE+="|"
fi fi
STREAM_TEE+="[f=matroska]${ARCHIVE_PATH}/${ARCHIVE_TEMP}" STREAM_TEE+="[f=matroska]${sprout_server_archive_path}/${sprout_server_archive_temp}"
fi fi
} }
@ -62,88 +155,252 @@ function add_service() {
function get_stream_tee() { function get_stream_tee() {
local SERVICE_ENABLED="" local SERVICE_ENABLED=""
local SERVICES="" local SERVICE_KEY=""
local SERVICE_NAME=""
local SERVICE_RTMP=""
local URI="" local URI=""
# Extract services from the YAML STREAM_TEE=""
SERVICES=$(yq e '.services | keys | .[]' "${STREAM_SPROUT_CONFIG}") # Iterate over all the sprout_services variables
for var in "${!sprout_services@}"; do
# Iterate over each service # Check the variable matches the pattern: sprout_services_*_enabled
for SERVICE in ${SERVICES}; do if [[ "${var}" =~ ^sprout_services_.*_enabled$ ]]; then
# Check if the service is enabled in the YAML configuration # Derive the service name
SERVICE_ENABLED=$(yq e ".services.${SERVICE}.enabled" "${STREAM_SPROUT_CONFIG}") # - First remove `sprout_services_` prefix from the beginning of the value stored in the variable $var.
if [[ "${SERVICE_ENABLED,,}" == "true" || "${SERVICE_ENABLED}" == "1" ]]; then # - Next remove the suffix `_enabled` from the end of the SERVICE_NAME variable's value.
echo " - Service: ${SERVICE}" SERVICE_NAME="${var#sprout_services_}"
URI=$(yq e ".services.${SERVICE}.rtmp_server" "${STREAM_SPROUT_CONFIG}") SERVICE_NAME="${SERVICE_NAME%_enabled}"
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then # Get the value of the variable $var
echo " - Invalid URL: ${SERVICE} is not a valid RTMP URL." SERVICE_ENABLED="${!var}"
return if [[ "${SERVICE_ENABLED,,}" == "true" || "${SERVICE_ENABLED}" == "1" ]]; then
echo -e " \e[35m\U1F4E1\e[0m ${SERVICE_NAME}"
# TODO: This assumes that the RTMP URL and key are set in the YAML file.
# Construct the variable name
SERVICE_RTMP="sprout_services_${SERVICE_NAME}_rtmp_server"
SERVICE_KEY="sprout_services_${SERVICE_NAME}_key"
# Use indirect expansion to get the value
# By concatenating these two indirectly referenced values, URI
# is set to the full URI needed for streaming. For instance, if
# SERVICE_RTMP points to a variable holding rtmp://example.com/live
# and SERVICE_KEY points to a variable holding abcd1234, then URI
# would be set to rtmp://example.com/live/abcd1234.
URI="${!SERVICE_RTMP}/${!SERVICE_KEY}"
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then
echo -e " \e[31m\U1F6AB\e[0m ${SERVICE_NAME} is not a valid RTMP service URL"
continue
fi
add_service "${URI}"
fi fi
URI+=$(yq e ".services.${SERVICE}.key" "${STREAM_SPROUT_CONFIG}")
add_service "${URI}"
fi fi
done done
add_archive add_archive
} }
# Check that ffmpeg and yq are available on the PATH function get_server_url() {
for CMD in ffmpeg pkill yq; do local asterisks=""
if ! command -v "${CMD}" &> /dev/null; then local key_length=0
echo "ERROR! ${CMD} is not installed. Exiting." # Check if the sprout_server_url is set and display a deprecation notice if it is
if [ -n "${sprout_server_url}" ]; then
echo -e " \e[31m\U1F6AB\e[0m server:"
echo -e " ╰─url: in the YAML is deprecated. Please configure ip: and port: instead."
exit 1 exit 1
fi fi
# Validate the sprout_server_ip is valid
if [[ ! "${sprout_server_ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─ip: in the YAML is not valid. Falling back to '127.0.0.1'."
sprout_server_ip="127.0.0.1"
fi
# Validate the sprout_server_port is valid
if [[ ! "${sprout_server_port}" =~ ^[0-9]+$ ]] || [[ "${sprout_server_port}" -lt 1024 ]] || [[ "${sprout_server_port}" -gt 65535 ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─port: in the YAML is not valid. Must be between 1024 and 65535. Falling back to '1935'."
sprout_server_port="1935"
fi
# Check that sprout_server_app is not empty
if [ -z "${sprout_server_app}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─app: is not configured in the YAML. Falling back to 'sprout'."
sprout_server_app="sprout"
fi
# Check that sprout_server_key is not empty
if [ -z "${sprout_server_key}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─key: is not configured in the YAML. \e[1;97mYour Stream Sprout server is unprotected.\e[0m"
fi
sprout_server_url="rtmp://${sprout_server_ip}:${sprout_server_port}/${sprout_server_app}"
if [ -n "${sprout_server_key}" ]; then
# Calculate the length of sprout_server_key
key_length=${#sprout_server_key}
# Create a string of asterisks equal to the length of sprout_server_key
asterisks=$(printf "%*s" "${key_length}" "" | tr ' ' '*')
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}/${asterisks}"
# Append the sprout_server_key to the sprout_server_url
sprout_server_url+="/${sprout_server_key}"
else
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}"
fi
}
function stream_details() {
local AUDIO=""
local VIDEO=""
local AUDIO_CODEC=""
local AUDIO_BITRATE=""
local AUDIO_FREQ=""
local AUDIO_CHANNELS=""
local VIDEO_CODEC=""
local VIDEO_FPS=""
local VIDEO_RES=""
local VIDEO_BITRATE=""
AUDIO="$(grep "Audio:" "${FFMPEG_LOG}" | head -n 1)"
VIDEO="$(grep "Video:" "${FFMPEG_LOG}" | head -n 1)"
# Correcting the parsing to accurately extract the required information
AUDIO_CODEC=$(echo "${AUDIO}" | awk -F', ' '{print $1}' | awk '{print $4 " " $5}')
AUDIO_FREQ=$(echo "${AUDIO}" | awk -F', ' '{print $2}' | awk '{print $1 " " $2}')
AUDIO_CHANNELS=$(echo "${AUDIO}" | awk -F', ' '{print $3}' | awk '{print $1}')
AUDIO_BITRATE=$(echo "${AUDIO}" | awk -F', ' '{print $5}' | awk '{print $1 " " $2}')
VIDEO_CODEC=$(echo "${VIDEO}" | awk -F', ' '{print $1}' | awk '{print $4 " " $5}')
VIDEO_FPS=$(echo "${VIDEO}" | awk -F', ' '{print $7}' | awk '{print $1 " " $2}')
VIDEO_RES=$(echo "${VIDEO}" | awk -F', ' '{print $5}' | awk '{print $1}')
VIDEO_BITRATE=$(echo "${VIDEO}" | awk -F', ' '{print $6}' | awk '{print $1 " " $2}')
echo -e " \e[32m\U1F441\e[0m FFmpeg detected a new stream"
echo -e " ├─ Audio: ${AUDIO_FREQ} ${AUDIO_CODEC} in ${AUDIO_CHANNELS^} ~${AUDIO_BITRATE}"
echo -e " ╰─ Video: ${VIDEO_RES} ${VIDEO_CODEC} at ${VIDEO_FPS} ~${VIDEO_BITRATE}"
}
function banner() {
echo -e $'\E[38;2;254;75;55m \E[39m\E[38;2;254;64;66m_\E[39m\E[38;2;254;54;77m_\E[39m\E[38;2;252;44;89m_\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m_\E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m \E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m \E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m \E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m \E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m_\E[39m\E[38;2;2;198;184m_\E[39m\E[38;2;3;208;172m_\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m \E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m \E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m \E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m \E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m \E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m \E[39m\E[38;2;199;183;2m_\E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m\E[39m'
echo -e $'\E[38;2;254;64;66m|\E[39m\E[38;2;254;54;77m \E[39m\E[38;2;252;44;89m \E[39m\E[38;2;249;35;101m \E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m_\E[39m\E[38;2;232;14;138m|\E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m|\E[39m\E[38;2;206;3;175m_\E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m_\E[39m\E[38;2;173;3;208m_\E[39m\E[38;2;161;6;217m_\E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m_\E[39m\E[38;2;124;21;240m_\E[39m\E[38;2;111;28;245m_\E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m_\E[39m\E[38;2;75;56;254m_\E[39m\E[38;2;64;66;254m_\E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m_\E[39m\E[38;2;34;102;248m_\E[39m\E[38;2;26;115;244m_\E[39m\E[38;2;19;127;238m_\E[39m\E[38;2;13;139;231m_\E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m|\E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m \E[39m\E[38;2;6;218;160m \E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m_\E[39m\E[38;2;21;240;123m|\E[39m\E[38;2;29;245;110m_\E[39m\E[38;2;37;250;98m_\E[39m\E[38;2;46;252;86m_\E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m_\E[39m\E[38;2;78;254;52m_\E[39m\E[38;2;90;251;43m_\E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m_\E[39m\E[38;2;128;238;19m_\E[39m\E[38;2;140;231;13m_\E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m_\E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m_\E[39m\E[38;2;199;183;2m|\E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m|\E[39m\E[38;2;227;147;10m_\E[39m\E[38;2;234;134;15m \E[39m\E[38;2;240;122;22m\E[39m'
echo -e $'\E[38;2;254;54;77m|\E[39m\E[38;2;252;44;89m_\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m \E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m \E[39m\E[38;2;224;9;151m|\E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m_\E[39m\E[38;2;184;2;198m|\E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m_\E[39m\E[38;2;136;15;233m|\E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m-\E[39m\E[38;2;99;36;249m_\E[39m\E[38;2;87;46;252m|\E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m.\E[39m\E[38;2;53;78;254m\'\E[39m\E[38;2;43;90;252m|\E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m|\E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m|\E[39m\E[38;2;3;208;172m_\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m \E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m|\E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m.\E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m|\E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m_\E[39m\E[38;2;115;244;26m|\E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m.\E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m|\E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m|\E[39m\E[38;2;199;183;2m \E[39m\E[38;2;209;171;3m|\E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m_\E[39m\E[38;2;240;122;22m|\E[39m\E[38;2;246;110;29m\E[39m'
echo -e $'\E[38;2;252;44;89m|\E[39m\E[38;2;249;35;101m_\E[39m\E[38;2;244;27;114m_\E[39m\E[38;2;238;20;126m_\E[39m\E[38;2;232;14;138m_\E[39m\E[38;2;224;9;151m_\E[39m\E[38;2;215;5;163m|\E[39m\E[38;2;206;3;175m_\E[39m\E[38;2;195;2;187m|\E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m|\E[39m\E[38;2;161;6;217m_\E[39m\E[38;2;148;10;226m|\E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m|\E[39m\E[38;2;111;28;245m_\E[39m\E[38;2;99;36;249m_\E[39m\E[38;2;87;46;252m_\E[39m\E[38;2;75;56;254m|\E[39m\E[38;2;64;66;254m_\E[39m\E[38;2;53;78;254m_\E[39m\E[38;2;43;90;252m,\E[39m\E[38;2;34;102;248m|\E[39m\E[38;2;26;115;244m_\E[39m\E[38;2;19;127;238m|\E[39m\E[38;2;13;139;231m_\E[39m\E[38;2;9;151;224m|\E[39m\E[38;2;5;164;215m_\E[39m\E[38;2;3;176;205m|\E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m|\E[39m\E[38;2;6;218;160m_\E[39m\E[38;2;10;226;147m_\E[39m\E[38;2;15;234;135m_\E[39m\E[38;2;21;240;123m_\E[39m\E[38;2;29;245;110m_\E[39m\E[38;2;37;250;98m|\E[39m\E[38;2;46;252;86m \E[39m\E[38;2;56;254;74m \E[39m\E[38;2;67;254;63m_\E[39m\E[38;2;78;254;52m|\E[39m\E[38;2;90;251;43m_\E[39m\E[38;2;103;248;34m|\E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m|\E[39m\E[38;2;140;231;13m_\E[39m\E[38;2;152;223;8m_\E[39m\E[38;2;164;214;5m_\E[39m\E[38;2;176;205;3m|\E[39m\E[38;2;188;194;2m_\E[39m\E[38;2;199;183;2m_\E[39m\E[38;2;209;171;3m_\E[39m\E[38;2;218;159;6m|\E[39m\E[38;2;227;147;10m_\E[39m\E[38;2;234;134;15m|\E[39m\E[38;2;240;122;22m \E[39m\E[38;2;246;110;29m \E[39m\E[38;2;250;97;37m\E[39m'
echo -e $'\E[38;2;249;35;101m \E[39m\E[38;2;244;27;114m \E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m \E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m \E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m \E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m \E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m \E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m \E[39m\E[38;2;6;218;160m \E[39m\E[38;2;10;226;147m \E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m \E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m|\E[39m\E[38;2;56;254;74m_\E[39m\E[38;2;67;254;63m|\E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m \E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m \E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m \E[39m\E[38;2;199;183;2m \E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m \E[39m\E[38;2;240;122;22m \E[39m\E[38;2;246;110;29m \E[39m\E[38;2;250;97;37m \E[39m\E[38;2;253;85;47m\E[39m'
}
if ((BASH_VERSINFO[0] < 5)); then
echo -e " \e[31m\U1F6AB\e[0m bash 5.0 or newer is required to run this script. You have ${BASH_VERSION}"
exit 1
fi
# Check that ffmpeg are available on the PATH
if ! command -v ffmpeg &> /dev/null; then
echo -e " \e[31m\U1F6AB\e[0m ffmpeg is not installed. Exiting."
exit 1
fi
FFMPEG_VER="$(ffmpeg -version | head -n 1 | cut -d' ' -f3)"
# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
case "${1}" in
--config)
STREAM_SPROUT_CONFIG="${2}"
shift
if [ ! -f "${STREAM_SPROUT_CONFIG}" ]; then
echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_CONFIG} was not found. Exiting."
exit 1
fi;;
--info)
show_info
exit 0;;
--version)
show_version
exit 0;;
--help)
show_help
exit 0;;
*)
echo "Unknown option: ${1}"
show_help
exit 1;;
esac
shift
done done
# Check in the current working directory # Check if a custom config path was not provided
if [ -f "./${STREAM_SPROUT_YAML}" ]; then if [ -z "${STREAM_SPROUT_CONFIG}" ]; then
STREAM_SPROUT_CONFIG="./${STREAM_SPROUT_YAML}" # Check in the current working directory
# Check in the user's home directory, considering XDG on Linux and compatibility with macOS if [ -f "./${STREAM_SPROUT_YAML}" ]; then
elif [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" ]; then STREAM_SPROUT_CONFIG="./${STREAM_SPROUT_YAML}"
STREAM_SPROUT_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" # Check in the user's home directory, considering XDG on Linux and compatibility with macOS
# Check in /etc elif [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" ]; then
elif [ -f "/etc/${STREAM_SPROUT_YAML}" ]; then STREAM_SPROUT_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}"
STREAM_SPROUT_CONFIG="/etc/${STREAM_SPROUT_YAML}" # Check in /etc
else elif [ -f "/etc/${STREAM_SPROUT_YAML}" ]; then
echo "ERROR: ${STREAM_SPROUT_YAML} was not found." STREAM_SPROUT_CONFIG="/etc/${STREAM_SPROUT_YAML}"
exit 1 else
fi echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_YAML} was not found. Exiting."
# Check if the file is valid YAML
if ! yq eval '.' "${STREAM_SPROUT_CONFIG}" &>/dev/null; then
echo "ERROR: ${STREAM_SPROUT_CONFIG} is not valid YAML."
exit 1
fi
# trap ctrl-c and call ctrl_c() to clean up
trap ctrl_c INT
while true; do
echo "Stream Sprout v${VERSION} using ${STREAM_SPROUT_CONFIG}"
SERVER_URL=$(yq e ".server.url" "${STREAM_SPROUT_CONFIG}")
SERVER_KEY=$(yq e ".server.key" "${STREAM_SPROUT_CONFIG}")
if [[ ! "${SERVER_URL}" =~ ^rtmp://.* ]]; then
echo " - Invalid URL: ${SERVER_URL} is not a valid RTMP URL."
exit 1 exit 1
fi fi
echo -n " - Server: ${SERVER_URL}" fi
if [ "${SERVER_KEY}" != "null" ]; then
SERVER_URL+="/${SERVER_KEY}" banner
echo " (key required)"
else # trap relevant signals and call cleanup()
echo "" trap cleanup INT QUIT TERM
fi
STREAM_TEE="" while true; do
eval "$(parse_yaml "${STREAM_SPROUT_CONFIG}" sprout_)"
show_version
echo -e " \U2699 ${STREAM_SPROUT_CONFIG}"
get_server_url
get_stream_tee get_stream_tee
FFMPEG_LOG=$(mktemp /tmp/stream-sprout.XXXXXX.log)
ffmpeg \ ffmpeg \
-hide_banner \ -hide_banner \
-flags +global_header \ -flags +global_header \
-fflags nobuffer \ -fflags nobuffer \
-listen 1 -i "${SERVER_URL}?rtmp_buffer=0&rtmp_live=live" \ -listen 1 -i "${sprout_server_url}" \
-flvflags no_duration_filesize \ -flvflags no_duration_filesize \
-c:v copy -c:a copy -map 0 \ -c:v copy -c:a copy -map 0 \
-movflags +faststart \ -movflags +faststart \
-f tee -use_fifo 1 "${STREAM_TEE}" 2>/dev/null -f tee -use_fifo 1 "${STREAM_TEE}" >"${FFMPEG_LOG}" 2>&1 &
echo " - Server: Stopping..."
# Capture the PID of the ffmpeg process
FFMPEG_PID=$!
echo -e " \U2B07 FFmpeg process (${FFMPEG_PID}) logging to ${FFMPEG_LOG}"
COUNTER=0
# 0 for standing-by
# 1 for streaming
STREAMING_STATUS=0
# Monitor the FFmpeg process
while sleep 1; do
STAMP="[$(date +%H:%M:%S)]"
if ! kill -0 "${FFMPEG_PID}" 2>/dev/null; then
echo -e " \e[31m\U23F9\e[0m FFmpeg has stopped"
break
else
if grep "Input #0, flv, from 'rtmp://" "${FFMPEG_LOG}" > /dev/null; then
NEW_STATUS=1
else
NEW_STATUS=0
fi
# Check if status changed or if it's time to log the status again
if [ ${NEW_STATUS} -ne ${STREAMING_STATUS} ] || (( COUNTER % 30 == 0 )); then
# If the status has changed, then show the details
if [ ${NEW_STATUS} -ne ${STREAMING_STATUS} ]; then
stream_details
fi
if [ ${NEW_STATUS} -eq 1 ]; then
echo -e " \e[32m\U25B6\e[0m FFmpeg is streaming ${STAMP}"
else
echo -e " \e[33m\U23F8\e[0m FFmpeg is standing-by ${STAMP}"
fi
# Update the current status
STREAMING_STATUS=${NEW_STATUS}
fi
((COUNTER++))
fi
done
rename_archive rename_archive
echo echo
unset sprout_server_url
done done

View file

@ -1,19 +1,21 @@
server: server:
url: "rtmp://127.0.0.1:1935" ip: 127.0.0.1
key: "<insert your own key here>" port: 1935
app: sprout
key: create your key with uuidgen here
archive_stream: false archive_stream: false
archive_path: "${HOME}/Streams" archive_path: ~/Streams
services: services:
trovo: trovo:
enabled: false enabled: false
rtmp_server: "rtmp://livepush.trovo.live/live/" rtmp_server: rtmp://livepush.trovo.live/live/
key: "<your_stream_key>" key: your_trovo_stream_key
twitch: twitch:
enabled: true enabled: true
rtmp_server: "rtmp://live.twitch.tv/app/" rtmp_server: rtmp://live.twitch.tv/app/
key: "<your_stream_key>" key: your_twitch_stream_key
youtube: youtube:
enabled: true enabled: true
rtmp_server: "rtmp://a.rtmp.youtube.com/live2/" rtmp_server: rtmp://a.rtmp.youtube.com/live2/
key: "<your_stream_key>" key: your_youtube_stream_key