Compare commits

...

111 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
Martin Wimpress
906f57b1c0 docs: improve README.md 2024-07-22 23:43:12 +01:00
dependabot[bot]
e09446bb19 chore(deps): bump DeterminateSystems/nix-installer-action from 12 to 13
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 12 to 13.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v12...v13)

---
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-07-22 23:00:29 +01:00
Martin Wimpress
979b5b967e
docs: add a chart emoji to README 2024-07-22 17:19:08 +01:00
Martin Wimpress
fc412d22d8 feat: add support for protecting the stream sprout server with a key
- Close #8
2024-07-22 17:14:16 +01:00
Martin Wimpress
a57ee9d0b4
chore: bump version to 0.1.1 2024-07-22 16:29:46 +01:00
Martin Wimpress
61a38c579d docs: update README 2024-07-22 16:24:12 +01:00
24 changed files with 959 additions and 207 deletions

View file

@ -21,21 +21,25 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Stream Sprout output**
Run `stream-sprout` and include the output of the failure below:
**Stream Sprout output and logs**
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>
<summary>Stream Sprout output</summary>
```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>
**System information**
- OS: [e.g. Ubuntu 20.04]
- stream-sprout version: [e.g. 0.1.0]
- FFmpeg version: [e.g. 4.2.4]
Run `stream-sprout --info` and include the output here.
**Screenshots**
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

BIN
.github/obs-settings.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

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

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@ jobs:
name: "Check versions ⚖️"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Compare App and Git versions 🟰"
@ -37,8 +37,11 @@ jobs:
name: "Build Release 👨‍🔧"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: "Build .deb 🍥"
env:
DEBFULLNAME: "Martin Wimpress"
DEBEMAIL: "code@wimpress.io"
run: |
sudo apt-get -y update
sudo apt-get -y install debhelper devscripts
@ -66,7 +69,7 @@ jobs:
id-token: "write"
contents: "read"
steps:
- uses: "actions/checkout@v4"
- uses: "actions/checkout@v6"
with:
ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
- uses: "DeterminateSystems/nix-installer-action@main"
@ -76,3 +79,45 @@ jobs:
visibility: "public"
name: "wimpysworld/stream-sprout"
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:
- main
paths:
- .github/workflows/*.yml
- stream-sprout
- debian/**
- flake.nix
- package.nix
- Containerfile
- snap/snapcraft.yaml
push:
branches:
- main
paths:
- .github/workflows/*.yml
- stream-sprout
- debian/**
- flake.nix
- package.nix
- Containerfile
- snap/snapcraft.yaml
workflow_dispatch:
# TODO: arm64 runner
@ -27,9 +33,11 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: "Build & Test .deb 🍥"
env:
DEBFULLNAME: "Martin Wimpress"
DEBEMAIL: "code@wimpress.io"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sudo apt-get -y update
@ -47,13 +55,69 @@ jobs:
contents: "read"
steps:
- name: "Checkout 🥡"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
- name: "Install Nix ❄️"
uses: "DeterminateSystems/nix-installer-action@v12"
uses: "DeterminateSystems/nix-installer-action@v21"
- name: "Enable Magic Nix Cache 🪄"
uses: "DeterminateSystems/magic-nix-cache-action@v7"
uses: "DeterminateSystems/magic-nix-cache-action@v13"
- name: "Build & Test .nix ❄️"
run: |
nix build .#stream-sprout
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.
- 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/wimpys-world/stream-sprout/edit/master/README.md) 📖
- File bug reports and feature requests in the [Issues](https://github.com/wimpys-world/stream-sprout/issues) 📁
- Submit [Pull requests](https://github.com/wimpys-world/stream-sprout/pulls) to fix bugs 🐞 or add new features ✨
- Improve the documentation in [this README](https://github.com/wimpysworld/stream-sprout/edit/master/README.md) 📖
- File bug reports and feature requests in the [Issues](https://github.com/wimpysworld/stream-sprout/issues) 📁
- Submit [Pull requests](https://github.com/wimpysworld/stream-sprout/pulls) to fix bugs 🐞 or add new features ✨
- 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) 💖

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

235
README.md
View file

@ -3,7 +3,7 @@
# Stream Sprout
**Restream a video source to multiple destinations such as Twitch, YouTube, and Owncast.**
**Restream a video source to multiple destinations such as Twitch, YouTube, Owncast and Peertube**
**Made with 💝 for <img src=".github/tux.png" align="top" width="24" alt="Tux (Linux)"/> & <img src=".github/apple.png" align="top" width="24" alt="Apple (macOS)"/>**
</div>
@ -17,45 +17,131 @@
# Introduction
Stream Sprout uses FFmpeg to re-stream a video source to multiple destinations such as Twitch, YouTube, and Owncast.
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/) 📡
<div align="center">
<img src=".github/demo.gif" alt="Stream Sprout" width="505" height="415"/>
</div>
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 🎞️
The stream is received and then restreamed to the destinations you configure without modification.
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.** 📈
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` and `ffmpeg` 👍️
## Get Started
- [Install](#installation) Stream Sprout
- [Configure](#configuration) Stream Sprout
- [Configure](#configure-obs-studio) OBS Studio
- Start `stream-sprout`
- Click the *Start Streaming* button in OBS Studio
- [Install](#installation) Stream Sprout 🧑‍💻
- [Configure](#configure-stream-sprout) Stream Sprout 🧑‍💻
- [Configure](#configure-obs-studio) OBS Studio 🎛️
- Start `stream-sprout` ⌨️
- Click the *Start Streaming* button in OBS Studio 🖱️
- Do you your thing 🎥
- Click the *Stop Streaming* button in OBS Studio 🖱️
- <kbd>Ctrl</kbd> + <kbd>C</kbd> to stop `stream-sprout` ⌨️
## Installation
### Debian
`yq` 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) 📦️
- Install it with `apt-get install ./stream-sprout_0.1.5-1_all.deb`.
- Download the Stream Sprout .deb package from the [releases page]()
- Install it with `apt-get install ./stream-sprout_0.1.0-1_all.deb`.
### macOS
### NixOS
Install the Stream Sprout requirements using `brew`:
```shell
brew install bash ffmpeg
```
Now clone the project:
```shell
git clone https://github.com/wimpysworld/stream-sprout.git
cd stream-sprout
```
### Nix & NixOS
[![FlakeHub](https://img.shields.io/endpoint?url=https://flakehub.com/f/wimpysworld/stream-sprout/badge)](https://flakehub.com/flake/wimpysworld/stream-sprout)
Stable releases of Stream Sprout are published to FlakeHub for Nix users. See the flake on FlakeHub for more details:
Stable releases of Stream Sprout are published to FlakeHub for Nix users ❄️
See the flake on FlakeHub for more details:
- <https://flakehub.com/flake/wimpysworld/stream-sprout>
## Ubuntu 24.04 and newer
### Snap
`yq` is available in Ubuntu 24.04 and newer.
[![stream-sprout](https://snapcraft.io/stream-sprout/badge.svg)](https://snapcraft.io/stream-sprout)
- Download the Stream Sprout .deb package from the [releases page]()
- Install it with `apt-get install ./stream-sprout_0.1.0-1_all.deb`.
For Linux distributions that support snap packages, Stream Sprout is available from the Snap Store 🛍️
For Ubuntu versions earlier than 24.04 you can [install Stream Sprout from source](#from-source).
```shell
sudo snap install stream-sprout
```
### Ubuntu
- 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.5-1_all.deb`.
### 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
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
git clone https://github.com/wimpysworld/stream-sprout.git
@ -64,9 +150,59 @@ cd stream-sprout
## Configure Stream Sprout
Copy the [example Stream Sprout configuration](https://github.com/wimpysworld/stream-sprout/blob/main/stream-sprout.yaml.example) file 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 📝
`services:` are arbitrarily named. Just create an entry for each RTMP destination you want to stream to.
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`
- XDG configuration directory `$XDG_CONFIG_HOME/stream-sprout.yaml` (*Linux*) or `~/.config/stream-sprout.yaml` (*macOS*)
- `/etc/stream-sprout.yaml`
### Server
Here's an example configuration for the Stream Sprout `server:` section.
```yaml
server:
ip: 127.0.0.1
port: 1935
app: sprout
key: create your key with uuidgen here
archive_stream: false
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:` are arbitrarily named.
**Just create an entry for each RTMP destination you want to stream to.**
The example configuration includes entries for Trovo, Twitch, and YouTube but any RTMP destination can be added.
```yaml
services:
@ -76,52 +212,42 @@ services:
key: "my_super_secret_stream_key"
```
### Trovo
Here's are example configurations for Trovo.
```yaml
services:
trovo:
enabled: false
rtmp_server: "rtmp://livepush.trovo.live/live/"
key: "<your_stream_key>"
```
### Twitch
Here's are example configurations for Twitch.
Here's an example configuration for Twitch.
```yaml
services:
twitch:
enabled: true
rtmp_server: "rtmp://live.twitch.tv/app/"
key: "<your_stream_key>"
key: "your_twitch_stream_key"
```
#### Ingest servers
The example configuration uses the primary Twitch ingest server, which is `rtmp://live.twitch.tv/app/`. If you want to optimise your stream quality, you can use a server closer to your location. A short list of recommended servers based on your location is available from [Recommended Ingest Endpoints For You](https://help.twitch.tv/s/twitch-ingest-recommendation).
The example configuration uses the primary Twitch ingest endpoint, which is `rtmp://live.twitch.tv/app/`.
If you want to optimize your stream latency, you can use a Twitch ingest endpoint closer to your location.
A short list of recommended endpoints, based on your whereabouts, is available from [Recommended Ingest Endpoints For You](https://help.twitch.tv/s/twitch-ingest-recommendation).
You can find a complete list of Twitch ingest servers from <https://twitchstatus.com/>.
You can find a complete list of Twitch ingest endpoints from <https://twitchstatus.com/>.
#### Testing
If you want to test streaming to Twitch without going live, you can use the `?bandwidthtest=true` query parameter.
Add `?bandwidthtest=true` to the end of your Twitch stream key, this will enable bandwidth testing, which you can monitor at <https://inspector.twitch.tv/>, and the stream will not go live on your channel.
Add `?bandwidthtest=true` to the end of your Twitch stream key, this will enable bandwidth testing, which you can monitor using <https://inspector.twitch.tv/>, and the stream will not go live on your channel.
### YouTube
Here's are example configurations for YouTube.
Here's an example configuration for YouTube.
```yaml
services:
youtube:
enabled: true
rtmp_server: "rtmp://a.rtmp.youtube.com/live2/"
key: "<your_stream_key>"
key: "your_youtube_stream_key"
```
## Configure OBS Studio
@ -130,15 +256,32 @@ services:
- Go to `Settings` > `Stream`
- Select `Custom` from the `Service` dropdown
- Copy the server `url:` from your Stream Sprout configuration to the `Server` field:
- `rtmp://127.0.0.1:1935`
- `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
![OBS Studio Stream Settings](.github/obs-settings.png)
## Limitations
- Protecting the Stream Sprout RTMP server with a key does not work
- 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.
## References
These are some of the references used to create this project:
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites
- https://ffmpeg.org/ffmpeg-protocols.html#rtmp
- https://ffmpeg.org/ffmpeg-formats.html#flv
- 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://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites
- https://ffmpeg.org/ffmpeg-protocols.html#rtmp
- https://ffmpeg.org/ffmpeg-formats.html#flv
- 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://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
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.
- 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:
debhelper-compat (= 12),
Standards-Version: 4.5.1
Homepage: https://github.com/wimpys-world/stream-sprout
Vcs-Browser: https://github.com/wimpys-world/stream-sprout
Vcs-Git: https://github.com/wimpys-world/stream-sprout.git
Homepage: https://github.com/wimpysworld/stream-sprout
Vcs-Browser: https://github.com/wimpysworld/stream-sprout
Vcs-Git: https://github.com/wimpysworld/stream-sprout.git
Rules-Requires-Root: no
Package: stream-sprout
@ -15,11 +15,25 @@ Architecture: all
Depends:
coreutils,
ffmpeg,
procps,
yq,
grep,
mawk,
sed,
${misc:Depends},
${shlibs:Depends},
Description: Restream a video source to multiple destinations such as Twitch, YouTube, and Owncast.
Stream Sprout uses FFmpeg to re-stream 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 is a simple, self-contained, and easy-to-use solution for
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/
Upstream-Name: stream-sprout
Upstream-Contact: Martin Wimpress <code@wimpress.io>
Source: https://github.com/wimpys-world/stream-sprout
Source: https://github.com/wimpysworld/stream-sprout
Files: *
Copyright: 2024 Martin Wimpress <code@wimpress.io>
License: APACHE-2.0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
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:
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
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
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Unless required by applicable law or agreed to in writing, software
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 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/*
Copyright: 2024 Martin Wimpress <code@wimpress.io>
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
This package is distributed in the hope that it will be useful,
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.
http://www.apache.org/licenses/LICENSE-2.0
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>
Unless required by applicable law or agreed to in writing, software
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
Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
On Debian systems, the complete text of the Apache License, Version 2
can be found in "/usr/share/common-licenses/Apache-2.0".

View file

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

20
flake.lock generated
View file

@ -2,12 +2,12 @@
"nodes": {
"flake-schemas": {
"locked": {
"lastModified": 1721078157,
"narHash": "sha256-c2AZH9cOnSpPXV8Lwy19/I8EgW7G+E+Zh6YQBZZwzxI=",
"rev": "29e53dd33b1a38f235ef073e768c62821cb6146e",
"revCount": 66,
"lastModified": 1761577921,
"narHash": "sha256-eK3/xbUOrxp9fFlei09XNjqcdiHXxndzrTXp7jFpOk8=",
"rev": "47849c7625e223d36766968cc6dc23ba0e135922",
"revCount": 107,
"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": {
"type": "tarball",
@ -16,12 +16,12 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1721548954,
"narHash": "sha256-7cCC8+Tdq1+3OPyc3+gVo9dzUNkNIQfwSDJ2HSi2u3o=",
"rev": "63d37ccd2d178d54e7fb691d7ec76000740ea24a",
"revCount": 633334,
"lastModified": 1769318308,
"narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=",
"rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c",
"revCount": 906484,
"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": {
"type": "tarball",

View file

@ -2,15 +2,20 @@
, installShellFiles
, makeWrapper
, stdenv
, coreutils-full
, ffmpeg-headless
, gawk
, gnugrep
, gnused
, procps
, yq
}:
let
runtimePaths = [
coreutils-full
ffmpeg-headless
procps
yq
gawk
gnugrep
gnused
];
versionMatches =
builtins.match ''
@ -29,12 +34,14 @@ stdenv.mkDerivation rec {
installPhase = ''
runHook preInstall
install -Dm755 -t "$out/bin" stream-sprout
wrapProgram $out/bin/stream-sprout \
--prefix PATH : "${lib.makeBinPath runtimePaths}"
runHook postInstall
'';
meta = {
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";
license = lib.licenses.asl20;
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
# shellcheck disable=SC2154
# Disable echo of control characters like ^C
stty -echoctl
readonly STREAM_SPROUT_YAML="stream-sprout.yaml"
readonly VERSION="0.1.0"
readonly VERSION="0.1.6"
function ctrl_c() {
echo " - Trapped: CTRL-C"
pkill ffmpeg
function cleanup() {
echo -e " \e[31m\U26D4\e[0m Control-C"
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
exit
}
function get_archive_path() {
local ARCHIVE_PATH=""
ARCHIVE_PATH=$(yq e ".server.archive_path" "${STREAM_SPROUT_CONFIG}")
# Expand any environment variables in the path
ARCHIVE_PATH=$(eval echo "${ARCHIVE_PATH}")
if [ -z "${ARCHIVE_PATH}" ]; then
echo "./"
# Function to display help
function show_help() {
echo "Restream a video source to multiple destinations such as Twitch, YouTube, Owncast and Peertube."
echo ""
echo "Usage: $(basename "${0}") [options]"
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
mkdir -p "${ARCHIVE_PATH}" 2> /dev/null
echo "${ARCHIVE_PATH}"
PRETTY_NAME="Unknown OS"
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() {
local STAMP=""
# 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)
echo " - Rename: ${ARCHIVE_PATH}/${ARCHIVE_TEMP} to ${ARCHIVE_PATH}/stream-sprout-${STAMP}.mkv"
mv "${ARCHIVE_PATH}/${ARCHIVE_TEMP}" "${ARCHIVE_PATH}/stream-sprout-${STAMP}.mkv"
echo -e " \U1F500 ${sprout_server_archive_path}/${sprout_server_archive_temp}"
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
}
function add_archive() {
local ARCHIVE_ENABLED=""
ARCHIVE_PATH="$(get_archive_path)"
ARCHIVE_TEMP="stream-temp-$(date +%s%N).mkv"
sprout_server_archive_temp="stream-temp-$(date +%s%N).mkv"
# Check if recording is enabled in the YAML configuration
ARCHIVE_ENABLED=$(yq e ".server.archive_stream" "${STREAM_SPROUT_CONFIG}")
if [[ "${ARCHIVE_ENABLED,,}" == "true" || "${ARCHIVE_ENABLED}" == "1" ]]; then
echo " - Archive: ${ARCHIVE_PATH}/${ARCHIVE_TEMP}"
if [[ "${sprout_server_archive_stream,,}" == "true" || "${sprout_server_archive_stream}" == "1" ]]; then
if [ -z "${sprout_server_archive_path}" ]; then
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
STREAM_TEE+="|"
fi
STREAM_TEE+="[f=matroska]${ARCHIVE_PATH}/${ARCHIVE_TEMP}"
STREAM_TEE+="[f=matroska]${sprout_server_archive_path}/${sprout_server_archive_temp}"
fi
}
@ -62,81 +155,252 @@ function add_service() {
function get_stream_tee() {
local SERVICE_ENABLED=""
local SERVICES=""
local SERVICE_KEY=""
local SERVICE_NAME=""
local SERVICE_RTMP=""
local URI=""
# Extract services from the YAML
SERVICES=$(yq e '.services | keys | .[]' "${STREAM_SPROUT_CONFIG}")
# Iterate over each service
for SERVICE in ${SERVICES}; do
# Check if the service is enabled in the YAML configuration
SERVICE_ENABLED=$(yq e ".services.${SERVICE}.enabled" "${STREAM_SPROUT_CONFIG}")
if [[ "${SERVICE_ENABLED,,}" == "true" || "${SERVICE_ENABLED}" == "1" ]]; then
echo " - Service: ${SERVICE}"
URI=$(yq e ".services.${SERVICE}.rtmp_server" "${STREAM_SPROUT_CONFIG}")
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then
echo " - Invalid URL: ${SERVICE} is not a valid RTMP URL."
return
STREAM_TEE=""
# Iterate over all the sprout_services variables
for var in "${!sprout_services@}"; do
# Check the variable matches the pattern: sprout_services_*_enabled
if [[ "${var}" =~ ^sprout_services_.*_enabled$ ]]; then
# Derive the service name
# - First remove `sprout_services_` prefix from the beginning of the value stored in the variable $var.
# - Next remove the suffix `_enabled` from the end of the SERVICE_NAME variable's value.
SERVICE_NAME="${var#sprout_services_}"
SERVICE_NAME="${SERVICE_NAME%_enabled}"
# Get the value of the variable $var
SERVICE_ENABLED="${!var}"
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
URI+=$(yq e ".services.${SERVICE}.key" "${STREAM_SPROUT_CONFIG}")
add_service "${URI}"
fi
done
add_archive
}
# Check that ffmpeg and yq are available on the PATH
for CMD in ffmpeg pkill yq; do
if ! command -v "${CMD}" &> /dev/null; then
echo "ERROR! ${CMD} is not installed. Exiting."
function get_server_url() {
local asterisks=""
local key_length=0
# 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
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
# Check in the current working directory
if [ -f "./${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="./${STREAM_SPROUT_YAML}"
# Check in the user's home directory, considering XDG on Linux and compatibility with macOS
elif [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}"
# Check in /etc
elif [ -f "/etc/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="/etc/${STREAM_SPROUT_YAML}"
else
echo "ERROR: ${STREAM_SPROUT_YAML} was not found."
exit 1
fi
# 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}")
if [[ ! "${SERVER_URL}" =~ ^rtmp://.* ]]; then
echo " - Invalid URL: ${SERVER_URL} is not a valid RTMP URL."
# Check if a custom config path was not provided
if [ -z "${STREAM_SPROUT_CONFIG}" ]; then
# Check in the current working directory
if [ -f "./${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="./${STREAM_SPROUT_YAML}"
# Check in the user's home directory, considering XDG on Linux and compatibility with macOS
elif [ -f "${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="${XDG_CONFIG_HOME:-${HOME}/.config}/${STREAM_SPROUT_YAML}"
# Check in /etc
elif [ -f "/etc/${STREAM_SPROUT_YAML}" ]; then
STREAM_SPROUT_CONFIG="/etc/${STREAM_SPROUT_YAML}"
else
echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_YAML} was not found. Exiting."
exit 1
fi
echo " - Server: ${SERVER_URL}"
STREAM_TEE=""
fi
banner
# trap relevant signals and call cleanup()
trap cleanup INT QUIT TERM
while true; do
eval "$(parse_yaml "${STREAM_SPROUT_CONFIG}" sprout_)"
show_version
echo -e " \U2699 ${STREAM_SPROUT_CONFIG}"
get_server_url
get_stream_tee
FFMPEG_LOG=$(mktemp /tmp/stream-sprout.XXXXXX.log)
ffmpeg \
-hide_banner \
-flags +global_header \
-fflags nobuffer \
-listen 1 -i "${SERVER_URL}?rtmp_buffer=0&rtmp_live=live" \
-listen 1 -i "${sprout_server_url}" \
-flvflags no_duration_filesize \
-c:v copy -c:a copy -map 0 \
-movflags +faststart \
-f tee -use_fifo 1 "${STREAM_TEE}" 2>/dev/null
echo " - Server: Stopping..."
-f tee -use_fifo 1 "${STREAM_TEE}" >"${FFMPEG_LOG}" 2>&1 &
# 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
echo
unset sprout_server_url
done

View file

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