Initial import: sources, staging packages, build scripts and CI workflow
Build and Publish Debian Packages / Build Packages (push) Failing after 18s
Build and Publish Debian Packages / Publish to eendoe repository (push) Skipped

This commit is contained in:
bart
2026-10-20 21:20:13 +02:00
commit 9cc903b2a4
269 changed files with 73441 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
name: Build and Publish Debian Packages
on:
push:
branches:
- main
- master
paths:
- 'sources/**'
- 'buildpackages.sh'
- '.gitea/workflows/**'
workflow_dispatch:
jobs:
build:
name: Build Packages
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y dpkg-dev rsync
- name: Build all packages
run: |
chmod +x buildpackages.sh
./buildpackages.sh all
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: debian-packages
path: staging/*.deb
publish:
name: Publish to eendoe repository
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download built artifacts
uses: actions/download-artifact@v4
with:
name: debian-packages
path: staging
- name: Deploy packages to eendoe server
env:
SSH_KEY: ${{ secrets.EENDOE_SSH_KEY }}
REMOTE_HOST: "eendoe.nl"
REMOTE_USER: "bart"
REMOTE_DIR: "/sites/repository/staging/"
run: |
if [ -n "$SSH_KEY" ]; then
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "$REMOTE_HOST" >> ~/.ssh/known_hosts 2>/dev/null
rsync -avz staging/*.deb "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"
echo "Successfully deployed packages to $REMOTE_HOST:$REMOTE_DIR"
else
echo "Secret EENDOE_SSH_KEY not configured. Skipping automated remote sync."
fi
+5
View File
@@ -0,0 +1,5 @@
.DS_Store
*.swp
*~
# Exclude locally generated deb files in source folders
sources/*/*.deb
+26
View File
@@ -0,0 +1,26 @@
# package-repo
Debian / APT package repository bronbestanden en build tools voor Eendoe.
## Inhoud
- **`sources/`**: Bronbestanden, control definities en packaging scripts voor de te bouwen packages:
- `eendoe_repository`: Repository configuratie package (`/etc/apt/sources.list.d/`)
- `eendoe_hyperdeck`: Hyperdeck Python service en systemd unit
- `eendoe_srs`: SRS streaming server package en systemd unit
- **`staging/`**: Bestaande en nieuw gebouwde `.deb` packages (exclusief Oracle database packages).
- **`binaries/`**: Publiek geserveerde repository bestanden (`pool/`, `dists/`).
- **`buildpackages.sh`**: Script om één of alle packages te bouwen (`./buildpackages.sh [all|<package_name>]`).
- **`.gitea/workflows/build-and-publish.yaml`**: Gitea Actions CI/CD workflow voor het automatisch bouwen en publiceren van packages naar de eendoe server.
## Bouwen
Om alle packages lokaal te bouwen:
```bash
./buildpackages.sh all
```
Om een specifiek package te bouwen:
```bash
./buildpackages.sh eendoe_hyperdeck
```
Binary file not shown.
+57
View File
@@ -0,0 +1,57 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Origin: . stable
Label: . stable
Suite: stable
Codename: stable
Date: Wed, 27 Dec 2023 15:09:09 UTC
Architectures: amd64
Components: main
Description: Generated by aptly
MD5Sum:
cf5f351b126aaab2bf6ca97c13b378c5 3405449 Contents-amd64
9f23a2449f3abc4c6fccecfd01fd86e3 152525 Contents-amd64.gz
cf5f351b126aaab2bf6ca97c13b378c5 3405449 main/Contents-amd64
9f23a2449f3abc4c6fccecfd01fd86e3 152525 main/Contents-amd64.gz
f8778b24beb2988de929e2d59d1f4c58 2761 main/binary-amd64/Packages
a9ae692d17b8e39db0555ab28ca1207d 1357 main/binary-amd64/Packages.bz2
9a64097df252b063fd82b0127ffc57ae 1268 main/binary-amd64/Packages.gz
1a863a05e2aa38a0db0bf785722d7fae 99 main/binary-amd64/Release
SHA1:
5fe9dd2d9aa59f1f24de2ca35728e05db5f74655 3405449 Contents-amd64
3913d6a2268e0599ee051d4a267b70a4740c03fa 152525 Contents-amd64.gz
5fe9dd2d9aa59f1f24de2ca35728e05db5f74655 3405449 main/Contents-amd64
3913d6a2268e0599ee051d4a267b70a4740c03fa 152525 main/Contents-amd64.gz
7236da7bf2d82d5b66a9305d78f7ec7876792c71 2761 main/binary-amd64/Packages
a2ed1b4117915d2349051ada9ddc089113aa7803 1357 main/binary-amd64/Packages.bz2
a628182db07c468ccf6236597693c3624a0eb9f8 1268 main/binary-amd64/Packages.gz
279ffa18634b8d6c311595f6e6db2c1b2324b3ef 99 main/binary-amd64/Release
SHA256:
0ec939a6371353e8ba850811ecba0bd6749ecf5ad4e6ffe4d3f4d48ed68dc8ee 3405449 Contents-amd64
915bdf7b19675b1eb09b22f0aada96d0d1557fa9ed70a3e71db197c71376db02 152525 Contents-amd64.gz
0ec939a6371353e8ba850811ecba0bd6749ecf5ad4e6ffe4d3f4d48ed68dc8ee 3405449 main/Contents-amd64
915bdf7b19675b1eb09b22f0aada96d0d1557fa9ed70a3e71db197c71376db02 152525 main/Contents-amd64.gz
f2197b98eadd153d364ff7948bca222763f70e18b76a963b504c1ebbbeb0f2b7 2761 main/binary-amd64/Packages
7dc41b5f359d26cb8c1bcc4b512c94c5db1b402efb15e76d5d0d23625cdca933 1357 main/binary-amd64/Packages.bz2
64f1a3fd43c28053b3c2301a123b4cf6dc5a1370a94463d861fab3856554366e 1268 main/binary-amd64/Packages.gz
101f8d561bb536f600918ab98ffb742828149f0d90ed0b007b7b761e9ad4f8a2 99 main/binary-amd64/Release
SHA512:
ec29ceab750bab737fbbcc9f00b74c334d0e1ea2f62c0081ed9b695d76bac1296f7560b2ee80361c9f8ed2b538a6adad21d8d3b55200fa71096ac09cd6c7eebd 3405449 Contents-amd64
69bbc10540b2bb90941dd2b6804544c91fd335e91f7674f50b2d32312257b997f34ec5d0147d69a16164e41e2bdbdc008a74c863b6de0c42c15eba062c540efa 152525 Contents-amd64.gz
ec29ceab750bab737fbbcc9f00b74c334d0e1ea2f62c0081ed9b695d76bac1296f7560b2ee80361c9f8ed2b538a6adad21d8d3b55200fa71096ac09cd6c7eebd 3405449 main/Contents-amd64
69bbc10540b2bb90941dd2b6804544c91fd335e91f7674f50b2d32312257b997f34ec5d0147d69a16164e41e2bdbdc008a74c863b6de0c42c15eba062c540efa 152525 main/Contents-amd64.gz
19efa178a219564af52c8eff763c820d973a9f00f43a6fd5d1599179661b025a043da630874ba56814ca43608d68aa2e555bb5f303a3d84deb1412c4c8923ff6 2761 main/binary-amd64/Packages
87099e5b28836adde21419be72bc16bbb6b02bd901f8372df2de3543d03f8de9ff58fd058ef7bc90a7bafc9b8d4c38fdd25c507b788b8054d79d448c97ef32d7 1357 main/binary-amd64/Packages.bz2
dc706a07d45f70d0bf645c4d65e1b6b235a66dfa294f8a77825b1395cf40aa3a2ed097f7092e4635c99ff231fc9f44852d0b91ed09fbbd1cc8a78bb758afc2e7 1268 main/binary-amd64/Packages.gz
fd75f52e3d8ab190e919a646d1ccc84e447b32e45691f0f9f65c3ac5ac806f024f7edef6e0c3c9e2d19a5f2621dcd3744f684d9b87a7cdc493b0eb55e7a0b393 99 main/binary-amd64/Release
-----BEGIN PGP SIGNATURE-----
wsBcBAEBCAAQBQJljD4VCRBWSZ+SBjn1cQAANDUIAKjuNqznCTWjUgwAdRkmCLyb
HzmxTbtSfc4Eguri0QsUCQg7FA5nlupNketz2NNJ3TIkPJ7KVHAvcYdifnDlL0k2
WQMRC4L94jxYSvRnMj4f+/pWt1UxHs5wCHGaojvf8s9ZzSTme52g3MLtikQCEpgv
zqIgSH4bzAJu0fc5Je1qRpxiwATyf7B7vC276XSWkDIxDZBUSupLoZNua0NdZ7mh
cTXyFIexrWOJk3xJjKfcTQZEB+RxVZk/vSADAnaVKo/YuzBoSx2A1HEwVDOPc4TD
vwPIfM9LDoS6YrN4mKDvkjlUtgds/5rXcOVxEQAAaG/md5RvRTOAFbpBKs/pbZc=
=2HIC
-----END PGP SIGNATURE-----
+44
View File
@@ -0,0 +1,44 @@
Origin: . stable
Label: . stable
Suite: stable
Codename: stable
Date: Wed, 27 Dec 2023 15:09:09 UTC
Architectures: amd64
Components: main
Description: Generated by aptly
MD5Sum:
cf5f351b126aaab2bf6ca97c13b378c5 3405449 Contents-amd64
9f23a2449f3abc4c6fccecfd01fd86e3 152525 Contents-amd64.gz
cf5f351b126aaab2bf6ca97c13b378c5 3405449 main/Contents-amd64
9f23a2449f3abc4c6fccecfd01fd86e3 152525 main/Contents-amd64.gz
f8778b24beb2988de929e2d59d1f4c58 2761 main/binary-amd64/Packages
a9ae692d17b8e39db0555ab28ca1207d 1357 main/binary-amd64/Packages.bz2
9a64097df252b063fd82b0127ffc57ae 1268 main/binary-amd64/Packages.gz
1a863a05e2aa38a0db0bf785722d7fae 99 main/binary-amd64/Release
SHA1:
5fe9dd2d9aa59f1f24de2ca35728e05db5f74655 3405449 Contents-amd64
3913d6a2268e0599ee051d4a267b70a4740c03fa 152525 Contents-amd64.gz
5fe9dd2d9aa59f1f24de2ca35728e05db5f74655 3405449 main/Contents-amd64
3913d6a2268e0599ee051d4a267b70a4740c03fa 152525 main/Contents-amd64.gz
7236da7bf2d82d5b66a9305d78f7ec7876792c71 2761 main/binary-amd64/Packages
a2ed1b4117915d2349051ada9ddc089113aa7803 1357 main/binary-amd64/Packages.bz2
a628182db07c468ccf6236597693c3624a0eb9f8 1268 main/binary-amd64/Packages.gz
279ffa18634b8d6c311595f6e6db2c1b2324b3ef 99 main/binary-amd64/Release
SHA256:
0ec939a6371353e8ba850811ecba0bd6749ecf5ad4e6ffe4d3f4d48ed68dc8ee 3405449 Contents-amd64
915bdf7b19675b1eb09b22f0aada96d0d1557fa9ed70a3e71db197c71376db02 152525 Contents-amd64.gz
0ec939a6371353e8ba850811ecba0bd6749ecf5ad4e6ffe4d3f4d48ed68dc8ee 3405449 main/Contents-amd64
915bdf7b19675b1eb09b22f0aada96d0d1557fa9ed70a3e71db197c71376db02 152525 main/Contents-amd64.gz
f2197b98eadd153d364ff7948bca222763f70e18b76a963b504c1ebbbeb0f2b7 2761 main/binary-amd64/Packages
7dc41b5f359d26cb8c1bcc4b512c94c5db1b402efb15e76d5d0d23625cdca933 1357 main/binary-amd64/Packages.bz2
64f1a3fd43c28053b3c2301a123b4cf6dc5a1370a94463d861fab3856554366e 1268 main/binary-amd64/Packages.gz
101f8d561bb536f600918ab98ffb742828149f0d90ed0b007b7b761e9ad4f8a2 99 main/binary-amd64/Release
SHA512:
ec29ceab750bab737fbbcc9f00b74c334d0e1ea2f62c0081ed9b695d76bac1296f7560b2ee80361c9f8ed2b538a6adad21d8d3b55200fa71096ac09cd6c7eebd 3405449 Contents-amd64
69bbc10540b2bb90941dd2b6804544c91fd335e91f7674f50b2d32312257b997f34ec5d0147d69a16164e41e2bdbdc008a74c863b6de0c42c15eba062c540efa 152525 Contents-amd64.gz
ec29ceab750bab737fbbcc9f00b74c334d0e1ea2f62c0081ed9b695d76bac1296f7560b2ee80361c9f8ed2b538a6adad21d8d3b55200fa71096ac09cd6c7eebd 3405449 main/Contents-amd64
69bbc10540b2bb90941dd2b6804544c91fd335e91f7674f50b2d32312257b997f34ec5d0147d69a16164e41e2bdbdc008a74c863b6de0c42c15eba062c540efa 152525 main/Contents-amd64.gz
19efa178a219564af52c8eff763c820d973a9f00f43a6fd5d1599179661b025a043da630874ba56814ca43608d68aa2e555bb5f303a3d84deb1412c4c8923ff6 2761 main/binary-amd64/Packages
87099e5b28836adde21419be72bc16bbb6b02bd901f8372df2de3543d03f8de9ff58fd058ef7bc90a7bafc9b8d4c38fdd25c507b788b8054d79d448c97ef32d7 1357 main/binary-amd64/Packages.bz2
dc706a07d45f70d0bf645c4d65e1b6b235a66dfa294f8a77825b1395cf40aa3a2ed097f7092e4635c99ff231fc9f44852d0b91ed09fbbd1cc8a78bb758afc2e7 1268 main/binary-amd64/Packages.gz
fd75f52e3d8ab190e919a646d1ccc84e447b32e45691f0f9f65c3ac5ac806f024f7edef6e0c3c9e2d19a5f2621dcd3744f684d9b87a7cdc493b0eb55e7a0b393 99 main/binary-amd64/Release
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN PGP SIGNATURE-----
wsBcBAABCAAQBQJljD4VCRBWSZ+SBjn1cQAA8egIADfaxAQ/X/sjUAVIXODYu2cu
CWzVmXJ9PcNAQhTo+3rCgLKfs2o8Vw4lKGbNlAUAUZuLTooAUAmIqqnkgaiJsXLO
wGWqbHRi/opVVvYRATpuQJfpZ6Du9z8bhmnm7klVlZhluxuFia1analsT3lOopI1
GrXrN4TMXwUp3tOk/7hWaP/ME3v0MsMIqtrkDwseA8lQ7pMhA8TnmabDZTSmf94R
KxYzw8TaI/Ddrs9HBsP9d0hUqOvyhicwqC5XF4Rtn9GgjN2H0+/vUPkNta7I8zOV
UlnOj0r8owjVKg4opRqrGfk14MmyliQZ2KlFHhsgdxUoTB0QPuFKnQLflwx7Ztw=
=tgYK
-----END PGP SIGNATURE-----
Binary file not shown.
@@ -0,0 +1,65 @@
Package: eendoe-repository
Section: utils
Maintainer: Bart Meijer
Architecture: all
Version: 1.0
Filename: pool/main/e/eendoe-repository/eendoe-repository-1.0_all.deb
Size: 7224
MD5sum: 1dec21c569795d9a7c408b49bc462351
SHA1: f81b15730a43fc3e8bb002046a09755c83b00794
SHA256: 87fe58023583065366484a08af3789e276a0ab87516eae549e550dd6e74ca78f
SHA512: 6c5ccca79318822ea39cfe04bbf8c11e9be06e8f53aafdef9a47b8bbcf3b2370b1ad1d15e23fb36378aa8cf3304fce8563f5e963299eda348636f0e1eead4809
Description: Eendoe package repository
Package: oracle-database-preinstall-21c
Priority: extra
Section: alien
Installed-Size: 101
Maintainer: Bart Meijer <bart@vmserver>
Architecture: amd64
Version: 1.0-2
Filename: pool/main/o/oracle-database-preinstall-21c/oracle-database-preinstall-21c_1.0-2_amd64.deb
Size: 19796
MD5sum: 9a95793469fb85f535a26266b60738bf
SHA1: 6869aad4114275c479e40865bba021c6ca02e6cb
SHA256: a6ad8c2b5b204f631f88199ed799784ad6fd73bd65d899277b8e786ecf5bb578
SHA512: 617ce833e5771b2d55f8d1c39c77adf1f775590565ff056610625f8c24450150d4a563a7a4cac25966b41c6becd90e73c38c379bc198096acd0b673eaa0b48e2
Description: Oracle Database Preinstallation RPM
The Oracle Database Preinstallation RPM automatically installs any additional
packages needed for installing Oracle Grid Infrastructure and Oracle Database
and configures your server operating system automatically, including setting
kernel parameters and other basic operating system requirements for install.
.
(Converted from a rpm package by alien version 8.95.)
Package: oracle-database-xe-21c
Section: utils
Maintainer: Bart Meijer
Architecture: all
Version: 1.0.3
Filename: pool/main/o/oracle-database-xe-21c/oracle-database-xe-21c-1.0.3_all.deb
Size: 2188370848
MD5sum: b89910db6b9ba0ab149db6ee78a711f8
SHA1: 39e2f816b81ccc4dbbc4bc89d20361272dd59161
SHA256: ec96ebd544d9492e3696fc609d18a539357c653b3fb4264d144fbd0a27b23eec
SHA512: 1922b4fec8b7e6b03a298a2f8d4f400ec9aa9b2d7ad6fe4ceaa83d136c6d5910316f46c6025b352b05ce18ad8fc9e3b7286bf3948e000a1857b6fce2d2c19c03
Description: Eendoe package repository
Package: oracle-database-xe-21c
Priority: extra
Section: alien
Installed-Size: 6067591
Maintainer: Bart Meijer <bart@vmserver>
Architecture: amd64
Version: 1.0-2
Filename: pool/main/o/oracle-database-xe-21c/oracle-database-xe-21c_1.0-2_amd64.deb
Size: 2188841976
MD5sum: 0b39df8f78e8c909557d1f028c9e7e5f
SHA1: 882d1f5f63d842c6051a9aa0ec57a2d34cdb1a3f
SHA256: 5b1401ac3e34ac5c0c636b8b41a6b3762f9b8297fb6f42c3bc2d1d05677ae01d
SHA512: 9ff82c1b2d09582f1c456ba99ecb3f3be5079bd00a5101c7f927fb3752bc869fe4e982b51d170feca64e9ab6ab58c41566a30e57b1ff9fe066041bc0465e3d2a
Description: Oracle 21c Express Edition Database
Oracle 21c Express Edition Database
.
(Converted from a rpm package by alien version 8.95.)
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
Origin: . stable
Label: . stable
Archive: stable
Suite: stable
Architecture: amd64
Component: main
+6
View File
@@ -0,0 +1,6 @@
<a href="/pool/main/e/eendoe-repository/eendoe-repository-1.0_all.deb">Download</a>
wget https://repository.eendoe.nl/pool/main/e/eendoe-repository/eendoe-repository-1.0_all.deb && sudo dpkg -i eendoe-repository-1.0_all.deb
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT_DIR="${OUTPUT_DIR:-$SCRIPT_DIR/staging}"
mkdir -p "$OUTPUT_DIR"
build_package() {
local pkg_dir="$1"
local pkg_name
pkg_name="$(basename "$pkg_dir")"
if [ -f "$pkg_dir/bin/build.sh" ]; then
echo "=== Building $pkg_name ==="
(
cd "$pkg_dir"
./bin/build.sh
# Move built debian packages to output dir
mv -v *.deb "$OUTPUT_DIR/" 2>/dev/null || true
)
else
echo "Skipping $pkg_name: no bin/build.sh found"
fi
}
TARGET="${1:-all}"
if [ "$TARGET" = "all" ]; then
echo "Building all packages in sources/..."
for dir in "$SCRIPT_DIR"/sources/*; do
if [ -d "$dir" ]; then
build_package "$dir"
fi
done
else
if [ -d "$SCRIPT_DIR/sources/$TARGET" ]; then
build_package "$SCRIPT_DIR/sources/$TARGET"
else
echo "Error: Source directory sources/$TARGET not found."
exit 1
fi
fi
echo "Build complete. Packages available in: $OUTPUT_DIR"
Executable
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
cd /data/repository/binary/
aptly repo add -force-replace rrw *.deb
aptly publish update stable
rsync -avh /home/bartlocal/.aptly/public/ bart@eendoe.nl:/sites/repository/
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
CONTROL_FILE=$(ls -1 *.control)
echo "Using control file $CONTROL_FILE"
PACKAGE_NAME=$(awk '/^Package: /{print $NF}' $CONTROL_FILE)
VERSION=$(awk '/^Version: /{print $NF}' $CONTROL_FILE)
# tag git repo
# git tag -f release-${VERSION}
echo "Building package $PACKAGE_NAME ($VERSION): $PACKAGE_NAME-${VERSION}_all.deb"
# VERSION
sed "s/{VERSION}/${VERSION}/" $PACKAGE_NAME.control > packaging/$PACKAGE_NAME/DEBIAN/control
#create debian package
dpkg --build packaging/$PACKAGE_NAME $PACKAGE_NAME-${VERSION}_all.deb
# dpkg-sig -k 14AF7F05 --sign builder icosi-${VERSION}_all.deb
echo "clean up"
rm packaging/$PACKAGE_NAME/DEBIAN/control
+7
View File
@@ -0,0 +1,7 @@
Package: eendoe-hyperdeck
Version: {VERSION}
Section: utils
Maintainer: nl.eendoe.bart
Architecture: all
Depends: python3, python3-pip, ffmpeg, eendoe-srs
Description: Eendoe hyperdeck client module
@@ -0,0 +1,41 @@
#!/bin/bash
#set -e
case "$1" in
configure|abort-upgrade)
id -u vid &>/dev/null || useradd --system -m vid
runuser -l vid -c "pip3 install -r /usr/share/hyperdeck/requirements.txt"
FILE="/etc/hyperdeck/config.json"
SOURCEFILE="/usr/share/hyperdeck/config.json.orig"
if [ -f "$FILE" ]; then
echo "$FILE exists."
else
mkdir -p /etc/hyperdeck/
echo "Copy default config"
cp "$SOURCEFILE" "$FILE"
chmod 744 "$FILE"
fi
systemd-notify --booted
if [ "$?" -eq 0 ]
then
echo "Systemd running, registering service"
systemctl daemon-reload && systemctl enable hyperdeck
else
echo "Systemd not running, skipping"
fi
mkdir -p /var/log/hyperdeck/
chown vid:vid "/var/log/hyperdeck/"
echo "Installation done"
;;
esac
exit 0
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
case "$1" in
install|upgrade)
;;
esac
exit 0
@@ -0,0 +1,4 @@
#!/usr/bin/make -f
%:
dh $@
@@ -0,0 +1,43 @@
#
# Systemd unit file for Hyperdeck recorder
#
[Unit]
Description=Eendoe Hyperdeck recorder manager
After=network.target
RequiresMountsFor=/mnt/video
[Service]
# Lifecycle
Type=simple
ExecStart=/usr/bin/python3 /usr/share/hyperdeck/main.py -c /etc/hyperdeck/config.json
SuccessExitStatus=0
Restart=on-abort
WorkingDirectory=/usr/share/hyperdeck/
# Logging
SyslogIdentifier=hyperdeck
# Security
User=vid
Group=vid
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,6 @@
{
"videopath": "/mnt/video/",
"hyperdeck": {"ip": "10.31.35.221", "port": 9993},
"rtsp_url" : "rtsp://admin:admin@10.31.35.205:554/1",
"log_path": "/var/log/hyperdeck/"
}
@@ -0,0 +1,264 @@
import os
from typing import BinaryIO
import logging
from fastapi import Request,FastAPI, HTTPException, status
from fastapi.responses import JSONResponse, FileResponse,StreamingResponse
from hyperdeck.application import HyperDeck
from datetime import datetime
from pydantic import BaseModel
class ScheduleItemRequest(BaseModel):
start: datetime| None
end: datetime| None
id: str| None
title: str| None
def send_bytes_range_requests(
file_obj: BinaryIO, start: int, end: int, chunk_size: int = 10_000
):
"""Send a file in chunks using Range Requests specification RFC7233
`start` and `end` parameters are inclusive due to specification
"""
with file_obj as f:
f.seek(start)
while (pos := f.tell()) <= end:
read_size = min(chunk_size, end + 1 - pos)
yield f.read(read_size)
def _get_range_header(range_header: str, file_size: int) -> tuple[int, int]:
def _invalid_range():
return HTTPException(
status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
detail=f"Invalid request range (Range:{range_header!r})",
)
try:
h = range_header.replace("bytes=", "").split("-")
start = int(h[0]) if h[0] != "" else 0
end = int(h[1]) if h[1] != "" else file_size - 1
except ValueError:
raise _invalid_range()
if start > end or start < 0 or end > file_size - 1:
raise _invalid_range()
return start, end
def range_requests_response(
request: Request, file_path: str, content_type: str
):
"""Returns StreamingResponse using Range Requests of a given file"""
file_size = os.stat(file_path).st_size
range_header = request.headers.get("range")
headers = {
"content-type": content_type,
"accept-ranges": "bytes",
"content-encoding": "identity",
"content-length": str(file_size),
"access-control-expose-headers": (
"content-type, accept-ranges, content-length, "
"content-range, content-encoding"
),
}
start = 0
end = file_size - 1
status_code = status.HTTP_200_OK
if range_header is not None:
start, end = _get_range_header(range_header, file_size)
size = end - start + 1
headers["content-length"] = str(size)
headers["content-range"] = f"bytes {start}-{end}/{file_size}"
status_code = status.HTTP_206_PARTIAL_CONTENT
return StreamingResponse(
send_bytes_range_requests(open(file_path, mode="rb"), start, end),
headers=headers,
status_code=status_code,
)
app = FastAPI()
CHUNK_SIZE = 1024*1024
@app.exception_handler(RuntimeError)
async def validation_exception_handler(request: Request, exc: RuntimeError) -> JSONResponse:
return JSONResponse({
"status":"error",
"message": str(exc)
},500)
@app.get("/videos/", response_class=JSONResponse)
def read_fonts():
app = HyperDeck()
files = []
for f in app.getVideos():
files.append({
"name": f["name"],
"filesize": f["filesize"],
"start": f["start"],
"end": f["end"],
"title": f["title"],
})
return files
@app.get("/status/", response_class=JSONResponse)
def getStatus():
app = HyperDeck()
date = datetime.now()
next = app.getNextScheduled()
if next:
nexttime = next.start.isoformat()
else:
nexttime = "none"
return JSONResponse({
"status": app.getStatus().value,
"errormessage" : app.getLastError(),
"date": date.isoformat(), #strftime("%Y%m%d%H%M%S"),
"nextrecording": nexttime
})
@app.post("/status/record", response_class=JSONResponse)
async def startManualRecording(request: Request):
logging.info("Manually set status to recording" )
app = HyperDeck()
if app.startRecording():
return {"status":"ok"}
else:
return {"status":"error"}
@app.post("/status/stop", response_class=JSONResponse)
async def stopRecording(request: Request):
logging.info("Manually set status to idle" )
app = HyperDeck()
if app.stopRecording():
return {"status":"ok"}
else:
return {"status":"error"}
@app.get("/schedule/active", response_class=JSONResponse)
async def getScheduleActive(request: Request):
app = HyperDeck()
return app.schedule.getActiveItems()
@app.get("/schedule", response_class=JSONResponse)
async def getSchedule(request: Request):
app = HyperDeck()
return app.getSchedule()
@app.get("/schedule/lastday", response_class=JSONResponse)
async def getItemsLastDay(request: Request):
app = HyperDeck()
return app.schedule.getItemsLastDay()
@app.post("/schedule", response_class=JSONResponse)
async def createScheduledItem(item: ScheduleItemRequest ):
app = HyperDeck()
item.start = item.start.replace(tzinfo=None)
item.end = item.end.replace(tzinfo=None)
if item.end<= item.start:
raise HTTPException(status_code=422, detail="End time must be later then start time")
if not app.isFree(item.start, item.end,""):
raise HTTPException(status_code=422, detail="Time slot already allocated")
app.addScheduledRecording(item.start, item.end, item.title)
return app.getSchedule()
@app.put("/schedule", response_class=JSONResponse)
async def editScheduledItem(item: ScheduleItemRequest ):
app = HyperDeck()
item.start = item.start.replace(tzinfo=None)
item.end = item.end.replace(tzinfo=None)
if item.end<= item.start:
raise HTTPException(status_code=422, detail="End time must be later then start time")
if not app.isFree(item.start, item.end,item.id):
raise HTTPException(status_code=422, detail="Time slot already allocated")
i = app.schedule.getScheduleItem(item.id)
if i.status != "waiting":
raise HTTPException(status_code=422, detail="Unable to update recorded item")
i.start = item.start
i.end = item.end
i.title = item.title
app.saveSchedule()
return app.getSchedule()
@app.delete("/schedule", response_class=JSONResponse)
async def deleteScheduledItem(item: ScheduleItemRequest ):
app = HyperDeck()
i = app.schedule.getScheduleItem(item.id)
if i.status != "waiting":
raise HTTPException(status_code=422, detail="Unable to delete recorded item")
app.removeScheduledItem(i)
return app.getSchedule()
@app.get("/videos/get/{video}", response_class=FileResponse)
async def read_item(video: str):
app = HyperDeck()
f = app.getVideo(video)
logging.info("Downloading video " + f["path"])
return FileResponse(f["path"],headers={'Content-Disposition': 'attachement; filename="'+f["filename"]+'"'})
@app.get("/videos/view/{video}", response_class=StreamingResponse)
async def stream_item(video: str, request: Request):
app = HyperDeck()
f = app.getVideo(video)
return range_requests_response(
request, file_path=f["path"], content_type="video/mp4"
)
@app.get("/videos/{video}", response_class=JSONResponse)
async def stream_item_alt(video: str, request: Request):
app = HyperDeck()
f = app.getVideo(video)
return f
@app.delete("/videos/{video}", response_class=JSONResponse)
async def deleteVideo(video: str ):
app = HyperDeck()
app.removeVideo(video)
return app.getSchedule()
@@ -0,0 +1,436 @@
from enum import Enum
import json
import socket
from hyperdeck.db import Schedule, ScheduleItem, ScheduleItemStatus
from hyperdeck.utils import Singleton
import os
import time
import logging
import datetime
class Config(metaclass=Singleton):
def open(self, filename):
f = open(filename, "r")
j = json.load(f)
self.fromJson(j)
def save(self, filename):
with open(filename, "w") as f:
f.write(json.dumps(self.toJson()))
def toJson(self):
return {}
def fromJson(self, j):
self.videopath = j["videopath"]
self.hyperdeck_ip = j["hyperdeck"]["ip"]
self.hyperdeck_port = j["hyperdeck"]["port"]
if "max_age" in j:
self.max_age =int( j["max_age"])
else:
self.max_age = 90
if "max_originals_age" in j:
self.maxmax_originals_age__originals_age =int( j["max_originals_age"])
else:
self.max_originals_age = 5
if "rtsp_url" in j:
self.rtsp_url = j["rtsp_url"]
self.videopath_originals = os.path.join(self.videopath, "originals")
self.videopath_convert = os.path.join(self.videopath, "convert")
if "log_path" in j:
self.log_path = j["log_path"]
else:
self.log_path = "/var/log/hyperdeck/"
pass
def __init__(self, configfile) -> None:
self.open(configfile)
pass
class Release(metaclass=Singleton):
def __init__(self) -> None:
f = open("release.json")
release = json.load(f)
self.version = release["version"]
class HyperDeckStatus(Enum):
IDLE = "idle"
RECORDING = "recording"
UNKNOWN = "unknown"
ERROR ="error"
class HyperDeck(metaclass=Singleton):
def load_config(self, configfile:str ):
self.config = Config(configfile)
if not os.path.isdir(self.config.videopath):
raise Exception("Directory {self.config.videopath} does not exist")
if not os.path.isdir(self.config.videopath_originals):
logging.info(
"Creating directory {}".format(self.config.videopath_originals)
)
os.mkdir(self.config.videopath_originals)
if not os.path.isdir(self.config.videopath_originals):
raise Exception(
"Directory {} does not exist".format(
self.config.videopath_originals
)
)
if not os.path.isdir(self.config.videopath_convert):
logging.info("Creating directory {}".format(self.config.videopath_convert))
os.mkdir(self.config.videopath_convert)
if not os.path.isdir(self.config.videopath_convert):
raise Exception(
"Directory {} does not exist".format(self.config.videopath_convert)
)
self.status = HyperDeckStatus.UNKNOWN
self.videofiles = []
self.current = None
self.lasterror = ""
self.schedule = Schedule()
self.processSchedule()
def __init__(self) -> None:
self.config = None
os.environ["TZ"] = "Europe/Amsterdam"
time.tzset()
self.release = Release()
def getConvertedFilename(self, filename:str):
return os.path.join(self.config.videopath_convert, 'c_' + filename)
def getTargetFilename(self, filename:str):
return os.path.join(self.config.videopath, 'c_' + filename)
def getStatus(self):
return self.status
def cleanup(self):
logging.info(f"Ckeaning up originals older than {self.config.max_originals_age} days from {self.config.videopath_originals}")
maxdate = datetime.datetime.now() - datetime.timedelta(days=self.config.max_originals_age)
for f in os.listdir(self.config.videopath_originals):
filename = os.path.join(self.config.videopath_originals,f)
if os.path.getmtime(filename) < maxdate.timestamp():
logging.info(f"Deleting file {filename}")
os.remove(filename)
logging.info(f"Doing cleanup for items older than {self.config.max_age} days")
items = self.schedule.getItemsOlderThan(self.config.max_age)
for item in items:
logging.info(f"Cleaning {item.id}")
f = self.getConvertedFilename(item.file)
if os.path.exists(f):
logging.info(f"Removing file {f}")
os.remove(f)
f = self.getTargetFilename(item.file)
if os.path.exists(f):
logging.info(f"Removing file {f}")
os.remove(f)
self.schedule.removeItem(item)
logging.info("Cleanup done")
def sendData(self, req):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((self.config.hyperdeck_ip, self.config.hyperdeck_port))
logging.info(f"Connected to {self.config.hyperdeck_ip}")
data = s.recv(1024).decode()
logging.info("Got data from Hyperdeck: " + data)
s.sendall(req.encode())
data = s.recv(1024).decode()
logging.info("Got data from Hyperdeck: " + data)
s.shutdown(socket.SHUT_RDWR)
s.close()
if data[:3] == "200":
self.lasterror = ""
return True
else:
self.status = HyperDeckStatus.ERROR
self.lasterror = data[3:]
return False
except TimeoutError:
logging.error("Kon geen verbinding maken met hyperdeck")
return False
except Exception as e:
logging.error("Got data from Hyperdeck: " + str(e))
return False
def getLastError(self):
return self.lasterror
def startRecording(self):
if self.status == ScheduleItemStatus.RECORDING:
logging.error("Already recording")
return False
n: ScheduleItem = ScheduleItem.create(datetime.datetime.now().replace(microsecond=0), None, "recording")
n.status = ScheduleItemStatus.RECORDING
filename = "vrm_" + n.start.strftime("%Y%m%d%H%M%S")
n.file = filename
item = n.to_schema()
self.schedule.saveItem(n)
if self.startRecordingFile(item.file):
self.current = item
return True
else:
n = self.schedule.getScheduleItem(item.id)
n.status = ScheduleItemStatus.FAILED
self.schedule.saveItem(n)
return False
def startRecordingFile(self, filename: str):
if self.status == HyperDeckStatus.RECORDING:
logging.error("Already recording")
return False
logging.info("Start recording")
if self.sendData("record: name: " + filename + "\n"):
self.status = HyperDeckStatus.RECORDING
return True
else:
self.status = HyperDeckStatus.ERROR
return False
def stopRecording(self):
if not self.current:
logging.error("No current recording")
return False
if self.status == ScheduleItemStatus.IDLE:
logging.warning("Unable to stop recording: nothing recording")
self.current.status = ScheduleItemStatus.FAILED
return False
logging.info("Stop recording")
if self.sendData("stop\n"):
item = self.schedule.getScheduleItem(self.current.id)
item.status = ScheduleItemStatus.RECORDED
self.schedule.saveItem(item)
self.current = None
self.status = HyperDeckStatus.IDLE
return True
else:
self.status = HyperDeckStatus.ERROR
return False
def getMachines(self):
return self.config.machines
def loadVideos(self):
# if len(self.videofiles)==0 :
newlist = []
for file in os.listdir(self.config.videopath):
if file.startswith("c_") and file.endswith(".mp4"):
filename = os.path.join(self.config.videopath, file)
file_stats = os.stat(filename)
item = {
"name": file,
"path": filename,
"type": "mp4",
"filename": file,
"filesize": file_stats.st_size,
"start": None,
"end": None,
"title": None,
}
rec = self.schedule.getScheduleItemByFile(item["name"])
if rec:
item["start"] = rec.start
item["end"] = rec.end
item["title"] = rec.title
newlist.append(item)
self.videofiles = sorted(newlist, key=lambda q: q["name"].lower())
def getVideo(self, name):
self.loadVideos()
limited_list = [
element for element in self.videofiles if element["name"] == name
]
return limited_list[0]
def getVideos(self):
self.loadVideos()
return self.videofiles
"""
"""
def removeScheduledItem(self, itm: ScheduleItem):
self.schedule.remove(itm)
self.saveSchedule()
def removeVideo(self, file: str):
filename = os.path.join(self.config.videopath, file)
logging.info("Removing file {}".format(filename))
os.remove(filename)
itm = self.schedule.getScheduleItemByFile(file)
if itm:
self.removeScheduledItem(itm)
def isFree(self, start: datetime, end: datetime, exclude_id: str) -> bool:
return self.schedule.getFree(start, end, exclude_id)
def addScheduledRecording(self, start: datetime, end: datetime, title: str):
n = ScheduleItem.create(start, end, title)
s = self.schedule.getScheduleItem(n.id)
if s:
raise Exception("Schedule item already exists")
self.schedule.saveItem(n)
def getSchedule(self):
retval = []
for s in self.schedule.getAll():
j = {
"id": s.id,
"start": s.start,
"status": s.status,
"end": s.end,
"file": None,
"start_recording": s.start_recording,
"end_recording": s.end_recording,
"start_copying": s.start_copying,
"end_copying": s.end_copying,
"start_processing": s.start_processing,
"end_processing": s.end_processing,
"title": s.title,
}
if not j["end"]:
j["end"] = j["start"]
if j["title"]:
j["display"] = (
j["title"]
+ ": "
+ s.start.strftime("%H:%M")
+ " - "
+ j["end"].strftime("%H:%M")
)
else:
j["display"] = (
s.start.strftime("%H:%M") + " - " + j["end"].strftime("%H:%M")
)
if s.status == ScheduleItemStatus.DONE:
j["file"] = "c_" + s.file + ".mp4"
retval.append(j)
return retval
def getNextScheduled(self):
return self.schedule.getNextScheduled()
def hasProcessing(self) -> bool:
return self.getNextProcessing is None
"""
def markCopied(self, filename):
s = self.getByFile(filename)
if s and s.status == ScheduleItemStatus.COPYING:
s.status = ScheduleItemStatus.COPIED
self.saveSchedule()
"""
def processSchedule(self):
for s in self.schedule.getItemsByStatus([ScheduleItemStatus.WAITING, ScheduleItemStatus.RECORDING]):
if s.status == ScheduleItemStatus.WAITING:
if s.end < datetime.datetime.now():
id = s.id
s.status = ScheduleItemStatus.FAILED
self.schedule.saveItem(s)
s = self.schedule.getScheduleItem(id)
if s.start < datetime.datetime.now():
if self.startRecordingFile(s.file):
s.status = ScheduleItemStatus.RECORDING
logging.info("Start recording " + s.file)
s.start_recording = datetime.datetime.now().replace(
microsecond=0
)
else:
s.status = ScheduleItemStatus.FAILED
logging.error("Unable to start recording")
self.schedule.saveItem(s)
elif s.status ==ScheduleItemStatus.RECORDING:
self.current = s
logging.info(s.end)
if s.end and (s.end < datetime.datetime.now()):
logging.info("End of schedule for " + s.file)
if not self.stopRecording():
logging.error("Unable to stop recording")
else:
s.end_recording = datetime.datetime.now().replace(microsecond=0)
self.schedule.saveItem(s)
elif s.end is None:
self.status = HyperDeckStatus.RECORDING
def getWorksheet(self):
return self.worksheet
@@ -0,0 +1,294 @@
from datetime import datetime, timedelta
import enum
import logging
import os
from typing import List, Optional
from pydantic import BaseModel
from sqlmodel import Enum,Field, Column, SQLModel, Session, create_engine, not_, or_, select
def enum_values(enum_class: type[enum.Enum]) -> list:
"""Get values for enum."""
return [status.value for status in enum_class]
class ScheduleItemStatus(str, enum.Enum):
NEW="new"
WAITING="waiting"
IDLE="idle"
RECORDING="recording"
RECORDED="recorded"
COPYING="copying"
COPIED="copied"
PROCESSING="processing"
FAILED="failed"
DONE="done"
class ScheduleItemSchema(BaseModel):
id: Optional[str] = None
status: ScheduleItemStatus
file: str = ""
title: str = ""
# Tijden
start: Optional[datetime] = None
end: Optional[datetime] = None
# Proces tijden
start_recording: Optional[datetime] = None
end_recording: Optional[datetime] = None
start_copying: Optional[datetime] = None
end_copying: Optional[datetime] = None
start_processing: Optional[datetime] = None
end_processing: Optional[datetime] = None
last_update: Optional[datetime] = None
class Config:
# In v1 moet dit 'orm_mode' zijn in plaats van 'from_attributes'
orm_mode = True
class ScheduleItem(SQLModel, table=True):
status:ScheduleItemStatus = Field(
sa_column=Column(
name="status",
nullable=False,
type_= Enum(ScheduleItemStatus, values_callable=enum_values),
)
)
file:str = ""
start:Optional[datetime] = None
end:Optional[datetime] = None
id: str | None = Field(default=None, primary_key=True)
start_recording:datetime = None
end_recording:datetime = None
start_copying:datetime = None
end_copying:datetime = None
start_processing:datetime = None
end_processing:datetime = None
title:str = ""
last_update:datetime = None
def to_schema(self) -> ScheduleItemSchema:
"""
Converteert het database-object naar een sessie-loos Pydantic model.
Dit voorkomt DetachedInstanceErrors buiten de database scope.
"""
return ScheduleItemSchema.from_orm(self)
# Je kunt dit ook integreren in je assign of create logica
@classmethod
def from_schema(cls, schema: ScheduleItemSchema):
"""Maakt een database-object aan op basis van een schema."""
return cls(**schema.dict())
def assign(self, item):
self.status = item.status
self.title = item.title
self.end_processing = item.end_processing
self.end_copying = item.end_copying
self.end_recording = item.end_recording
self.last_update = datetime.now()
def create(start: datetime, end: datetime, title: str) :
retval = ScheduleItem()
retval.start = start.replace(tzinfo=None)
if end:
retval.end = end.replace(tzinfo=None)
if retval.end <= retval.start:
raise Exception("End time must be later then start time")
retval.last_update = datetime.now()
retval.id = start.strftime("R%Y%m%d%H%M%S")
retval.title = title
retval.status = ScheduleItemStatus.WAITING
retval.file = "vr_" + start.strftime("%Y%m%d%H%M%S")
return retval
def setCopying(self):
self.status = ScheduleItemStatus.COPYING
self.start_copying = datetime.now().replace(microsecond=0)
def setProcessing(self):
self.status = ScheduleItemStatus.PROCESSING
self.start_processing = datetime.now().replace(microsecond=0)
def fromJson(j):
retval = ScheduleItem()
retval.start = datetime.fromisoformat(j["start"])
if "title" in j:
retval.title = j["title"]
else:
retval.title = None
if "end" in j:
retval.end = datetime.fromisoformat(j["end"])
else:
retval.end = None
if "start_recording" in j and j["start_recording"]:
retval.start_recording = datetime.fromisoformat(
j["start_recording"]
)
if "end_recording" in j and j["end_recording"]:
retval.end_recording = datetime.fromisoformat(j["end_recording"])
if "start_copying" in j and j["start_copying"]:
retval.start_copying = datetime.fromisoformat(j["start_copying"])
if "end_copying" in j and j["end_copying"]:
retval.end_copying = datetime.fromisoformat(j["end_copying"])
if "start_processing" in j and j["start_processing"]:
retval.start_processing = datetime.fromisoformat(
j["start_processing"]
)
if "end_processing" in j and j["end_processing"]:
retval.end_processing = datetime.fromisoformat(j["end_processing"])
retval.id = retval.start.strftime("R%Y%m%d%H%M%S")
retval.status = j["status"]
retval.file = j["file"]
return retval
class Schedule:
def __init__(self):
pass
def saveItems(self, items: List[ScheduleItem]):
with Session(engine) as session:
for item in items:
item.last_update = datetime.now()
session.add(item)
session.commit()
def saveItem(self, item: ScheduleItem):
with Session(engine) as session:
item.last_update = datetime.now()
session.add(item)
session.commit()
def removeItem(self, item: ScheduleItem):
with Session(engine) as session:
session.delete(item)
session.commit()
def getItemsOlderThan(self, age:int) -> List[ScheduleItem]:
periode = timedelta(days=age)
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.start < datetime.now()-periode)
return session.exec(statement).all()
def getFree(self, start: datetime, end: datetime, exclude_id: str) -> bool:
with Session(engine) as session:
statement = select(ScheduleItem).where(
not_(or_(
ScheduleItem.id == exclude_id,
ScheduleItem.status != ScheduleItemStatus.WAITING,
ScheduleItem.start >= end,
not ScheduleItem.end,
ScheduleItem.end <= start
)))
return session.exec(statement).first() is None
def getNextScheduled(self):
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.start > datetime.now()).order_by(ScheduleItem.start)
session.exec(statement).first()
def getItemsLastDay(self):
time = datetime.now()-timedelta(days=1)
with Session(engine) as session:
statement = select(ScheduleItem).where(
or_(
ScheduleItem.start_copying > time,
ScheduleItem.start_processing > time,
ScheduleItem.start_recording > time,
ScheduleItem.last_update > time
)).order_by(ScheduleItem.start
)
return session.exec(statement).all()
def getAll(self):
with Session(engine) as session:
statement = select(ScheduleItem).order_by(ScheduleItem.start)
return session.exec(statement).all()
def getActiveItems(self):
with Session(engine) as session:
statement = select(ScheduleItem).where(
ScheduleItem.status.in_([ScheduleItemStatus.PROCESSING,ScheduleItemStatus.COPYING])
)
return session.exec(statement).all()
def getItemsByStatus(self, statusses: List[ScheduleItem]):
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.status.in_( statusses))
return session.exec(statement).all()
def returnFirstByStatus(self, status: ScheduleItemStatus) -> ScheduleItem|None:
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.status == status).order_by(ScheduleItem.start)
return session.exec(statement).first()
def getNextCopying(self) -> ScheduleItem:
item = self.returnFirstByStatus(ScheduleItemStatus.RECORDED)
if item:
id = item.id
item.setCopying()
self.saveItem(item)
item = self.getScheduleItem(id)
return item
def getNextProcessing(self) -> ScheduleItem:
item = self.returnFirstByStatus(ScheduleItemStatus.COPIED)
if item:
id = item.id
item.setProcessing()
self.saveItem(item)
item = self.getScheduleItem(id)
return item
def getByFile(self, filename):
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.file == filename)
return session.exec(statement).first()
def getScheduleItemByFile(self, filename) -> ScheduleItem:
with Session(engine) as session:
statement = select(ScheduleItem).where("c_" +ScheduleItem.file+ ".mp4" == filename)
return session.exec(statement).first()
def getScheduleItem(self, id:str) -> ScheduleItem:
with Session(engine) as session:
statement = select(ScheduleItem).where(ScheduleItem.id == id)
return session.exec(statement).first()
sqlite_file_name =os.path.join(os.getcwd(), "hyperdeck.db")
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
@@ -0,0 +1,75 @@
from ftplib import FTP
from hyperdeck.application import HyperDeck
import logging,os ,re
logger = logging.getLogger('tasklogger')
def downloadFiles(name) -> bool:
app = HyperDeck()
logger.info("Download file " + name)
with FTP(host=app.config.hyperdeck_ip, user='anonymous', passwd='secret') as ftp:
logger.info("Connected to FTP")
logger.debug(ftp.getwelcome())
files = ftp.nlst('/1')
logger.info("{} files found".format(len(files)))
cnt = 0
found = False
for f in files:
if f.startswith( name) and f.endswith('.mp4'):
if re.search("_[0-9]{4}\.mp4$",f):
nr = f[-8:-4]
else:
nr = str(cnt).zfill(4)
targetfile = os.path.join(app.config.videopath_originals, nr + '_' + name + ".mp4")
logger.info("Retrieving file " + f + ' to '+targetfile)
if os.path.isfile(targetfile):
logger.error("Target file already exists: " + targetfile)
return False
with open(targetfile, 'wb') as fp:
ftp.retrbinary('RETR /1/' + f, fp.write)
ftp.delete('/1/' + f)
found = True
cnt += 1
files = ftp.nlst('/2')
for f in files:
if f.startswith( name) and f.endswith('.mp4'):
if re.search("_[0-9]{4}\.mp4$",f):
nr = f[-8:-4]
else:
nr = str(cnt).zfill(4)
targetfile = os.path.join(app.config.videopath_originals, nr + '_' + name + ".mp4")
logger.info("Retrieving file " + f + ' to '+targetfile)
if os.path.isfile(targetfile):
logger.error("Target file already exists: " + targetfile)
return False
with open(targetfile, 'wb') as fp:
ftp.retrbinary('RETR /2/' + f, fp.write)
ftp.delete('/2/' + f)
found = True
cnt += 1
if found:
return True
logger.error("File not found: " + name)
return False
logger.error("Unable to connect to FTP")
return False
@@ -0,0 +1,40 @@
from json import JSONEncoder
import json
import logging
import os
from hyperdeck.db import ScheduleItem
class ScheduleItemEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, ScheduleItem):
return o.toJson()
return json.JSONEncoder.default(self, o)
def ScheduleItemDecoder(j):
print(j)
return ScheduleItem.fromJson(j)
class jsonconverter:
def loadschedule(self):
try:
with open(os.path.join(os.getcwd(), "schedule.json"), "r") as f:
self.schedule = json.load(f, object_hook=ScheduleItemDecoder)
except Exception as e:
logging.error("Unable to load schedule" + repr(e))
self.schedule = []
raise e
def saveSchedule(self):
with open(os.path.join(os.getcwd(), "schedule.json"), "w") as f:
f.write(json.dumps(self.schedule, cls=ScheduleItemEncoder))
@@ -0,0 +1,148 @@
import logging
from rocketry import Rocketry
from rocketry.conds import every
from hyperdeck.application import HyperDeck
import datetime
from hyperdeck.db import ScheduleItemStatus
from hyperdeck.download import downloadFiles
from hyperdeck.transcode import transcodeFile
import os
import multiprocessing
from rocketry.conds import (
minutely, daily
)
manager = multiprocessing.Manager()
copyqueue = manager.Queue()
copyqueue_done = manager.Queue()
processqueue= manager.Queue()
application = HyperDeck()
# Creating the Rocketry app
app = Rocketry(config={"task_execution": "async"})
formatter = logging.Formatter('[%(asctime)s] %(message)s')
handler =logging.FileHandler(os.path.join(application.config.log_path, "scheduler.log"))
handler.setFormatter(formatter)
tasklogger = logging.getLogger("rocketry.task")
tasklogger.setLevel(logging.INFO)
tasklogger.addHandler(handler)
logger = logging.getLogger('tasklogger')
handler =logging.FileHandler(os.path.join(application.config.log_path, "task.log") )
handler.setFormatter(formatter)
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Creating some tasks
logger.info("Startup, checking for in progress items")
for s in application.schedule.getActiveItems():
if s.status == ScheduleItemStatus.COPYING:
s.start_copying = datetime.datetime.now().replace(microsecond=0)
logger.info("File " + s.file + " marked for copying, queuing")
copyqueue.put(s.to_schema())
if s.status == ScheduleItemStatus.PROCESSING:
s.start_processing = datetime.datetime.now().replace(microsecond=0)
logger.info("File " + s.file + " marked for processing, queuing")
processqueue.put(s.to_schema())
@app.task(minutely)
def do_checkschedule():
application.processSchedule()
@app.task(every('10 seconds'))
def get_NewFileName():
if not copyqueue_done.empty():
item = copyqueue_done.get()
logger.info("Process queue output for " + item.file)
itemdb=application.schedule.getScheduleItem(item.id)
itemdb.assign(item)
application.schedule.saveItem(itemdb)
if copyqueue.empty():
currentcopy = application.schedule.getNextCopying()
if currentcopy:
logger.info("Add to copy queue " + currentcopy.file)
copyqueue.put(currentcopy.to_schema())
if processqueue.empty():
currentcopy = application.schedule.getNextProcessing()
if currentcopy:
logger.info("Add to process queue " + currentcopy.file)
processqueue.put(currentcopy.to_schema())
@app.task(minutely,execution="process",timeout=60*60*24)
def do_copyfile(q=copyqueue,qd=copyqueue_done ):
if q.empty():
return
item = copyqueue.get()
logger.info("Start copying " + item.file)
if not downloadFiles(item.file):
item.status = ScheduleItemStatus.FAILED
logger.info("Copying failed for " + item.file)
else:
item.status = ScheduleItemStatus.COPIED
item.end_copying = datetime.datetime.now().replace(microsecond=0)
logger.info("Copying success for " + item.file)
qd.put(item)
return
@app.task(daily,execution="process",timeout=60*60*24)
def do_cleanup():
application.cleanup()
@app.task(minutely,execution="process",timeout=60*60*24)
def do_processfile(q=processqueue,qd=copyqueue_done ):
if q.empty():
return
item = processqueue.get()
logger.info("Start processing " + item.file)
if not transcodeFile(item.file):
item.status = ScheduleItemStatus.FAILED
else:
item.status = ScheduleItemStatus.DONE
item.end_processing = datetime.datetime.now().replace(microsecond=0)
qd.put(item)
return
#time.sleep(5)
#raise TaskTerminationException()
if __name__ == "__main__":
# If this script is run, only Rocketry is run
app.run()
@@ -0,0 +1,34 @@
import os
from hyperdeck.application import HyperDeck
import logging
logger = logging.getLogger('tasklogger')
def transcodeFile(name) -> bool:
app = HyperDeck()
filename= name + ".mp4"
tofind = os.path.join(app.config.videopath_originals, '*' + name + ".mp4")
convertedfile = os.path.join(app.config.videopath_convert, 'c_' + filename)
targetfile = os.path.join(app.config.videopath, 'c_' + filename)
r= os.system('for f in ' + tofind + '; do echo "file \'$f\'" ; done > /tmp/processlist.txt')
logger.info("Transcode file " + filename)
logfile = app.config.log_path + name +'.log'
r= os.system('/usr/bin/ffmpeg -hide_banner -loglevel warning -nostats -f concat -safe 0 -i /tmp/processlist.txt -y -c:v libx264 -preset medium -b:v 3000k -maxrate 3000k -bufsize 6000k -vf "scale=1280:-1,format=yuv420p" -g 50 -c:a aac -b:a 128k -ac 2 -ar 44100 -threads 2 ' + convertedfile + ' > ' + logfile)
logger.info("Converting done for file " + filename)
if not os.path.isfile(convertedfile):
logger.error("Converted file {} not found, transcode failed".format(convertedfile))
return False
os.rename(convertedfile , targetfile)
return r==0
@@ -0,0 +1,8 @@
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
@@ -0,0 +1,70 @@
import logging
import os
import sys
import getopt
from hyperdeck.application import HyperDeck
import asyncio
import uvicorn
from hyperdeck.db import create_db_and_tables
from hyperdeck.jsondb import jsonconverter
logging.basicConfig(level=logging.INFO)
opts, args = getopt.getopt(sys.argv[1:], "c:o:")
configfile = "config.json"
for opt, arg in opts:
if opt == "-h":
print("test.py -c <config>")
sys.exit()
elif opt in ("-c"):
configfile = arg
create_db_and_tables()
hApp = HyperDeck()
hApp.load_config(configfile)
if os.path.exists("schedule.json"):
logging.info("Found schedule.json, loading")
conv = jsonconverter()
conv.loadschedule()
logging.info("Loading done, creating database")
hApp.schedule.saveItems(conv.schedule)
logging.info("Database ready, moving file")
os.rename("schedule.json","schedule.json.bak")
from hyperdeck.api import app as app_fastapi
from hyperdeck.scheduler import app as app_rocketry
session = app_rocketry.session
class Server(uvicorn.Server):
def handle_exit(self, sig: int, frame) -> None:
app_rocketry.session.shut_down()
return super().handle_exit(sig, frame)
async def app():
server = Server(
config=uvicorn.Config(
app_fastapi, workers=1, host=["0.0.0.0", "::"], port=8180, loop="asyncio"
)
)
api = asyncio.create_task(server.serve())
sched = asyncio.create_task(app_rocketry.serve())
await asyncio.wait([sched, api])
if __name__ == "__main__":
asyncio.run(app())
@@ -0,0 +1 @@
{ "version" : "0.15.2" }
@@ -0,0 +1,17 @@
anyio==4.10.0
click==8.1.3
fastapi==0.116.1
h11==0.16.0
idna==3.10
sniffio==1.3.0
starlette==0.47.3
typing_extensions==4.15.0
pydantic==1.10.10
uvicorn
jinja2
python-multipart
requests
rocketry==2.4.1
opencv-python
ffmpeg-python
sqlmodel
@@ -0,0 +1,24 @@
#!/bin/bash
import sys, getopt,os
from hyperdeck.application import Config, HyperDeck
import logging
logging.basicConfig(level=logging.INFO)
opts, args = getopt.getopt(sys.argv[1:],"c:o:")
configfile="config.json"
for opt, arg in opts:
if opt == '-h':
print ('test.py -c <config>')
sys.exit()
elif opt in ("-c"):
configfile = arg
config = Config(configfile)
logging.info("Setting up connection to: " +config.rtsp_url )
os.system('/usr/bin/ffmpeg -hide_banner -loglevel warning -nostats -re -rtsp_transport tcp -i '+ config.rtsp_url +' -c copy -s hd720 -f flv -y rtmp://localhost/live/livestream > /tmp/ffmpeglive.log')
@@ -0,0 +1,10 @@
from rocketry import Rocketry
app = Rocketry()
@app.task('daily')
def do_things():
print("dd")
if __name__ == "__main__":
app.run()
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
CONTROL_FILE=$(ls -1 *.control)
echo "Using control file $CONTROL_FILE"
PACKAGE_NAME=$(awk '/^Package: /{print $NF}' $CONTROL_FILE)
VERSION=$(awk '/^Version: /{print $NF}' $CONTROL_FILE)
# tag git repo
# git tag -f release-${VERSION}
echo "Building package $PACKAGE_NAME ($VERSION): $PACKAGE_NAME-${VERSION}_all.deb"
# VERSION
sed "s/{VERSION}/${VERSION}/" $PACKAGE_NAME.control > packaging/$PACKAGE_NAME/DEBIAN/control
#create debian package
dpkg --build packaging/$PACKAGE_NAME $PACKAGE_NAME-${VERSION}_all.deb
# dpkg-sig -k 14AF7F05 --sign builder icosi-${VERSION}_all.deb
echo "clean up"
rm packaging/$PACKAGE_NAME/DEBIAN/control
+7
View File
@@ -0,0 +1,7 @@
Package: eendoe-repository
Version: 1.0
Section: utils
Maintainer: Bart Meijer
Architecture: all
Depends:
Description: Eendoe package repository
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
case "$1" in
configure|abort-upgrade)
esac
exit 0
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
case "$1" in
install|upgrade)
;;
esac
exit 0
@@ -0,0 +1,4 @@
#!/usr/bin/make -f
%:
dh $@
@@ -0,0 +1,4 @@
#Eendoe APT package list
deb [arch=amd64] https://repository.eendoe.nl/ stable main
@@ -0,0 +1,4 @@
#Eendoe APT package list
deb [arch=amd64] https://repository.eendoe.nl/ stable main
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
CONTROL_FILE=$(ls -1 *.control)
echo "Using control file $CONTROL_FILE"
PACKAGE_NAME=$(awk '/^Package: /{print $NF}' $CONTROL_FILE)
VERSION=$(awk '/^Version: /{print $NF}' $CONTROL_FILE)
# tag git repo
# git tag -f release-${VERSION}
echo "Building package $PACKAGE_NAME ($VERSION): $PACKAGE_NAME-${VERSION}_all.deb"
# VERSION
sed "s/{VERSION}/${VERSION}/" $PACKAGE_NAME.control > packaging/$PACKAGE_NAME/DEBIAN/control
#create debian package
dpkg --build packaging/$PACKAGE_NAME $PACKAGE_NAME-${VERSION}_all.deb
# dpkg-sig -k 14AF7F05 --sign builder icosi-${VERSION}_all.deb
echo "clean up"
rm packaging/$PACKAGE_NAME/DEBIAN/control
+7
View File
@@ -0,0 +1,7 @@
Package: eendoe-srs
Version: {VERSION}
Section: utils
Maintainer: nl.eendoe.bart
Architecture: all
Depends: python3, python3-pip, ffmpeg
Description: Eendoe Hyperdeck SRS module
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
case "$1" in
configure|abort-upgrade)
;;
esac
exit 0
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
case "$1" in
install|upgrade)
;;
esac
exit 0
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/make -f
%:
dh $@
+230
View File
@@ -0,0 +1,230 @@
#!/bin/bash
### BEGIN INIT INFO
# Provides: ossrs(srs)
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: ossrs(srs)
# Description: https://github.com/ossrs/srs
### END INIT INFO
# the config of ROOT, user must modify it when start srs from other directory,
# it's ok to use the script by command ./etc/init.d/ossrs
ROOT="/usr/share/srs"
APP="/usr/share/srs/objs/srs"
CONFIG="/etc/srs/srs.conf"
DEFAULT_PID_FILE='/tmp/srs.pid'
DEFAULT_LOG_FILE='/tmp/srs.log'
########################################################################
# utility functions
########################################################################
RED="\\033[31m"
GREEN="\\033[32m"
YELLOW="\\033[33m"
BLACK="\\033[0m"
POS="\\033[60G"
ok_msg() {
echo -e "${1}${POS}${BLACK}[${GREEN} OK ${BLACK}]"
}
failed_msg() {
echo -e "${1}${POS}${BLACK}[${RED}FAILED${BLACK}]"
}
# load process info of srs
# @set variable $srs_pid to the process id in srs.pid file.
# @return 0, if process exists; otherwise:
# 1, for pid file not exists.
# 2, for get proecess info by pid failed.
# @set variable $error_msg if error.
# @set variable $pid_file to pid file.
load_process_info() {
# get pid file
pid_file=`cd ${ROOT} && cat ${CONFIG} |grep ^pid|awk '{print $2}'|awk -F ';' '{print $1}'`
if [[ -z $pid_file ]]; then pid_file=${DEFAULT_PID_FILE}; fi
# get abs path
pid_dir=`dirname $pid_file`
pid_file=`(cd ${ROOT}; cd $pid_dir; pwd)`/`basename $pid_file`
srs_pid=`cat $pid_file 2>/dev/null`
ret=$?; if [[ 0 -ne $ret ]]; then error_msg="No pid file $pid_file"; return 1; fi
ps -p ${srs_pid} >/dev/null 2>/dev/null
ret=$?; if [[ 0 -ne $ret ]]; then error_msg="Process $srs_pid does not exists"; return 2; fi
return 0;
}
start() {
# if exists, exit.
load_process_info
if [[ 0 -eq $? ]]; then failed_msg "SRS started(pid ${srs_pid}), should not start it again."; return 1; fi
# not exists, start server
ok_msg "Starting SRS..."
# get log file
log_file=`cd ${ROOT} && cat ${CONFIG} |grep '^srs_log_file'| awk '{print $2}'| awk -F ';' '{print $1}'`
if [[ -z $log_file ]]; then log_file=${DEFAULT_LOG_FILE}; fi
# get abs path
log_dir=`dirname $log_file`
log_file=`(cd ${ROOT} && cd $log_dir && pwd)`/`basename $log_file`
# TODO: FIXME: set limit by, for instance, "ulimit -HSn 10000"
if [[ -z $log_file ]]; then
(ulimit -c unlimited && cd ${ROOT}; ${APP} -c ${CONFIG} >/dev/null 2>&1)
else
(ulimit -c unlimited && cd ${ROOT}; ${APP} -c ${CONFIG} >> $log_file.sys 2>&1)
fi
# check again after start server
for ((i = 0; i < 5; i++)); do
# sleep a little while, for srs may start then crash.
sleep 0.1
load_process_info
ret=$?; if [[ 0 -ne $ret ]]; then
failed_msg "SRS start failed";
failed_msg "see $log_file";
return $ret;
fi
done
# check whether started.
load_process_info
ret=$?; if [[ 0 -eq $ret ]]; then ok_msg "SRS started(pid ${srs_pid})"; return 0; fi
failed_msg "SRS not started"
return $ret
}
stop() {
# not start, exit
load_process_info
if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi
ok_msg "Stopping SRS(pid ${srs_pid})..."
# process exists, try to kill to stop normally
for((i=0;i<100;i++)); do
load_process_info
if [[ 0 -eq $? ]]; then
kill -s SIGTERM ${srs_pid} 2>/dev/null
ret=$?; if [[ 0 -ne $ret ]]; then failed_msg "send signal SIGTERM failed ret=$ret"; return $ret; fi
sleep 0.3
else
ok_msg "SRS stopped by SIGTERM"
# delete the pid file when stop success.
rm -f ${pid_file}
break;
fi
done
# process exists, use kill -9 to force to exit
load_process_info
if [[ 0 -eq $? ]]; then
kill -s SIGKILL ${srs_pid} 2>/dev/null
ret=$?; if [[ 0 -ne $ret ]]; then failed_msg "send signal SIGKILL failed ret=$ret"; return $ret; fi
ok_msg "SRS stopped by SIGKILL"
else
# delete the pid file when stop success.
rm -f ${pid_file}
fi
sleep 0.1
return 0
}
# get the status of srs process
# @return 0 if srs is running; otherwise, 1 for stopped.
status() {
load_process_info
ret=$?; if [[ 0 -eq $ret ]]; then ok_msg "SRS(pid ${srs_pid}) is running."; return 0; fi
failed_msg "Error: $error_msg"
return 1
}
reload() {
# not start, exit
load_process_info
if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi
ok_msg "Reload SRS(pid ${srs_pid})..."
# process exists, reload it
kill -s SIGHUP ${srs_pid} 2>/dev/null
ret=$?; if [[ 0 -ne $ret ]]; then failed_msg "Reload SRS failed ret=$ret"; return $ret; fi
load_process_info
if [[ 0 -ne $? ]]; then failed_msg "SRS reload failed."; return $ret; fi
ok_msg "SRS reloaded"
return 0
}
logrotate() {
# not start, exit
load_process_info
if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi
ok_msg "Reopen log file of SRS(pid ${srs_pid})..."
kill -s SIGUSR1 ${srs_pid}
ok_msg "Log rotated"
return 0
}
grace() {
# not start, exit
load_process_info
if [[ 0 -ne $? ]]; then failed_msg "SRS not start."; return 0; fi
ok_msg "Gracefully quit for SRS(pid ${srs_pid})..."
kill -s SIGQUIT ${srs_pid}
ok_msg "Gracefully quit"
return 0
}
menu() {
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
reload)
reload
;;
rotate)
logrotate
;;
grace)
grace
;;
*)
echo "Usage: $0 {start|stop|status|restart|reload|rotate|grace}"
echo " reload Apply log file by not restarting SRS"
echo " rotate For log rotate, to send SIGUSR1 to SRS to reopen the log file."
echo " grace For gracefully quit, to send SIGQUIT to SRS."
return 1
;;
esac
}
menu $1
code=$?
exit ${code}
@@ -0,0 +1,47 @@
# main config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
pid /tmp/srs.pid;
#srs_log_tank file;
srs_log_file /usr/share/srs/objs/srs.log;
daemon on;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir /usr/share/srs/objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;#
hls_m3u8_file recorder1/[app]/[stream].m3u8;
}
http_remux {
enabled on;
fast_cache 10;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
play{
gop_cache_max_frames 2500;
}
}
@@ -0,0 +1,43 @@
#
# Systemd unit file for Hyperdeck recorder
#
[Unit]
Description=Eendoe Hyperdeck live feed
After=network.target
RequiresMountsFor=/mnt/video
[Service]
# Lifecycle
Type=simple
ExecStart=/usr/bin/python3 /usr/share/hyperdeck/srsfeed.py -c /etc/hyperdeck/config.json
SuccessExitStatus=0
Restart=on-abort
WorkingDirectory=/usr/share/hyperdeck/
# Logging
SyslogIdentifier=hyperdeck_srs
# Security
User=vid
Group=vid
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,53 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
http_api {
enabled on;
listen 8080;
}
stats {
network 0;
}
rtc_server {
enabled on;
# Listen at udp://8000
listen 8000;
#
# The $CANDIDATE means fetch from env, if not configed, use * as default.
#
# The * means retrieving server IP automatically, from all network interfaces,
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
rtc {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
ingest livestream {
enabled on;
input {
type file;
url ./doc/source.200kbps.768x320.flv;
}
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine {
enabled off;
output rtmp://127.0.0.1:[port]/live/livestream;
}
}
}
@@ -0,0 +1,53 @@
# SRT config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 8080;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
srt_server {
enabled on;
listen 10080;
maxbw 1000000000;
connect_timeout 4000;
peerlatency 0;
recvlatency 0;
latency 0;
tsbpdmode off;
tlpktdrop off;
sendbuf 2000000;
recvbuf 2000000;
}
# @doc https://github.com/ossrs/srs/issues/1147#issuecomment-577607026
vhost __defaultVhost__ {
srt {
enabled on;
srt_to_rtmp on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
# For SRT to use vhost.
vhost srs.srt.com.cn {
}
stats {
network 0;
disk sda sdb xvda xvdb;
}
@@ -0,0 +1,66 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
http_api {
enabled on;
listen 8080;
}
stats {
network 0;
}
rtc_server {
enabled on;
# Listen at udp://8000
listen 8000;
#
# The $CANDIDATE means fetch from env, if not configed, use * as default.
#
# The * means retrieving server IP automatically, from all network interfaces,
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
srt_server {
enabled on;
listen 10080;
maxbw 1000000000;
connect_timeout 4000;
peerlatency 0;
recvlatency 0;
latency 0;
tsbpdmode off;
tlpktdrop off;
sendbuf 2000000;
recvbuf 2000000;
}
vhost __defaultVhost__ {
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
hls {
enabled on;
}
srt {
enabled on;
srt_to_rtmp on;
}
}
@@ -0,0 +1,79 @@
listen 1935;
pid ./objs/srs.pid;
srs_log_tank console;
srs_log_level trace;
max_connections 1000;
daemon off;
http_api {
enabled on;
listen 1985;
crossdomain on;
raw_api {
enabled on;
allow_reload on;
allow_query on;
allow_update on;
}
}
# for SRS1.
http_stream {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
# for SRS1.
http {
enabled on;
mount [vhost]/hls;
dir ./objs/nginx/html/hls;
}
# for SRS1.
refer github.com github.io;
refer_publish github.com github.io;
refer_play github.com github.io;
# for SRS2
publish_1stpkt_timeout 20000;
publish_normal_timeout 7000;
# for SRS2
mr {
enabled off;
latency 350;
}
# for SRS1
mode remote;
origin 127.0.0.1:1935 localhost:1935;
token_traverse off;
vhost same.edge.srs.com;
debug_srs_upnode off;
# for SRS1
forward 127.0.0.1:1936 127.0.0.1:1937;
# for SRS1
time_jitter full;
# for SRS2
mix_correct off;
#for SRS1
atc on;
atc_auto on;
# for SRS2
mw_latency 100;
# for SRS1
gop_cache off;
queue_length 10;
# for SRS2
send_min_interval 10.0;
reduce_sequence_header on;
}
@@ -0,0 +1,37 @@
# no-daemon and write log to console config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp on;
}
}
@@ -0,0 +1,17 @@
# no-daemon and write log to console config for srs.
# @see full.conf for detail config.
listen 1935 [::]:1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
vhost __defaultVhost__ {
}
@@ -0,0 +1,23 @@
# the config for srs to delivery dash
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-dash
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
dash {
enabled on;
dash_fragment 10;
dash_update_period 10;
dash_window_size 3;
dash_path ./objs/nginx/html;
dash_mpd_file [app]/[stream].mpd;
}
}
@@ -0,0 +1,22 @@
# the config for srs demo
# @see full.conf for detail config.
listen 19350;
max_connections 1000;
daemon off;
srs_log_tank console;
pid ./objs/srs.demo.19350.pid;
vhost __defaultVhost__ {
enabled on;
play {
gop_cache on;
}
hls {
enabled on;
hls_path ./objs/nginx/html/forward;
hls_fragment 5;
hls_window 30;
}
}
@@ -0,0 +1,205 @@
# the config for srs demo
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
pid ./objs/srs.demo.pid;
chunk_size 60000;
max_connections 2000;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
enabled on;
gop_cache on;
}
vhost demo.srs.com {
chunk_size 60000;
enabled on;
gop_cache on;
queue_length 30;
forward {
enabled on;
destination 127.0.0.1:19350;
}
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 5;
hls_window 30;
}
http_hooks {
enabled on;
on_publish http://127.0.0.1:8085/api/v1/streams;
on_unpublish http://127.0.0.1:8085/api/v1/streams;
on_play http://127.0.0.1:8085/api/v1/sessions;
on_stop http://127.0.0.1:8085/api/v1/sessions;
}
transcode {
enabled on;
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine ld {
enabled on;
vfilter {
i ./doc/srs-logo.png;
filter_complex 'overlay=10:10';
}
vcodec libx264;
vbitrate 300;
vfps 20;
vwidth 768;
vheight 320;
vthreads 1;
vprofile baseline;
vpreset superfast;
vparams {
}
acodec libfdk_aac;
abitrate 45;
asample_rate 44100;
achannels 2;
aparams {
}
output rtmp://127.0.0.1:[port]/[app]/[stream]_[engine]?vhost=[vhost];
}
engine sd {
enabled on;
vfilter {
vf 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2';
}
vcodec libx264;
vbitrate 500;
vfps 20;
vwidth 768;
vheight 320;
vthreads 1;
vprofile main;
vpreset fast;
vparams {
}
acodec libfdk_aac;
abitrate 40;
asample_rate 44100;
achannels 2;
aparams {
}
output rtmp://127.0.0.1:[port]/[app]/[stream]_[engine]?vhost=[vhost];
}
}
ingest {
enabled on;
input {
type file;
url ./doc/source.200kbps.768x320.flv;
}
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine {
enabled off;
output rtmp://127.0.0.1:[port]/live/livestream?vhost=[vhost];
}
}
}
vhost players {
enabled on;
gop_cache on;
transcode {
enabled on;
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine hls {
enabled on;
vfilter {
i ./doc/srs-logo.png;
filter_complex 'overlay=10:10';
}
vcodec libx264;
vbitrate 300;
vfps 20;
vwidth 768;
vheight 320;
vthreads 1;
vprofile baseline;
vpreset superfast;
vparams {
g 100;
}
acodec libfdk_aac;
abitrate 30;
asample_rate 44100;
achannels 2;
aparams {
}
output rtmp://127.0.0.1:[port]/[app]?vhost=players_pub/[stream];
}
}
ingest {
enabled on;
input {
type file;
url ./doc/source.200kbps.768x320.flv;
}
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine {
enabled off;
output rtmp://127.0.0.1:[port]/live/demo?vhost=[vhost];
}
}
}
vhost players_pub {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 5;
hls_window 30;
}
}
vhost players_chat {
gop_cache off;
hls {
enabled off;
}
transcode {
enabled on;
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine chat_hls {
enabled on;
vfilter {
}
vcodec libx264;
vbitrate 150;
vfps 15;
vwidth 576;
vheight 240;
vthreads 1;
vprofile baseline;
vpreset ultrafast;
vparams {
g 30;
}
acodec an;
output rtmp://127.0.0.1:[port]/[app]?vhost=chat_hls/[stream];
}
}
}
vhost chat_hls {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 2;
hls_window 10;
}
}
@@ -0,0 +1,40 @@
# docker config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
# For docker, please use docker logs to manage the logs of SRS.
# See https://docs.docker.com/config/containers/logging/
srs_log_tank console;
daemon off;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp on;
}
}
@@ -0,0 +1,15 @@
# the config for srs to dvr in session mode
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/dvr
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4;
dvr_plan session;
}
}
@@ -0,0 +1,18 @@
# the config for srs to dvr in custom path.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/dvr#custom-path
# @see https://ossrs.io/lts/en-us/docs/v4/doc/dvr#custom-path
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv;
dvr_plan segment;
dvr_duration 30;
dvr_wait_keyframe on;
}
}
@@ -0,0 +1,17 @@
# the config for srs to dvr in segment mode
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/dvr
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv;
dvr_plan segment;
dvr_duration 30;
dvr_wait_keyframe on;
}
}
@@ -0,0 +1,15 @@
# the config for srs to dvr in session mode
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/dvr
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
dvr {
enabled on;
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].flv;
dvr_plan session;
}
}
@@ -0,0 +1,24 @@
# the config for srs origin-edge cluster
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/edge
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
pid objs/edge.pid;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1:19350;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
@@ -0,0 +1,15 @@
# the config for srs for token traverse authentication
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/drm
# @see full.conf for detail config.
listen 1935
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1:19350;
token_traverse on;
}
}
@@ -0,0 +1,15 @@
# the config for srs origin-edge cluster
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/edge
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
pid objs/edge2.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1:19350;
}
}
@@ -0,0 +1,14 @@
# the config for srs to support nginx-rtmp exec.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/nginx-exec
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
exec {
enabled on;
publish ./objs/ffmpeg/bin/ffmpeg -f flv -i [url] -c copy -y ./[stream].flv;
}
}
@@ -0,0 +1,29 @@
# the config for srs use ffmpeg to transcode
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-ffmpeg
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
transcode {
enabled on;
ffmpeg ./objs/ffmpeg/bin/ffmpeg;
engine ff {
enabled on;
vfilter {
}
vcodec libx264;
vthreads 4;
vprofile main;
vpreset medium;
vparams {
}
acodec libfdk_aac;
aparams {
}
output rtmp://127.0.0.1:[port]/[app]/[stream]_[engine]?vhost=[vhost];
}
}
}
@@ -0,0 +1,34 @@
listen 1935;
max_connections 1000;
daemon on; disable_daemon_for_docker off; srs_log_tank file;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
}
@@ -0,0 +1,15 @@
# the config for srs to forward to slave service
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-forward
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
pid ./objs/srs.backend.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
forward {
enabled on;
backend http://127.0.0.1:8085/api/v1/forward;
}
}
@@ -0,0 +1,15 @@
# the config for srs to forward
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-forward
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
pid ./objs/srs.master.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
forward {
enabled on;
destination 127.0.0.1:19350;
}
}
@@ -0,0 +1,11 @@
# the config for srs to forward
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-forward
# @see full.conf for detail config.
listen 19350;
max_connections 1000;
pid ./objs/srs.slave.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
stream_caster {
enabled on;
caster gb28181;
output rtmp://127.0.0.1/live/[stream];
listen 9000;
sip {
enabled on;
listen 5060;
# @see https://ossrs.net/lts/zh-cn/docs/v5/doc/gb28181#config-candidate
candidate $CANDIDATE;
}
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
http_api {
enabled on;
listen 1985;
}
stats {
network 0;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
hls {
enabled on;
}
}
@@ -0,0 +1,15 @@
# the config for srs origin-edge cluster
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/edge
# @see full.conf for detail config.
listen 19351;
max_connections 1000;
pid objs/edge1.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1:19350;
}
}
@@ -0,0 +1,15 @@
# the config for srs origin-edge cluster
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/edge
# @see full.conf for detail config.
listen 19352;
max_connections 1000;
pid objs/edge2.pid;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1:19350;
}
}
@@ -0,0 +1,16 @@
# the config for srs to delivery hds
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
hds {
enabled on;
hds_fragment 10;
hds_window 60;
hds_path ./objs/nginx/html;
}
}
@@ -0,0 +1,26 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
srt_server {
enabled on;
listen 10080;
}
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
vhost __defaultVhost__ {
srt {
enabled on;
srt_to_rtmp on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
@@ -0,0 +1,26 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
srt_server {
enabled on;
listen 10080;
}
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
}
vhost __defaultVhost__ {
srt {
enabled on;
srt_to_rtmp on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].ts;
}
}
@@ -0,0 +1,21 @@
# the config for srs to delivery hls
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-hls
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
hls {
enabled on;
hls_path ./objs/nginx/html;
hls_fragment 10;
hls_window 60;
}
}
@@ -0,0 +1,39 @@
# Note that this is for NGINX server, not SRS.
worker_processes 3;
events {
worker_connections 10240;
}
http {
# For Proxy Cache.
proxy_cache_path /tmp/nginx-cache levels=1:2 keys_zone=srs_cache:8m max_size=1000m inactive=600m;
proxy_temp_path /tmp/nginx-cache/tmp;
server {
listen 8081;
# For Proxy Cache.
proxy_cache_valid 404 10s;
proxy_cache_lock on;
proxy_cache_lock_age 300s;
proxy_cache_lock_timeout 300s;
proxy_cache_min_uses 1;
location ~ /.+/.*\.(m3u8)$ {
proxy_pass http://127.0.0.1:8080$request_uri;
# For Proxy Cache.
proxy_cache srs_cache;
proxy_cache_key $scheme$proxy_host$uri$args;
proxy_cache_valid 200 302 10s;
}
location ~ /.+/.*\.(ts)$ {
proxy_pass http://127.0.0.1:8080$request_uri;
# For Proxy Cache.
proxy_cache srs_cache;
proxy_cache_key $scheme$proxy_host$uri;
proxy_cache_valid 200 302 60m;
}
}
}
@@ -0,0 +1,17 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
}
vhost __defaultVhost__ {
hls {
enabled on;
# Note that it will make NGINX edge cache always missed, so never enable HLS streaming if use NGINX edges.
hls_ctx off;
}
}
@@ -0,0 +1,21 @@
# the config for srs to delivery realtime RTMP stream
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-realtime
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
hls {
enabled on;
hls_fragment 0.2;
hls_window 2;
hls_wait_keyframe off;
}
}
@@ -0,0 +1,20 @@
# the config for srs to remux rtmp to aac live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
fast_cache 30;
mount [vhost]/[app]/[stream].aac;
}
}
@@ -0,0 +1,21 @@
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
auth {
enabled on;
username admin;
password admin;
}
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
}
@@ -0,0 +1,38 @@
# main config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
}
@@ -0,0 +1,38 @@
# main config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 8080;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
}
@@ -0,0 +1,50 @@
# main config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 8080;
https {
enabled on;
listen 8088;
key ./conf/server.key;
cert ./conf/server.crt;
}
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
https {
enabled on;
listen 8088;
key ./conf/server.key;
cert ./conf/server.crt;
}
}
rtc_server {
enabled on;
listen 8000; # UDP port
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#config-candidate
candidate $CANDIDATE;
}
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
rtc {
enabled on;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtmp-to-rtc
rtmp_to_rtc off;
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/webrtc#rtc-to-rtmp
rtc_to_rtmp off;
}
}
@@ -0,0 +1,26 @@
# main config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_api {
enabled on;
listen 1985;
raw_api {
enabled on;
allow_reload on;
}
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
stats {
network 0;
disk sda sdb xvda xvdb;
}
vhost __defaultVhost__ {
}
@@ -0,0 +1,19 @@
# the config for srs to remux rtmp to flv live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
@@ -0,0 +1,24 @@
# the config for srs to remux rtmp to flv live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 19351;
max_connections 1000;
pid objs/srs.flv.19351.pid;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8081;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
@@ -0,0 +1,24 @@
# the config for srs to remux rtmp to flv live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 19352;
max_connections 1000;
pid objs/srs.flv.19352.pid;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8082;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
cluster {
mode remote;
origin 127.0.0.1;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}
@@ -0,0 +1,25 @@
# the config for srs http heartbeat, report its info to api-server
# @see full.conf for detail config.
listen 1935
max_connections 1000;
daemon off;
srs_log_tank console;
heartbeat {
enabled on;
interval 9.3;
# for python api-server
url http://127.0.0.1:8085/api/v1/servers;
device_id "my-srs-device";
# for ossrs.net monitor, device_id is the key genereated by bsm.
#url http://www.ossrs.net:1977/api/v1/robots/servers;
#device_id "35c9b402c12a7246868752e2878f7e0e";
# with detail summaries
summaries on;
}
stats {
network 0;
disk sda sdb xvda xvdb;
}
vhost __defaultVhost__ {
}
@@ -0,0 +1,23 @@
# the config for srs to delivery hls
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-hls
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
hls {
enabled on;
hls_fragment 10;
hls_window 60;
hls_path ./objs/nginx/html;
hls_m3u8_file [app]/[stream].m3u8;
hls_ts_file [app]/[stream]-[seq].ts;
}
}
@@ -0,0 +1,16 @@
# http-hooks or http-callbacks config for srs.
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
vhost __defaultVhost__ {
http_hooks {
enabled on;
on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams;
on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams;
on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions;
on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions;
}
}
@@ -0,0 +1,20 @@
# the config for srs to remux rtmp to mp3 live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
fast_cache 30;
mount [vhost]/[app]/[stream].mp3;
}
}
@@ -0,0 +1,19 @@
# the config for srs to serve as http server
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost ossrs.net {
http_static {
enabled on;
mount [vhost]/;
dir ./objs/nginx/html;
}
}
@@ -0,0 +1,19 @@
# the config for srs to remux rtmp to ts live stream.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-http-flv
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
http_remux {
enabled on;
mount [vhost]/[app]/[stream].ts;
}
}

Some files were not shown because too many files have changed in this diff Show More