Add v16.7.2
This commit is contained in:
parent
8e70517dff
commit
9c0b535199
2368 changed files with 687657 additions and 1 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
/data/**
|
||||||
|
/test/**
|
||||||
|
Dockerfile*
|
||||||
|
*.md
|
||||||
|
.github/*
|
||||||
|
.github_build/*
|
||||||
20
.editorconfig
Normal file
20
.editorconfig
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# For more information about the properties used in
|
||||||
|
# this file, please see the EditorConfig documentation:
|
||||||
|
# http://editorconfig.org/
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = tab
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
spaces_around_brackets = outside
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.patch]
|
||||||
|
indent_style = space
|
||||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
/core*
|
||||||
|
/import*
|
||||||
|
/data/**
|
||||||
|
/test/**
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
*.ts
|
||||||
|
*.ts.tmp
|
||||||
|
*.m3u8
|
||||||
|
|
||||||
|
*.mp4
|
||||||
|
*.avi
|
||||||
|
*.flv
|
||||||
|
|
||||||
|
.VSCodeCounter
|
||||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
ARG GOLANG_IMAGE=golang:1.17.6-alpine3.15
|
||||||
|
|
||||||
|
ARG BUILD_IMAGE=alpine:3.15
|
||||||
|
|
||||||
|
FROM $GOLANG_IMAGE as builder
|
||||||
|
|
||||||
|
COPY . /dist/core
|
||||||
|
|
||||||
|
RUN apk add \
|
||||||
|
git \
|
||||||
|
make && \
|
||||||
|
cd /dist/core && \
|
||||||
|
go version && \
|
||||||
|
make release && \
|
||||||
|
make import
|
||||||
|
|
||||||
|
FROM $BUILD_IMAGE
|
||||||
|
|
||||||
|
COPY --from=builder /dist/core/core /core/bin/core
|
||||||
|
COPY --from=builder /dist/core/import /core/bin/import
|
||||||
|
COPY --from=builder /dist/core/mime.types /core/mime.types
|
||||||
|
COPY --from=builder /dist/core/run.sh /core/bin/run.sh
|
||||||
|
|
||||||
|
RUN mkdir /core/config /core/data
|
||||||
|
|
||||||
|
ENV CORE_CONFIGFILE=/core/config/config.json
|
||||||
|
ENV CORE_STORAGE_DISK_DIR=/core/data
|
||||||
|
ENV CORE_DB_DIR=/core/config
|
||||||
|
|
||||||
|
VOLUME ["/core/data", "/core/config"]
|
||||||
|
ENTRYPOINT ["/core/bin/run.sh"]
|
||||||
|
WORKDIR /core
|
||||||
33
Dockerfile.bundle
Normal file
33
Dockerfile.bundle
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
ARG GOLANG_IMAGE=golang:1.18.2-alpine3.15
|
||||||
|
|
||||||
|
ARG FFMPEG_IMAGE=datarhei/base:alpine-ffmpeg-3.15-4.4.2
|
||||||
|
|
||||||
|
FROM $GOLANG_IMAGE as builder
|
||||||
|
|
||||||
|
COPY . /dist/core
|
||||||
|
|
||||||
|
RUN apk add \
|
||||||
|
git \
|
||||||
|
make && \
|
||||||
|
cd /dist/core && \
|
||||||
|
go version && \
|
||||||
|
make release && \
|
||||||
|
make import
|
||||||
|
|
||||||
|
FROM $FFMPEG_IMAGE
|
||||||
|
|
||||||
|
COPY --from=builder /dist/core/core /core/bin/core
|
||||||
|
COPY --from=builder /dist/core/import /core/bin/import
|
||||||
|
COPY --from=builder /dist/core/mime.types /core/mime.types
|
||||||
|
COPY --from=builder /dist/core/run.sh /core/bin/run.sh
|
||||||
|
|
||||||
|
RUN ffmpeg -buildconf && \
|
||||||
|
mkdir /core/config /core/data
|
||||||
|
|
||||||
|
ENV CORE_CONFIGFILE=/core/config/config.json
|
||||||
|
ENV CORE_STORAGE_DISK_DIR=/core/data
|
||||||
|
ENV CORE_DB_DIR=/core/config
|
||||||
|
|
||||||
|
VOLUME ["/core/data", "/core/config"]
|
||||||
|
ENTRYPOINT ["/core/bin/run.sh"]
|
||||||
|
WORKDIR /core
|
||||||
18
Jenkinsfile
vendored
Normal file
18
Jenkinsfile
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
triggers {
|
||||||
|
cron('H 0 * * 6')
|
||||||
|
}
|
||||||
|
options {
|
||||||
|
buildDiscarder(logRotator(numToKeepStr: '5', artifactNumToKeepStr: '5'))
|
||||||
|
}
|
||||||
|
stages {
|
||||||
|
stage('Build AMD64') {
|
||||||
|
steps {
|
||||||
|
sh 'docker build -f Dockerfile -t docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64 .'
|
||||||
|
sh 'docker push docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64'
|
||||||
|
sh 'docker rmi docker-registry.marathon.l4lb.thisdcos.directory:5000/core:amd64'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Makefile
Normal file
83
Makefile
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
COMMIT := $(shell if [ -d .git ]; then git rev-parse HEAD; else echo "unknown"; fi)
|
||||||
|
SHORTCOMMIT := $(shell echo $(COMMIT) | head -c 7)
|
||||||
|
BRANCH := $(shell if [ -d .git ]; then git rev-parse --abbrev-ref HEAD; else echo "master"; fi)
|
||||||
|
BUILD := $(shell date -u "+%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
OSARCH := $(shell if [ "${GOOS}" -a "${GOARCH}" ]; then echo "-${GOOS}-${GOARCH}"; else echo ""; fi)
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
## build: Build core (default)
|
||||||
|
build:
|
||||||
|
go build -o core$(OSARCH)
|
||||||
|
|
||||||
|
## swagger: Update swagger API documentation (requires github.com/swaggo/swag)
|
||||||
|
swagger:
|
||||||
|
swag init -g http/server.go
|
||||||
|
|
||||||
|
## gqlgen: Regenerate GraphQL server from schema
|
||||||
|
gqlgen:
|
||||||
|
go run github.com/99designs/gqlgen generate --config http/graph/gqlgen.yml
|
||||||
|
|
||||||
|
## test: Run all tests
|
||||||
|
test:
|
||||||
|
go test -coverprofile=/dev/null ./...
|
||||||
|
|
||||||
|
## vet: Analyze code for potential errors
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
## fmt: Format code
|
||||||
|
fmt:
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
## update: Update dependencies
|
||||||
|
update:
|
||||||
|
go get -u
|
||||||
|
@-$(MAKE) tidy
|
||||||
|
|
||||||
|
## tidy: Tidy up go.mod
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
## vendor: Update vendored packages
|
||||||
|
vendor:
|
||||||
|
go mod vendor
|
||||||
|
|
||||||
|
## run: Build and run core
|
||||||
|
run: build
|
||||||
|
./core
|
||||||
|
|
||||||
|
## lint: Static analysis with staticcheck
|
||||||
|
lint:
|
||||||
|
staticcheck ./...
|
||||||
|
|
||||||
|
## import: Build import binary
|
||||||
|
import:
|
||||||
|
cd app/import && go build -o ../../import -ldflags="-s -w"
|
||||||
|
|
||||||
|
## coverage: Generate code coverage analysis
|
||||||
|
coverage:
|
||||||
|
go test -coverprofile test/cover.out ./...
|
||||||
|
go tool cover -html=test/cover.out -o test/cover.html
|
||||||
|
|
||||||
|
## commit: Prepare code for commit (vet, fmt, test)
|
||||||
|
commit: vet fmt lint test build
|
||||||
|
@echo "No errors found. Ready for a commit."
|
||||||
|
|
||||||
|
## release: Build a release binary of core
|
||||||
|
release:
|
||||||
|
go build -o core -ldflags="-s -w -X github.com/datarhei/core/app.Commit=$(COMMIT) -X github.com/datarhei/core/app.Branch=$(BRANCH) -X github.com/datarhei/core/app.Build=$(BUILD)"
|
||||||
|
|
||||||
|
## docker: Build standard Docker image
|
||||||
|
docker:
|
||||||
|
docker build -t core:$(SHORTCOMMIT) .
|
||||||
|
|
||||||
|
.PHONY: help build swagger test vet fmt vendor commit coverage lint release import
|
||||||
|
|
||||||
|
## help: Show all commands
|
||||||
|
help: Makefile
|
||||||
|
@echo
|
||||||
|
@echo " Choose a command:"
|
||||||
|
@echo
|
||||||
|
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
|
||||||
|
@echo
|
||||||
980
README.md
980
README.md
|
|
@ -1 +1,979 @@
|
||||||
# core
|
# Core
|
||||||
|
|
||||||
|
The cloud-native a/v media processor.
|
||||||
|
|
||||||
|
datarhei Core is management for FFmpeg processes without development effort. It is a central interface for mapping AV processes, is responsible for design and management, and provides all necessary interfaces to access the video content. The included control for FFmpeg can keep all used functions reliable and executable without the need for software developers to take care of it. In addition, process and resource limitation for all FFmpeg processes protects the host system from application overload. The overall system gives access to current process values (CPU, RAM) and complete control of system resources and loads with statistical access to process data and current and historical logs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Unrestricted FFmpeg process management
|
||||||
|
- Optimized for long-running tasks
|
||||||
|
- In-Memory- and Disk-Filesystem for media assets
|
||||||
|
- HTTP and RTMP services
|
||||||
|
- Let's Encrypt for HTTPS and RTMPS
|
||||||
|
- HLS/DASH Session tracking with bandwidth and current viewer limiters
|
||||||
|
- Multiple resource limiters and monitoring
|
||||||
|
- FFmpeg progress data
|
||||||
|
- Metrics incl. Prometheus support
|
||||||
|
- Logging and debugging for FFmpeg processes with history
|
||||||
|
- Multiple auth. by JWT and Auth0
|
||||||
|
- 100% JSON REST API (Swagger documented)
|
||||||
|
- GraphQL for metrics, process, and progress data
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
1. Run the Docker image
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --name core -d
|
||||||
|
-e CORE_API_AUTH_USERNAME=admin \
|
||||||
|
-e CORE_API_AUTH_PASSWORD=secret \
|
||||||
|
-p 8080:8080 \
|
||||||
|
-v ${HOME}/core/config:/core/config \
|
||||||
|
-v ${HOME}/core/data:/core/data \
|
||||||
|
datarhei/core:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open Swagger
|
||||||
|
http://host-ip:8080/api/swagger/index.html
|
||||||
|
|
||||||
|
3. Log in with Swagger
|
||||||
|
Authorize > Basic authorization > Username: admin, Password: secret
|
||||||
|
|
||||||
|
## Docker images
|
||||||
|
|
||||||
|
Native (linux/amd64,linux/arm64,linux/arm/v7)
|
||||||
|
|
||||||
|
- datarhei/base:alpine-core-latest
|
||||||
|
- datarhei/base:ubuntu-core-latest
|
||||||
|
|
||||||
|
Bundle with FFmpeg (linux/amd64,linux/arm64,linux/arm/v7)
|
||||||
|
|
||||||
|
- datarhei/core:latest
|
||||||
|
|
||||||
|
Bundle with FFmpeg for Raspberry Pi (linux/arm/v7)
|
||||||
|
|
||||||
|
- datarhei/core:rpi-latest
|
||||||
|
|
||||||
|
Bundle with FFmpeg for Nvidia Cuda (linux/amd64)
|
||||||
|
|
||||||
|
- datarhei/core:cuda-latest
|
||||||
|
|
||||||
|
Bundle with FFmpeg for Intel VAAPI (linux/amd64)
|
||||||
|
|
||||||
|
- datarhei/core:vaapi-latest
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
The environment variables can be set in the file `.env`, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
CORE_API_AUTH_USERNAME=admin
|
||||||
|
CORE_API_AUTH_PASSWORD=datarhei
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also provide them on the command line, whatever you prefer. If the same environment variable is set
|
||||||
|
in the `.env` file and on the command line, the one set on the command line will overrule the one from the `.env` file.
|
||||||
|
|
||||||
|
The currently known environment variables (but not all will be respected) are:
|
||||||
|
|
||||||
|
| Name | Default | Description |
|
||||||
|
| ----------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| CORE_CONFIGFILE | (not set) | Path to a config file. The following environment variables will override the respective values in the config file. |
|
||||||
|
| CORE_ADDRESS | `:8080` | HTTP listening address. |
|
||||||
|
| CORE_LOG_LEVEL | `info` | silent, error, warn, info, debug. |
|
||||||
|
| CORE_LOG_TOPICS | (not set) | List of topics to log (comma separated) |
|
||||||
|
| CORE_LOG_MAXLINES | `1000` | Number of latest log lines to keep in memory. |
|
||||||
|
| CORE_DB_DIR | `.` | Directory for holding the operational data. This directory must exist. |
|
||||||
|
| CORE_HOST_NAME | (not set) | Set to the domain name of the host this instance is running on. |
|
||||||
|
| CORE_HOST_AUTO | `true` | Enable detection of public IP addresses. |
|
||||||
|
| CORE_API_READ_ONLY | `false` | Allow only ready only access to the API |
|
||||||
|
| CORE_API_ACCESS_HTTP_ALLOW | (not set) | Comma separated list of IP ranges in CIDR notation (HTTP traffic), e.g. `127.0.0.1/32,::1/128`. |
|
||||||
|
| CORE_API_ACCESS_HTTP_BLOCK | (not set) | Comma separated list of IP ranges in CIDR notation (HTTP traffic), e.g. `127.0.0.1/32,::1/128`. |
|
||||||
|
| CORE_API_ACCESS_HTTPS_ALLOW | (not set) | Comma separated list of IP ranges in CIDR notation (HTTPS traffic), e.g. `127.0.0.1/32,::1/128`. |
|
||||||
|
| CORE_API_ACCESS_HTTPS_BLOCK | (not set) | Comma separated list of IP ranges in CIDR notation (HTTPS traffic), e.g. `127.0.0.1/32,::1/128`. |
|
||||||
|
| CORE_API_AUTH_ENABLE | `true` | Set to `false` to disable auth for all clients. |
|
||||||
|
| CORE_API_AUTH_DISABLE_LOCALHOST | `false` | Set to `true` to disable auth for clients from localhost. |
|
||||||
|
| CORE_API_AUTH_USERNAME | (required) | Username for auth. |
|
||||||
|
| CORE_API_AUTH_PASSWORD | (required) | Password for auth. |
|
||||||
|
| CORE_API_AUTH_JWT_SECRET | (not set) | A secret for en- and decrypting the JWT. If not set, a secret will be generated. |
|
||||||
|
| CORE_API_AUTH_AUTH0_ENABLE | `false` | Enable Auth0. |
|
||||||
|
| CORE_API_AUTH_AUTH0_TENANTS | (not set) | List of base64 encoded Auth0 tenant JSON objects (comma-separated). The tenant JSON object is defined as `{"domain":string,"audience":string,"users":array of strings}` |
|
||||||
|
| CORE_TLS_ADDRESS | `:8181` | Port to listen on for HTTPS requests. |
|
||||||
|
| CORE_TLS_ENABLE | `false` | Set to `true` to enable TLS support. |
|
||||||
|
| CORE_TLS_AUTO | `false` | Set to `true` to enable automatic retrieval of a Let's Encrypt certificate. Requires `CORE_TLS_ENABLE` to be `true` and `CORE_HOST_NAME` to be set with `CORE_HOST_AUTO` to `false`. |
|
||||||
|
| CORE_TLS_CERTFILE | (not set) | TLS certificate file in PEM format. |
|
||||||
|
| CORE_TLS_KEYFILE | (not set) | TLS key file in PEM format. |
|
||||||
|
| CORE_STORAGE_DISK_DIR | `.` | A directory that will be exposed by HTTP on /. This directory must exist. |
|
||||||
|
| CORE_STORAGE_DISK_MAXSIZEMBYTES | `0` | Max. allowed megabytes for `CORE_STORAGE_DISK_DIR`. |
|
||||||
|
| CORE_STORAGE_DISK_CACHE_ENABLE | `true` | Enable cache for files from `CORE_STORAGE_DISK_DIR`. |
|
||||||
|
| CORE_STORAGE_DISK_CACHE_MAXSIZEMBYTES | `0` | Max. allowed cache size, 0 for unlimited. |
|
||||||
|
| CORE_STORAGE_DISK_CACHE_TTLSECONDS | `300` | Seconds to keep files in cache. |
|
||||||
|
| CORE_STORAGE_DISK_CACHE_MAXFILESIZEMBYTES | `1` | Max. file size to put in cache. |
|
||||||
|
| CORE_STORAGE_DISK_CACHE_TYPES | (not set) | List of file extensions to cache (space-separated, e.g. ".html .js"), empty for all. |
|
||||||
|
| CORE_STORAGE_MEMORY_AUTH_ENABLE | `true` | Enable basic auth for PUT,POST, and DELETE on /memfs. |
|
||||||
|
| CORE_STORAGE_MEMORY_AUTH_USERNAME | (not set) | Username for Basic-Auth of `/memfs`. Required if auth is enabled. |
|
||||||
|
| CORE_STORAGE_MEMORY_AUTH_PASSWORD | (not set) | Password for Basic-Auth of `/memfs`. Required if auth is enabled. |
|
||||||
|
| CORE_STORAGE_MEMORY_MAXSIZEMBYTES | `0` | Max. allowed megabytes for `/memfs`. Any value <= 0 means unlimited. |
|
||||||
|
| CORE_STORAGE_MEMORY_PURGE | `false` | Set to `true` to remove the oldest entries if the `/memfs` is full. |
|
||||||
|
| CORE_STORAGE_COCORE_ORIGINS | `*` | List of allowed CORS origins (comma separated). Will be used for `/` and `/memfs`. |
|
||||||
|
| CORE_STORAGE_MIMETYPES_FILE | `mime.types` | Path to file with MIME type definitions. |
|
||||||
|
| CORE_RTMP_ENABLE | `false` | Enable RTMP server. |
|
||||||
|
| CORE_RTMP_ENABLE_TLS | `false` | Enable RTMP over TLS (RTMPS). Requires `CORE_TLS_ENABLE` to be `true`. |
|
||||||
|
| CORE_RTMP_ADDRESS | `:1935` | RTMP server listen address. |
|
||||||
|
| CORE_RTMP_APP | `/` | RTMP app for publishing. |
|
||||||
|
| CORE_RTMP_TOKEN | (not set) | RTMP token for publishing and playing. The token is the value of the URL query parameter `token`. |
|
||||||
|
| CORE_FFMPEG_BINARY | `ffmpeg` | Path to FFmpeg binary. |
|
||||||
|
| CORE_FFMPEG_MAXPROCESSES | `0` | Max. allowed simultaneously running FFmpeg instances. Any value <= 0 means unlimited. |
|
||||||
|
| CORE_FFMPEG_ACCESS_INPUT_ALLOW | (not set) | List of pattern for allowed input URI (space-separated), leave emtpy to allow any. |
|
||||||
|
| CORE_FFMPEG_ACCESS_INPUT_BLOCK | (not set) | List of pattern for blocked input URI (space-separated), leave emtpy to block none. |
|
||||||
|
| CORE_FFMPEG_ACCESS_OUTPUT_ALLOW | (not set) | List of pattern for allowed output URI (space-separated), leave emtpy to allow any. |
|
||||||
|
| CORE_FFMPEG_ACCESS_OUTPUT_BLOCK | (not set) | List of pattern for blocked output URI (space-separated), leave emtpy to block none. |
|
||||||
|
| CORE_FFMPEG_LOG_MAXLINES | `50` | Number of latest log lines to keep for each process. |
|
||||||
|
| CORE_FFMPEG_LOG_MAXHISTORY | `3` | Number of latest logs to keep for each process. |
|
||||||
|
| CORE_PLAYOUT_ENABLE | `false` | Enable playout API where available |
|
||||||
|
| CORE_PLAYOUT_MINPORT | `0` | Min. port a playout server per input can run on. |
|
||||||
|
| CORE_PLAYOUT_MAXPORT | `0` | Max. port a playout server per input can run on. |
|
||||||
|
| CORE_DEBUG_PROFILING | `false` | Set to `true` to enable profiling endpoint on `/profiling`. |
|
||||||
|
| CORE_DEBUG_FORCEGC | `0` | Number of seconds between forcing GC to return memory to the OS. Use in conjuction with `GODEBUG=madvdontneed=1`. Any value <= 0 means not to force GC. |
|
||||||
|
| CORE_METRICS_ENABLE | `false` | Enable collecting historic metrics data. |
|
||||||
|
| CORE_METRICS_ENABLE_PROMETHEUS | `false` | Enable prometheus endpoint /metrics. |
|
||||||
|
| CORE_METRICS_RANGE_SECONDS | `300` | Seconds to keep history metric data. |
|
||||||
|
| CORE_METRICS_INTERVAL_SECONDS | `2` | Interval for collecting metrics. |
|
||||||
|
| CORE_SESSIONS_ENABLE | `false` | Enable HLS statistics for `/memfs`. |
|
||||||
|
| CORE_SESSIONS_IP_IGNORELIST | (not set) | Comma separated list of IP ranges in CIDR notation, e.g. `127.0.0.1/32,::1/128`. |
|
||||||
|
| CORE_SESSIONS_SESSION_TIMEOUT_SEC | `30` | Timeout of a session in seconds. |
|
||||||
|
| CORE_SESSIONS_PERSIST | `false` | Whether to persist the session history. Will be stored in `CORE_DB_DIR`. |
|
||||||
|
| CORE_SESSIONS_MAXBITRATE_MBIT | `0` | Max. allowed outgoing bitrate in mbit/s, 0 for unlimited. |
|
||||||
|
| CORE_SESSIONS_MAXSESSIONS | `0` | Max. allowed number of simultaneous sessions, 0 for unlimited. |
|
||||||
|
| CORE_ROUTER_BLOCKED_PREFIXES | `/api` | List of path prefixes that can't be routed. |
|
||||||
|
| CORE_ROUTER_ROUTES | (not set) | List of route mappings of the form [from]:[to], e.g. `/foo:/bar`. Leave empty for no routings. |
|
||||||
|
| CORE_ROUTER_UI_PATH | (not set) | Path to directory with files for a UI. It will be mounted to `/ui` and uses `index.html` as default index page. |
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
The minimum config file has to look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All other values will be filled with default values and persisted on disk. The entire default config file:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"id": "[will be generated if not given]",
|
||||||
|
"name": "[will be generated if not given]",
|
||||||
|
"address": ":8080",
|
||||||
|
"log": {
|
||||||
|
"level": "info",
|
||||||
|
"topics": [],
|
||||||
|
"max_lines": 1000
|
||||||
|
},
|
||||||
|
"db": {
|
||||||
|
"dir": "./config"
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"name": [],
|
||||||
|
"auto": true
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"read_only": false,
|
||||||
|
"access": {
|
||||||
|
"http": {
|
||||||
|
"allow": [],
|
||||||
|
"block": []
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"allow": [],
|
||||||
|
"block": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enable": true,
|
||||||
|
"disable_localhost": false,
|
||||||
|
"username": "",
|
||||||
|
"password": "",
|
||||||
|
"jwt": {
|
||||||
|
"secret": ""
|
||||||
|
},
|
||||||
|
"auth0": {
|
||||||
|
"enable": false,
|
||||||
|
"tenants": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"address": ":8181",
|
||||||
|
"enable": false,
|
||||||
|
"auto": false,
|
||||||
|
"cert_file": "",
|
||||||
|
"key_file": ""
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"disk": {
|
||||||
|
"dir": "./data",
|
||||||
|
"max_size_mbytes": 0,
|
||||||
|
"cache": {
|
||||||
|
"enable": true,
|
||||||
|
"max_size_mbytes": 0,
|
||||||
|
"ttl_seconds": 300,
|
||||||
|
"max_file_size_mbytes": 1,
|
||||||
|
"types": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"auth": {
|
||||||
|
"enable": true,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "vxbx0ViqfA75P1KCyw"
|
||||||
|
},
|
||||||
|
"max_size_mbytes": 0,
|
||||||
|
"purge": false
|
||||||
|
},
|
||||||
|
"cors": {
|
||||||
|
"origins": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mimetypes_file": "mime.types"
|
||||||
|
},
|
||||||
|
"rtmp": {
|
||||||
|
"enable": false,
|
||||||
|
"enable_tls": false,
|
||||||
|
"address": ":1935",
|
||||||
|
"app": "/",
|
||||||
|
"token": ""
|
||||||
|
},
|
||||||
|
"ffmpeg": {
|
||||||
|
"binary": "ffmpeg",
|
||||||
|
"max_processes": 0,
|
||||||
|
"access": {
|
||||||
|
"input": {
|
||||||
|
"allow": [],
|
||||||
|
"block": []
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"allow": [],
|
||||||
|
"block": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"max_lines": 50,
|
||||||
|
"max_history": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playout": {
|
||||||
|
"enable": false,
|
||||||
|
"min_port": 0,
|
||||||
|
"max_port": 0
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"profiling": false,
|
||||||
|
"force_gc": 0
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"enable": true,
|
||||||
|
"ip_ignorelist": [
|
||||||
|
"127.0.0.1/32",
|
||||||
|
"::1/128"
|
||||||
|
],
|
||||||
|
"session_timeout_sec": 30,
|
||||||
|
"persist": false,
|
||||||
|
"persist_interval_sec": 300,
|
||||||
|
"max_bitrate_mbit": 0,
|
||||||
|
"max_sessions": 0
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"enable": false,
|
||||||
|
"token": "",
|
||||||
|
"url": "https://service.datarhei.com"
|
||||||
|
},
|
||||||
|
"router": {
|
||||||
|
"blocked_prefixes": [
|
||||||
|
"/api"
|
||||||
|
],
|
||||||
|
"routes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't provide a path to a config file, the default config will be used, and nothing will be persisted to the disk. Default values can be overruled by environment variables.
|
||||||
|
|
||||||
|
## TLS / HTTPS
|
||||||
|
|
||||||
|
Enable TLS / HTTPS support by setting `CORE_TLS_ENABLE=true` and provide the certificate file and key file in PEM format by setting the environment variables `CORE_TLS_CERTFILE` and `CORE_TLS_KEYFILE` accordingly. If a certificate authority signs the certificate, the certificate file should be the concatenation of the server's certificate, any intermediates, and the CA's certificate.
|
||||||
|
|
||||||
|
If TLS with given certificates is enabled, an HTTP server listening on `CORE_ADDRESS` (address) will be additionally started. This server provides access to the same memory filesystem as the HTTPS server (including limits and authorization), but its access is restricted to localhost only.
|
||||||
|
|
||||||
|
### Let's Encrypt
|
||||||
|
|
||||||
|
If you want to use automatic certificates from Let's Encrypt, set the environment variable `CORE_TLS_AUTO` to `true.` To work, the
|
||||||
|
environment variables `CORE_TLS_ENABLE` have to be `true,` and `CORE_HOST_NAME` has to be set to the host this host will be reachable. Otherwise, the ACME challenge will not work. The environment variables `CORE_TLS_CERTFILE` and `CORE_TLS_KEYFILE` will be ignored.
|
||||||
|
|
||||||
|
If automatic TLS is enabled, the HTTP server (CORE_ADDRESS, resp. address) must listen on port 80. It is required to automatically acquire the certificate (serving the `HTTP-01` challenge). As a further requirement, `CORE_HOST_NAME` (host.name) must be set because it is used a the canonical name for the certificate.
|
||||||
|
|
||||||
|
The obtained certificates will be stored in `CORE_DB_DIR/cert` to be available after a restart.
|
||||||
|
|
||||||
|
The obtained certificates will be stored in `CORE_DB_DIR/cert` to be available after a restart.
|
||||||
|
|
||||||
|
### Self-Signed certificates
|
||||||
|
|
||||||
|
To create a self-signed certificate and key file pair, run this command and provide a reasonable value for the Common Name (CN). The CN is the fully qualified name of the host the instance is running on (e.g., `localhost`). You can also use an IP address or a wildcard name, e.g., `*.example.com`.
|
||||||
|
|
||||||
|
RSA SSL certificate
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem -subj '/CN=localhost'
|
||||||
|
```
|
||||||
|
|
||||||
|
ECDSA SSL certificate
|
||||||
|
|
||||||
|
```sh
|
||||||
|
openssl ecparam -name secp521r1 -genkey -out key.pem
|
||||||
|
openssl req -new -x509 -key key.pem -out cert.pem -days 365 -subj '/CN=localhost'
|
||||||
|
```
|
||||||
|
|
||||||
|
Call `openssl ecparam -list_curves` to see all available supported curves listed.
|
||||||
|
|
||||||
|
## Access Control
|
||||||
|
|
||||||
|
To control who has access to the API, a list of allowed IPs can be defined. This list is provided at startup with the environment variables `CORE_API_ACCESS_HTTP_BLOCK` and `CORE_API_ACCESS_HTTP_ALLOW.` This is a comma-separated list of IPs in CIDR notation,
|
||||||
|
e.g. `127.0.0.1/32,::1/128`. If the list is empty, then all IPs are allowed. If the list contains any invalid IP range, the server
|
||||||
|
will refuse to start. This can be separately defined for the HTTP and HTTPS server if you have TLS enabled with the environment variables `CORE_API_ACCESS_HTTPS_BLOCK` and `CORE_API_ACCESS_HTTPS_ALLOW.`
|
||||||
|
|
||||||
|
## Input/Output Control
|
||||||
|
|
||||||
|
To control where FFmpeg can read and where FFmpeg can write, you can define a pattern that matches the
|
||||||
|
input addresses or the output addresses. These patterns are regular expressions that can be provided at startup with the
|
||||||
|
environment variables `CORE_FFMPEG_ACCESS_INPUT` and `CORE_FFMPEG_ACCESS_OUTPUT.` The expressions need to be space-separated, e.g.
|
||||||
|
`HTTPS?:// RTSP:// RTMP://`. If one of the lists is empty, then no restriction on input, resp. The output will be applied.
|
||||||
|
|
||||||
|
Independently of the value of `CORE_FFMPEG_ACCESS_OUTPUT` there's a check that verifies that output can only be written to the specified `CORE_STORAGE_DISK_DIR` and works as follows: If the address has a protocol specifier other than `file:,` then no further checks will be applied. If the protocol is `file:` or no protocol specifier is given, the address is assumed to be a path that is checked against the path shown in `CORE_STORAGE_DISK_DIR.`
|
||||||
|
|
||||||
|
It will be rejected if the address is outside the `CORE_STORAGE_DISK_DIR` directory. Otherwise, the protocol `file:` will be prepended. If you give some expressions for `CORE_FFMPEG_ACCESS_OUTPUT,` you should also allow `file:.`
|
||||||
|
|
||||||
|
Special cases are the output addresses `-` (which will be rewritten to `pipe:`), and `/dev/null` (which will be allowed even though it's outside of `CORE_STORAGE_DISK_DIR`).
|
||||||
|
|
||||||
|
If you set a value for `CORE_STORAGE_DISK_CACHE_MAXSIZEMBYTES`, which is larger than `0`, it will be interpreted as max—allowed megabytes for the `CORE_STORAGE_DISK_DIR.` As soon as the limit is reached, all processes that have outputs writing to `CORE_STORAGE_DISK_DIR` will be stopped. You are responsible for cleaning up the directory and restarting these processes.
|
||||||
|
|
||||||
|
## RTMP
|
||||||
|
|
||||||
|
The datarhei Core includes a simple RTMP server for publishing and playing streams. Set the environment variable `CORE_RTMP_ENABLE` to `true` to enable the RTMP server. It is listening on `CORE_RTMP_ADDRESS.` Use `CORE_RTMP_APP` to limit the app a stream can be published on, e.g. `/live` to require URLs to start with `/live`. To prevent anybody can publish streams, set `CORE_RTMP_TOKEN` to a secret only known to the publishers. The token has to be put in the query of the stream URL, e.g. `/live/stream?token=...`.
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| ------ | ------------ | ------------------------------------- |
|
||||||
|
| GET | /api/v3/rtmp | List all currently published streams. |
|
||||||
|
|
||||||
|
## Playout
|
||||||
|
|
||||||
|
FFmpeg processes with a `avstream:` (or `playout:`) input stream can expose an HTTP API to control the playout of that stream. With
|
||||||
|
`CORE_PLAYOUT_ENABLE` you enable exposing this API. The API is only exposed to `localhost` and is transparently connected to the datarhei Core API. You have to provide a port range (`CORE_PLAYOUT_MINPORT` and `CORE_PLAYOUT_MAXPORT`) where datarhei/core can use ports to assign it to the playout API.
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| -------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| GET | /api/v3/process/:id/playout/:inputid/status | Retrieve the current status as JSON. |
|
||||||
|
| GET | /api/v3/process/:id/playout/:inputid/keyframe/\*name | Retrieve the last deliverd key frame from the input stream as JPEG (if `name` has the ending `.jpg`) or PNG (if `name` has the ending `.png`). |
|
||||||
|
| GET | /api/v3/process/:id/playout/:inputid/errorframe/encode | Immediately encode the error frame to a GOP. Will only have an effect if the last key frame is currently in a loop. |
|
||||||
|
| PUT/POST | /api/v3/process/:id/playout/:inputid/errorframe/\*name | Upload any image or video media that can be decoded and will be used to replace the key frame loop. If the key frame is currently in a loop, it will be repaced immediately. Otherwise, it will be used the next time the key frame is in a loop. The body of the request is the media file. |
|
||||||
|
| PUT | /api/v3/process/:id/playout/:inputid/stream | Replace the current stream. The body of the request is the URL of the new stream. |
|
||||||
|
|
||||||
|
## MIME Types
|
||||||
|
|
||||||
|
The file with the MIME types has one MIME type per line followed by a list of file extensions (including the ".").
|
||||||
|
|
||||||
|
```
|
||||||
|
text/plain .txt
|
||||||
|
text/html .htm .html
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Memory Filesystem
|
||||||
|
|
||||||
|
AA very simple in-memory filesystem is available. The uploaded data is stored in a map, where the path used to upload the file
|
||||||
|
is used as the key. Use the `POST` or `PUT` method with the proper direction for uploading a file. The body of the request contains the contents of the file. No particular encoding or `Content-Type` is required. The file can then be downloaded from the same path.
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| ------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| POST | /memfs/\*path | Upload a file to the memory filesystem. The filename is `path` which can contain slashes. If there's already a file with the same `path`, it will be overwritten. |
|
||||||
|
| PUT | /memfs/\*path | Same as POST. |
|
||||||
|
| GET | /memfs/\*path | Download the file stored under `path`. The MIME types are applied based on the extension in the `path`. |
|
||||||
|
| DELETE | /memfs/\*path | Delete the file stored under `path`. |
|
||||||
|
| POST | /api/v3/memfs/\*path | Upload a file to the memory filesystem. |
|
||||||
|
| PUT | /api/v3/memfs/\*path | Same as POST. |
|
||||||
|
| GET | /api/v3/memfs/\*path | Download the file stored under `path`. |
|
||||||
|
| PATCH | /api/v3/memfs/\*path | Create a link to a file. The body contains the path to that file. |
|
||||||
|
| DELETE | /api/v3/memfs/\*path | Delete the file stored under `path`. |
|
||||||
|
| GET | /api/v3/memfs | List all files that are currently stored in the in-memory filesystem. |
|
||||||
|
|
||||||
|
Use these endpoints to, e.g., store HLS chunks and .m3u8 files (in contrast to an actual disk or a ramdisk):
|
||||||
|
|
||||||
|
```
|
||||||
|
ffmpeg -f lavfi -re -i testsrc2=size=640x480:rate=25 -c:v libx264 -preset:v ultrafast -r 25 -g 50 -f hls -start_number 0 -hls_time 2 -hls_list_size 6 -hls_flags delete_segments+temp_file+append_list -method PUT -hls_segment_filename http://localhost:8080/memfs/foobar_%04d.ts -y http://localhost:8080/memfs/foobar.m3u8
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can play it generally with, e.g., `ffplay http://localhost:3000/memfs/foobar.m3u8`.
|
||||||
|
|
||||||
|
Use the environment variables `CORE_STORAGE_MEMORY_AUTH_USERNAME` and `CORE_STORAGE_MEMORY_AUTH_PASSWORD` to protect the `/memfs` with Basic-Auth. Basic-Auth will only be enabled
|
||||||
|
if both environment variables are set to non-empty values. The `GET /memfs/:path` will not be protected with Basic-Auth.
|
||||||
|
|
||||||
|
Use the environment variable `CORE_STORAGE_MEMORY_MAXSIZEMBYTES` to limit the amount of data that is allowed to be stored. The value is interpreted as megabytes. Use a value equal to or smaller than `0` not to impose any limits. A `507 Insufficient Storage` will be returned if you hit the limit.
|
||||||
|
|
||||||
|
Listing all currently stored files is done by calling `/v3/memfs` with the credentials set by the environment variables `CORE_API_AUTH_USERNAME` and `CORE_API_AUTH_PASSWORD`.
|
||||||
|
It also accepts the query parameter `sort` (`name,` `size,` or `lastmod`) and `order` (`asc` or `desc`). If a valid value for `sort` is given, the results are sorted in ascending order.
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
All contents in `CORE_STORAGE_DISK_DIR` are served from `/.` If you want to redirect some paths to an existing file, you can add static routes in `router.routes` by providing a direct mapping, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
router: {
|
||||||
|
routes: {
|
||||||
|
"/foo.txt": "/bar.txt",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The paths have to start with a `/.` Alternatively, you can serve whole directories from another root than `CORE_STORAGE_DISK_DIR.` Use a `/*` at the end of a path as key and a path on the filesystem as the target, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
router: {
|
||||||
|
routes: {
|
||||||
|
"/ui/*": "/path/to/ui",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use a relative path as target, then it will be added to the current working directory.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
Check the detailed API description on `/api/swagger/index.html`.
|
||||||
|
|
||||||
|
### Login / Auth
|
||||||
|
|
||||||
|
With auth enabled, you have to retrieve a JWT/OAuth token before you can access the `/v3/` API calls.
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| ------ | --------------------- | ---------------------------------------------- |
|
||||||
|
| POST | /api/login | Retrieve a token to access the API. |
|
||||||
|
| GET | /api/v3/refresh_token | Retrieve a fresh token with a new expiry date. |
|
||||||
|
|
||||||
|
For the login you have to send
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"username": "...",
|
||||||
|
"password": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `username` and the `password` are set by the environment variables `CORE_API_AUTH_USERNAME` and `CORE_API_AUTH_PASSWORD`.
|
||||||
|
|
||||||
|
On successful login, the response looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"expire": "2019-01-18T19:55:55+01:00",
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDc4Mzc3NTUsImlkIjpudWxsLCJvcmlnX2lhdCI6MTU0NzgzNDE1NX0.ZcrpD4oRBqG3wUrfnh1DOVpXdUT7dvUnvetKFEVRKKc"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the `token` in all subsequent calls to the `/api/v3/` endpoints, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
http http://localhost:8080/api/v3/process "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NDc4Mzc3NTUsImlkIjpudWxsLCJvcmlnX2lhdCI6MTU0NzgzNDE1NX0.ZcrpD4oRBqG3wUrfnh1DOVpXdUT7dvUnvetKFEVRKKc"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Config
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| GET | /api/v3/config | Retrieve the current config without the override values from the environment variables. |
|
||||||
|
| GET | /api/v3/config/active | Retrieve the current config with the override values from the environment variables are taken into account. |
|
||||||
|
| PUT | /api/v3/config | Store a new config. Only some values are respected, and the new config will only be used after a restart. |
|
||||||
|
| GET | /api/v3/config/reload | Reload the config. The config will be re-read and validated from the store. It will cause a restart of all services. |
|
||||||
|
|
||||||
|
When retrieving the config via the API, critical values (such as passwords) will be disguised if not required otherwise.
|
||||||
|
|
||||||
|
### Process
|
||||||
|
|
||||||
|
With the process API call, you can manage different FFmpeg processes. A process is defined as:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": "SomeId",
|
||||||
|
"reference": "SomeId",
|
||||||
|
"type": "ffmpeg",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "inputid",
|
||||||
|
"address": "rtsp://1.2.3.4/stream.sdp",
|
||||||
|
"options": [
|
||||||
|
... list of input options ...
|
||||||
|
]
|
||||||
|
},
|
||||||
|
... list of inputs ...
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "outputid",
|
||||||
|
"address": "rtmp://rtmp.youtube.com/live2/...",
|
||||||
|
"options": [
|
||||||
|
... list of output options ...
|
||||||
|
],
|
||||||
|
"cleanup": [{
|
||||||
|
"pattern": "(memfs|diskfs):...",
|
||||||
|
"max_files: "number,
|
||||||
|
"max_file_age_seconds": "number"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
... list of outputs ...
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
... list of global options ...
|
||||||
|
],
|
||||||
|
"reconnect": (true|false),
|
||||||
|
"reconnect_delay_seconds": 10,
|
||||||
|
"autostart": (true|false),
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The input, output, and global options are interpreted as command-line options for FFmpeg.
|
||||||
|
|
||||||
|
#### Process Cleanup
|
||||||
|
|
||||||
|
With the optional array of cleanup rules for each output, it is possible to define rules for removing files from the
|
||||||
|
memory filesystem or disk. Each rule consists of a glob pattern and a max. allowed number of files matching that pattern or
|
||||||
|
permitted maximum age for the files matching that pattern. The pattern starts with either `memfs:` or `diskfs:` depending on
|
||||||
|
which filesystem this rule is designated to. Then a [glob pattern](https://pkg.go.dev/path/filepath#Match) follows to
|
||||||
|
identify the files. If `max_files` is set to a number > 0, then the oldest files from the matching files will be deleted if
|
||||||
|
the list of matching files is longer than that number. If `max_file_age_seconds` is set to a number > 0, then all files
|
||||||
|
that are older than this number of seconds from the matching files will be deleted.
|
||||||
|
|
||||||
|
The API calls are
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
| ------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| POST | /api/v3/process | Adds a process. Overwriting an existing ID will result in an error. |
|
||||||
|
| GET | /api/v3/process | Retrieve a list of all known processes. Use the query parameter `ids` to list (comma separated) the IDs of the process you want to be part of the response. If the list is empty, all processes will be listed. Use the query parameter `filter` to list (comma separated) the wanted details per process (`config`, `state`, `log`). If the list is empty, all details will be included. |
|
||||||
|
| GET | /api/v3/process/:id | Retreive the details of a process including the config, state, and logs. Use the query parameter `filter` to list (comma separated) the wanted details per process (`config`, `state`, `log`). If the list is empty, all details will be included. |
|
||||||
|
| PUT | /api/v3/process/:id | Replaces the process with a new config. |
|
||||||
|
| GET | /api/v3/process/:id/config | Retrieve the config of a process as it was provided. |
|
||||||
|
| GET | /api/v3/process/:id/state | Retrieve the current state of a process. This includes the progress data if the process is running. |
|
||||||
|
| GET | /api/v3/process/:id/report | Retrieve the report and logs of a process. |
|
||||||
|
| GET | /api/v3/process/:id/debug | Retrieve an anonymized version of the details of a process. |
|
||||||
|
| DELETE | /api/v3/process/:id | Remove a specific process. Only possible if the process is not running. |
|
||||||
|
| PUT | /api/v3/process/:id/command | Send a command to a process. |
|
||||||
|
| GET | /api/v3/process/:id/data | Get all arbitrary JSON data that is stored with this process. |
|
||||||
|
| GET | /api/v3/process/:id/data/:key | Get arbitrary JSON data that is stored under the key `key.` |
|
||||||
|
| PUT | /api/v3/process/:id/data/:key | Store aribtrary JSON data under the key `key.` If the data is `null,` the key will be removed. |
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
A command is defined as:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"command": ("start"|"stop"|"restart"|"reload")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
| --------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `start` | Start the process. If the process is already started, this won't have any effect. |
|
||||||
|
| `stop` | Stop the process. If the process is already stopped, this won't have any effect. |
|
||||||
|
| `restart` | Restart the process. If the process is not running, this won't have any effect. |
|
||||||
|
| `reload` | Reload the process. If the process was running, the reloaded process will start automatically. |
|
||||||
|
|
||||||
|
### Placeholder
|
||||||
|
|
||||||
|
Currently supported placeholders are:
|
||||||
|
|
||||||
|
| Placeholder | Description | Location |
|
||||||
|
| ------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `{diskfs}` | Will be replaced by the provided `CORE_STORAGE_DISK_DIR`. | `options`, `input.address`, `input.options`, `output.address`, `output.options` |
|
||||||
|
| `{memfs}` | Will be replace by the base URL of the MemFS. | `input.address`, `input.options`, `output.address`, `output.options` |
|
||||||
|
| `{processid}` | Will be replaced by the ID of the process. | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
|
| `{reference}` | Will be replaced by the reference of the process | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
|
| `{inputid}` | Will be replaced by the ID of the input. | `input.address`, `input.options` |
|
||||||
|
| `{outputid}` | Will be replaced by the ID of the output. | `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
|
|
||||||
|
Before replacing the placeholder in the process, all references will be resolved, i.e., you can put the placeholder also in the params for an
|
||||||
|
option.
|
||||||
|
|
||||||
|
### References
|
||||||
|
|
||||||
|
The input address of a process may contain a reference to the output of another process. It has the form `#[processid]:output=[id]`.
|
||||||
|
|
||||||
|
A reference starts with a `#` followed by the process ID it refers to, followed by a `:.` Then comes `output,` followed by a `=.`
|
||||||
|
and the ID of the output.
|
||||||
|
|
||||||
|
## FFmpeg
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
This repository contains a patch for the FFmpeg program to provide detailed progress information. With this patch, FFmpeg will output
|
||||||
|
the progress information in a JSON string that contains the data for each input and output stream individually. The JSON output is enabled
|
||||||
|
by default. It can be enabled with the global `-jsonstats` switch on the command line. Use the `-stats` switch
|
||||||
|
on the command line for the standard progress output.
|
||||||
|
|
||||||
|
The Docker image that you can build with the provided Dockerfile includes the patched version of FFmpeg for your convenience.
|
||||||
|
|
||||||
|
Example output with `-stats`:
|
||||||
|
|
||||||
|
```
|
||||||
|
frame= 143 fps= 25 q=-1.0 Lsize= 941kB time=00:00:05.68 bitrate=1357.0kbits/s speed=0.995x
|
||||||
|
```
|
||||||
|
|
||||||
|
Example output with `-jsonstats`:
|
||||||
|
|
||||||
|
```
|
||||||
|
JSONProgress:{"inputs":[{"id":0, "stream":0, "type":"video", "codec":"rawvideo", "coder":"rawvideo", "pix_fmt":"rgb24", "frame":188, "fps":24.95, "width":1280, "height":720, "size_kb":507600, "bitrate_kbps":552960.0},{"id":1, "stream":0, "type":"audio", "codec":"pcm_u8", "coder":"pcm_u8", "frame":314, "sampling_hz":44100, "layout":"stereo", "size_kb":628, "bitrate_kbps":705.6}], "outputs":[{"id":0, "stream":0, "type":"video", "codec":"h264", "coder":"libx264", "pix_fmt":"yuv420p", "frame":188, "fps":24.95, "q":-1.0, "width":1280, "height":720, "size_kb":1247, "bitrate_kbps":1365.6},{"id":0, "stream":1, "type":"audio", "codec":"aac", "coder":"aac", "frame":315, "sampling_hz":44100, "layout":"stereo", "size_kb":2, "bitrate_kbps":2.1}], "frame":188, "fps":24.95, "q":-1.0, "size_kb":1249, "bitrate_kbps":1367.7, "time":"0h0m7.48s", "speed":0.993, "dup":0, "drop":0}
|
||||||
|
```
|
||||||
|
|
||||||
|
The same output but nicely formatted:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 1367.7,
|
||||||
|
"drop": 0,
|
||||||
|
"dup": 0,
|
||||||
|
"fps": 24.95,
|
||||||
|
"frame": 188,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 552960.0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "rawvideo",
|
||||||
|
"fps": 24.95,
|
||||||
|
"frame": 188,
|
||||||
|
"height": 720,
|
||||||
|
"id": 0,
|
||||||
|
"pix_fmt": "rgb24",
|
||||||
|
"size_kb": 507600,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 705.6,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "pcm_u8",
|
||||||
|
"frame": 314,
|
||||||
|
"id": 1,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"size_kb": 628,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 1365.6,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"fps": 24.95,
|
||||||
|
"frame": 188,
|
||||||
|
"height": 720,
|
||||||
|
"id": 0,
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"q": -1.0,
|
||||||
|
"size_kb": 1247,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 2.1,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"frame": 315,
|
||||||
|
"id": 0,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"size_kb": 2,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"q": -1.0,
|
||||||
|
"size_kb": 1249,
|
||||||
|
"speed": 0.993,
|
||||||
|
"time": "0h0m7.48s"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resilient Streaming
|
||||||
|
|
||||||
|
Prepend the input source with `avstream:`, e.g. `... -i avstream:rtsp://1.2.3.4/stream.sdp ...`. It will reconnect to the stream if it breaks and repeats the last known intraframe until new data from the input stream is available.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Start `core` with the proper environment variables. Create a `.env` file or provide them on the command line. For this example, please use the following command line:
|
||||||
|
|
||||||
|
```
|
||||||
|
env CORE_API_AUTH_USERNAME=admin CORE_API_AUTH_PASSWORD=datarhei CORE_LOGLEVEL=debug CORE_STORAGE_DISK_DIR=./data ./core
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, make sure that the directory `./data` exists. Otherwise, the state will not be stored and will be lost after a restart of
|
||||||
|
datarhei/core and the FFmpeg process will not be able to write the files.
|
||||||
|
|
||||||
|
In this example, we will add a fake video and audio source. The video will be encoded with H264, and the audio will be encoded with AAC. The output will be an m3u8 stream.
|
||||||
|
|
||||||
|
To talk to the API, we use the program [httpie](https://httpie.org/).
|
||||||
|
|
||||||
|
First, we create a JSON file with the process definition (e.g. `testsrc.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "testsrc",
|
||||||
|
"type": "ffmpeg",
|
||||||
|
"options": ["-loglevel", "info", "-err_detect", "ignore_err"],
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"address": "testsrc=size=1280x720:rate=25",
|
||||||
|
"id": "video",
|
||||||
|
"options": ["-f", "lavfi", "-re"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"id": "audio",
|
||||||
|
"options": ["-f", "lavfi"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"address": "http://127.0.0.1:8080/memfs/{processid}_{outputid}.m3u8",
|
||||||
|
"id": "hls",
|
||||||
|
"options": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-b:v",
|
||||||
|
"1024k",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"10",
|
||||||
|
"-hls_flags",
|
||||||
|
"delete_segments+temp_file+append_list",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"http://127.0.0.1:8080/memfs/{processid}_{outputid}_%04d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 10,
|
||||||
|
"stale_timeout_seconds": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and POST it to the API:
|
||||||
|
|
||||||
|
```
|
||||||
|
http POST http://localhost:8080/v3/process < testsrc.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Then check if it is there (as provided)
|
||||||
|
|
||||||
|
```
|
||||||
|
http http://localhost:8080/v3/process/testsrc
|
||||||
|
```
|
||||||
|
|
||||||
|
For the advanced, create another JSON file (e.g. `dump.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "dump",
|
||||||
|
"type": "ffmpeg",
|
||||||
|
"options": ["-loglevel", "info", "-err_detect", "ignore_err"],
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"address": "#testsrc:output=hls",
|
||||||
|
"id": "video",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"address": "{diskfs}/{processid}.mp4",
|
||||||
|
"id": "hls",
|
||||||
|
"options": ["-codec", "copy", "-y"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 10,
|
||||||
|
"stale_timeout_seconds": 10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
and POST it to the API:
|
||||||
|
|
||||||
|
```
|
||||||
|
http POST http://localhost:8080/v3/process < dump.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Then check if it is there (as provided)
|
||||||
|
|
||||||
|
```
|
||||||
|
http http://localhost:8080/v3/process/dump
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's start the `testsrc` process
|
||||||
|
|
||||||
|
```
|
||||||
|
http PUT http://localhost:8080/v3/process/testsrc/command command=start
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can observe the progress of the process
|
||||||
|
|
||||||
|
```
|
||||||
|
http http://localhost:8080/v3/process/testsrc
|
||||||
|
```
|
||||||
|
|
||||||
|
or the log of the process
|
||||||
|
|
||||||
|
```
|
||||||
|
http http://localhost:8080/v3/process/testsrc/log
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to change the video bitrate, edit the `testsrc.json` file accordingly and replace the process:
|
||||||
|
|
||||||
|
```
|
||||||
|
http PUT http://localhost:8080/v3/process/testsrc < testsrc.json
|
||||||
|
```
|
||||||
|
|
||||||
|
It will stop the process, replace the config, and restart it.
|
||||||
|
|
||||||
|
Now open, e.g., VLC, and load the stream `http://localhost:8080/memfs/testsrc_hls.m3u8`.
|
||||||
|
|
||||||
|
This is enough; let's stop it
|
||||||
|
|
||||||
|
```
|
||||||
|
http PUT http://localhost:8080/v3/process/testsrc/command command=stop
|
||||||
|
```
|
||||||
|
|
||||||
|
and check its progress again
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP http://localhost:8080/v3/process/testsrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete the process
|
||||||
|
|
||||||
|
```
|
||||||
|
HTTP DELETE http://localhost:8080/v3/process/testsrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
Metrics for the processes and other aspects are provided for a Prometheus scraper on `/metrics.`
|
||||||
|
|
||||||
|
Currently, available metrics are:
|
||||||
|
|
||||||
|
| Metric | Type | Dimensions | Description |
|
||||||
|
| --------------------- | ------- | --------------------------------------------------------- | ----------------------------------------------- |
|
||||||
|
| ffmpeg_process | gauge | `core`, `process`, `name` | General stats per process. |
|
||||||
|
| ffmpeg_process_io | gauge | `core`, `process`, `type`, `id`, `index`, `media`, `name` | Stats per input and output of a process. |
|
||||||
|
| mem_limit_bytes | gauge | `core` | Total available memory in bytes. |
|
||||||
|
| mem_free_bytes | gauge | `core` | Free memory in bytes. |
|
||||||
|
| net_rx_bytes | gauge | `core`, `interface` | Number of received bytes by interface. |
|
||||||
|
| net_tx_bytes | gauge | `core`, `interface` | Number of sent bytes by interface. |
|
||||||
|
| cpus_system_time_secs | gauge | `core`, `cpu` | System time per CPU in seconds. |
|
||||||
|
| cpus_user_time_secs | gauge | `core`, `cpu` | User time per CPU in seconds. |
|
||||||
|
| cpus_idle_time_secs | gauge | `core`, `cpu` | Idle time per CPU in seconds. |
|
||||||
|
| session_total | counter | `core`, `collector` | Total number of sessions by collector. |
|
||||||
|
| session_active | gauge | `core`, `collector` | Current number of active sessions by collector. |
|
||||||
|
| session_rx_bytes | counter | `core`, `collector` | Total received bytes by collector. |
|
||||||
|
| session_tx_bytes | counter | `core`, `collector` | Total sent bytes by collector. |
|
||||||
|
|
||||||
|
## Profiling
|
||||||
|
|
||||||
|
Profiling information is available under `/profiling.` Set the environment variable `CORE_DEBUG_PROFILING=true` to make this endpoint
|
||||||
|
available. If authentication is enabled, you have to provide the token in the header.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Requirement
|
||||||
|
|
||||||
|
- Go v1.16+ ([Download here](https://golang.org/dl/))
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
Clone the repository and build the binary
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@github.com:datarhei/core.git
|
||||||
|
cd core
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
After the build process, the binary is available as `core`
|
||||||
|
|
||||||
|
For more build options, run `make help.`
|
||||||
|
|
||||||
|
### Cross Compile
|
||||||
|
|
||||||
|
If you want to run the binary on a different operating system and/or architecture, you create the appropriate binary by simply setting some
|
||||||
|
environment variables, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
env GOOS=linux GOARCH=arm go build -o core-linux-arm
|
||||||
|
env GOOS=linux GOARCH=arm64 go build -o core-linux-arm64
|
||||||
|
env GOOS=freebsd GOARCH=amd64 go build -o core-freebsd-amd64
|
||||||
|
env GOOS=windows GOARCH=amd64 go build -o core-windows-amd64
|
||||||
|
env GOOS=macos GOARCH=amd64 go build -o core-macos-amd64
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Build the Docker image and run it to try out the API
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t core .
|
||||||
|
docker run -it --rm -v ${PWD}/data:/core/data -p 8080:8080 core
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
|
||||||
|
The documentation of the API is available on `/api/swagger/index.html.`
|
||||||
|
|
||||||
|
To generate the API documentation from the code, use [swag](https://github.com/swaggo/swag).
|
||||||
|
|
||||||
|
```
|
||||||
|
go install github.com/swaggo/swag (unless already installed)
|
||||||
|
make swagger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code style
|
||||||
|
|
||||||
|
The source code is formatted with `go fmt`, or simply run `make fmt`. Static analysis of the source code is done with `staticcheck`
|
||||||
|
(see [staticcheck](https://staticcheck.io/docs/)), or simply run `make lint`.
|
||||||
|
|
||||||
|
Before committing changes, you should run `make commit` to ensure that the source code is in shape.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See the [LICENSE](./LICENSE) file for licensing information.
|
||||||
|
|
||||||
|
|
|
||||||
1105
app/api/api.go
Normal file
1105
app/api/api.go
Normal file
File diff suppressed because it is too large
Load diff
1
app/import/.gitignore
vendored
Normal file
1
app/import/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import
|
||||||
28
app/import/README.md
Normal file
28
app/import/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Restreamer v1 database import
|
||||||
|
|
||||||
|
This programe reads a Restreamer v1 database (v1.json, created with 0.6.7+) and writes a new database including the metadata for the UI.
|
||||||
|
Also static routes will be added to the configuration file in order to main the paths to the player, snapshot, and m3u8.
|
||||||
|
|
||||||
|
This will make the migration from the old to the new Restreamer seamless.
|
||||||
|
|
||||||
|
If the v1 database is from a Restreamer before 0.6.7, then some assumptions are made that the input stream has a H264 video track.
|
||||||
|
|
||||||
|
All the environment variables for defining the USB device or Rapsicam are respected and are used to define the inputs.
|
||||||
|
|
||||||
|
## Processing
|
||||||
|
|
||||||
|
The importer understands and respects the same environment variables as the Restreamer. It uses `RS_CONFIGFILE` (and possibly
|
||||||
|
`RS_DB_DIR`) to find out where to look for the databases. If the v1 database is not found in this directory, there's nothing
|
||||||
|
to import and the importer aborts. If there's a v1 database, the importer checks if there's also a non-empty new database. If
|
||||||
|
there is an existing non-empty new database then the v1 database will not be imported. The existing non-empty new database is
|
||||||
|
interpreted that this is not a fresh Restreamer installation and should not be altered.
|
||||||
|
|
||||||
|
In case the new database is considered empty (i.e. no process and no metadata entries), then the v1 database will be read and
|
||||||
|
transformed to the corresponding new database format. In order keep compatibility with the v1 Restreamer, the importer also
|
||||||
|
alters the configuration file by adding static redirect routes to the new locations of the player, snapshot, and m3u8.
|
||||||
|
|
||||||
|
## Return Values
|
||||||
|
|
||||||
|
The importer exits with `0` if the import was successful or if the import did not happen (either because there wasn't a v1 database,
|
||||||
|
or there was already a non-empty new database). If there were any error during the importing process, the importer exits with `1`.
|
||||||
|
During the runtime of the importer, log messages are written to `stderr`.
|
||||||
59
app/import/fixtures/v1_empty.json
Normal file
59
app/import/fixtures/v1_empty.json
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "",
|
||||||
|
"optionalOutputAddress": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none"
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "http://127.0.0.1:8080/memfs/live.stream.m3u8",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 2,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
app/import/fixtures/v1_pre-0.6.7.json
Normal file
61
app/import/fixtures/v1_pre-0.6.7.json
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "44100",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "44100",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "mp3",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "none",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "44100",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "http://admin:foobar@127.0.0.1:8080/memfs/stream.m3u8",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 2
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "hls",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 2
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "encode",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "encode",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "encode",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "mp3",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "mp3",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "44100"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "mp3",
|
||||||
|
"preset": "encode",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "128",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": null,
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "slow",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "29.95",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "encode",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "aac",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "copy",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "none",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"addresses": {
|
||||||
|
"srcAddress": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"optionalOutputAddress": "",
|
||||||
|
"srcStreams": {
|
||||||
|
"audio": {
|
||||||
|
"index": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"codec": "aac",
|
||||||
|
"layout": "stereo",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": "11000"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"index": 0,
|
||||||
|
"type": "video",
|
||||||
|
"codec": "h264",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"format": "yuv420p"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"rtspTcp": true,
|
||||||
|
"video": {
|
||||||
|
"codec": "h264",
|
||||||
|
"preset": "slow",
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "30",
|
||||||
|
"profile": "high",
|
||||||
|
"tune": "grain",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"codec": "auto",
|
||||||
|
"preset": "silence",
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "mono",
|
||||||
|
"sampling": "22050",
|
||||||
|
"id": "a"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false,
|
||||||
|
"color": "#3daa48",
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"position": "bottom-right",
|
||||||
|
"link": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "rtmp",
|
||||||
|
"rtmp": {},
|
||||||
|
"hls": {
|
||||||
|
"method": "POST",
|
||||||
|
"time": "2",
|
||||||
|
"listSize": "10",
|
||||||
|
"timeout": "10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"userActions": {
|
||||||
|
"repeatToLocalNginx": "stop",
|
||||||
|
"repeatToOptionalOutput": "stop"
|
||||||
|
},
|
||||||
|
"states": {
|
||||||
|
"repeatToLocalNginx": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"repeatToOptionalOutput": {
|
||||||
|
"type": "disconnected",
|
||||||
|
"message": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
app/import/fixtures/v4_empty.json
Normal file
8
app/import/fixtures/v4_empty.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "http://127.0.0.1:8080/memfs/live.stream.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-analyzeduration",
|
||||||
|
"20000000",
|
||||||
|
"-re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:2",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"codec": "",
|
||||||
|
"coder": "default",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"codec": "",
|
||||||
|
"coder": "default",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "http://127.0.0.1:8080/memfs/live.stream.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-analyzeduration",
|
||||||
|
"20000000",
|
||||||
|
"-re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "http://127.0.0.1:8080/memfs/live.stream.m3u8",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 2,
|
||||||
|
"type": "video",
|
||||||
|
"url": "http://127.0.0.1:8080/memfs/live.stream.m3u8",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
342
app/import/fixtures/v4_pre-0.6.7.json
Normal file
342
app/import/fixtures/v4_pre-0.6.7.json
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "32",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "44100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-analyzeduration",
|
||||||
|
"3000000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-analyzeduration",
|
||||||
|
"3000000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,342 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "32",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "44100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,342 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,342 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "mp3",
|
||||||
|
"coder": "libmp3lame",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,437 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:egress:hls:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:egress:hls:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:egress:hls:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": [
|
||||||
|
"-re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "http://admin:foobar@127.0.0.1:8080/memfs/stream.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-codec",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-hls_init_time",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"10",
|
||||||
|
"-hls_delete_threshold",
|
||||||
|
"1",
|
||||||
|
"-hls_start_number_source",
|
||||||
|
"generic",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_allow_cache",
|
||||||
|
"0",
|
||||||
|
"-hls_enc",
|
||||||
|
"0",
|
||||||
|
"-hls_segment_type",
|
||||||
|
"mpegts",
|
||||||
|
"-hls_flags",
|
||||||
|
"delete_segments,append_list",
|
||||||
|
"-method",
|
||||||
|
"POST",
|
||||||
|
"-http_persistent",
|
||||||
|
"0",
|
||||||
|
"-timeout",
|
||||||
|
"10",
|
||||||
|
"-ignore_io_errors",
|
||||||
|
"0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 30,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:egress:hls:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 30,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "HLS",
|
||||||
|
"output": {
|
||||||
|
"address": "http://admin:foobar@127.0.0.1:8080/memfs/stream.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-codec",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-hls_init_time",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"10",
|
||||||
|
"-hls_delete_threshold",
|
||||||
|
"1",
|
||||||
|
"-hls_start_number_source",
|
||||||
|
"generic",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_allow_cache",
|
||||||
|
"0",
|
||||||
|
"-hls_enc",
|
||||||
|
"0",
|
||||||
|
"-hls_segment_type",
|
||||||
|
"mpegts",
|
||||||
|
"-hls_flags",
|
||||||
|
"delete_segments,append_list",
|
||||||
|
"-method",
|
||||||
|
"POST",
|
||||||
|
"-http_persistent",
|
||||||
|
"0",
|
||||||
|
"-timeout",
|
||||||
|
"10",
|
||||||
|
"-ignore_io_errors",
|
||||||
|
"0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"address": "admin:foobar@127.0.0.1:8080/memfs/stream.m3u8",
|
||||||
|
"options": {
|
||||||
|
"hls_delete_threshold": "1",
|
||||||
|
"hls_flags": [
|
||||||
|
"delete_segments",
|
||||||
|
"append_list"
|
||||||
|
],
|
||||||
|
"hls_init_time": "0",
|
||||||
|
"hls_list_size": "10",
|
||||||
|
"hls_segment_type": "mpegts",
|
||||||
|
"hls_start_number_source": "generic",
|
||||||
|
"hls_time": "2",
|
||||||
|
"method": "POST",
|
||||||
|
"start_number": "0",
|
||||||
|
"timeout": "10",
|
||||||
|
"ignore_io_errors": "0",
|
||||||
|
"hls_enc": "0",
|
||||||
|
"hls_allow_cache": "0",
|
||||||
|
"http_persistent": "0"
|
||||||
|
},
|
||||||
|
"protocol": "http://"
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "copy",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "copy",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,366 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:egress:rtmp:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:egress:rtmp:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:egress:rtmp:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": [
|
||||||
|
"-re"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-codec",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"flv",
|
||||||
|
"-rtmp_flashver",
|
||||||
|
"FMLE/3.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 30,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:egress:rtmp:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 30,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "RTMP",
|
||||||
|
"output": {
|
||||||
|
"address": "rtmp://127.0.0.1:1936/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-codec",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"flv",
|
||||||
|
"-rtmp_flashver",
|
||||||
|
"FMLE/3.0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1:1936/live/stream",
|
||||||
|
"protocol": "rtmp://"
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "copy",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,315 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-bsf:a",
|
||||||
|
"aac_adtstoasc",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-bsf:a",
|
||||||
|
"aac_adtstoasc"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,361 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,330 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "copy",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-bsf:a",
|
||||||
|
"aac_adtstoasc",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-bsf:a",
|
||||||
|
"aac_adtstoasc"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"2048k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"2048k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"2048k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "2048",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"copy",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "copy",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"copy"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "mp3",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero",
|
||||||
|
"-map",
|
||||||
|
"0:1",
|
||||||
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "mp3",
|
||||||
|
"coder": "libmp3lame",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"libmp3lame",
|
||||||
|
"-b:a",
|
||||||
|
"64k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "copy",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"copy",
|
||||||
|
"-vsync",
|
||||||
|
"0",
|
||||||
|
"-copyts",
|
||||||
|
"-start_at_zero"
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"options": [
|
||||||
|
"-fflags",
|
||||||
|
"+genpts",
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-stimeout",
|
||||||
|
"5000000",
|
||||||
|
"-rtsp_transport",
|
||||||
|
"tcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"general": {
|
||||||
|
"fflags": [
|
||||||
|
"genpts"
|
||||||
|
],
|
||||||
|
"thread_queue_size": 512
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"forceFramerate": false,
|
||||||
|
"framerate": 25,
|
||||||
|
"readNative": true
|
||||||
|
},
|
||||||
|
"mode": "pull",
|
||||||
|
"rtsp": {
|
||||||
|
"stimeout": 5000000,
|
||||||
|
"udp": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "yuv420p",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 1280
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "mp3",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 1,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "rtsp://127.0.0.1:8554/live/stream",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "network"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "32",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "44100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"128k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"128k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=22050:ocl=mono"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "128",
|
||||||
|
"channels": "1",
|
||||||
|
"layout": "mono",
|
||||||
|
"sampling": "22050"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"slow",
|
||||||
|
"-b:v",
|
||||||
|
"4096k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"4096k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"4096k",
|
||||||
|
"-r",
|
||||||
|
"29.95",
|
||||||
|
"-g",
|
||||||
|
"60",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"32k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=44100:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "32",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "44100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"slow",
|
||||||
|
"-b:v",
|
||||||
|
"4096k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"4096k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"4096k",
|
||||||
|
"-r",
|
||||||
|
"29.95",
|
||||||
|
"-g",
|
||||||
|
"60",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "29.95",
|
||||||
|
"preset": "slow",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"options": [
|
||||||
|
"-f",
|
||||||
|
"lavfi"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"amplitude": 1,
|
||||||
|
"beepfactor": 4,
|
||||||
|
"color": "white",
|
||||||
|
"frequency": 440,
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": 44100,
|
||||||
|
"source": "silence"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "lavfi",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 44100,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "anullsrc=r=44100:cl=stereo",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "virtualaudio"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "28",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "11000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"device": "1,0",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": 11000,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "alsa",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 11000,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "hw:1,0",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "alsa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "28",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "11000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"device": "1,0",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": 11000,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "alsa",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 11000,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "hw:1,0",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "alsa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "28",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "11000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"device": "1,0",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": 11000,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "alsa",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 11000,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "hw:1,0",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "alsa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "28",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "11000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"device": "1,0",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": 11000,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "alsa",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 11000,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "hw:1,0",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "alsa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency",
|
||||||
|
"-an",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "none",
|
||||||
|
"coder": "none",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"source": -1,
|
||||||
|
"stream": -1
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"ultrafast",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"5000k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"5000k",
|
||||||
|
"-r",
|
||||||
|
"25",
|
||||||
|
"-g",
|
||||||
|
"50",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-tune:v",
|
||||||
|
"zerolatency"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "5000",
|
||||||
|
"fps": "25",
|
||||||
|
"preset": "ultrafast",
|
||||||
|
"profile": "auto",
|
||||||
|
"tune": "zerolatency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input_1",
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.m3u8",
|
||||||
|
"options": [
|
||||||
|
"-dn",
|
||||||
|
"-sn",
|
||||||
|
"-map",
|
||||||
|
"0:0",
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"slow",
|
||||||
|
"-b:v",
|
||||||
|
"4096k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"4096k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"4096k",
|
||||||
|
"-r",
|
||||||
|
"30",
|
||||||
|
"-g",
|
||||||
|
"60",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-profile:v",
|
||||||
|
"high",
|
||||||
|
"-tune:v",
|
||||||
|
"grain",
|
||||||
|
"-map",
|
||||||
|
"1:0",
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo",
|
||||||
|
"-f",
|
||||||
|
"hls",
|
||||||
|
"-start_number",
|
||||||
|
"0",
|
||||||
|
"-hls_time",
|
||||||
|
"2",
|
||||||
|
"-hls_list_size",
|
||||||
|
"6",
|
||||||
|
"-hls_flags",
|
||||||
|
"append_list+delete_segments",
|
||||||
|
"-hls_segment_filename",
|
||||||
|
"{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f_%04d.ts",
|
||||||
|
"-y",
|
||||||
|
"-method",
|
||||||
|
"PUT"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 15,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"config": {
|
||||||
|
"id": "restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot",
|
||||||
|
"reference": "4186b095-7f0a-4e94-8c3d-f17459ab252f",
|
||||||
|
"input": [
|
||||||
|
{
|
||||||
|
"id": "input_0",
|
||||||
|
"address": "#restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f:output=output_0",
|
||||||
|
"options": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
{
|
||||||
|
"id": "output_0",
|
||||||
|
"address": "{memfs}/4186b095-7f0a-4e94-8c3d-f17459ab252f.jpg",
|
||||||
|
"options": [
|
||||||
|
"-vframes",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"image2",
|
||||||
|
"-update",
|
||||||
|
"1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": [
|
||||||
|
"-err_detect",
|
||||||
|
"ignore_err"
|
||||||
|
],
|
||||||
|
"reconnect": true,
|
||||||
|
"reconnect_delay_seconds": 60,
|
||||||
|
"autostart": true,
|
||||||
|
"stale_timeout_seconds": 30
|
||||||
|
},
|
||||||
|
"created_at": 0,
|
||||||
|
"order": "stop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"system": {},
|
||||||
|
"process": {
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f": {
|
||||||
|
"restreamer-ui": {
|
||||||
|
"control": {
|
||||||
|
"hls": {
|
||||||
|
"lhls": false,
|
||||||
|
"listSize": 6,
|
||||||
|
"segmentDuration": 2
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"autostart": true,
|
||||||
|
"delay": 15,
|
||||||
|
"reconnect": true,
|
||||||
|
"staleTimeout": 30
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"enable": true,
|
||||||
|
"interval": 60
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imported": true,
|
||||||
|
"license": "CC BY 4.0",
|
||||||
|
"meta": {
|
||||||
|
"author": {
|
||||||
|
"description": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"description": "Live from earth. Powered by datarhei Restreamer.",
|
||||||
|
"name": "Livestream"
|
||||||
|
},
|
||||||
|
"player": {
|
||||||
|
"autoplay": false,
|
||||||
|
"color": {
|
||||||
|
"buttons": "#3daa48",
|
||||||
|
"seekbar": "#3daa48"
|
||||||
|
},
|
||||||
|
"ga": {
|
||||||
|
"account": "",
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"image": "",
|
||||||
|
"link": "",
|
||||||
|
"position": "bottom-right"
|
||||||
|
},
|
||||||
|
"mute": false,
|
||||||
|
"statistics": false
|
||||||
|
},
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "aac",
|
||||||
|
"coder": "aac",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:a",
|
||||||
|
"aac",
|
||||||
|
"-b:a",
|
||||||
|
"28k",
|
||||||
|
"-shortest",
|
||||||
|
"-af",
|
||||||
|
"aresample=osr=11000:ocl=stereo"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "28",
|
||||||
|
"channels": "2",
|
||||||
|
"layout": "stereo",
|
||||||
|
"sampling": "11000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 1,
|
||||||
|
"stream": 0
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"decoder": {
|
||||||
|
"coder": "default",
|
||||||
|
"codec": "",
|
||||||
|
"mapping": [],
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"codec": "h264",
|
||||||
|
"coder": "libx264",
|
||||||
|
"mapping": [
|
||||||
|
"-codec:v",
|
||||||
|
"libx264",
|
||||||
|
"-preset:v",
|
||||||
|
"slow",
|
||||||
|
"-b:v",
|
||||||
|
"4096k",
|
||||||
|
"-maxrate:v",
|
||||||
|
"4096k",
|
||||||
|
"-bufsize:v",
|
||||||
|
"4096k",
|
||||||
|
"-r",
|
||||||
|
"30",
|
||||||
|
"-g",
|
||||||
|
"60",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
"-vsync",
|
||||||
|
"1",
|
||||||
|
"-profile:v",
|
||||||
|
"high",
|
||||||
|
"-tune:v",
|
||||||
|
"grain"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"bitrate": "4096",
|
||||||
|
"fps": "30",
|
||||||
|
"preset": "slow",
|
||||||
|
"profile": "high",
|
||||||
|
"tune": "grain"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source": 0,
|
||||||
|
"stream": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "/dev/video",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"v4l2",
|
||||||
|
"-framerate",
|
||||||
|
"25",
|
||||||
|
"-video_size",
|
||||||
|
"1280x720",
|
||||||
|
"-input_format",
|
||||||
|
"nv12"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"device": "/dev/video",
|
||||||
|
"format": "nv12",
|
||||||
|
"framerate": 25,
|
||||||
|
"size": "1280x720"
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 0,
|
||||||
|
"codec": "rawvideo",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 720,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "",
|
||||||
|
"pix_fmt": "nv12",
|
||||||
|
"sampling_hz": 0,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "video",
|
||||||
|
"url": "/dev/video",
|
||||||
|
"width": 1280
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "video4linux2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"options": [
|
||||||
|
"-thread_queue_size",
|
||||||
|
"512",
|
||||||
|
"-f",
|
||||||
|
"alsa",
|
||||||
|
"-ac",
|
||||||
|
"2",
|
||||||
|
"-ar",
|
||||||
|
"11000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"address": "hw:1,0",
|
||||||
|
"device": "1,0",
|
||||||
|
"channels": 2,
|
||||||
|
"sampling": 11000,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"bitrate_kbps": 0,
|
||||||
|
"channels": 2,
|
||||||
|
"codec": "pcm_u8",
|
||||||
|
"coder": "",
|
||||||
|
"duration_sec": 0,
|
||||||
|
"format": "alsa",
|
||||||
|
"fps": 0,
|
||||||
|
"height": 0,
|
||||||
|
"index": 0,
|
||||||
|
"language": "",
|
||||||
|
"layout": "stereo",
|
||||||
|
"pix_fmt": "",
|
||||||
|
"sampling_hz": 11000,
|
||||||
|
"stream": 0,
|
||||||
|
"type": "audio",
|
||||||
|
"url": "hw:1,0",
|
||||||
|
"width": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "alsa"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restreamer-ui:ingest:4186b095-7f0a-4e94-8c3d-f17459ab252f_snapshot": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1434
app/import/import.go
Normal file
1434
app/import/import.go
Normal file
File diff suppressed because it is too large
Load diff
315
app/import/import_test.go
Normal file
315
app/import/import_test.go
Normal file
|
|
@ -0,0 +1,315 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
gojson "encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/encoding/json"
|
||||||
|
"github.com/datarhei/core/restream/store"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scenarios:
|
||||||
|
// - Empty v1 file - v1/v4 empty
|
||||||
|
// - Input: RTSP (h264/), copy video, no audio, Output: none - v1/v4 rtsp_h264,-_copyvideo_noaudio_output_none
|
||||||
|
// - Input: HLS (h264/), copy video, no audio, Output: none - v1/v4 hls_h264,-_copyvideo_noaudio_output_none
|
||||||
|
// - Input: RTMP (h264/), copy video, no audio, Output: none - v1/v4 rtmp_h264,-_copyvideo_noaudio_output_none
|
||||||
|
// - Input: RTSP (h264/), copy video, auto audio, Output: none - v1/v4 rtsp_h264,-_copyvideo_autoaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), copy video, copy audio, Output: none - v1/v4 rtsp_h264,aac_copyvideo_copyaudio_output_none
|
||||||
|
// - Input: RTSP (h264/mp3), copy video, copy audio, Output: none - v1/v4 rtsp_h264,mp3_copyvideo_copyaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), copy video, encode audio aac, Output: none - v1/v4 rtsp_h264,aac_copyvideo_encodeaudioaac_output_none
|
||||||
|
// - Input: RTSP (h264/mp3), copy video, encode audio mp3, Output: none - v1/v4 rtsp_h264,mp3_copyvideo_encodeaudiomp3_output_none
|
||||||
|
// - Input: RTSP (h264/aac), copy video, no audio, Output: none - v1/v4 rtsp_h264,aac_copyvideo_noaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), copy video, encode silence aac, Output: none - v1/v4 rtsp_h264,aac_copyvideo_encodesilenceaac_output_none
|
||||||
|
// - Input: RTSP (h264/), copy video, encode silence aac, Output: none - v1/v4 rtsp_h264,-_copyvideo_encodesilenceaac_output_none
|
||||||
|
// - Input: RTSP (h264/), copy video, encode silence mp3, Output: none - v1/v4 rtsp_h264_copyvideo_encodesilencemp3_output_none
|
||||||
|
// - Input: RTSP (h264/), encode video, no audio, Output: none - v1v4/ rtsp_h264,-_encodevideo_noaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), encode video, copy audio, Output: none - v1/v4 rtsp_h264,aac_encodevideo_copyaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), encode video, encode audio aac, Output: none - v1/v4 rtsp_h264,aac_encodevideo_encodeaudioaac_output_none
|
||||||
|
// - Input: RTSP (h264/aac), encode video, encode silence aac, Output: none - v1/v4 rtsp_h264,aac_encodevideo_encodesilenceaac_output_none
|
||||||
|
// - Input: RTSP (h264/aac), encode video, no audio, Output: none - v1/v4 rtsp_h264,aac_encodevideo_noaudio_output_none
|
||||||
|
// - Input: RTSP (h264/aac), copy video, copy audio, Output: RTMP - v1/v4 rtsp_h264,aac_copyvideo_copyaudio_output_rtmp
|
||||||
|
// - Input: RTSP (h264/aac), copy video, copy audio, Output: HLS - v1/v4 rtsp_h264,aac_copyvideo_copyaudio_output_hls
|
||||||
|
|
||||||
|
var id string = "4186b095-7f0a-4e94-8c3d-f17459ab252f"
|
||||||
|
|
||||||
|
func testV1Import(t *testing.T, v1Fixture, v4Fixture string, config importConfig) {
|
||||||
|
// Import v1 database
|
||||||
|
v4, err := importV1(v1Fixture, config)
|
||||||
|
require.Equal(t, nil, err)
|
||||||
|
|
||||||
|
// Reset variants
|
||||||
|
for n := range v4.Process {
|
||||||
|
v4.Process[n].CreatedAt = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to JSON
|
||||||
|
datav4, err := gojson.MarshalIndent(&v4, "", " ")
|
||||||
|
require.Equal(t, nil, err)
|
||||||
|
|
||||||
|
// Read the wanted result
|
||||||
|
wantdatav4, err := ioutil.ReadFile(v4Fixture)
|
||||||
|
require.Equal(t, nil, err)
|
||||||
|
|
||||||
|
var wantv4 store.StoreData
|
||||||
|
|
||||||
|
err = gojson.Unmarshal(wantdatav4, &wantv4)
|
||||||
|
require.Equal(t, nil, err, json.FormatError(wantdatav4, err))
|
||||||
|
|
||||||
|
// Convert to JSON
|
||||||
|
wantdatav4, err = gojson.MarshalIndent(&wantv4, "", " ")
|
||||||
|
require.Equal(t, nil, err)
|
||||||
|
|
||||||
|
// Re-convert both to golang type
|
||||||
|
gojson.Unmarshal(wantdatav4, &wantv4)
|
||||||
|
gojson.Unmarshal(datav4, &v4)
|
||||||
|
|
||||||
|
require.Equal(t, wantv4, v4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1Import(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"empty",
|
||||||
|
"hls_h264,-_copyvideo_noaudio_output_none",
|
||||||
|
"rtmp_h264,-_copyvideo_noaudio_output_none",
|
||||||
|
"rtsp_h264,-_copyvideo_autoaudio_output_none",
|
||||||
|
"rtsp_h264,-_copyvideo_encodesilenceaac_output_none",
|
||||||
|
"rtsp_h264,-_copyvideo_encodesilencemp3_output_none",
|
||||||
|
"rtsp_h264,-_copyvideo_noaudio_output_none",
|
||||||
|
"rtsp_h264,-_encodevideo_noaudio_output_none",
|
||||||
|
"rtsp_h264,aac_copyvideo_copyaudio_output_hls",
|
||||||
|
"rtsp_h264,aac_copyvideo_copyaudio_output_none",
|
||||||
|
"rtsp_h264,aac_copyvideo_copyaudio_output_rtmp",
|
||||||
|
"rtsp_h264,aac_copyvideo_encodeaudioaac_output_none",
|
||||||
|
"rtsp_h264,aac_copyvideo_encodesilenceaac_output_none",
|
||||||
|
"rtsp_h264,aac_copyvideo_noaudio_output_none",
|
||||||
|
"rtsp_h264,aac_encodevideo_copyaudio_output_none",
|
||||||
|
"rtsp_h264,aac_encodevideo_encodeaudioaac_output_none",
|
||||||
|
"rtsp_h264,aac_encodevideo_encodesilenceaac_output_none",
|
||||||
|
"rtsp_h264,aac_encodevideo_noaudio_output_none",
|
||||||
|
"rtsp_h264,mp3_copyvideo_copyaudio_output_none",
|
||||||
|
"rtsp_h264,mp3_copyvideo_encodeaudiomp3_output_none",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
testV1Import(t, "./fixtures/v1_"+test+".json", "./fixtures/v4_"+test+".json", importConfig{
|
||||||
|
id: id,
|
||||||
|
snapshotInterval: 60,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportSnapshotInterval(t *testing.T) {
|
||||||
|
type testdata struct {
|
||||||
|
value string
|
||||||
|
expected int
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []testdata{
|
||||||
|
{"0", 0},
|
||||||
|
{"0ms", 0},
|
||||||
|
{"0s", 0},
|
||||||
|
{"0m", 0},
|
||||||
|
{"60000", 60},
|
||||||
|
{"60000ms", 60},
|
||||||
|
{"60s", 60},
|
||||||
|
{"1m", 60},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.value, func(t *testing.T) {
|
||||||
|
actual := importSnapshotInterval(test.value, -1)
|
||||||
|
require.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios:
|
||||||
|
// Input: V4L (h264), copy video, auto audio, Output: none - v1/v4 v4l_h264,-_copyvideo_autoaudio_output_none
|
||||||
|
// Input: V4L (h264), copy video, no audio, Output: none - v1/v4 v4l_h264,-_copyvideo_noaudio_output_none
|
||||||
|
// Input: V4L (h264), copy video, encode silence aac, Output: none - v1/v4 v4l_h264,-_copyvideo_encodesilenceaac_output_none
|
||||||
|
// Input: V4L (h264), encode video, auto audio, Output: none - v1/v4 v4l_h264,-_encodevideo_autoaudio_output_none
|
||||||
|
|
||||||
|
func TestImportUSBCamWithoutAudio(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"v4l_h264,-_copyvideo_autoaudio_output_none",
|
||||||
|
"v4l_h264,-_copyvideo_noaudio_output_none",
|
||||||
|
"v4l_h264,-_copyvideo_encodesilenceaac_output_none",
|
||||||
|
"v4l_h264,-_encodevideo_autoaudio_output_none",
|
||||||
|
}
|
||||||
|
|
||||||
|
config := importConfig{
|
||||||
|
id: id,
|
||||||
|
snapshotInterval: 60,
|
||||||
|
usbcam: importConfigUSBCam{
|
||||||
|
enable: true,
|
||||||
|
device: "/dev/video",
|
||||||
|
fps: "25",
|
||||||
|
gop: "50",
|
||||||
|
bitrate: "5000000",
|
||||||
|
preset: "ultrafast",
|
||||||
|
profile: "auto",
|
||||||
|
width: "1280",
|
||||||
|
height: "720",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
testV1Import(t, "./fixtures/v1_"+test+".json", "./fixtures/v4_"+test+".json", config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenarios:
|
||||||
|
// Input: V4L/ALSA (h264/aac), copy video, auto audio, Output: none - v1/v4 v4lalsa_h264,aac_copyvideo_autoaudio_output_none
|
||||||
|
// Input: V4L/ALSA (h264/aac), copy video, copy audio, Output: none - v1/v4 v4lalsa_h264,aac_copyvideo_copyaudio_output_none
|
||||||
|
// Input: V4L/ALSA (h264/aac), copy video, no audio, Output: none - v1/v4 v4lalsa_h264,aac_copyvideo_noaudio_output_none
|
||||||
|
// Input: V4L/ALSA (h264/aac), copy video, encode silence aac, Output: none - v1/v4 v4lalsa_h264,aac_copyvideo_encodesilenceaac_output_none
|
||||||
|
// Input: V4L/ALSA (h264/aac), copy video, encode audio aac, Output: none - v1/v4 v4lalsa_h264,aac_copyvideo_encodeaudioaac_output_none
|
||||||
|
// Input: V4L/ALSA (h264/aac), encode video, auto audio, Output: none - v1/v4 v4lalsa_h264,aac_encodevideo_autoaudio_output_none
|
||||||
|
|
||||||
|
func TestImportUSBCamWithAudio(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"v4lalsa_h264,aac_copyvideo_autoaudio_output_none",
|
||||||
|
"v4lalsa_h264,aac_copyvideo_copyaudio_output_none",
|
||||||
|
"v4lalsa_h264,aac_copyvideo_noaudio_output_none",
|
||||||
|
"v4lalsa_h264,aac_copyvideo_encodesilenceaac_output_none",
|
||||||
|
"v4lalsa_h264,aac_copyvideo_encodeaudioaac_output_none",
|
||||||
|
"v4lalsa_h264,aac_encodevideo_autoaudio_output_none",
|
||||||
|
}
|
||||||
|
|
||||||
|
config := importConfig{
|
||||||
|
id: id,
|
||||||
|
snapshotInterval: 60,
|
||||||
|
usbcam: importConfigUSBCam{
|
||||||
|
enable: true,
|
||||||
|
device: "/dev/video",
|
||||||
|
fps: "25",
|
||||||
|
gop: "50",
|
||||||
|
bitrate: "5000000",
|
||||||
|
preset: "ultrafast",
|
||||||
|
profile: "auto",
|
||||||
|
width: "1280",
|
||||||
|
height: "720",
|
||||||
|
},
|
||||||
|
audio: importConfigAudio{
|
||||||
|
enable: true,
|
||||||
|
device: "1,0",
|
||||||
|
bitrate: "28000",
|
||||||
|
channels: "2",
|
||||||
|
layout: "stereo",
|
||||||
|
sampling: "11000",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
testV1Import(t, "./fixtures/v1_"+test+".json", "./fixtures/v4_"+test+".json", config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1EnvironmentDefaults(t *testing.T) {
|
||||||
|
for key := range v1Environment {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
initV1Environment()
|
||||||
|
|
||||||
|
for _, val := range v1Environment {
|
||||||
|
require.Equal(t, val.defval, val.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := importConfigFromEnvironment()
|
||||||
|
|
||||||
|
require.Equal(t, false, config.usbcam.enable)
|
||||||
|
require.Equal(t, false, config.raspicam.enable)
|
||||||
|
require.Equal(t, false, config.audio.enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1EnvironmentUSBCam(t *testing.T) {
|
||||||
|
for key := range v1Environment {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("RS_MODE", "USBCAM")
|
||||||
|
|
||||||
|
config := importConfigFromEnvironment()
|
||||||
|
|
||||||
|
require.Equal(t, true, config.usbcam.enable)
|
||||||
|
require.Equal(t, "/dev/video", config.usbcam.device)
|
||||||
|
require.Equal(t, "25", config.usbcam.fps)
|
||||||
|
require.Equal(t, "50", config.usbcam.gop)
|
||||||
|
require.Equal(t, "5000000", config.usbcam.bitrate)
|
||||||
|
require.Equal(t, "ultrafast", config.usbcam.preset)
|
||||||
|
require.Equal(t, "baseline", config.usbcam.profile)
|
||||||
|
require.Equal(t, "1280", config.usbcam.width)
|
||||||
|
require.Equal(t, "720", config.usbcam.height)
|
||||||
|
|
||||||
|
os.Setenv("RS_USBCAM_AUDIO", "true")
|
||||||
|
|
||||||
|
config = importConfigFromEnvironment()
|
||||||
|
|
||||||
|
require.Equal(t, true, config.audio.enable)
|
||||||
|
require.Equal(t, "0", config.audio.device)
|
||||||
|
require.Equal(t, "64000", config.audio.bitrate)
|
||||||
|
require.Equal(t, "1", config.audio.channels)
|
||||||
|
require.Equal(t, "mono", config.audio.layout)
|
||||||
|
require.Equal(t, "44100", config.audio.sampling)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1EnvironmentRASPICam(t *testing.T) {
|
||||||
|
for key := range v1Environment {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("RS_MODE", "RASPICAM")
|
||||||
|
|
||||||
|
config := importConfigFromEnvironment()
|
||||||
|
|
||||||
|
require.Equal(t, true, config.raspicam.enable)
|
||||||
|
require.Equal(t, "25", config.raspicam.fps)
|
||||||
|
require.Equal(t, "1920", config.raspicam.width)
|
||||||
|
require.Equal(t, "1080", config.raspicam.height)
|
||||||
|
|
||||||
|
os.Setenv("RS_RASPICAM_AUDIO", "true")
|
||||||
|
|
||||||
|
config = importConfigFromEnvironment()
|
||||||
|
|
||||||
|
require.Equal(t, true, config.audio.enable)
|
||||||
|
require.Equal(t, "0", config.audio.device)
|
||||||
|
require.Equal(t, "64000", config.audio.bitrate)
|
||||||
|
require.Equal(t, "1", config.audio.channels)
|
||||||
|
require.Equal(t, "mono", config.audio.layout)
|
||||||
|
require.Equal(t, "44100", config.audio.sampling)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1EnvironmentInputstream(t *testing.T) {
|
||||||
|
for key := range v1Environment {
|
||||||
|
os.Unsetenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("RS_INPUTSTREAM", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1Pre067(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"pre-0.6.7",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test, func(t *testing.T) {
|
||||||
|
testV1Import(t, "./fixtures/v1_"+test+".json", "./fixtures/v4_"+test+".json", importConfig{
|
||||||
|
id: id,
|
||||||
|
snapshotInterval: 60,
|
||||||
|
binary: "ffmpeg",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
126
app/import/main.go
Normal file
126
app/import/main.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/config"
|
||||||
|
"github.com/datarhei/core/log"
|
||||||
|
"github.com/datarhei/core/restream/store"
|
||||||
|
|
||||||
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if ok := doImport(); !ok {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doImport() bool {
|
||||||
|
logger := log.New("Import").WithOutput(log.NewConsoleWriter(os.Stderr, log.Linfo, true)).WithField("version", "v1")
|
||||||
|
|
||||||
|
logger.Info().Log("Database import")
|
||||||
|
|
||||||
|
configstore, err := config.NewJSONStore(os.Getenv("CORE_CONFIGFILE"), nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().WithError(err).Log("Loading configuration failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := configstore.Get()
|
||||||
|
|
||||||
|
// Merging the persisted config with the environment variables
|
||||||
|
cfg.Merge()
|
||||||
|
|
||||||
|
cfg.Validate(false)
|
||||||
|
if cfg.HasErrors() {
|
||||||
|
logger.Error().Log("The configuration contains errors")
|
||||||
|
cfg.Messages(func(level string, v config.Variable, message string) {
|
||||||
|
if level == "error" {
|
||||||
|
logger.Error().WithFields(log.Fields{
|
||||||
|
"variable": v.Name,
|
||||||
|
"value": v.Value,
|
||||||
|
"env": v.EnvName,
|
||||||
|
"description": v.Description,
|
||||||
|
}).Log(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Log("Checking for database ...")
|
||||||
|
|
||||||
|
// Check if there's a v1.json from the old Restreamer
|
||||||
|
v1filename := cfg.DB.Dir + "/v1.json"
|
||||||
|
|
||||||
|
logger = logger.WithField("database", v1filename)
|
||||||
|
|
||||||
|
if _, err := os.Stat(v1filename); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
logger.Info().Log("Database doesn't exist and nothing will be imported")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Error().WithError(err).Log("Checking for v1 database")
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Log("Found database")
|
||||||
|
|
||||||
|
// Load an existing DB
|
||||||
|
datastore := store.NewJSONStore(store.JSONConfig{
|
||||||
|
Dir: cfg.DB.Dir,
|
||||||
|
})
|
||||||
|
|
||||||
|
data, err := datastore.Load()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().WithError(err).Log("Loading new database failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the existing DB has already some data in it.
|
||||||
|
// If it's not empty, we will not import any v1 DB.
|
||||||
|
if !data.IsEmpty() {
|
||||||
|
logger.Info().Log("There's already information stored in the new database and the v1 database will not be imported")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Log("Importing database ...")
|
||||||
|
|
||||||
|
// Read the Restreamer config from the environment variables
|
||||||
|
importConfig := importConfigFromEnvironment()
|
||||||
|
importConfig.binary = cfg.FFmpeg.Binary
|
||||||
|
|
||||||
|
// Rewrite the old database to the new database
|
||||||
|
r, err := importV1(v1filename, importConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error().WithError(err).Log("Importing database failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the imported DB
|
||||||
|
if err := datastore.Store(r); err != nil {
|
||||||
|
logger.Error().WithError(err).Log("Storing imported data to new database failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the unmerged config for persisting
|
||||||
|
cfg = configstore.Get()
|
||||||
|
|
||||||
|
// Add static routes to mimic the old URLs
|
||||||
|
cfg.Router.Routes["/hls/live.stream.m3u8"] = "/memfs/" + importConfig.id + ".m3u8"
|
||||||
|
cfg.Router.Routes["/images/live.jpg"] = "/memfs/" + importConfig.id + ".jpg"
|
||||||
|
cfg.Router.Routes["/player.html"] = "/" + importConfig.id + ".html"
|
||||||
|
|
||||||
|
// Persist the modified config
|
||||||
|
if err := configstore.Set(cfg); err != nil {
|
||||||
|
logger.Error().WithError(err).Log("Storing adjusted config failed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info().Log("Successfully imported data")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
49
app/version.go
Normal file
49
app/version.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name of the app
|
||||||
|
const Name = "datarhei-core"
|
||||||
|
|
||||||
|
type versionInfo struct {
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
Patch int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v versionInfo) String() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v versionInfo) MajorString() string {
|
||||||
|
return fmt.Sprintf("%d.0.0", v.Major)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v versionInfo) MinorString() string {
|
||||||
|
return fmt.Sprintf("%d.%d.0", v.Major, v.Minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version of the app
|
||||||
|
var Version = versionInfo{
|
||||||
|
Major: 16,
|
||||||
|
Minor: 7,
|
||||||
|
Patch: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit is the git commit the app is build from. It should be filled in during compilation
|
||||||
|
var Commit = ""
|
||||||
|
|
||||||
|
// Branch is the git branch the app is build from. It should be filled in during compilation
|
||||||
|
var Branch = ""
|
||||||
|
|
||||||
|
// Build is the timestamp of when the app has been build. It should be filled in during compilation
|
||||||
|
var Build = ""
|
||||||
|
|
||||||
|
// Arch is the OS and CPU architecture this app is build for.
|
||||||
|
var Arch = runtime.GOOS + "/" + runtime.GOARCH
|
||||||
|
|
||||||
|
// Compiler is the golang version this app has been build with.
|
||||||
|
var Compiler = runtime.Version()
|
||||||
3
build.sh
Executable file
3
build.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env /bin/sh
|
||||||
|
|
||||||
|
make release
|
||||||
679
config/config.go
Normal file
679
config/config.go
Normal file
|
|
@ -0,0 +1,679 @@
|
||||||
|
// Package config implements types for handling the configuation for the app.
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/math/rand"
|
||||||
|
|
||||||
|
haikunator "github.com/atrox/haikunatorgo/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type variable struct {
|
||||||
|
value value // The actual value
|
||||||
|
defVal string // The default value in string representation
|
||||||
|
name string // A name for this value
|
||||||
|
envName string // The environment variable that corresponds to this value
|
||||||
|
envAltNames []string // Alternative environment variable names
|
||||||
|
description string // A desriptions for this value
|
||||||
|
required bool // Whether a non-empty value is required
|
||||||
|
disguise bool // Whether the value should be disguised if printed
|
||||||
|
merged bool // Whether this value has been replaced by its corresponding environment variable
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variable struct {
|
||||||
|
Value string
|
||||||
|
Name string
|
||||||
|
EnvName string
|
||||||
|
Description string
|
||||||
|
Merged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
message string // The log message
|
||||||
|
variable Variable // The config field this message refers to
|
||||||
|
level string // The loglevel for this message
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth0Tenant struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Audience string `json:"audience"`
|
||||||
|
ClientID string `json:"clientid"`
|
||||||
|
Users []string `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data is the actual configuration data for the app
|
||||||
|
type Data struct {
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
LoadedAt time.Time `json:"-"`
|
||||||
|
UpdatedAt time.Time `json:"-"`
|
||||||
|
Version int64 `json:"version" jsonschema:"minimum=1,maximum=1"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
CheckForUpdates bool `json:"update_check"`
|
||||||
|
Log struct {
|
||||||
|
Level string `json:"level" enums:"debug,info,warn,error,silent" jsonschema:"enum=debug,enum=info,enum=warn,enum=error,enum=silent"`
|
||||||
|
Topics []string `json:"topics"`
|
||||||
|
MaxLines int `json:"max_lines"`
|
||||||
|
} `json:"log"`
|
||||||
|
DB struct {
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
} `json:"db"`
|
||||||
|
Host struct {
|
||||||
|
Name []string `json:"name"`
|
||||||
|
Auto bool `json:"auto"`
|
||||||
|
} `json:"host"`
|
||||||
|
API struct {
|
||||||
|
ReadOnly bool `json:"read_only"`
|
||||||
|
Access struct {
|
||||||
|
HTTP struct {
|
||||||
|
Allow []string `json:"allow"`
|
||||||
|
Block []string `json:"block"`
|
||||||
|
} `json:"http"`
|
||||||
|
HTTPS struct {
|
||||||
|
Allow []string `json:"allow"`
|
||||||
|
Block []string `json:"block"`
|
||||||
|
} `json:"https"`
|
||||||
|
} `json:"access"`
|
||||||
|
Auth struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
DisableLocalhost bool `json:"disable_localhost"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
JWT struct {
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
} `json:"jwt"`
|
||||||
|
Auth0 struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Tenants []Auth0Tenant `json:"tenants"`
|
||||||
|
} `json:"auth0"`
|
||||||
|
} `json:"auth"`
|
||||||
|
} `json:"api"`
|
||||||
|
TLS struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Auto bool `json:"auto"`
|
||||||
|
CertFile string `json:"cert_file"`
|
||||||
|
KeyFile string `json:"key_file"`
|
||||||
|
} `json:"tls"`
|
||||||
|
Storage struct {
|
||||||
|
Disk struct {
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Size int64 `json:"max_size_mbytes"`
|
||||||
|
Cache struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Size uint64 `json:"max_size_mbytes"`
|
||||||
|
TTL int64 `json:"ttl_seconds"`
|
||||||
|
FileSize uint64 `json:"max_file_size_mbytes"`
|
||||||
|
Types []string `json:"types"`
|
||||||
|
} `json:"cache"`
|
||||||
|
} `json:"disk"`
|
||||||
|
Memory struct {
|
||||||
|
Auth struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
} `json:"auth"`
|
||||||
|
Size int64 `json:"max_size_mbytes"`
|
||||||
|
Purge bool `json:"purge"`
|
||||||
|
} `json:"memory"`
|
||||||
|
CORS struct {
|
||||||
|
Origins []string `json:"origins"`
|
||||||
|
} `json:"cors"`
|
||||||
|
MimeTypes string `json:"mimetypes_file"`
|
||||||
|
} `json:"storage"`
|
||||||
|
RTMP struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
EnableTLS bool `json:"enable_tls"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
App string `json:"app"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
} `json:"rtmp"`
|
||||||
|
FFmpeg struct {
|
||||||
|
Binary string `json:"binary"`
|
||||||
|
MaxProcesses int64 `json:"max_processes"`
|
||||||
|
Access struct {
|
||||||
|
Input struct {
|
||||||
|
Allow []string `json:"allow"`
|
||||||
|
Block []string `json:"block"`
|
||||||
|
} `json:"input"`
|
||||||
|
Output struct {
|
||||||
|
Allow []string `json:"allow"`
|
||||||
|
Block []string `json:"block"`
|
||||||
|
} `json:"output"`
|
||||||
|
} `json:"access"`
|
||||||
|
Log struct {
|
||||||
|
MaxLines int `json:"max_lines"`
|
||||||
|
MaxHistory int `json:"max_history"`
|
||||||
|
} `json:"log"`
|
||||||
|
} `json:"ffmpeg"`
|
||||||
|
Playout struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
MinPort int `json:"min_port"`
|
||||||
|
MaxPort int `json:"max_port"`
|
||||||
|
} `json:"playout"`
|
||||||
|
Debug struct {
|
||||||
|
Profiling bool `json:"profiling"`
|
||||||
|
ForceGC int `json:"force_gc"`
|
||||||
|
} `json:"debug"`
|
||||||
|
Metrics struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
EnablePrometheus bool `json:"enable_prometheus"`
|
||||||
|
Range int64 `json:"range_sec"` // seconds
|
||||||
|
Interval int64 `json:"interval_sec"` // seconds
|
||||||
|
} `json:"metrics"`
|
||||||
|
Sessions struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
IPIgnoreList []string `json:"ip_ignorelist"`
|
||||||
|
SessionTimeout int `json:"session_timeout_sec"`
|
||||||
|
Persist bool `json:"persist"`
|
||||||
|
PersistInterval int `json:"persist_interval_sec"`
|
||||||
|
MaxBitrate uint64 `json:"max_bitrate_mbit"`
|
||||||
|
MaxSessions uint64 `json:"max_sessions"`
|
||||||
|
} `json:"sessions"`
|
||||||
|
Service struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"service"`
|
||||||
|
Router struct {
|
||||||
|
BlockedPrefixes []string `json:"blocked_prefixes"`
|
||||||
|
Routes map[string]string `json:"routes"`
|
||||||
|
UIPath string `json:"ui_path"`
|
||||||
|
} `json:"router"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is a wrapper for Data
|
||||||
|
type Config struct {
|
||||||
|
vars []*variable
|
||||||
|
logs []message
|
||||||
|
|
||||||
|
Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a Config which is initialized with its default values
|
||||||
|
func New() *Config {
|
||||||
|
data := &Config{}
|
||||||
|
|
||||||
|
data.init()
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigFrom returns a clone of a Config
|
||||||
|
func NewConfigFrom(d *Config) *Config {
|
||||||
|
data := New()
|
||||||
|
|
||||||
|
data.CreatedAt = d.CreatedAt
|
||||||
|
data.LoadedAt = d.LoadedAt
|
||||||
|
data.UpdatedAt = d.UpdatedAt
|
||||||
|
|
||||||
|
data.Version = d.Version
|
||||||
|
data.ID = d.ID
|
||||||
|
data.Name = d.Name
|
||||||
|
data.Address = d.Address
|
||||||
|
data.CheckForUpdates = d.CheckForUpdates
|
||||||
|
|
||||||
|
data.Log = d.Log
|
||||||
|
data.DB = d.DB
|
||||||
|
data.Host = d.Host
|
||||||
|
data.API = d.API
|
||||||
|
data.TLS = d.TLS
|
||||||
|
data.Storage = d.Storage
|
||||||
|
data.RTMP = d.RTMP
|
||||||
|
data.FFmpeg = d.FFmpeg
|
||||||
|
data.Playout = d.Playout
|
||||||
|
data.Debug = d.Debug
|
||||||
|
data.Metrics = d.Metrics
|
||||||
|
data.Sessions = d.Sessions
|
||||||
|
data.Service = d.Service
|
||||||
|
data.Router = d.Router
|
||||||
|
|
||||||
|
data.Log.Topics = copyStringSlice(d.Log.Topics)
|
||||||
|
|
||||||
|
data.Host.Name = copyStringSlice(d.Host.Name)
|
||||||
|
|
||||||
|
data.API.Access.HTTP.Allow = copyStringSlice(d.API.Access.HTTP.Allow)
|
||||||
|
data.API.Access.HTTP.Block = copyStringSlice(d.API.Access.HTTP.Block)
|
||||||
|
data.API.Access.HTTPS.Allow = copyStringSlice(d.API.Access.HTTPS.Allow)
|
||||||
|
data.API.Access.HTTPS.Block = copyStringSlice(d.API.Access.HTTPS.Block)
|
||||||
|
|
||||||
|
data.API.Auth.Auth0.Tenants = copyTenantSlice(d.API.Auth.Auth0.Tenants)
|
||||||
|
|
||||||
|
data.Storage.CORS.Origins = copyStringSlice(d.Storage.CORS.Origins)
|
||||||
|
|
||||||
|
data.FFmpeg.Access.Input.Allow = copyStringSlice(d.FFmpeg.Access.Input.Allow)
|
||||||
|
data.FFmpeg.Access.Input.Block = copyStringSlice(d.FFmpeg.Access.Input.Block)
|
||||||
|
data.FFmpeg.Access.Output.Allow = copyStringSlice(d.FFmpeg.Access.Output.Allow)
|
||||||
|
data.FFmpeg.Access.Output.Block = copyStringSlice(d.FFmpeg.Access.Output.Block)
|
||||||
|
|
||||||
|
data.Sessions.IPIgnoreList = copyStringSlice(d.Sessions.IPIgnoreList)
|
||||||
|
|
||||||
|
data.Router.BlockedPrefixes = copyStringSlice(d.Router.BlockedPrefixes)
|
||||||
|
data.Router.Routes = copyStringMap(d.Router.Routes)
|
||||||
|
|
||||||
|
for i, v := range d.vars {
|
||||||
|
data.vars[i].merged = v.merged
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Config) init() {
|
||||||
|
d.val(newInt64Value(&d.Version, 1), "version", "", nil, "Configuration file layout version", true, false)
|
||||||
|
d.val(newTimeValue(&d.CreatedAt, time.Now()), "created_at", "", nil, "Configuration file creation time", false, false)
|
||||||
|
d.val(newStringValue(&d.ID, uuid.New().String()), "id", "CORE_ID", nil, "ID for this instance", true, false)
|
||||||
|
d.val(newStringValue(&d.Name, haikunator.New().Haikunate()), "name", "CORE_NAME", nil, "A human readable name for this instance", false, false)
|
||||||
|
d.val(newAddressValue(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false)
|
||||||
|
d.val(newBoolValue(&d.CheckForUpdates, true), "update_check", "CORE_UPDATE_CHECK", nil, "Check for updates and send anonymized data", false, false)
|
||||||
|
|
||||||
|
// Log
|
||||||
|
d.val(newStringValue(&d.Log.Level, "info"), "log.level", "CORE_LOG_LEVEL", nil, "Loglevel: silent, error, warn, info, debug", false, false)
|
||||||
|
d.val(newStringListValue(&d.Log.Topics, []string{}, ","), "log.topics", "CORE_LOG_TOPICS", nil, "Show only selected log topics", false, false)
|
||||||
|
d.val(newIntValue(&d.Log.MaxLines, 1000), "log.max_lines", "CORE_LOG_MAXLINES", nil, "Number of latest log lines to keep in memory", false, false)
|
||||||
|
|
||||||
|
// DB
|
||||||
|
d.val(newMustDirValue(&d.DB.Dir, "./config"), "db.dir", "CORE_DB_DIR", nil, "Directory for holding the operational data", false, false)
|
||||||
|
|
||||||
|
// Host
|
||||||
|
d.val(newStringListValue(&d.Host.Name, []string{}, ","), "host.name", "CORE_HOST_NAME", nil, "Comma separated list of public host/domain names or IPs", false, false)
|
||||||
|
d.val(newBoolValue(&d.Host.Auto, true), "host.auto", "CORE_HOST_AUTO", nil, "Enable detection of public IP addresses", false, false)
|
||||||
|
|
||||||
|
// API
|
||||||
|
d.val(newBoolValue(&d.API.ReadOnly, false), "api.read_only", "CORE_API_READ_ONLY", nil, "Allow only ready only access to the API", false, false)
|
||||||
|
d.val(newCIDRListValue(&d.API.Access.HTTP.Allow, []string{}, ","), "api.access.http.allow", "CORE_API_ACCESS_HTTP_ALLOW", nil, "List of IPs in CIDR notation (HTTP traffic)", false, false)
|
||||||
|
d.val(newCIDRListValue(&d.API.Access.HTTP.Block, []string{}, ","), "api.access.http.block", "CORE_API_ACCESS_HTTP_BLOCK", nil, "List of IPs in CIDR notation (HTTP traffic)", false, false)
|
||||||
|
d.val(newCIDRListValue(&d.API.Access.HTTPS.Allow, []string{}, ","), "api.access.https.allow", "CORE_API_ACCESS_HTTPS_ALLOW", nil, "List of IPs in CIDR notation (HTTPS traffic)", false, false)
|
||||||
|
d.val(newCIDRListValue(&d.API.Access.HTTPS.Block, []string{}, ","), "api.access.https.block", "CORE_API_ACCESS_HTTPS_BLOCK", nil, "List of IPs in CIDR notation (HTTPS traffic)", false, false)
|
||||||
|
d.val(newBoolValue(&d.API.Auth.Enable, false), "api.auth.enable", "CORE_API_AUTH_ENABLE", nil, "Enable authentication for all clients", false, false)
|
||||||
|
d.val(newBoolValue(&d.API.Auth.DisableLocalhost, false), "api.auth.disable_localhost", "CORE_API_AUTH_DISABLE_LOCALHOST", nil, "Disable authentication for clients from localhost", false, false)
|
||||||
|
d.val(newStringValue(&d.API.Auth.Username, ""), "api.auth.username", "CORE_API_AUTH_USERNAME", []string{"RS_USERNAME"}, "Username", false, false)
|
||||||
|
d.val(newStringValue(&d.API.Auth.Password, ""), "api.auth.password", "CORE_API_AUTH_PASSWORD", []string{"RS_PASSWORD"}, "Password", false, true)
|
||||||
|
|
||||||
|
// Auth JWT
|
||||||
|
d.val(newStringValue(&d.API.Auth.JWT.Secret, rand.String(32)), "api.auth.jwt.secret", "CORE_API_AUTH_JWT_SECRET", nil, "JWT secret, leave empty for generating a random value", false, true)
|
||||||
|
|
||||||
|
// Auth Auth0
|
||||||
|
d.val(newBoolValue(&d.API.Auth.Auth0.Enable, false), "api.auth.auth0.enable", "CORE_API_AUTH_AUTH0_ENABLE", nil, "Enable Auth0", false, false)
|
||||||
|
d.val(newTenantListValue(&d.API.Auth.Auth0.Tenants, []Auth0Tenant{}, ","), "api.auth.auth0.tenants", "CORE_API_AUTH_AUTH0_TENANTS", nil, "List of Auth0 tenants", false, false)
|
||||||
|
|
||||||
|
// TLS
|
||||||
|
d.val(newAddressValue(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false)
|
||||||
|
d.val(newBoolValue(&d.TLS.Enable, false), "tls.enable", "CORE_TLS_ENABLE", nil, "Enable HTTPS", false, false)
|
||||||
|
d.val(newBoolValue(&d.TLS.Auto, false), "tls.auto", "CORE_TLS_AUTO", nil, "Enable Let's Encrypt certificate", false, false)
|
||||||
|
d.val(newFileValue(&d.TLS.CertFile, ""), "tls.cert_file", "CORE_TLS_CERTFILE", nil, "Path to certificate file in PEM format", false, false)
|
||||||
|
d.val(newFileValue(&d.TLS.KeyFile, ""), "tls.key_file", "CORE_TLS_KEYFILE", nil, "Path to key file in PEM format", false, false)
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
d.val(newFileValue(&d.Storage.MimeTypes, "./mime.types"), "storage.mimetypes_file", "CORE_STORAGE_MIMETYPES_FILE", []string{"CORE_MIMETYPES_FILE"}, "Path to file with mime-types", false, false)
|
||||||
|
|
||||||
|
// Storage (Disk)
|
||||||
|
d.val(newMustDirValue(&d.Storage.Disk.Dir, "./data"), "storage.disk.dir", "CORE_STORAGE_DISK_DIR", nil, "Directory on disk, exposed on /", false, false)
|
||||||
|
d.val(newInt64Value(&d.Storage.Disk.Size, 0), "storage.disk.max_size_mbytes", "CORE_STORAGE_DISK_MAXSIZEMBYTES", nil, "Max. allowed megabytes for storage.disk.dir, 0 for unlimited", false, false)
|
||||||
|
d.val(newBoolValue(&d.Storage.Disk.Cache.Enable, true), "storage.disk.cache.enable", "CORE_STORAGE_DISK_CACHE_ENABLE", nil, "Enable cache for /", false, false)
|
||||||
|
d.val(newUint64Value(&d.Storage.Disk.Cache.Size, 0), "storage.disk.cache.max_size_mbytes", "CORE_STORAGE_DISK_CACHE_MAXSIZEMBYTES", nil, "Max. allowed cache size, 0 for unlimited", false, false)
|
||||||
|
d.val(newInt64Value(&d.Storage.Disk.Cache.TTL, 300), "storage.disk.cache.ttl_seconds", "CORE_STORAGE_DISK_CACHE_TTLSECONDS", nil, "Seconds to keep files in cache", false, false)
|
||||||
|
d.val(newUint64Value(&d.Storage.Disk.Cache.FileSize, 1), "storage.disk.cache.max_file_size_mbytes", "CORE_STORAGE_DISK_CACHE_MAXFILESIZEMBYTES", nil, "Max. file size to put in cache", false, false)
|
||||||
|
d.val(newStringListValue(&d.Storage.Disk.Cache.Types, []string{}, " "), "storage.disk.cache.types", "CORE_STORAGE_DISK_CACHE_TYPES", nil, "File extensions to cache, empty for all", false, false)
|
||||||
|
|
||||||
|
// Storage (Memory)
|
||||||
|
d.val(newBoolValue(&d.Storage.Memory.Auth.Enable, true), "storage.memory.auth.enable", "CORE_STORAGE_MEMORY_AUTH_ENABLE", nil, "Enable basic auth for PUT,POST, and DELETE on /memfs", false, false)
|
||||||
|
d.val(newStringValue(&d.Storage.Memory.Auth.Username, "admin"), "storage.memory.auth.username", "CORE_STORAGE_MEMORY_AUTH_USERNAME", nil, "Username for Basic-Auth of /memfs", false, false)
|
||||||
|
d.val(newStringValue(&d.Storage.Memory.Auth.Password, rand.StringAlphanumeric(18)), "storage.memory.auth.password", "CORE_STORAGE_MEMORY_AUTH_PASSWORD", nil, "Password for Basic-Auth of /memfs", false, true)
|
||||||
|
d.val(newInt64Value(&d.Storage.Memory.Size, 0), "storage.memory.max_size_mbytes", "CORE_STORAGE_MEMORY_MAXSIZEMBYTES", nil, "Max. allowed megabytes for /memfs, 0 for unlimited", false, false)
|
||||||
|
d.val(newBoolValue(&d.Storage.Memory.Purge, false), "storage.memory.purge", "CORE_STORAGE_MEMORY_PURGE", nil, "Automatically remove the oldest files if /memfs is full", false, false)
|
||||||
|
|
||||||
|
// Storage (CORS)
|
||||||
|
d.val(newCORSOriginsValue(&d.Storage.CORS.Origins, []string{"*"}, ","), "storage.cors.origins", "CORE_STORAGE_CORS_ORIGINS", nil, "Allowed CORS origins for /memfs and /data", false, false)
|
||||||
|
|
||||||
|
// RTMP
|
||||||
|
d.val(newBoolValue(&d.RTMP.Enable, false), "rtmp.enable", "CORE_RTMP_ENABLE", nil, "Enable RTMP server", false, false)
|
||||||
|
d.val(newBoolValue(&d.RTMP.EnableTLS, false), "rtmp.enable_tls", "CORE_RTMP_ENABLE_TLS", nil, "Enable RTMPS server instead of RTMP", false, false)
|
||||||
|
d.val(newAddressValue(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false)
|
||||||
|
d.val(newStringValue(&d.RTMP.App, "/"), "rtmp.app", "CORE_RTMP_APP", nil, "RTMP app for publishing", false, false)
|
||||||
|
d.val(newStringValue(&d.RTMP.Token, ""), "rtmp.token", "CORE_RTMP_TOKEN", nil, "RTMP token for publishing and playing", false, true)
|
||||||
|
|
||||||
|
// FFmpeg
|
||||||
|
d.val(newExecValue(&d.FFmpeg.Binary, "ffmpeg"), "ffmpeg.binary", "CORE_FFMPEG_BINARY", nil, "Path to ffmpeg binary", true, false)
|
||||||
|
d.val(newInt64Value(&d.FFmpeg.MaxProcesses, 0), "ffmpeg.max_processes", "CORE_FFMPEG_MAXPROCESSES", nil, "Max. allowed simultaneously running ffmpeg instances, 0 for unlimited", false, false)
|
||||||
|
d.val(newStringListValue(&d.FFmpeg.Access.Input.Allow, []string{}, " "), "ffmpeg.access.input.allow", "CORE_FFMPEG_ACCESS_INPUT_ALLOW", nil, "List of allowed expression to match against the input addresses", false, false)
|
||||||
|
d.val(newStringListValue(&d.FFmpeg.Access.Input.Block, []string{}, " "), "ffmpeg.access.input.block", "CORE_FFMPEG_ACCESS_INPUT_BLOCK", nil, "List of blocked expression to match against the input addresses", false, false)
|
||||||
|
d.val(newStringListValue(&d.FFmpeg.Access.Output.Allow, []string{}, " "), "ffmpeg.access.output.allow", "CORE_FFMPEG_ACCESS_OUTPUT_ALLOW", nil, "List of allowed expression to match against the output addresses", false, false)
|
||||||
|
d.val(newStringListValue(&d.FFmpeg.Access.Output.Block, []string{}, " "), "ffmpeg.access.output.block", "CORE_FFMPEG_ACCESS_OUTPUT_BLOCK", nil, "List of blocked expression to match against the output addresses", false, false)
|
||||||
|
d.val(newIntValue(&d.FFmpeg.Log.MaxLines, 50), "ffmpeg.log.max_lines", "CORE_FFMPEG_LOG_MAXLINES", nil, "Number of latest log lines to keep for each process", false, false)
|
||||||
|
d.val(newIntValue(&d.FFmpeg.Log.MaxHistory, 3), "ffmpeg.log.max_history", "CORE_FFMPEG_LOG_MAXHISTORY", nil, "Number of latest logs to keep for each process", false, false)
|
||||||
|
|
||||||
|
// Playout
|
||||||
|
d.val(newBoolValue(&d.Playout.Enable, false), "playout.enable", "CORE_PLAYOUT_ENABLE", nil, "Enable playout proxy where available", false, false)
|
||||||
|
d.val(newPortValue(&d.Playout.MinPort, 0), "playout.min_port", "CORE_PLAYOUT_MINPORT", nil, "Min. playout server port", false, false)
|
||||||
|
d.val(newPortValue(&d.Playout.MaxPort, 0), "playout.max_port", "CORE_PLAYOUT_MAXPORT", nil, "Max. playout server port", false, false)
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
d.val(newBoolValue(&d.Debug.Profiling, false), "debug.profiling", "CORE_DEBUG_PROFILING", nil, "Enable profiling endpoint on /profiling", false, false)
|
||||||
|
d.val(newIntValue(&d.Debug.ForceGC, 0), "debug.force_gc", "CORE_DEBUG_FORCEGC", nil, "Number of seconds between forcing GC to return memory to the OS", false, false)
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
d.val(newBoolValue(&d.Metrics.Enable, false), "metrics.enable", "CORE_METRICS_ENABLE", nil, "Enable collecting historic metrics data", false, false)
|
||||||
|
d.val(newBoolValue(&d.Metrics.EnablePrometheus, false), "metrics.enable_prometheus", "CORE_METRICS_ENABLE_PROMETHEUS", nil, "Enable prometheus endpoint /metrics", false, false)
|
||||||
|
d.val(newInt64Value(&d.Metrics.Range, 300), "metrics.range_seconds", "CORE_METRICS_RANGE_SECONDS", nil, "Seconds to keep history data", false, false)
|
||||||
|
d.val(newInt64Value(&d.Metrics.Interval, 2), "metrics.interval_seconds", "CORE_METRICS_INTERVAL_SECONDS", nil, "Interval for collecting metrics", false, false)
|
||||||
|
|
||||||
|
// Sessions
|
||||||
|
d.val(newBoolValue(&d.Sessions.Enable, true), "sessions.enable", "CORE_SESSIONS_ENABLE", nil, "Enable collecting HLS session stats for /memfs", false, false)
|
||||||
|
d.val(newCIDRListValue(&d.Sessions.IPIgnoreList, []string{"127.0.0.1/32", "::1/128"}, ","), "sessions.ip_ignorelist", "CORE_SESSIONS_IP_IGNORELIST", nil, "List of IP ranges in CIDR notation to ignore", false, false)
|
||||||
|
d.val(newIntValue(&d.Sessions.SessionTimeout, 30), "sessions.session_timeout_sec", "CORE_SESSIONS_SESSION_TIMEOUT_SEC", nil, "Timeout for an idle session", false, false)
|
||||||
|
d.val(newBoolValue(&d.Sessions.Persist, false), "sessions.persist", "CORE_SESSIONS_PERSIST", nil, "Whether to persist session history. Will be stored as sessions.json in db.dir", false, false)
|
||||||
|
d.val(newIntValue(&d.Sessions.PersistInterval, 300), "sessions.persist_interval_sec", "CORE_SESSIONS_PERSIST_INTERVAL_SEC", nil, "Interval in seconds in which to persist the current session history", false, false)
|
||||||
|
d.val(newUint64Value(&d.Sessions.MaxBitrate, 0), "sessions.max_bitrate_mbit", "CORE_SESSIONS_MAXBITRATE_MBIT", nil, "Max. allowed outgoing bitrate in mbit/s, 0 for unlimited", false, false)
|
||||||
|
d.val(newUint64Value(&d.Sessions.MaxSessions, 0), "sessions.max_sessions", "CORE_SESSIONS_MAXSESSIONS", nil, "Max. allowed number of simultaneous sessions, 0 for unlimited", false, false)
|
||||||
|
|
||||||
|
// Service
|
||||||
|
d.val(newBoolValue(&d.Service.Enable, false), "service.enable", "CORE_SERVICE_ENABLE", nil, "Enable connecting to the Restreamer Service", false, false)
|
||||||
|
d.val(newStringValue(&d.Service.Token, ""), "service.token", "CORE_SERVICE_TOKEN", nil, "Restreamer Service account token", false, true)
|
||||||
|
d.val(newURLValue(&d.Service.URL, "https://service.datarhei.com"), "service.url", "CORE_SERVICE_URL", nil, "URL of the Restreamer Service", false, false)
|
||||||
|
|
||||||
|
// Router
|
||||||
|
d.val(newStringListValue(&d.Router.BlockedPrefixes, []string{"/api"}, ","), "router.blocked_prefixes", "CORE_ROUTER_BLOCKED_PREFIXES", nil, "List of path prefixes that can't be routed", false, false)
|
||||||
|
d.val(newStringMapStringValue(&d.Router.Routes, nil), "router.routes", "CORE_ROUTER_ROUTES", nil, "List of route mappings", false, false)
|
||||||
|
d.val(newDirValue(&d.Router.UIPath, ""), "router.ui_path", "CORE_ROUTER_UI_PATH", nil, "Path to a directory holding UI files mounted as /ui", false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Config) val(val value, name, envName string, envAltNames []string, description string, required, disguise bool) {
|
||||||
|
d.vars = append(d.vars, &variable{
|
||||||
|
value: val,
|
||||||
|
defVal: val.String(),
|
||||||
|
name: name,
|
||||||
|
envName: envName,
|
||||||
|
envAltNames: envAltNames,
|
||||||
|
description: description,
|
||||||
|
required: required,
|
||||||
|
disguise: disguise,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Config) log(level string, v *variable, format string, args ...interface{}) {
|
||||||
|
variable := Variable{
|
||||||
|
Value: v.value.String(),
|
||||||
|
Name: v.name,
|
||||||
|
EnvName: v.envName,
|
||||||
|
Description: v.description,
|
||||||
|
Merged: v.merged,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.disguise {
|
||||||
|
variable.Value = "***"
|
||||||
|
}
|
||||||
|
|
||||||
|
l := message{
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
variable: variable,
|
||||||
|
level: level,
|
||||||
|
}
|
||||||
|
|
||||||
|
d.logs = append(d.logs, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges the values of the known environment variables into the configuration
|
||||||
|
func (d *Config) Merge() {
|
||||||
|
for _, v := range d.vars {
|
||||||
|
if len(v.envName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var envval string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
envval, ok = os.LookupEnv(v.envName)
|
||||||
|
if !ok {
|
||||||
|
foundAltName := false
|
||||||
|
|
||||||
|
for _, envName := range v.envAltNames {
|
||||||
|
envval, ok = os.LookupEnv(envName)
|
||||||
|
if ok {
|
||||||
|
foundAltName = true
|
||||||
|
d.log("warn", v, "deprecated name, please use %s", v.envName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundAltName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.value.Set(envval)
|
||||||
|
if err != nil {
|
||||||
|
d.log("error", v, "%s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
v.merged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the current state of the Config for completeness and sanity. Errors are
|
||||||
|
// written to the log. Use resetLogs to indicate to reset the logs prior validation.
|
||||||
|
func (d *Config) Validate(resetLogs bool) {
|
||||||
|
if resetLogs {
|
||||||
|
d.logs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Version != 1 {
|
||||||
|
d.log("error", d.findVariable("version"), "unknown configuration layout version")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range d.vars {
|
||||||
|
d.log("info", v, "%s", "")
|
||||||
|
|
||||||
|
err := v.value.Validate()
|
||||||
|
if err != nil {
|
||||||
|
d.log("error", v, "%s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.required && v.value.IsEmpty() {
|
||||||
|
d.log("error", v, "a value is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual sanity checks
|
||||||
|
|
||||||
|
// If HTTP Auth is enabled, check that the username and password are set
|
||||||
|
if d.API.Auth.Enable {
|
||||||
|
if len(d.API.Auth.Username) == 0 || len(d.API.Auth.Password) == 0 {
|
||||||
|
d.log("error", d.findVariable("api.auth.enable"), "api.auth.username and api.auth.password must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Auth0 is enabled, check that domain, audience, and clientid are set
|
||||||
|
if d.API.Auth.Auth0.Enable {
|
||||||
|
if len(d.API.Auth.Auth0.Tenants) == 0 {
|
||||||
|
d.log("error", d.findVariable("api.auth.auth0.enable"), "at least one tenants must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range d.API.Auth.Auth0.Tenants {
|
||||||
|
if len(t.Domain) == 0 || len(t.Audience) == 0 || len(t.ClientID) == 0 {
|
||||||
|
d.log("error", d.findVariable("api.auth.auth0.tenants"), "domain, audience, and clientid must be set (tenant %d)", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If TLS is enabled and Let's Encrypt is disabled, require certfile and keyfile
|
||||||
|
if d.TLS.Enable && !d.TLS.Auto {
|
||||||
|
if len(d.TLS.CertFile) == 0 || len(d.TLS.KeyFile) == 0 {
|
||||||
|
d.log("error", d.findVariable("tls.enable"), "tls.certfile and tls.keyfile must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If TLS and Let's Encrypt certificate is enabled, we require a public hostname
|
||||||
|
if d.TLS.Enable && d.TLS.Auto {
|
||||||
|
if len(d.Host.Name) == 0 {
|
||||||
|
d.log("error", d.findVariable("host.name"), "a hostname must be set in order to get an automatic TLS certificate")
|
||||||
|
} else {
|
||||||
|
r := &net.Resolver{
|
||||||
|
PreferGo: true,
|
||||||
|
StrictErrors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range d.Host.Name {
|
||||||
|
// Don't lookup IP addresses
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
d.log("error", d.findVariable("host.name"), "only host names are allowed if automatic TLS is enabled, but found IP address: %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup host name with a timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
|
||||||
|
_, err := r.LookupHost(ctx, host)
|
||||||
|
if err != nil {
|
||||||
|
d.log("error", d.findVariable("host.name"), "the host '%s' can't be resolved and will not work with automatic TLS", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If TLS for RTMP is enabled, TLS must be enabled
|
||||||
|
if d.RTMP.EnableTLS {
|
||||||
|
if !d.TLS.Enable {
|
||||||
|
d.log("error", d.findVariable("rtmp.enable_tls"), "RTMPS server can only be enabled if TLS is enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CORE_MEMFS_USERNAME and CORE_MEMFS_PASSWORD are set, automatically active/deactivate Basic-Auth for memfs
|
||||||
|
if d.findVariable("storage.memory.auth.username").merged && d.findVariable("storage.memory.auth.password").merged {
|
||||||
|
d.Storage.Memory.Auth.Enable = true
|
||||||
|
|
||||||
|
if len(d.Storage.Memory.Auth.Username) == 0 && len(d.Storage.Memory.Auth.Password) == 0 {
|
||||||
|
d.Storage.Memory.Auth.Enable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Basic-Auth for memfs is enable, check that the username and password are set
|
||||||
|
if d.Storage.Memory.Auth.Enable {
|
||||||
|
if len(d.Storage.Memory.Auth.Username) == 0 || len(d.Storage.Memory.Auth.Password) == 0 {
|
||||||
|
d.log("error", d.findVariable("storage.memory.auth.enable"), "storage.memory.auth.username and storage.memory.auth.password must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If playout is enabled, check that the port range is sane
|
||||||
|
if d.Playout.Enable {
|
||||||
|
if d.Playout.MinPort >= d.Playout.MaxPort {
|
||||||
|
d.log("error", d.findVariable("playout.min_port"), "must be bigger than playout.max_port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cache is enabled, a valid TTL has to be set to a useful value
|
||||||
|
if d.Storage.Disk.Cache.Enable && d.Storage.Disk.Cache.TTL < 0 {
|
||||||
|
d.log("error", d.findVariable("storage.disk.cache.ttl_seconds"), "must be equal or greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the stats are enabled, the session timeout has to be set to a useful value
|
||||||
|
if d.Sessions.Enable && d.Sessions.SessionTimeout < 1 {
|
||||||
|
d.log("error", d.findVariable("stats.session_timeout_sec"), "must be equal or greater than 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the stats and their persistence are enabled, the persist interval has to be set to a useful value
|
||||||
|
if d.Sessions.Enable && d.Sessions.PersistInterval < 0 {
|
||||||
|
d.log("error", d.findVariable("stats.persist_interval_sec"), "must be at equal or greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the service is enabled, the token and enpoint have to be defined
|
||||||
|
if d.Service.Enable {
|
||||||
|
if len(d.Service.Token) == 0 {
|
||||||
|
d.log("error", d.findVariable("service.token"), "must be non-empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.Service.URL) == 0 {
|
||||||
|
d.log("error", d.findVariable("service.url"), "must be non-empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If historic metrics are enabled, the timerange and interval have to be valid
|
||||||
|
if d.Metrics.Enable {
|
||||||
|
if d.Metrics.Range <= 0 {
|
||||||
|
d.log("error", d.findVariable("metrics.range"), "must be greater 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Metrics.Interval <= 0 {
|
||||||
|
d.log("error", d.findVariable("metrics.interval"), "must be greater 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Metrics.Interval > d.Metrics.Range {
|
||||||
|
d.log("error", d.findVariable("metrics.interval"), "must be smaller than the range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Config) findVariable(name string) *variable {
|
||||||
|
for _, v := range d.vars {
|
||||||
|
if v.name == name {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Messages calls for each log entry the provided callback. The level has the values 'error', 'warn', or 'info'.
|
||||||
|
// The name is the name of the configuration value, e.g. 'api.auth.enable'. The message is the log message.
|
||||||
|
func (d *Config) Messages(logger func(level string, v Variable, message string)) {
|
||||||
|
for _, l := range d.logs {
|
||||||
|
logger(l.level, l.variable, l.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasErrors returns whether there are some error messages in the log.
|
||||||
|
func (d *Config) HasErrors() bool {
|
||||||
|
for _, l := range d.logs {
|
||||||
|
if l.level == "error" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides returns a list of configuration value names that have been overriden by an environment variable.
|
||||||
|
func (d *Config) Overrides() []string {
|
||||||
|
overrides := []string{}
|
||||||
|
|
||||||
|
for _, v := range d.vars {
|
||||||
|
if v.merged {
|
||||||
|
overrides = append(overrides, v.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return overrides
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyStringSlice(src []string) []string {
|
||||||
|
dst := make([]string, len(src))
|
||||||
|
copy(dst, src)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyStringMap(src map[string]string) map[string]string {
|
||||||
|
dst := make(map[string]string)
|
||||||
|
|
||||||
|
for k, v := range src {
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyTenantSlice(src []Auth0Tenant) []Auth0Tenant {
|
||||||
|
dst := make([]Auth0Tenant, len(src))
|
||||||
|
copy(dst, src)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
55
config/config_test.go
Normal file
55
config/config_test.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigCopy(t *testing.T) {
|
||||||
|
config1 := New()
|
||||||
|
|
||||||
|
config1.Version = 42
|
||||||
|
config1.DB.Dir = "foo"
|
||||||
|
|
||||||
|
val1 := config1.findVariable("version")
|
||||||
|
val2 := config1.findVariable("db.dir")
|
||||||
|
val3 := config1.findVariable("host.name")
|
||||||
|
|
||||||
|
assert.Equal(t, "42", val1.value.String())
|
||||||
|
assert.Equal(t, nil, val1.value.Validate())
|
||||||
|
assert.Equal(t, false, val1.value.IsEmpty())
|
||||||
|
|
||||||
|
assert.Equal(t, "foo", val2.value.String())
|
||||||
|
assert.Equal(t, "(empty)", val3.value.String())
|
||||||
|
|
||||||
|
val3.value.Set("foo.com")
|
||||||
|
|
||||||
|
assert.Equal(t, "foo.com", val3.value.String())
|
||||||
|
|
||||||
|
config2 := NewConfigFrom(config1)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(42), config2.Version)
|
||||||
|
assert.Equal(t, "foo", config2.DB.Dir)
|
||||||
|
assert.Equal(t, []string{"foo.com"}, config2.Host.Name)
|
||||||
|
|
||||||
|
val1.value.Set("77")
|
||||||
|
|
||||||
|
assert.Equal(t, int64(77), config1.Version)
|
||||||
|
assert.Equal(t, int64(42), config2.Version)
|
||||||
|
|
||||||
|
val2.value.Set("bar")
|
||||||
|
|
||||||
|
assert.Equal(t, "bar", config1.DB.Dir)
|
||||||
|
assert.Equal(t, "foo", config2.DB.Dir)
|
||||||
|
|
||||||
|
config2.DB.Dir = "baz"
|
||||||
|
|
||||||
|
assert.Equal(t, "bar", config1.DB.Dir)
|
||||||
|
assert.Equal(t, "baz", config2.DB.Dir)
|
||||||
|
|
||||||
|
config1.Host.Name[0] = "bar.com"
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"bar.com"}, config1.Host.Name)
|
||||||
|
assert.Equal(t, []string{"foo.com"}, config2.Host.Name)
|
||||||
|
}
|
||||||
52
config/dummy.go
Normal file
52
config/dummy.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type dummyStore struct{}
|
||||||
|
|
||||||
|
// NewDummyStore returns a store that returns the default config
|
||||||
|
func NewDummyStore() Store {
|
||||||
|
return &dummyStore{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dummyStore) Get() *Config {
|
||||||
|
cfg := New()
|
||||||
|
|
||||||
|
cfg.DB.Dir = "."
|
||||||
|
cfg.Storage.Disk.Dir = "."
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dummyStore) Set(d *Config) error {
|
||||||
|
d.Validate(true)
|
||||||
|
|
||||||
|
if d.HasErrors() {
|
||||||
|
return fmt.Errorf("configuration data has errors after validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dummyStore) GetActive() *Config {
|
||||||
|
cfg := New()
|
||||||
|
|
||||||
|
cfg.DB.Dir = "."
|
||||||
|
cfg.Storage.Disk.Dir = "."
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dummyStore) SetActive(d *Config) error {
|
||||||
|
d.Validate(true)
|
||||||
|
|
||||||
|
if d.HasErrors() {
|
||||||
|
return fmt.Errorf("configuration data has errors after validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dummyStore) Reload() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
71
config/ip.go
Normal file
71
config/ip.go
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetPublicIPs will try to figure out the public IPs (v4 and v6)
|
||||||
|
// we're running on. There's a timeout of max. 5 seconds to do it.
|
||||||
|
// If it fails, the IPs will simply not be set.
|
||||||
|
func (d *Config) SetPublicIPs() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
ipv4 := ""
|
||||||
|
ipv6 := ""
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
ipv4 = doRequest("https://api.ipify.org")
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
ipv6 = doRequest("https://api6.ipify.org")
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if len(ipv4) != 0 {
|
||||||
|
d.Host.Name = append(d.Host.Name, ipv4)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ipv6) != 0 && ipv4 != ipv6 {
|
||||||
|
d.Host.Name = append(d.Host.Name, ipv6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(url string) string {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(body)
|
||||||
|
}
|
||||||
163
config/json.go
Normal file
163
config/json.go
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
gojson "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/encoding/json"
|
||||||
|
"github.com/datarhei/core/io/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonStore struct {
|
||||||
|
path string
|
||||||
|
|
||||||
|
data map[string]*Config
|
||||||
|
|
||||||
|
reloadFn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSONStore will read a JSON config file from the given path. After successfully reading it in, it will be written
|
||||||
|
// back to the path. The returned error will be nil if everything went fine.
|
||||||
|
// If the path doesn't exist, a default JSON config file will be written to that path.
|
||||||
|
// The returned ConfigStore can be used to retrieve or write the config.
|
||||||
|
func NewJSONStore(path string, reloadFn func()) (Store, error) {
|
||||||
|
c := &jsonStore{
|
||||||
|
path: path,
|
||||||
|
data: make(map[string]*Config),
|
||||||
|
reloadFn: reloadFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.data["base"] = New()
|
||||||
|
|
||||||
|
if err := c.load(c.data["base"]); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read JSON from '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.store(c.data["base"]); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to write JSON to '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) Get() *Config {
|
||||||
|
return NewConfigFrom(c.data["base"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) Set(d *Config) error {
|
||||||
|
if d.HasErrors() {
|
||||||
|
return fmt.Errorf("configuration data has errors after validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := NewConfigFrom(d)
|
||||||
|
|
||||||
|
data.CreatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := c.store(data); err != nil {
|
||||||
|
return fmt.Errorf("failed to write JSON to '%s': %w", c.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
c.data["base"] = data
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) GetActive() *Config {
|
||||||
|
if x, ok := c.data["merged"]; ok {
|
||||||
|
return NewConfigFrom(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if x, ok := c.data["base"]; ok {
|
||||||
|
return NewConfigFrom(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) SetActive(d *Config) error {
|
||||||
|
d.Validate(true)
|
||||||
|
|
||||||
|
if d.HasErrors() {
|
||||||
|
return fmt.Errorf("configuration data has errors after validation")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.data["merged"] = NewConfigFrom(d)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) Reload() error {
|
||||||
|
if c.reloadFn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.reloadFn()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) load(data *Config) error {
|
||||||
|
if len(c.path) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(c.path); os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jsondata, err := ioutil.ReadFile(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = gojson.Unmarshal(jsondata, data); err != nil {
|
||||||
|
return json.FormatError(jsondata, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.LoadedAt = time.Now()
|
||||||
|
data.UpdatedAt = data.LoadedAt
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *jsonStore) store(data *Config) error {
|
||||||
|
data.CreatedAt = time.Now()
|
||||||
|
|
||||||
|
if len(c.path) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jsondata, err := gojson.MarshalIndent(data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, filename := filepath.Split(c.path)
|
||||||
|
|
||||||
|
tmpfile, err := ioutil.TempFile(dir, filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(tmpfile.Name())
|
||||||
|
|
||||||
|
if _, err := tmpfile.Write(jsondata); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpfile.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.Rename(tmpfile.Name(), c.path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
23
config/store.go
Normal file
23
config/store.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// Store is a store for the configuration data.
|
||||||
|
type Store interface {
|
||||||
|
// Get the current configuration.
|
||||||
|
Get() *Config
|
||||||
|
|
||||||
|
// Set a new configuration for persistence.
|
||||||
|
Set(data *Config) error
|
||||||
|
|
||||||
|
// GetActive returns the configuration that has been set as
|
||||||
|
// active before, otherwise it return nil.
|
||||||
|
GetActive() *Config
|
||||||
|
|
||||||
|
// SetActive will keep the given configuration
|
||||||
|
// as active in memory. It can be retrieved later with GetActive()
|
||||||
|
SetActive(data *Config) error
|
||||||
|
|
||||||
|
// Reload will reload the stored configuration. It has to make sure
|
||||||
|
// that all affected components will receiver their potentially
|
||||||
|
// changed configuration.
|
||||||
|
Reload() error
|
||||||
|
}
|
||||||
774
config/types.go
Normal file
774
config/types.go
Normal file
|
|
@ -0,0 +1,774 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/http/cors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type value interface {
|
||||||
|
// String returns a string representation of the value.
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// Set a new value for the value. Returns an
|
||||||
|
// error if the given string representation can't
|
||||||
|
// be transformed to the value. Returns nil
|
||||||
|
// if the new value has been set.
|
||||||
|
Set(string) error
|
||||||
|
|
||||||
|
// Validate the value. The returned error will
|
||||||
|
// indicate what is wrong with the current value.
|
||||||
|
// Returns nil if the value is OK.
|
||||||
|
Validate() error
|
||||||
|
|
||||||
|
// IsEmpty returns whether the value represents an empty
|
||||||
|
// representation for that value.
|
||||||
|
IsEmpty() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// string
|
||||||
|
|
||||||
|
type stringValue string
|
||||||
|
|
||||||
|
func newStringValue(p *string, val string) *stringValue {
|
||||||
|
*p = val
|
||||||
|
return (*stringValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) Set(val string) error {
|
||||||
|
*s = stringValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) String() string {
|
||||||
|
return string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringValue) IsEmpty() bool {
|
||||||
|
return len(string(*s)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// address (host?:port)
|
||||||
|
|
||||||
|
type addressValue string
|
||||||
|
|
||||||
|
func newAddressValue(p *string, val string) *addressValue {
|
||||||
|
*p = val
|
||||||
|
return (*addressValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *addressValue) Set(val string) error {
|
||||||
|
// Check if the new value is only a port number
|
||||||
|
re := regexp.MustCompile("^[0-9]+$")
|
||||||
|
if re.MatchString(val) {
|
||||||
|
val = ":" + val
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = addressValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *addressValue) String() string {
|
||||||
|
return string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *addressValue) Validate() error {
|
||||||
|
_, port, err := net.SplitHostPort(string(*s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile("^[0-9]+$")
|
||||||
|
if !re.MatchString(port) {
|
||||||
|
return fmt.Errorf("the port must be numerical")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *addressValue) IsEmpty() bool {
|
||||||
|
return s.Validate() != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// array of strings
|
||||||
|
|
||||||
|
type stringListValue struct {
|
||||||
|
p *[]string
|
||||||
|
separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringListValue(p *[]string, val []string, separator string) *stringListValue {
|
||||||
|
v := &stringListValue{
|
||||||
|
p: p,
|
||||||
|
separator: separator,
|
||||||
|
}
|
||||||
|
*p = val
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringListValue) Set(val string) error {
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
for _, elm := range strings.Split(val, s.separator) {
|
||||||
|
elm = strings.TrimSpace(elm)
|
||||||
|
if len(elm) != 0 {
|
||||||
|
list = append(list, elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringListValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(*s.p, s.separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringListValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringListValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// array of auth0 tenants
|
||||||
|
|
||||||
|
type tenantListValue struct {
|
||||||
|
p *[]Auth0Tenant
|
||||||
|
separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTenantListValue(p *[]Auth0Tenant, val []Auth0Tenant, separator string) *tenantListValue {
|
||||||
|
v := &tenantListValue{
|
||||||
|
p: p,
|
||||||
|
separator: separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = val
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tenantListValue) Set(val string) error {
|
||||||
|
list := []Auth0Tenant{}
|
||||||
|
|
||||||
|
for i, elm := range strings.Split(val, s.separator) {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(elm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid base64 encoding of tenant %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := Auth0Tenant{}
|
||||||
|
if err := json.Unmarshal(data, &t); err != nil {
|
||||||
|
return fmt.Errorf("invalid JSON in tenant %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tenantListValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
for _, t := range *s.p {
|
||||||
|
list = append(list, fmt.Sprintf("%s (%d users)", t.Domain, len(t.Users)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(list, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tenantListValue) Validate() error {
|
||||||
|
for i, t := range *s.p {
|
||||||
|
if len(t.Domain) == 0 {
|
||||||
|
return fmt.Errorf("the domain for tenant %d is missing", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Audience) == 0 {
|
||||||
|
return fmt.Errorf("the audience for tenant %d is missing", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tenantListValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// map of strings to strings
|
||||||
|
|
||||||
|
type stringMapStringValue struct {
|
||||||
|
p *map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringMapStringValue(p *map[string]string, val map[string]string) *stringMapStringValue {
|
||||||
|
v := &stringMapStringValue{
|
||||||
|
p: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
if *p == nil {
|
||||||
|
*p = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != nil {
|
||||||
|
*p = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapStringValue) Set(val string) error {
|
||||||
|
mappings := make(map[string]string)
|
||||||
|
|
||||||
|
for _, elm := range strings.Split(val, " ") {
|
||||||
|
elm = strings.TrimSpace(elm)
|
||||||
|
if len(elm) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping := strings.SplitN(elm, ":", 2)
|
||||||
|
|
||||||
|
mappings[mapping[0]] = mapping[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = mappings
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapStringValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings := make([]string, len(*s.p))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k, v := range *s.p {
|
||||||
|
mappings[i] = k + ":" + v
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(mappings, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapStringValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapStringValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// array of CIDR notation IP adresses
|
||||||
|
|
||||||
|
type cidrListValue struct {
|
||||||
|
p *[]string
|
||||||
|
separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCIDRListValue(p *[]string, val []string, separator string) *cidrListValue {
|
||||||
|
v := &cidrListValue{
|
||||||
|
p: p,
|
||||||
|
separator: separator,
|
||||||
|
}
|
||||||
|
*p = val
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cidrListValue) Set(val string) error {
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
for _, elm := range strings.Split(val, s.separator) {
|
||||||
|
elm = strings.TrimSpace(elm)
|
||||||
|
if len(elm) != 0 {
|
||||||
|
list = append(list, elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cidrListValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(*s.p, s.separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cidrListValue) Validate() error {
|
||||||
|
for _, cidr := range *s.p {
|
||||||
|
_, _, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *cidrListValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// array of origins for CORS
|
||||||
|
|
||||||
|
type corsOriginsValue struct {
|
||||||
|
p *[]string
|
||||||
|
separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCORSOriginsValue(p *[]string, val []string, separator string) *corsOriginsValue {
|
||||||
|
v := &corsOriginsValue{
|
||||||
|
p: p,
|
||||||
|
separator: separator,
|
||||||
|
}
|
||||||
|
*p = val
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *corsOriginsValue) Set(val string) error {
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
for _, elm := range strings.Split(val, s.separator) {
|
||||||
|
elm = strings.TrimSpace(elm)
|
||||||
|
if len(elm) != 0 {
|
||||||
|
list = append(list, elm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *corsOriginsValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(*s.p, s.separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *corsOriginsValue) Validate() error {
|
||||||
|
return cors.Validate(*s.p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *corsOriginsValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// boolean
|
||||||
|
|
||||||
|
type boolValue bool
|
||||||
|
|
||||||
|
func newBoolValue(p *bool, val bool) *boolValue {
|
||||||
|
*p = val
|
||||||
|
return (*boolValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Set(val string) error {
|
||||||
|
v, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*b = boolValue(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) String() string {
|
||||||
|
return strconv.FormatBool(bool(*b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) IsEmpty() bool {
|
||||||
|
return !bool(*b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// int
|
||||||
|
|
||||||
|
type intValue int
|
||||||
|
|
||||||
|
func newIntValue(p *int, val int) *intValue {
|
||||||
|
*p = val
|
||||||
|
return (*intValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intValue) Set(val string) error {
|
||||||
|
v, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*i = intValue(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intValue) String() string {
|
||||||
|
return strconv.Itoa(int(*i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *intValue) IsEmpty() bool {
|
||||||
|
return int(*i) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// int64
|
||||||
|
|
||||||
|
type int64Value int64
|
||||||
|
|
||||||
|
func newInt64Value(p *int64, val int64) *int64Value {
|
||||||
|
*p = val
|
||||||
|
return (*int64Value)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *int64Value) Set(val string) error {
|
||||||
|
v, err := strconv.ParseInt(val, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*u = int64Value(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *int64Value) String() string {
|
||||||
|
return strconv.FormatInt(int64(*u), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *int64Value) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *int64Value) IsEmpty() bool {
|
||||||
|
return int64(*u) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint64
|
||||||
|
|
||||||
|
type uint64Value uint64
|
||||||
|
|
||||||
|
func newUint64Value(p *uint64, val uint64) *uint64Value {
|
||||||
|
*p = val
|
||||||
|
return (*uint64Value)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *uint64Value) Set(val string) error {
|
||||||
|
v, err := strconv.ParseUint(val, 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*u = uint64Value(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *uint64Value) String() string {
|
||||||
|
return strconv.FormatUint(uint64(*u), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *uint64Value) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *uint64Value) IsEmpty() bool {
|
||||||
|
return uint64(*u) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// network port
|
||||||
|
|
||||||
|
type portValue int
|
||||||
|
|
||||||
|
func newPortValue(p *int, val int) *portValue {
|
||||||
|
*p = val
|
||||||
|
return (*portValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *portValue) Set(val string) error {
|
||||||
|
v, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*i = portValue(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *portValue) String() string {
|
||||||
|
return strconv.Itoa(int(*i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *portValue) Validate() error {
|
||||||
|
val := int(*i)
|
||||||
|
|
||||||
|
if val < 0 || val >= (1<<16) {
|
||||||
|
return fmt.Errorf("%d is not in the range of [0, %d]", val, 1<<16-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *portValue) IsEmpty() bool {
|
||||||
|
return int(*i) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// must directory
|
||||||
|
|
||||||
|
type mustDirValue string
|
||||||
|
|
||||||
|
func newMustDirValue(p *string, val string) *mustDirValue {
|
||||||
|
*p = val
|
||||||
|
return (*mustDirValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *mustDirValue) Set(val string) error {
|
||||||
|
*u = mustDirValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *mustDirValue) String() string {
|
||||||
|
return string(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *mustDirValue) Validate() error {
|
||||||
|
val := string(*u)
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(val)) == 0 {
|
||||||
|
return fmt.Errorf("path name must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
finfo, err := os.Stat(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s does not exist", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !finfo.IsDir() {
|
||||||
|
return fmt.Errorf("%s is not a directory", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *mustDirValue) IsEmpty() bool {
|
||||||
|
return len(string(*u)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// directory
|
||||||
|
|
||||||
|
type dirValue string
|
||||||
|
|
||||||
|
func newDirValue(p *string, val string) *dirValue {
|
||||||
|
*p = val
|
||||||
|
return (*dirValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *dirValue) Set(val string) error {
|
||||||
|
*u = dirValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *dirValue) String() string {
|
||||||
|
return string(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *dirValue) Validate() error {
|
||||||
|
val := string(*u)
|
||||||
|
|
||||||
|
if len(strings.TrimSpace(val)) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
finfo, err := os.Stat(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s does not exist", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !finfo.IsDir() {
|
||||||
|
return fmt.Errorf("%s is not a directory", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *dirValue) IsEmpty() bool {
|
||||||
|
return len(string(*u)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// executable
|
||||||
|
|
||||||
|
type execValue string
|
||||||
|
|
||||||
|
func newExecValue(p *string, val string) *execValue {
|
||||||
|
*p = val
|
||||||
|
return (*execValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *execValue) Set(val string) error {
|
||||||
|
*u = execValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *execValue) String() string {
|
||||||
|
return string(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *execValue) Validate() error {
|
||||||
|
val := string(*u)
|
||||||
|
|
||||||
|
_, err := exec.LookPath(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s not found or is not executable", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *execValue) IsEmpty() bool {
|
||||||
|
return len(string(*u)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// regular file
|
||||||
|
|
||||||
|
type fileValue string
|
||||||
|
|
||||||
|
func newFileValue(p *string, val string) *fileValue {
|
||||||
|
*p = val
|
||||||
|
return (*fileValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *fileValue) Set(val string) error {
|
||||||
|
*u = fileValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *fileValue) String() string {
|
||||||
|
return string(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *fileValue) Validate() error {
|
||||||
|
val := string(*u)
|
||||||
|
|
||||||
|
if len(val) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
finfo, err := os.Stat(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s does not exist", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !finfo.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("%s is not a regular file", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *fileValue) IsEmpty() bool {
|
||||||
|
return len(string(*u)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// time
|
||||||
|
|
||||||
|
type timeValue time.Time
|
||||||
|
|
||||||
|
func newTimeValue(p *time.Time, val time.Time) *timeValue {
|
||||||
|
*p = val
|
||||||
|
return (*timeValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *timeValue) Set(val string) error {
|
||||||
|
v, err := time.Parse(time.RFC3339, val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*u = timeValue(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *timeValue) String() string {
|
||||||
|
v := time.Time(*u)
|
||||||
|
return v.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *timeValue) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *timeValue) IsEmpty() bool {
|
||||||
|
v := time.Time(*u)
|
||||||
|
return v.IsZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
// url
|
||||||
|
|
||||||
|
type urlValue string
|
||||||
|
|
||||||
|
func newURLValue(p *string, val string) *urlValue {
|
||||||
|
*p = val
|
||||||
|
return (*urlValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) Set(val string) error {
|
||||||
|
*u = urlValue(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) String() string {
|
||||||
|
return string(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) Validate() error {
|
||||||
|
val := string(*u)
|
||||||
|
|
||||||
|
if len(val) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
URL, err := url.Parse(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s is not a valid URL", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(URL.Scheme) == 0 || len(URL.Host) == 0 {
|
||||||
|
return fmt.Errorf("%s is not a valid URL", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) IsEmpty() bool {
|
||||||
|
return len(string(*u)) == 0
|
||||||
|
}
|
||||||
58
config/types_test.go
Normal file
58
config/types_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntValue(t *testing.T) {
|
||||||
|
var i int
|
||||||
|
|
||||||
|
ivar := newIntValue(&i, 11)
|
||||||
|
|
||||||
|
assert.Equal(t, "11", ivar.String())
|
||||||
|
assert.Equal(t, nil, ivar.Validate())
|
||||||
|
assert.Equal(t, false, ivar.IsEmpty())
|
||||||
|
|
||||||
|
i = 42
|
||||||
|
|
||||||
|
assert.Equal(t, "42", ivar.String())
|
||||||
|
assert.Equal(t, nil, ivar.Validate())
|
||||||
|
assert.Equal(t, false, ivar.IsEmpty())
|
||||||
|
|
||||||
|
ivar.Set("77")
|
||||||
|
|
||||||
|
assert.Equal(t, int(77), i)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testdata struct {
|
||||||
|
value1 int
|
||||||
|
value2 int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyStruct(t *testing.T) {
|
||||||
|
data1 := testdata{}
|
||||||
|
|
||||||
|
newIntValue(&data1.value1, 1)
|
||||||
|
newIntValue(&data1.value2, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, int(1), data1.value1)
|
||||||
|
assert.Equal(t, int(2), data1.value2)
|
||||||
|
|
||||||
|
data2 := testdata{}
|
||||||
|
|
||||||
|
val21 := newIntValue(&data2.value1, 3)
|
||||||
|
val22 := newIntValue(&data2.value2, 4)
|
||||||
|
|
||||||
|
assert.Equal(t, int(3), data2.value1)
|
||||||
|
assert.Equal(t, int(4), data2.value2)
|
||||||
|
|
||||||
|
data2 = data1
|
||||||
|
|
||||||
|
assert.Equal(t, int(1), data2.value1)
|
||||||
|
assert.Equal(t, int(2), data2.value2)
|
||||||
|
|
||||||
|
assert.Equal(t, "1", val21.String())
|
||||||
|
assert.Equal(t, "2", val22.String())
|
||||||
|
}
|
||||||
3967
docs/docs.go
Normal file
3967
docs/docs.go
Normal file
File diff suppressed because it is too large
Load diff
3903
docs/swagger.json
Normal file
3903
docs/swagger.json
Normal file
File diff suppressed because it is too large
Load diff
2568
docs/swagger.yaml
Normal file
2568
docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load diff
60
encoding/json/json.go
Normal file
60
encoding/json/json.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Package json implements
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unmarshal is a wrapper for json.Unmarshal
|
||||||
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatError takes the marshalled data and the error from Unmarshal and returns a detailed
|
||||||
|
// error message where the error was and what the error is.
|
||||||
|
func FormatError(input []byte, err error) error {
|
||||||
|
if jsonError, ok := err.(*json.SyntaxError); ok {
|
||||||
|
line, character, offsetError := lineAndCharacter(input, int(jsonError.Offset))
|
||||||
|
if offsetError != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("syntax error at line %d, character %d: %w", line, character, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonError, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
|
line, character, offsetError := lineAndCharacter(input, int(jsonError.Offset))
|
||||||
|
if offsetError != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("expect type '%s' for '%s' at line %d, character %d: %w", jsonError.Type.String(), jsonError.Field, line, character, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func lineAndCharacter(input []byte, offset int) (line int, character int, err error) {
|
||||||
|
lf := byte(0x0A)
|
||||||
|
|
||||||
|
if offset > len(input) || offset < 0 {
|
||||||
|
return 0, 0, fmt.Errorf("couldn't find offset %d within the input", offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Humans tend to count from 1.
|
||||||
|
line = 1
|
||||||
|
|
||||||
|
for i, b := range input {
|
||||||
|
if b == lf {
|
||||||
|
line++
|
||||||
|
character = 0
|
||||||
|
}
|
||||||
|
character++
|
||||||
|
if i == offset {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return line, character, nil
|
||||||
|
}
|
||||||
70
ffmpeg/collector.go
Normal file
70
ffmpeg/collector.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/datarhei/core/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type wrappedCollector struct {
|
||||||
|
session.Collector
|
||||||
|
|
||||||
|
prefix string
|
||||||
|
reference string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWrappedCollector(prefix, reference string, collector session.Collector) session.Collector {
|
||||||
|
w := &wrappedCollector{
|
||||||
|
prefix: prefix,
|
||||||
|
reference: reference,
|
||||||
|
Collector: collector,
|
||||||
|
}
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Register(id, reference, location, peer string) {
|
||||||
|
w.Collector.Register(w.prefix+id, w.reference, location, peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Activate(id string) bool {
|
||||||
|
return w.Collector.Activate(w.prefix + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) RegisterAndActivate(id, reference, location, peer string) {
|
||||||
|
w.Collector.RegisterAndActivate(w.prefix+id, w.reference, location, peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Extra(id, extra string) {
|
||||||
|
w.Collector.Extra(w.prefix+id, extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Unregister(id string) {
|
||||||
|
w.Collector.Unregister(w.prefix + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Ingress(id string, size int64) {
|
||||||
|
w.Collector.Ingress(w.prefix+id, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) Egress(id string, size int64) {
|
||||||
|
w.Collector.Egress(w.prefix+id, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) IsKnownSession(id string) bool {
|
||||||
|
return w.Collector.IsKnownSession(w.prefix + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) SessionTopIngressBitrate(id string) float64 {
|
||||||
|
return w.Collector.SessionTopIngressBitrate(w.prefix + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) SessionTopEgressBitrate(id string) float64 {
|
||||||
|
return w.Collector.SessionTopEgressBitrate(w.prefix + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) SessionSetTopIngressBitrate(id string, bitrate float64) {
|
||||||
|
w.Collector.SessionSetTopIngressBitrate(w.prefix+id, bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedCollector) SessionSetTopEgressBitrate(id string, bitrate float64) {
|
||||||
|
w.Collector.SessionSetTopEgressBitrate(w.prefix+id, bitrate)
|
||||||
|
}
|
||||||
200
ffmpeg/ffmpeg.go
Normal file
200
ffmpeg/ffmpeg.go
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/ffmpeg/parse"
|
||||||
|
"github.com/datarhei/core/ffmpeg/probe"
|
||||||
|
"github.com/datarhei/core/ffmpeg/skills"
|
||||||
|
"github.com/datarhei/core/log"
|
||||||
|
"github.com/datarhei/core/net"
|
||||||
|
"github.com/datarhei/core/process"
|
||||||
|
"github.com/datarhei/core/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FFmpeg interface {
|
||||||
|
New(config ProcessConfig) (process.Process, error)
|
||||||
|
NewProcessParser(logger log.Logger, id, reference string) parse.Parser
|
||||||
|
NewProbeParser(logger log.Logger) probe.Parser
|
||||||
|
ValidateInputAddress(address string) bool
|
||||||
|
ValidateOutputAddress(address string) bool
|
||||||
|
Skills() skills.Skills
|
||||||
|
ReloadSkills() error
|
||||||
|
GetPort() (int, error)
|
||||||
|
PutPort(port int)
|
||||||
|
States() process.States
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProcessConfig struct {
|
||||||
|
Reconnect bool
|
||||||
|
ReconnectDelay time.Duration
|
||||||
|
StaleTimeout time.Duration
|
||||||
|
Command []string
|
||||||
|
Parser process.Parser
|
||||||
|
Logger log.Logger
|
||||||
|
OnExit func()
|
||||||
|
OnStart func()
|
||||||
|
OnStateChange func(from, to string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for ffmpeg that is part of the configuration
|
||||||
|
// for the restreamer instance.
|
||||||
|
type Config struct {
|
||||||
|
Binary string
|
||||||
|
MaxProc int64
|
||||||
|
MaxLogLines int
|
||||||
|
LogHistoryLength int
|
||||||
|
ValidatorInput Validator
|
||||||
|
ValidatorOutput Validator
|
||||||
|
Portrange net.Portranger
|
||||||
|
Collector session.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpeg struct {
|
||||||
|
binary string
|
||||||
|
validatorIn Validator
|
||||||
|
validatorOut Validator
|
||||||
|
portrange net.Portranger
|
||||||
|
skills skills.Skills
|
||||||
|
|
||||||
|
logLines int
|
||||||
|
historyLength int
|
||||||
|
|
||||||
|
collector session.Collector
|
||||||
|
|
||||||
|
states process.States
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) (FFmpeg, error) {
|
||||||
|
f := &ffmpeg{}
|
||||||
|
|
||||||
|
binary, err := exec.LookPath(config.Binary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid ffmpeg binary given: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.binary = binary
|
||||||
|
f.historyLength = config.LogHistoryLength
|
||||||
|
f.logLines = config.MaxLogLines
|
||||||
|
|
||||||
|
f.portrange = config.Portrange
|
||||||
|
if f.portrange == nil {
|
||||||
|
f.portrange = net.NewDummyPortrange()
|
||||||
|
}
|
||||||
|
|
||||||
|
f.validatorIn = config.ValidatorInput
|
||||||
|
if f.validatorIn == nil {
|
||||||
|
f.validatorIn, _ = NewValidator(nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.validatorOut = config.ValidatorOutput
|
||||||
|
if f.validatorOut == nil {
|
||||||
|
f.validatorOut, _ = NewValidator(nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.collector = config.Collector
|
||||||
|
if f.collector == nil {
|
||||||
|
f.collector = session.NewNullCollector()
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := skills.New(f.binary)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid ffmpeg binary given: %w", err)
|
||||||
|
}
|
||||||
|
f.skills = s
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) New(config ProcessConfig) (process.Process, error) {
|
||||||
|
ffmpeg, err := process.New(process.Config{
|
||||||
|
Binary: f.binary,
|
||||||
|
Args: config.Command,
|
||||||
|
Reconnect: config.Reconnect,
|
||||||
|
ReconnectDelay: config.ReconnectDelay,
|
||||||
|
StaleTimeout: config.StaleTimeout,
|
||||||
|
Parser: config.Parser,
|
||||||
|
Logger: config.Logger,
|
||||||
|
OnStart: config.OnStart,
|
||||||
|
OnExit: config.OnExit,
|
||||||
|
OnStateChange: func(from, to string) {
|
||||||
|
switch to {
|
||||||
|
case "finished":
|
||||||
|
f.states.Finished++
|
||||||
|
case "starting":
|
||||||
|
f.states.Starting++
|
||||||
|
case "running":
|
||||||
|
f.states.Running++
|
||||||
|
case "finishing":
|
||||||
|
f.states.Finishing++
|
||||||
|
case "failed":
|
||||||
|
f.states.Failed++
|
||||||
|
case "killed":
|
||||||
|
f.states.Killed++
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.OnStateChange != nil {
|
||||||
|
config.OnStateChange(from, to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return ffmpeg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) NewProcessParser(logger log.Logger, id, reference string) parse.Parser {
|
||||||
|
p := parse.New(parse.Config{
|
||||||
|
LogHistory: f.historyLength,
|
||||||
|
LogLines: f.logLines,
|
||||||
|
Logger: logger,
|
||||||
|
Collector: NewWrappedCollector(id, reference, f.collector),
|
||||||
|
})
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) NewProbeParser(logger log.Logger) probe.Parser {
|
||||||
|
p := probe.New(probe.Config{
|
||||||
|
Logger: logger,
|
||||||
|
})
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) ValidateInputAddress(address string) bool {
|
||||||
|
return f.validatorIn.IsValid(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) ValidateOutputAddress(address string) bool {
|
||||||
|
return f.validatorOut.IsValid(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) Skills() skills.Skills {
|
||||||
|
return f.skills
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) ReloadSkills() error {
|
||||||
|
s, err := skills.New(f.binary)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid ffmpeg binary given: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.skills = s
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) GetPort() (int, error) {
|
||||||
|
return f.portrange.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) PutPort(port int) {
|
||||||
|
f.portrange.Put(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ffmpeg) States() process.States {
|
||||||
|
return f.states
|
||||||
|
}
|
||||||
25
ffmpeg/parse/average.go
Normal file
25
ffmpeg/parse/average.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prep/average"
|
||||||
|
)
|
||||||
|
|
||||||
|
type averager struct {
|
||||||
|
fps *average.SlidingWindow
|
||||||
|
pps *average.SlidingWindow
|
||||||
|
bitrate *average.SlidingWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *averager) init(window, granularity time.Duration) {
|
||||||
|
a.fps, _ = average.New(window, granularity)
|
||||||
|
a.pps, _ = average.New(window, granularity)
|
||||||
|
a.bitrate, _ = average.New(window, granularity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *averager) stop() {
|
||||||
|
a.fps.Stop()
|
||||||
|
a.pps.Stop()
|
||||||
|
a.bitrate.Stop()
|
||||||
|
}
|
||||||
800
ffmpeg/parse/parser.go
Normal file
800
ffmpeg/parse/parser.go
Normal file
|
|
@ -0,0 +1,800 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/ring"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/log"
|
||||||
|
"github.com/datarhei/core/net/url"
|
||||||
|
"github.com/datarhei/core/process"
|
||||||
|
"github.com/datarhei/core/restream/app"
|
||||||
|
"github.com/datarhei/core/session"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser is an extension to the process.Parser interface
|
||||||
|
type Parser interface {
|
||||||
|
process.Parser
|
||||||
|
|
||||||
|
// Progress returns the current progress information of the process
|
||||||
|
Progress() app.Progress
|
||||||
|
|
||||||
|
// Prelude returns an array of the lines before the progress information started
|
||||||
|
Prelude() []string
|
||||||
|
|
||||||
|
// Report returns the current logs
|
||||||
|
Report() Report
|
||||||
|
|
||||||
|
// ReportHistory returns an array of previews logs
|
||||||
|
ReportHistory() []Report
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the config for the Parser implementation
|
||||||
|
type Config struct {
|
||||||
|
LogHistory int
|
||||||
|
LogLines int
|
||||||
|
PreludeHeadLines int
|
||||||
|
PreludeTailLines int
|
||||||
|
Logger log.Logger
|
||||||
|
Collector session.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
re struct {
|
||||||
|
frame *regexp.Regexp
|
||||||
|
quantizer *regexp.Regexp
|
||||||
|
size *regexp.Regexp
|
||||||
|
time *regexp.Regexp
|
||||||
|
speed *regexp.Regexp
|
||||||
|
drop *regexp.Regexp
|
||||||
|
dup *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
prelude struct {
|
||||||
|
headLines int
|
||||||
|
tailLines int
|
||||||
|
truncatedLines uint64
|
||||||
|
data []string
|
||||||
|
tail *ring.Ring
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
log *ring.Ring
|
||||||
|
logLines int
|
||||||
|
logStart time.Time
|
||||||
|
|
||||||
|
logHistory *ring.Ring
|
||||||
|
logHistoryLength int
|
||||||
|
|
||||||
|
progress struct {
|
||||||
|
ffmpeg ffmpegProgress
|
||||||
|
avstream map[string]ffmpegAVstream
|
||||||
|
}
|
||||||
|
|
||||||
|
process ffmpegProcess
|
||||||
|
|
||||||
|
stats struct {
|
||||||
|
initialized bool
|
||||||
|
main stats
|
||||||
|
input []stats
|
||||||
|
output []stats
|
||||||
|
}
|
||||||
|
|
||||||
|
averager struct {
|
||||||
|
initialized bool
|
||||||
|
window time.Duration
|
||||||
|
granularity time.Duration
|
||||||
|
main averager
|
||||||
|
input []averager
|
||||||
|
output []averager
|
||||||
|
}
|
||||||
|
|
||||||
|
collector session.Collector
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
|
||||||
|
lock struct {
|
||||||
|
progress sync.RWMutex
|
||||||
|
prelude sync.RWMutex
|
||||||
|
log sync.RWMutex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a Parser that satisfies the Parser interface
|
||||||
|
func New(config Config) Parser {
|
||||||
|
p := &parser{
|
||||||
|
logHistoryLength: config.LogHistory,
|
||||||
|
logLines: config.LogLines,
|
||||||
|
logger: config.Logger,
|
||||||
|
collector: config.Collector,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.logger == nil {
|
||||||
|
p.logger = log.New("Parser")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.logLines <= 0 {
|
||||||
|
p.logLines = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.window = 30 * time.Second
|
||||||
|
p.averager.granularity = time.Second
|
||||||
|
|
||||||
|
p.re.frame = regexp.MustCompile(`frame=\s*([0-9]+)`)
|
||||||
|
p.re.quantizer = regexp.MustCompile(`q=\s*([0-9\.]+)`)
|
||||||
|
p.re.size = regexp.MustCompile(`size=\s*([0-9]+)kB`)
|
||||||
|
p.re.time = regexp.MustCompile(`time=\s*([0-9]+):([0-9]{2}):([0-9]{2}).([0-9]{2})`)
|
||||||
|
p.re.speed = regexp.MustCompile(`speed=\s*([0-9\.]+)x`)
|
||||||
|
p.re.drop = regexp.MustCompile(`drop=\s*([0-9]+)`)
|
||||||
|
p.re.dup = regexp.MustCompile(`dup=\s*([0-9]+)`)
|
||||||
|
|
||||||
|
p.prelude.headLines = config.PreludeHeadLines
|
||||||
|
if p.prelude.headLines <= 0 {
|
||||||
|
p.prelude.headLines = 100
|
||||||
|
}
|
||||||
|
p.prelude.tailLines = config.PreludeTailLines
|
||||||
|
if p.prelude.tailLines <= 0 {
|
||||||
|
p.prelude.tailLines = 50
|
||||||
|
}
|
||||||
|
p.prelude.tail = ring.New(p.prelude.tailLines)
|
||||||
|
|
||||||
|
p.log = ring.New(config.LogLines)
|
||||||
|
|
||||||
|
if p.logHistoryLength > 0 {
|
||||||
|
p.logHistory = ring.New(p.logHistoryLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.collector == nil {
|
||||||
|
p.collector = session.NewNullCollector()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.logStart = time.Now()
|
||||||
|
|
||||||
|
p.ResetStats()
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Parse(line string) uint64 {
|
||||||
|
isDefaultProgress := strings.HasPrefix(line, "frame=")
|
||||||
|
isFFmpegInputs := strings.HasPrefix(line, "ffmpeg.inputs:")
|
||||||
|
isFFmpegOutputs := strings.HasPrefix(line, "ffmpeg.outputs:")
|
||||||
|
isFFmpegProgress := strings.HasPrefix(line, "ffmpeg.progress:")
|
||||||
|
isAVstreamProgress := strings.HasPrefix(line, "avstream.progress:")
|
||||||
|
|
||||||
|
if p.logStart.IsZero() {
|
||||||
|
p.logStart = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.prelude.done {
|
||||||
|
if isAVstreamProgress {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFFmpegProgress {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFFmpegInputs {
|
||||||
|
if err := p.parseIO("input", strings.TrimPrefix(line, "ffmpeg.inputs:")); err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing inputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFFmpegOutputs {
|
||||||
|
if err := p.parseIO("output", strings.TrimPrefix(line, "ffmpeg.outputs:")); err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing outputs")
|
||||||
|
} else {
|
||||||
|
p.logger.WithField("prelude", p.Prelude()).Debug().Log("")
|
||||||
|
p.prelude.done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDefaultProgress {
|
||||||
|
if !p.parsePrelude() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p.logger.WithField("prelude", p.Prelude()).Debug().Log("")
|
||||||
|
p.prelude.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isDefaultProgress && !isFFmpegProgress && !isAVstreamProgress {
|
||||||
|
// Write the current non-progress line to the log
|
||||||
|
p.addLog(line)
|
||||||
|
|
||||||
|
if !p.prelude.done {
|
||||||
|
if len(p.prelude.data) < p.prelude.headLines {
|
||||||
|
p.prelude.data = append(p.prelude.data, line)
|
||||||
|
} else {
|
||||||
|
p.lock.prelude.Lock()
|
||||||
|
p.prelude.tail.Value = line
|
||||||
|
p.prelude.tail = p.prelude.tail.Next()
|
||||||
|
p.lock.prelude.Unlock()
|
||||||
|
p.prelude.truncatedLines++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.prelude.done {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p.lock.progress.Lock()
|
||||||
|
defer p.lock.progress.Unlock()
|
||||||
|
|
||||||
|
// Initialize the averagers
|
||||||
|
|
||||||
|
if !p.averager.initialized {
|
||||||
|
p.averager.main.init(p.averager.window, p.averager.granularity)
|
||||||
|
|
||||||
|
p.averager.input = make([]averager, len(p.process.input))
|
||||||
|
for i := range p.averager.input {
|
||||||
|
p.averager.input[i].init(p.averager.window, p.averager.granularity)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.output = make([]averager, len(p.process.output))
|
||||||
|
for i := range p.averager.output {
|
||||||
|
p.averager.output[i].init(p.averager.window, p.averager.granularity)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the stats
|
||||||
|
|
||||||
|
if !p.stats.initialized {
|
||||||
|
p.stats.input = make([]stats, len(p.process.input))
|
||||||
|
p.stats.output = make([]stats, len(p.process.output))
|
||||||
|
|
||||||
|
p.collector.Register("", "", "", "")
|
||||||
|
|
||||||
|
p.stats.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the progress
|
||||||
|
|
||||||
|
if isAVstreamProgress {
|
||||||
|
if err := p.parseAVstreamProgress(strings.TrimPrefix(line, "avstream.progress:")); err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing AVStream progress")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDefaultProgress {
|
||||||
|
if err := p.parseDefaultProgress(line); err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing default progress")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
} else if isFFmpegProgress {
|
||||||
|
if err := p.parseFFmpegProgress(strings.TrimPrefix(line, "ffmpeg.progress:")); err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing progress")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the averages
|
||||||
|
|
||||||
|
p.stats.main.updateFromProgress(&p.progress.ffmpeg)
|
||||||
|
|
||||||
|
if len(p.stats.input) != 0 && len(p.stats.input) == len(p.progress.ffmpeg.Input) {
|
||||||
|
for i := range p.progress.ffmpeg.Input {
|
||||||
|
p.stats.input[i].updateFromProgressIO(&p.progress.ffmpeg.Input[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.stats.output) != 0 && len(p.stats.output) == len(p.progress.ffmpeg.Output) {
|
||||||
|
for i := range p.progress.ffmpeg.Output {
|
||||||
|
p.stats.output[i].updateFromProgressIO(&p.progress.ffmpeg.Output[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.main.fps.Add(int64(p.stats.main.diff.frame))
|
||||||
|
p.averager.main.pps.Add(int64(p.stats.main.diff.packet))
|
||||||
|
p.averager.main.bitrate.Add(int64(p.stats.main.diff.size) * 8)
|
||||||
|
|
||||||
|
p.progress.ffmpeg.FPS = p.averager.main.fps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.PPS = p.averager.main.pps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.Bitrate = p.averager.main.bitrate.Average(p.averager.window)
|
||||||
|
|
||||||
|
if len(p.averager.input) != 0 && len(p.averager.input) == len(p.progress.ffmpeg.Input) {
|
||||||
|
for i := range p.progress.ffmpeg.Input {
|
||||||
|
p.averager.input[i].fps.Add(int64(p.stats.input[i].diff.frame))
|
||||||
|
p.averager.input[i].pps.Add(int64(p.stats.input[i].diff.packet))
|
||||||
|
p.averager.input[i].bitrate.Add(int64(p.stats.input[i].diff.size) * 8)
|
||||||
|
|
||||||
|
p.progress.ffmpeg.Input[i].FPS = p.averager.input[i].fps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.Input[i].PPS = p.averager.input[i].pps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.Input[i].Bitrate = p.averager.input[i].bitrate.Average(p.averager.window)
|
||||||
|
|
||||||
|
if p.collector.IsCollectableIP(p.process.input[i].IP) {
|
||||||
|
p.collector.Activate("")
|
||||||
|
p.collector.Ingress("", int64(p.stats.input[i].diff.size)*1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.averager.output) != 0 && len(p.averager.output) == len(p.progress.ffmpeg.Output) {
|
||||||
|
for i := range p.progress.ffmpeg.Output {
|
||||||
|
p.averager.output[i].fps.Add(int64(p.stats.output[i].diff.frame))
|
||||||
|
p.averager.output[i].pps.Add(int64(p.stats.output[i].diff.packet))
|
||||||
|
p.averager.output[i].bitrate.Add(int64(p.stats.output[i].diff.size) * 8)
|
||||||
|
|
||||||
|
p.progress.ffmpeg.Output[i].FPS = p.averager.output[i].fps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.Output[i].PPS = p.averager.output[i].pps.Average(p.averager.window)
|
||||||
|
p.progress.ffmpeg.Output[i].Bitrate = p.averager.output[i].bitrate.Average(p.averager.window)
|
||||||
|
|
||||||
|
if p.collector.IsCollectableIP(p.process.output[i].IP) {
|
||||||
|
p.collector.Activate("")
|
||||||
|
p.collector.Egress("", int64(p.stats.output[i].diff.size)*1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate if any of the processed frames staled.
|
||||||
|
// If one number of frames in an output is the same as
|
||||||
|
// before, then pFrames becomes 0.
|
||||||
|
var pFrames uint64 = 0
|
||||||
|
|
||||||
|
pFrames = p.stats.main.diff.frame
|
||||||
|
|
||||||
|
if isFFmpegProgress {
|
||||||
|
for i := range p.stats.output {
|
||||||
|
pFrames *= p.stats.output[i].diff.frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pFrames
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseDefaultProgress(line string) error {
|
||||||
|
var matches []string
|
||||||
|
|
||||||
|
if matches = p.re.frame.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Frame = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.quantizer.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Quantizer = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.size.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Size = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.time.FindStringSubmatch(line); matches != nil {
|
||||||
|
s := fmt.Sprintf("%sh%sm%ss%s0ms", matches[1], matches[2], matches[3], matches[4])
|
||||||
|
if x, err := time.ParseDuration(s); err == nil {
|
||||||
|
p.progress.ffmpeg.Time.Duration = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.speed.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Speed = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.drop.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Drop = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = p.re.dup.FindStringSubmatch(line); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
p.progress.ffmpeg.Dup = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseIO(kind, line string) error {
|
||||||
|
processIO := []ffmpegProcessIO{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(line), &processIO)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(processIO) == 0 {
|
||||||
|
return fmt.Errorf("the %s length must not be 0", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range processIO {
|
||||||
|
if ip, _ := url.Lookup(processIO[i].Address); len(ip) != 0 {
|
||||||
|
processIO[i].IP = ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == "input" {
|
||||||
|
p.process.input = processIO
|
||||||
|
} else if kind == "output" {
|
||||||
|
p.process.output = processIO
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseFFmpegProgress(line string) error {
|
||||||
|
progress := ffmpegProgress{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(line), &progress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(progress.Input) != len(p.process.input) {
|
||||||
|
return fmt.Errorf("input length mismatch (have: %d, want: %d)", len(progress.Input), len(p.process.input))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(progress.Output) != len(p.process.output) {
|
||||||
|
return fmt.Errorf("output length mismatch (have: %d, want: %d)", len(progress.Output), len(p.process.output))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.progress.ffmpeg = progress
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parseAVstreamProgress(line string) error {
|
||||||
|
progress := ffmpegAVstream{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(line), &progress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.progress.avstream[progress.Address] = progress
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Progress() app.Progress {
|
||||||
|
p.lock.progress.RLock()
|
||||||
|
defer p.lock.progress.RUnlock()
|
||||||
|
|
||||||
|
progress := p.process.export()
|
||||||
|
|
||||||
|
p.progress.ffmpeg.exportTo(&progress)
|
||||||
|
|
||||||
|
for i, io := range progress.Input {
|
||||||
|
av, ok := p.progress.avstream[io.Address]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.Input[i].AVstream = av.export()
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Prelude() []string {
|
||||||
|
if p.prelude.data == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
prelude := make([]string, len(p.prelude.data))
|
||||||
|
copy(prelude, p.prelude.data)
|
||||||
|
|
||||||
|
tail := []string{}
|
||||||
|
|
||||||
|
p.lock.prelude.RLock()
|
||||||
|
|
||||||
|
p.prelude.tail.Do(func(l interface{}) {
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tail = append(tail, l.(string))
|
||||||
|
})
|
||||||
|
|
||||||
|
p.lock.prelude.RUnlock()
|
||||||
|
|
||||||
|
if len(tail) != 0 {
|
||||||
|
if p.prelude.truncatedLines > uint64(p.prelude.tailLines) {
|
||||||
|
prelude = append(prelude, fmt.Sprintf("... truncated %d lines ...", p.prelude.truncatedLines-uint64(p.prelude.tailLines)))
|
||||||
|
}
|
||||||
|
prelude = append(prelude, tail...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prelude
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parsePrelude() bool {
|
||||||
|
process := ffmpegProcess{}
|
||||||
|
|
||||||
|
p.lock.progress.Lock()
|
||||||
|
defer p.lock.progress.Unlock()
|
||||||
|
|
||||||
|
// Input #0, lavfi, from 'testsrc=size=1280x720:rate=25':
|
||||||
|
// Input #1, lavfi, from 'anullsrc=r=44100:cl=stereo':
|
||||||
|
// Output #0, hls, to './data/testsrc.m3u8':
|
||||||
|
reFormat := regexp.MustCompile(`^(Input|Output) #([0-9]+), (.*?), (from|to) '([^']+)`)
|
||||||
|
|
||||||
|
// Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 1280x720 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
|
||||||
|
// Stream #1:0: Audio: pcm_u8, 44100 Hz, stereo, u8, 705 kb/s
|
||||||
|
// Stream #0:0: Video: h264 (libx264), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 90k tbn, 25 tbc
|
||||||
|
// Stream #0:1(eng): Audio: aac (LC), 44100 Hz, stereo, fltp, 64 kb/s
|
||||||
|
reStream := regexp.MustCompile(`Stream #([0-9]+):([0-9]+)(?:\(([a-z]+)\))?: (Video|Audio|Subtitle): (.*)`)
|
||||||
|
reStreamCodec := regexp.MustCompile(`^([^\s,]+)`)
|
||||||
|
reStreamVideoSize := regexp.MustCompile(`, ([0-9]+)x([0-9]+)`)
|
||||||
|
//reStreamVideoFPS := regexp.MustCompile(`, ([0-9]+) fps`)
|
||||||
|
reStreamAudio := regexp.MustCompile(`, ([0-9]+) Hz, ([^,]+)`)
|
||||||
|
//reStreamBitrate := regexp.MustCompile(`, ([0-9]+) kb/s`)
|
||||||
|
|
||||||
|
reStreamMapping := regexp.MustCompile(`^Stream mapping:`)
|
||||||
|
reStreamMap := regexp.MustCompile(`^[\s]+Stream #[0-9]+:[0-9]+`)
|
||||||
|
|
||||||
|
//format := InputOutput{}
|
||||||
|
|
||||||
|
formatType := ""
|
||||||
|
formatURL := ""
|
||||||
|
|
||||||
|
var noutputs int
|
||||||
|
streamMapping := false
|
||||||
|
|
||||||
|
data := p.Prelude()
|
||||||
|
|
||||||
|
for _, line := range data {
|
||||||
|
if reStreamMapping.MatchString(line) {
|
||||||
|
streamMapping = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if streamMapping {
|
||||||
|
if reStreamMap.MatchString(line) {
|
||||||
|
noutputs++
|
||||||
|
} else {
|
||||||
|
streamMapping = false
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches := reFormat.FindStringSubmatch(line); matches != nil {
|
||||||
|
formatType = strings.ToLower(matches[1])
|
||||||
|
formatURL = matches[5]
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches := reStream.FindStringSubmatch(line); matches != nil {
|
||||||
|
format := ffmpegProcessIO{}
|
||||||
|
|
||||||
|
format.Address = formatURL
|
||||||
|
if ip, _ := url.Lookup(format.Address); len(ip) != 0 {
|
||||||
|
format.IP = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
format.Index = x
|
||||||
|
}
|
||||||
|
|
||||||
|
if x, err := strconv.ParseUint(matches[2], 10, 64); err == nil {
|
||||||
|
format.Stream = x
|
||||||
|
}
|
||||||
|
format.Type = strings.ToLower(matches[4])
|
||||||
|
|
||||||
|
streamDetail := matches[5]
|
||||||
|
|
||||||
|
if matches = reStreamCodec.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
format.Codec = matches[1]
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if matches = reStreamBitrate.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
format.Bitrate = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if format.Type == "video" {
|
||||||
|
if matches = reStreamVideoSize.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
format.Width = x
|
||||||
|
}
|
||||||
|
if x, err := strconv.ParseUint(matches[2], 10, 64); err == nil {
|
||||||
|
format.Height = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if matches = reStreamVideoFPS.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
format.FPS = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} else if format.Type == "audio" {
|
||||||
|
if matches = reStreamAudio.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
format.Sampling = x
|
||||||
|
}
|
||||||
|
format.Layout = matches[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatType == "input" {
|
||||||
|
process.input = append(process.input, format)
|
||||||
|
} else {
|
||||||
|
process.output = append(process.output, format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(process.output) != noutputs {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.process.input = process.input
|
||||||
|
p.process.output = process.output
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) addLog(line string) {
|
||||||
|
p.lock.log.Lock()
|
||||||
|
defer p.lock.log.Unlock()
|
||||||
|
|
||||||
|
p.log.Value = process.Line{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Data: line,
|
||||||
|
}
|
||||||
|
p.log = p.log.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Log() []process.Line {
|
||||||
|
var log = []process.Line{}
|
||||||
|
|
||||||
|
p.lock.log.RLock()
|
||||||
|
defer p.lock.log.RUnlock()
|
||||||
|
|
||||||
|
p.log.Do(func(l interface{}) {
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log = append(log, l.(process.Line))
|
||||||
|
})
|
||||||
|
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) ResetStats() {
|
||||||
|
p.lock.progress.Lock()
|
||||||
|
defer p.lock.progress.Unlock()
|
||||||
|
|
||||||
|
if p.averager.initialized {
|
||||||
|
p.averager.main.stop()
|
||||||
|
|
||||||
|
p.averager.main = averager{}
|
||||||
|
|
||||||
|
for i := range p.averager.input {
|
||||||
|
p.averager.input[i].stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.input = []averager{}
|
||||||
|
|
||||||
|
for i := range p.averager.output {
|
||||||
|
p.averager.output[i].stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.averager.output = []averager{}
|
||||||
|
|
||||||
|
p.averager.initialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.stats.initialized {
|
||||||
|
p.stats.main = stats{}
|
||||||
|
|
||||||
|
p.stats.input = []stats{}
|
||||||
|
p.stats.output = []stats{}
|
||||||
|
|
||||||
|
p.stats.initialized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.process = ffmpegProcess{}
|
||||||
|
p.progress.ffmpeg = ffmpegProgress{}
|
||||||
|
p.progress.avstream = make(map[string]ffmpegAVstream)
|
||||||
|
|
||||||
|
p.prelude.done = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) ResetLog() {
|
||||||
|
p.storeLogHistory()
|
||||||
|
|
||||||
|
p.prelude.data = []string{}
|
||||||
|
p.lock.prelude.Lock()
|
||||||
|
p.prelude.tail = ring.New(p.prelude.tailLines)
|
||||||
|
p.lock.prelude.Unlock()
|
||||||
|
p.prelude.truncatedLines = 0
|
||||||
|
p.prelude.done = false
|
||||||
|
|
||||||
|
p.lock.log.Lock()
|
||||||
|
p.log = ring.New(p.logLines)
|
||||||
|
p.lock.log.Unlock()
|
||||||
|
|
||||||
|
p.logStart = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report represents a log report, including the prelude and the last log lines
|
||||||
|
// of the process.
|
||||||
|
type Report struct {
|
||||||
|
CreatedAt time.Time
|
||||||
|
Prelude []string
|
||||||
|
Log []process.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) storeLogHistory() {
|
||||||
|
if p.logHistory == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h := p.Report()
|
||||||
|
|
||||||
|
if len(h.Prelude) != 0 {
|
||||||
|
p.logHistory.Value = h
|
||||||
|
p.logHistory = p.logHistory.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) Report() Report {
|
||||||
|
h := Report{
|
||||||
|
CreatedAt: p.logStart,
|
||||||
|
Prelude: p.Prelude(),
|
||||||
|
Log: p.Log(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) ReportHistory() []Report {
|
||||||
|
var history = []Report{}
|
||||||
|
|
||||||
|
p.logHistory.Do(func(l interface{}) {
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, l.(Report))
|
||||||
|
})
|
||||||
|
|
||||||
|
return history
|
||||||
|
}
|
||||||
523
ffmpeg/parse/parser_test.go
Normal file
523
ffmpeg/parse/parser_test.go
Normal file
|
|
@ -0,0 +1,523 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/restream/app"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParserProgress(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
parser.prelude.done = true
|
||||||
|
parser.Parse("frame= 5968 fps= 25 q=19.4 size=443kB time=00:03:58.44 bitrate=5632kbits/s speed=0.999x skip=9733 drop=3522 dup=87463")
|
||||||
|
|
||||||
|
d, _ := time.ParseDuration("3m58s440ms")
|
||||||
|
wantP := app.Progress{
|
||||||
|
Frame: 5968,
|
||||||
|
FPS: 25,
|
||||||
|
Quantizer: 19.4,
|
||||||
|
Size: 453632,
|
||||||
|
Time: d.Seconds(),
|
||||||
|
Bitrate: 5632,
|
||||||
|
Speed: 0.999,
|
||||||
|
Drop: 3522,
|
||||||
|
Dup: 87463,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := parser.Progress()
|
||||||
|
|
||||||
|
require.Equal(t, wantP.Frame, p.Frame)
|
||||||
|
require.Equal(t, wantP.Quantizer, p.Quantizer)
|
||||||
|
require.Equal(t, wantP.Size, p.Size)
|
||||||
|
require.Equal(t, wantP.Time, p.Time)
|
||||||
|
require.Equal(t, wantP.Speed, p.Speed)
|
||||||
|
require.Equal(t, wantP.Drop, p.Drop)
|
||||||
|
require.Equal(t, wantP.Dup, p.Dup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserPrelude(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
PreludeHeadLines: 100,
|
||||||
|
PreludeTailLines: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
log := parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
|
||||||
|
parser.Parse("prelude")
|
||||||
|
|
||||||
|
log = parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(log))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserLongPrelude(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
PreludeHeadLines: 100,
|
||||||
|
PreludeTailLines: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
log := parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
|
||||||
|
for i := 0; i < 150; i++ {
|
||||||
|
parser.Parse(fmt.Sprintf("prelude %3d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
log = parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 150, len(log))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserVeryLongPrelude(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
PreludeHeadLines: 100,
|
||||||
|
PreludeTailLines: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
log := parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
|
||||||
|
for i := 0; i < 300; i++ {
|
||||||
|
parser.Parse(fmt.Sprintf("prelude %3d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
log = parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 151, len(log))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserLog(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
log := parser.Log()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
|
||||||
|
parser.Parse("bla")
|
||||||
|
|
||||||
|
log = parser.Log()
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(log))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserReset(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
PreludeHeadLines: 100,
|
||||||
|
PreludeTailLines: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
log := parser.Log()
|
||||||
|
prelude := parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
require.Equal(t, 0, len(prelude))
|
||||||
|
|
||||||
|
parser.Parse("prelude")
|
||||||
|
|
||||||
|
log = parser.Log()
|
||||||
|
prelude = parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(log))
|
||||||
|
require.Equal(t, 1, len(prelude))
|
||||||
|
|
||||||
|
parser.ResetStats()
|
||||||
|
|
||||||
|
log = parser.Log()
|
||||||
|
prelude = parser.Prelude()
|
||||||
|
|
||||||
|
require.NotEqual(t, 0, len(log))
|
||||||
|
require.NotEqual(t, 0, len(prelude))
|
||||||
|
|
||||||
|
parser.ResetLog()
|
||||||
|
|
||||||
|
log = parser.Log()
|
||||||
|
prelude = parser.Prelude()
|
||||||
|
|
||||||
|
require.Equal(t, 0, len(log))
|
||||||
|
require.Equal(t, 0, len(prelude))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserDefault(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
|
||||||
|
built with Apple LLVM version 9.1.0 (clang-902.0.39.2)
|
||||||
|
configuration: --prefix=/usr/local/Cellar/ffmpeg/4.0.2 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-gpl --enable-libmp3lame --enable-libx264 --enable-libx265 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma
|
||||||
|
libavutil 56. 14.100 / 56. 14.100
|
||||||
|
libavcodec 58. 18.100 / 58. 18.100
|
||||||
|
libavformat 58. 12.100 / 58. 12.100
|
||||||
|
libavdevice 58. 3.100 / 58. 3.100
|
||||||
|
libavfilter 7. 16.100 / 7. 16.100
|
||||||
|
libavresample 4. 0. 0 / 4. 0. 0
|
||||||
|
libswscale 5. 1.100 / 5. 1.100
|
||||||
|
libswresample 3. 1.100 / 3. 1.100
|
||||||
|
libpostproc 55. 1.100 / 55. 1.100
|
||||||
|
Input #0, lavfi, from 'testsrc=size=1280x720:rate=25':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 1280x720 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
|
||||||
|
Input #1, lavfi, from 'anullsrc=r=44100:cl=stereo':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: 705 kb/s
|
||||||
|
Stream #1:0: Audio: pcm_u8, 44100 Hz, stereo, u8, 705 kb/s
|
||||||
|
Stream #1:1(eng): Audio: aac (LC), 48000 Hz, stereo, fltp (default)
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
|
||||||
|
Stream #1:0 -> #0:1 (pcm_u8 (native) -> aac (native))
|
||||||
|
Press [q] to stop, [?] for help
|
||||||
|
[libx264 @ 0x7fa96a800600] using SAR=1/1
|
||||||
|
[libx264 @ 0x7fa96a800600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
|
||||||
|
[libx264 @ 0x7fa96a800600] profile Constrained Baseline, level 3.1
|
||||||
|
[libx264 @ 0x7fa96a800600] 264 - core 152 r2854 e9a5903 - H.264/MPEG-4 AVC codec - Copyleft 2003-2017 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=0:0:0 analyse=0:0 me=dia subme=0 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=50 keyint_min=5 scenecut=0 intra_refresh=0 rc=crf mbtree=0 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=0
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc5375.ts.tmp' for writing
|
||||||
|
Output #0, hls, to './data/testsrc.m3u8':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.12.100
|
||||||
|
Stream #0:0: Video: h264 (libx264), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 90k tbn, 25 tbc
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavc58.18.100 libx264
|
||||||
|
Side data:
|
||||||
|
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: -1
|
||||||
|
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 64 kb/s
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavc58.18.100 aac
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc5376.ts.tmp' for writing=0.872x
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc.m3u8.tmp' for writing
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc.m3u8.tmp' for writing
|
||||||
|
frame= 58 fps= 25 q=-1.0 Lsize=N/A time=00:00:02.32 bitrate=N/A speed=0.999x`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
parser.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 3, len(parser.process.input), "expected 3 inputs")
|
||||||
|
require.Equal(t, 2, len(parser.process.output), "expected 2 outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserDefaultDelayed(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
|
||||||
|
built with Apple LLVM version 9.1.0 (clang-902.0.39.2)
|
||||||
|
configuration: --prefix=/usr/local/Cellar/ffmpeg/4.0.2 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-gpl --enable-libmp3lame --enable-libx264 --enable-libx265 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma
|
||||||
|
libavutil 56. 14.100 / 56. 14.100
|
||||||
|
libavcodec 58. 18.100 / 58. 18.100
|
||||||
|
libavformat 58. 12.100 / 58. 12.100
|
||||||
|
libavdevice 58. 3.100 / 58. 3.100
|
||||||
|
libavfilter 7. 16.100 / 7. 16.100
|
||||||
|
libavresample 4. 0. 0 / 4. 0. 0
|
||||||
|
libswscale 5. 1.100 / 5. 1.100
|
||||||
|
libswresample 3. 1.100 / 3. 1.100
|
||||||
|
libpostproc 55. 1.100 / 55. 1.100
|
||||||
|
Input #0, lavfi, from 'testsrc=size=1280x720:rate=25':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 1280x720 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
|
||||||
|
Input #1, lavfi, from 'anullsrc=r=44100:cl=stereo':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: 705 kb/s
|
||||||
|
Stream #1:0: Audio: pcm_u8, 44100 Hz, stereo, u8, 705 kb/s
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
|
||||||
|
Stream #1:0 -> #0:1 (pcm_u8 (native) -> aac (native))
|
||||||
|
Press [q] to stop, [?] for help
|
||||||
|
frame= 29 fps= 25 q=-1.0 Lsize=N/A time=00:00:01.32 bitrate=N/A speed=0.999x
|
||||||
|
[libx264 @ 0x7fa96a800600] using SAR=1/1
|
||||||
|
[libx264 @ 0x7fa96a800600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
|
||||||
|
[libx264 @ 0x7fa96a800600] profile Constrained Baseline, level 3.1
|
||||||
|
[libx264 @ 0x7fa96a800600] 264 - core 152 r2854 e9a5903 - H.264/MPEG-4 AVC codec - Copyleft 2003-2017 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=0:0:0 analyse=0:0 me=dia subme=0 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=50 keyint_min=5 scenecut=0 intra_refresh=0 rc=crf mbtree=0 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=0
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc5375.ts.tmp' for writing
|
||||||
|
Output #0, hls, to './data/testsrc.m3u8':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.12.100
|
||||||
|
Stream #0:0: Video: h264 (libx264), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 90k tbn, 25 tbc
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavc58.18.100 libx264
|
||||||
|
Side data:
|
||||||
|
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: -1
|
||||||
|
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 64 kb/s
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavc58.18.100 aac
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc5376.ts.tmp' for writing=0.872x
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc.m3u8.tmp' for writing
|
||||||
|
[hls @ 0x7fa969803a00] Opening './data/testsrc.m3u8.tmp' for writing
|
||||||
|
frame= 58 fps= 25 q=-1.0 Lsize=N/A time=00:00:02.32 bitrate=N/A speed=0.999x`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
parser.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 2, len(parser.process.input), "expected 2 inputs")
|
||||||
|
require.Equal(t, 2, len(parser.process.output), "expected 2 outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserJSON(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
||||||
|
built with gcc 9.3.0 (Alpine 9.3.0)
|
||||||
|
configuration: --prefix=/usr/local --enable-nonfree --enable-gpl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-openssl --enable-shared --enable-libfreetype --disable-ffplay --disable-debug --disable-doc
|
||||||
|
libavutil 56. 51.100 / 56. 51.100
|
||||||
|
libavcodec 58. 91.100 / 58. 91.100
|
||||||
|
libavformat 58. 45.100 / 58. 45.100
|
||||||
|
libavdevice 58. 10.100 / 58. 10.100
|
||||||
|
libavfilter 7. 85.100 / 7. 85.100
|
||||||
|
libswscale 5. 7.100 / 5. 7.100
|
||||||
|
libswresample 3. 7.100 / 3. 7.100
|
||||||
|
libpostproc 55. 7.100 / 55. 7.100
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-SESSION-DATA:DATA-ID="net.z60wzayk.metadata",URI="https://vpu.livespotting.com/e9slfpe3/z60wzayk_metadata.json"')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100788.ts' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100789.ts' for reading
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100790.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100791.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100792.ts' for reading
|
||||||
|
Input #0, playout, from 'https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: h264 (Baseline) ([27][0][0][0] / 0x001B), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], 20.67 fps, 25 tbr, 1000k tbn, 2000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
ffmpeg.inputs:[{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","format":"playout","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
Output #1, mp4, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #1:0: Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 20.67 fps, 25 tbr, 1000k tbn, 1000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
|
||||||
|
Stream #0:0 -> #1:0 (copy)
|
||||||
|
Press [q] to stop, [?] for help
|
||||||
|
[libx264 @ 0x557c8443e200] using SAR=1/1
|
||||||
|
[libx264 @ 0x557c8443e200] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
|
||||||
|
[libx264 @ 0x557c8443e200] profile Constrained Baseline, level 3.1, 4:2:0, 8-bit
|
||||||
|
[libx264 @ 0x557c8443e200] 264 - core 161 - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=0:0:0 analyse=0:0 me=dia subme=0 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=250 keyint_min=25 scenecut=0 intra_refresh=0 rc=crf mbtree=0 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=0
|
||||||
|
Output #0, flv, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #0:0: Video: h264 (libx264) ([7][0][0][0] / 0x0007), yuvj420p(pc), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 1k tbn, 25 tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
encoder : Lavc58.91.100 libx264
|
||||||
|
Side data:
|
||||||
|
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
|
||||||
|
ffmpeg.outputs:[{"url":"/dev/null","format":"flv","index":0,"stream":0,"type":"video","codec":"h264","coder":"libx264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"/dev/null","format":"mp4","index":1,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":2,"packet":6,"size_kb":222}],"outputs":[{"index":0,"stream":0,"frame":2,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":6,"packet":6,"q":-1.0,"size_kb":222}],"frame":2,"packet":0,"q":0.0,"size_kb":222,"time":"0h0m0.20s","speed":0.281,"dup":0,"drop":0}
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100793.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":7,"packet":11,"size_kb":226}],"outputs":[{"index":0,"stream":0,"frame":7,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":11,"packet":11,"q":-1.0,"size_kb":226}],"frame":7,"packet":0,"q":0.0,"size_kb":226,"time":"0h0m0.56s","speed":0.4,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":10,"packet":14,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":10,"packet":3,"q":16.0,"size_kb":281},{"index":1,"stream":0,"frame":14,"packet":14,"q":-1.0,"size_kb":227}],"frame":10,"packet":3,"q":16.0,"size_kb":508,"time":"0h0m0.68s","speed":0.358,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":13,"packet":17,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":13,"packet":6,"q":14.0,"size_kb":350},{"index":1,"stream":0,"frame":17,"packet":17,"q":-1.0,"size_kb":227}],"frame":13,"packet":6,"q":14.0,"size_kb":577,"time":"0h0m0.80s","speed":0.333,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":16,"packet":20,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":16,"packet":9,"q":13.0,"size_kb":489},{"index":1,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":227}],"frame":16,"packet":9,"q":13.0,"size_kb":716,"time":"0h0m0.92s","speed":0.306,"dup":0,"drop":0}
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100794.ts' for reading
|
||||||
|
[https @ 0x557c840f3180] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100795.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":21,"packet":24,"size_kb":228}],"outputs":[{"index":0,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":562},{"index":1,"stream":0,"frame":24,"packet":24,"q":-1.0,"size_kb":228}],"frame":20,"packet":20,"q":-1.0,"size_kb":789,"time":"0h0m1.8s","speed":0.245,"dup":0,"drop":0}`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
parser.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(parser.process.input), "expected 1 input")
|
||||||
|
require.Equal(t, 2, len(parser.process.output), "expected 2 outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserJSONDelayed(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
||||||
|
built with gcc 9.3.0 (Alpine 9.3.0)
|
||||||
|
configuration: --prefix=/usr/local --enable-nonfree --enable-gpl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-openssl --enable-shared --enable-libfreetype --disable-ffplay --disable-debug --disable-doc
|
||||||
|
libavutil 56. 51.100 / 56. 51.100
|
||||||
|
libavcodec 58. 91.100 / 58. 91.100
|
||||||
|
libavformat 58. 45.100 / 58. 45.100
|
||||||
|
libavdevice 58. 10.100 / 58. 10.100
|
||||||
|
libavfilter 7. 85.100 / 7. 85.100
|
||||||
|
libswscale 5. 7.100 / 5. 7.100
|
||||||
|
libswresample 3. 7.100 / 3. 7.100
|
||||||
|
libpostproc 55. 7.100 / 55. 7.100
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-SESSION-DATA:DATA-ID="net.z60wzayk.metadata",URI="https://vpu.livespotting.com/e9slfpe3/z60wzayk_metadata.json"')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100788.ts' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100789.ts' for reading
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100790.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100791.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100792.ts' for reading
|
||||||
|
Input #0, playout, from 'https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: h264 (Baseline) ([27][0][0][0] / 0x001B), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], 20.67 fps, 25 tbr, 1000k tbn, 2000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
ffmpeg.inputs:[{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","format":"playout","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
Output #1, mp4, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #1:0: Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 20.67 fps, 25 tbr, 1000k tbn, 1000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
|
||||||
|
Stream #0:0 -> #1:0 (copy)
|
||||||
|
Press [q] to stop, [?] for help
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":2,"packet":6,"size_kb":222}],"outputs":[{"index":0,"stream":0,"frame":2,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":6,"packet":6,"q":-1.0,"size_kb":222}],"frame":2,"packet":0,"q":0.0,"size_kb":222,"time":"0h0m0.20s","speed":0.281,"dup":0,"drop":0}
|
||||||
|
[libx264 @ 0x557c8443e200] using SAR=1/1
|
||||||
|
[libx264 @ 0x557c8443e200] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
|
||||||
|
[libx264 @ 0x557c8443e200] profile Constrained Baseline, level 3.1, 4:2:0, 8-bit
|
||||||
|
[libx264 @ 0x557c8443e200] 264 - core 161 - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=0:0:0 analyse=0:0 me=dia subme=0 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=250 keyint_min=25 scenecut=0 intra_refresh=0 rc=crf mbtree=0 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=0
|
||||||
|
Output #0, flv, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #0:0: Video: h264 (libx264) ([7][0][0][0] / 0x0007), yuvj420p(pc), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 1k tbn, 25 tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
encoder : Lavc58.91.100 libx264
|
||||||
|
Side data:
|
||||||
|
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
|
||||||
|
ffmpeg.outputs:[{"url":"/dev/null","format":"flv","index":0,"stream":0,"type":"video","codec":"h264","coder":"libx264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"/dev/null","format":"mp4","index":1,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100793.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":7,"packet":11,"size_kb":226}],"outputs":[{"index":0,"stream":0,"frame":7,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":11,"packet":11,"q":-1.0,"size_kb":226}],"frame":7,"packet":0,"q":0.0,"size_kb":226,"time":"0h0m0.56s","speed":0.4,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":10,"packet":14,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":10,"packet":3,"q":16.0,"size_kb":281},{"index":1,"stream":0,"frame":14,"packet":14,"q":-1.0,"size_kb":227}],"frame":10,"packet":3,"q":16.0,"size_kb":508,"time":"0h0m0.68s","speed":0.358,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":13,"packet":17,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":13,"packet":6,"q":14.0,"size_kb":350},{"index":1,"stream":0,"frame":17,"packet":17,"q":-1.0,"size_kb":227}],"frame":13,"packet":6,"q":14.0,"size_kb":577,"time":"0h0m0.80s","speed":0.333,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":16,"packet":20,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":16,"packet":9,"q":13.0,"size_kb":489},{"index":1,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":227}],"frame":16,"packet":9,"q":13.0,"size_kb":716,"time":"0h0m0.92s","speed":0.306,"dup":0,"drop":0}
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100794.ts' for reading
|
||||||
|
[https @ 0x557c840f3180] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100795.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":21,"packet":24,"size_kb":228}],"outputs":[{"index":0,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":562},{"index":1,"stream":0,"frame":24,"packet":24,"q":-1.0,"size_kb":228}],"frame":20,"packet":20,"q":-1.0,"size_kb":789,"time":"0h0m1.8s","speed":0.245,"dup":0,"drop":0}`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
parser.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(parser.process.input), "expected 1 input")
|
||||||
|
require.Equal(t, 2, len(parser.process.output), "expected 2 outputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserJSONDelayedPlayout(t *testing.T) {
|
||||||
|
parser := New(Config{
|
||||||
|
LogLines: 20,
|
||||||
|
}).(*parser)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.3.1 Copyright (c) 2000-2020 the FFmpeg developers
|
||||||
|
built with gcc 9.3.0 (Alpine 9.3.0)
|
||||||
|
configuration: --prefix=/usr/local --enable-nonfree --enable-gpl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-openssl --enable-shared --enable-libfreetype --disable-ffplay --disable-debug --disable-doc
|
||||||
|
libavutil 56. 51.100 / 56. 51.100
|
||||||
|
libavcodec 58. 91.100 / 58. 91.100
|
||||||
|
libavformat 58. 45.100 / 58. 45.100
|
||||||
|
libavdevice 58. 10.100 / 58. 10.100
|
||||||
|
libavfilter 7. 85.100 / 7. 85.100
|
||||||
|
libswscale 5. 7.100 / 5. 7.100
|
||||||
|
libswresample 3. 7.100 / 3. 7.100
|
||||||
|
libpostproc 55. 7.100 / 55. 7.100
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-SESSION-DATA:DATA-ID="net.z60wzayk.metadata",URI="https://vpu.livespotting.com/e9slfpe3/z60wzayk_metadata.json"')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100788.ts' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100789.ts' for reading
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100790.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100791.ts' for reading
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100792.ts' for reading
|
||||||
|
Input #0, playout, from 'https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: h264 (Baseline) ([27][0][0][0] / 0x001B), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], 20.67 fps, 25 tbr, 1000k tbn, 2000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
ffmpeg.inputs:[{"url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","format":"playout","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
Output #1, mp4, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #1:0: Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc, bt709), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 20.67 fps, 25 tbr, 1000k tbn, 1000k tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
|
||||||
|
Stream #0:0 -> #1:0 (copy)
|
||||||
|
Press [q] to stop, [?] for help
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":2,"packet":6,"size_kb":222}],"outputs":[{"index":0,"stream":0,"frame":2,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":6,"packet":6,"q":-1.0,"size_kb":222}],"frame":2,"packet":0,"q":0.0,"size_kb":222,"time":"0h0m0.20s","speed":0.281,"dup":0,"drop":0}
|
||||||
|
[libx264 @ 0x557c8443e200] using SAR=1/1
|
||||||
|
[libx264 @ 0x557c8443e200] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
|
||||||
|
[libx264 @ 0x557c8443e200] profile Constrained Baseline, level 3.1, 4:2:0, 8-bit
|
||||||
|
[libx264 @ 0x557c8443e200] 264 - core 161 - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=0 ref=1 deblock=0:0:0 analyse=0:0 me=dia subme=0 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=0 weightp=0 keyint=250 keyint_min=25 scenecut=0 intra_refresh=0 rc=crf mbtree=0 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=0
|
||||||
|
Output #0, flv, to '/dev/null':
|
||||||
|
Metadata:
|
||||||
|
encoder : Lavf58.45.100
|
||||||
|
Stream #0:0: Video: h264 (libx264) ([7][0][0][0] / 0x0007), yuvj420p(pc), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 1k tbn, 25 tbc
|
||||||
|
Metadata:
|
||||||
|
variant_bitrate : 1024000
|
||||||
|
encoder : Lavc58.91.100 libx264
|
||||||
|
Side data:
|
||||||
|
cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
|
||||||
|
ffmpeg.outputs:[{"url":"/dev/null","format":"flv","index":0,"stream":0,"type":"video","codec":"h264","coder":"libx264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"/dev/null","format":"mp4","index":1,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
avstream.progress:{"id":"playout:https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","stream":0,"queue":140,"aqueue":0,"dup":0,"drop":0,"enc":0,"looping":false,"duplicating":false,"gop":"none","input":{"state":"running","packet":148,"size_kb":1529,"time":5},"output":{"state":"running","packet":8,"size_kb":128,"time":1},"swap":{"url":"","status":"waiting","lasturl":"","lasterror":""}}
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100793.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":7,"packet":11,"size_kb":226}],"outputs":[{"index":0,"stream":0,"frame":7,"packet":0,"q":0.0,"size_kb":0},{"index":1,"stream":0,"frame":11,"packet":11,"q":-1.0,"size_kb":226}],"frame":7,"packet":0,"q":0.0,"size_kb":226,"time":"0h0m0.56s","speed":0.4,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":10,"packet":14,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":10,"packet":3,"q":16.0,"size_kb":281},{"index":1,"stream":0,"frame":14,"packet":14,"q":-1.0,"size_kb":227}],"frame":10,"packet":3,"q":16.0,"size_kb":508,"time":"0h0m0.68s","speed":0.358,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":13,"packet":17,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":13,"packet":6,"q":14.0,"size_kb":350},{"index":1,"stream":0,"frame":17,"packet":17,"q":-1.0,"size_kb":227}],"frame":13,"packet":6,"q":14.0,"size_kb":577,"time":"0h0m0.80s","speed":0.333,"dup":0,"drop":0}
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":16,"packet":20,"size_kb":227}],"outputs":[{"index":0,"stream":0,"frame":16,"packet":9,"q":13.0,"size_kb":489},{"index":1,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":227}],"frame":16,"packet":9,"q":13.0,"size_kb":716,"time":"0h0m0.92s","speed":0.306,"dup":0,"drop":0}
|
||||||
|
[https @ 0x557c840bf480] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720.m3u8' for reading
|
||||||
|
[hls @ 0x7f92a96e8100] Skip ('#EXT-X-VERSION:3')
|
||||||
|
[https @ 0x557c840d1080] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100794.ts' for reading
|
||||||
|
[https @ 0x557c840f3180] Opening 'https://ch-fra-n16.livespotting.com/vpu/e9slfpe3/z60wzayk_720_100795.ts' for reading
|
||||||
|
ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":21,"packet":24,"size_kb":228}],"outputs":[{"index":0,"stream":0,"frame":20,"packet":20,"q":-1.0,"size_kb":562},{"index":1,"stream":0,"frame":24,"packet":24,"q":-1.0,"size_kb":228}],"frame":20,"packet":20,"q":-1.0,"size_kb":789,"time":"0h0m1.8s","speed":0.245,"dup":0,"drop":0}`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
parser.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(parser.process.input), "expected 1 input")
|
||||||
|
require.Equal(t, 2, len(parser.process.output), "expected 2 outputs")
|
||||||
|
}
|
||||||
38
ffmpeg/parse/stats.go
Normal file
38
ffmpeg/parse/stats.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
type statsData struct {
|
||||||
|
frame uint64
|
||||||
|
packet uint64
|
||||||
|
size uint64 // kbytes
|
||||||
|
dup uint64
|
||||||
|
drop uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type stats struct {
|
||||||
|
last statsData
|
||||||
|
diff statsData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stats) updateFromProgress(p *ffmpegProgress) {
|
||||||
|
s.diff.frame = p.Frame - s.last.frame
|
||||||
|
s.diff.packet = p.Packet - s.last.packet
|
||||||
|
s.diff.size = p.Size - s.last.size
|
||||||
|
s.diff.drop = p.Drop - s.last.drop
|
||||||
|
s.diff.dup = p.Dup - s.last.dup
|
||||||
|
|
||||||
|
s.last.frame = p.Frame
|
||||||
|
s.last.packet = p.Packet
|
||||||
|
s.last.size = p.Size
|
||||||
|
s.last.dup = p.Dup
|
||||||
|
s.last.drop = p.Drop
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stats) updateFromProgressIO(p *ffmpegProgressIO) {
|
||||||
|
s.diff.frame = p.Frame - s.last.frame
|
||||||
|
s.diff.packet = p.Packet - s.last.packet
|
||||||
|
s.diff.size = p.Size - s.last.size
|
||||||
|
|
||||||
|
s.last.frame = p.Frame
|
||||||
|
s.last.packet = p.Packet
|
||||||
|
s.last.size = p.Size
|
||||||
|
}
|
||||||
226
ffmpeg/parse/types.go
Normal file
226
ffmpeg/parse/types.go
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/restream/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration represents a time.Duration
|
||||||
|
type Duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals a time.Duration to JSON
|
||||||
|
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(d.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals a JSON value to time.Duration. The JSON value
|
||||||
|
// can be either a float which is interpreted as seconds or a string
|
||||||
|
// that is interpreted as a formatted duration.
|
||||||
|
func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch value := v.(type) {
|
||||||
|
case float64:
|
||||||
|
d.Duration = time.Duration(value)
|
||||||
|
return nil
|
||||||
|
case string:
|
||||||
|
var err error
|
||||||
|
d.Duration, err = time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.New("invalid duration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegAVstreamIO struct {
|
||||||
|
State string `json:"state"`
|
||||||
|
Packet uint64 `json:"packet"`
|
||||||
|
Time uint64 `json:"time"`
|
||||||
|
Size uint64 `json:"size_kb"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (avio *ffmpegAVstreamIO) export() app.AVstreamIO {
|
||||||
|
return app.AVstreamIO{
|
||||||
|
State: avio.State,
|
||||||
|
Packet: avio.Packet,
|
||||||
|
Time: avio.Time,
|
||||||
|
Size: avio.Size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegAVstream struct {
|
||||||
|
Input ffmpegAVstreamIO `json:"input"`
|
||||||
|
Output ffmpegAVstreamIO `json:"output"`
|
||||||
|
Address string `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Stream uint64 `json:"stream"`
|
||||||
|
Aqueue uint64 `json:"aqueue"`
|
||||||
|
Queue uint64 `json:"queue"`
|
||||||
|
Dup uint64 `json:"dup"`
|
||||||
|
Drop uint64 `json:"drop"`
|
||||||
|
Enc uint64 `json:"enc"`
|
||||||
|
Looping bool `json:"looping"`
|
||||||
|
Duplicating bool `json:"duplicating"`
|
||||||
|
GOP string `json:"gop"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (av *ffmpegAVstream) export() *app.AVstream {
|
||||||
|
return &app.AVstream{
|
||||||
|
Aqueue: av.Aqueue,
|
||||||
|
Queue: av.Queue,
|
||||||
|
Drop: av.Drop,
|
||||||
|
Dup: av.Dup,
|
||||||
|
Enc: av.Enc,
|
||||||
|
Looping: av.Looping,
|
||||||
|
Duplicating: av.Duplicating,
|
||||||
|
GOP: av.GOP,
|
||||||
|
Input: av.Input.export(),
|
||||||
|
Output: av.Output.export(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegProgressIO struct {
|
||||||
|
// common
|
||||||
|
Index uint64 `json:"index"`
|
||||||
|
Stream uint64 `json:"stream"`
|
||||||
|
Size uint64 `json:"size_kb"` // kbytes
|
||||||
|
Bitrate float64 `json:"-"` // kbit/s
|
||||||
|
Frame uint64 `json:"frame"`
|
||||||
|
Packet uint64 `json:"packet"`
|
||||||
|
FPS float64 `json:"-"`
|
||||||
|
PPS float64 `json:"-"`
|
||||||
|
|
||||||
|
// video
|
||||||
|
Quantizer float64 `json:"q"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (io *ffmpegProgressIO) exportTo(progress *app.ProgressIO) {
|
||||||
|
progress.Index = io.Index
|
||||||
|
progress.Stream = io.Stream
|
||||||
|
progress.Frame = io.Frame
|
||||||
|
progress.Packet = io.Packet
|
||||||
|
progress.FPS = io.FPS
|
||||||
|
progress.PPS = io.PPS
|
||||||
|
progress.Quantizer = io.Quantizer
|
||||||
|
progress.Size = io.Size * 1024
|
||||||
|
progress.Bitrate = io.Bitrate * 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegProgress struct {
|
||||||
|
Input []ffmpegProgressIO `json:"inputs"`
|
||||||
|
Output []ffmpegProgressIO `json:"outputs"`
|
||||||
|
Frame uint64 `json:"frame"`
|
||||||
|
Packet uint64 `json:"packet"`
|
||||||
|
FPS float64 `json:"-"`
|
||||||
|
PPS float64 `json:"-"`
|
||||||
|
Quantizer float64 `json:"q"`
|
||||||
|
Size uint64 `json:"size_kb"` // kbytes
|
||||||
|
Bitrate float64 `json:"-"` // kbit/s
|
||||||
|
Time Duration `json:"time"`
|
||||||
|
Speed float64 `json:"speed"`
|
||||||
|
Drop uint64 `json:"drop"`
|
||||||
|
Dup uint64 `json:"dup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ffmpegProgress) exportTo(progress *app.Progress) {
|
||||||
|
progress.Frame = p.Frame
|
||||||
|
progress.Packet = p.Packet
|
||||||
|
progress.FPS = p.FPS
|
||||||
|
progress.PPS = p.PPS
|
||||||
|
progress.Quantizer = p.Quantizer
|
||||||
|
progress.Size = p.Size * 1024
|
||||||
|
progress.Time = p.Time.Seconds()
|
||||||
|
progress.Bitrate = p.Bitrate * 1024
|
||||||
|
progress.Speed = p.Speed
|
||||||
|
progress.Drop = p.Drop
|
||||||
|
progress.Dup = p.Dup
|
||||||
|
|
||||||
|
for i := range p.Input {
|
||||||
|
if len(progress.Input) <= i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Input[i].exportTo(&progress.Input[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range p.Output {
|
||||||
|
if len(progress.Output) <= i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Output[i].exportTo(&progress.Output[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegProcessIO struct {
|
||||||
|
// common
|
||||||
|
Address string `json:"url"`
|
||||||
|
IP string `json:"-"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Index uint64 `json:"index"`
|
||||||
|
Stream uint64 `json:"stream"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Codec string `json:"codec"`
|
||||||
|
Coder string `json:"coder"`
|
||||||
|
|
||||||
|
// video
|
||||||
|
Pixfmt string `json:"pix_fmt"`
|
||||||
|
Width uint64 `json:"width"`
|
||||||
|
Height uint64 `json:"height"`
|
||||||
|
|
||||||
|
// audio
|
||||||
|
Sampling uint64 `json:"sampling_hz"`
|
||||||
|
Layout string `json:"layout"`
|
||||||
|
Channels uint64 `json:"channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (io *ffmpegProcessIO) export() app.ProgressIO {
|
||||||
|
return app.ProgressIO{
|
||||||
|
Address: io.Address,
|
||||||
|
Format: io.Format,
|
||||||
|
Index: io.Index,
|
||||||
|
Stream: io.Stream,
|
||||||
|
Type: io.Type,
|
||||||
|
Codec: io.Codec,
|
||||||
|
Coder: io.Coder,
|
||||||
|
Pixfmt: io.Pixfmt,
|
||||||
|
Width: io.Width,
|
||||||
|
Height: io.Height,
|
||||||
|
Sampling: io.Sampling,
|
||||||
|
Layout: io.Layout,
|
||||||
|
Channels: io.Channels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ffmpegProcess struct {
|
||||||
|
input []ffmpegProcessIO
|
||||||
|
output []ffmpegProcessIO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ffmpegProcess) export() app.Progress {
|
||||||
|
progress := app.Progress{}
|
||||||
|
|
||||||
|
for _, io := range p.input {
|
||||||
|
aio := io.export()
|
||||||
|
|
||||||
|
progress.Input = append(progress.Input, aio)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, io := range p.output {
|
||||||
|
aio := io.export()
|
||||||
|
|
||||||
|
progress.Output = append(progress.Output, aio)
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress
|
||||||
|
}
|
||||||
239
ffmpeg/probe/prober.go
Normal file
239
ffmpeg/probe/prober.go
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/log"
|
||||||
|
"github.com/datarhei/core/process"
|
||||||
|
"github.com/datarhei/core/restream/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser interface {
|
||||||
|
process.Parser
|
||||||
|
|
||||||
|
Probe() app.Probe
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type prober struct {
|
||||||
|
data []process.Line
|
||||||
|
inputs []probeIO
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) Parser {
|
||||||
|
p := &prober{
|
||||||
|
logger: config.Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.logger == nil {
|
||||||
|
p.logger = log.New("Parser")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) Probe() app.Probe {
|
||||||
|
probe := app.Probe{}
|
||||||
|
|
||||||
|
for _, io := range p.inputs {
|
||||||
|
probe.Streams = append(probe.Streams, io.export())
|
||||||
|
}
|
||||||
|
|
||||||
|
probe.Log = make([]string, len(p.data))
|
||||||
|
for i, line := range p.data {
|
||||||
|
probe.Log[i] = line.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
return probe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) Parse(line string) uint64 {
|
||||||
|
if strings.HasPrefix(line, "avstream.progress:") {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p.data = append(p.data, process.Line{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Data: line,
|
||||||
|
})
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) parseJSON(line string) {
|
||||||
|
inputs := []probeIO{}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(line), &inputs)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.WithFields(log.Fields{
|
||||||
|
"line": line,
|
||||||
|
"error": err,
|
||||||
|
}).Error().Log("Failed parsing inputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.inputs = inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) parseDefault() {
|
||||||
|
// Input #0, lavfi, from 'testsrc=size=1280x720:rate=25':
|
||||||
|
// Input #1, lavfi, from 'anullsrc=r=44100:cl=stereo':
|
||||||
|
// Output #0, hls, to './data/testsrc.m3u8':
|
||||||
|
reFormat := regexp.MustCompile(`^Input #([0-9]+), (.*?), (from|to) '([^']+)`)
|
||||||
|
|
||||||
|
// Duration: 00:01:02.28, start: 0.000000, bitrate: 5895 kb/s
|
||||||
|
// Duration: N/A, start: 0.000000, bitrate: 5895 kb/s
|
||||||
|
reDuration := regexp.MustCompile(`Duration: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)`)
|
||||||
|
|
||||||
|
// Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 1280x720 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
|
||||||
|
// Stream #1:0: Audio: pcm_u8, 44100 Hz, stereo, u8, 705 kb/s
|
||||||
|
// Stream #0:0: Video: h264 (libx264), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], q=-1--1, 25 fps, 90k tbn, 25 tbc
|
||||||
|
// Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 64 kb/s
|
||||||
|
reStream := regexp.MustCompile(`Stream #([0-9]+):([0-9]+)(?:\(([a-z]+)\))?: (Video|Audio|Subtitle): (.*)`)
|
||||||
|
reStreamCodec := regexp.MustCompile(`^([^\s,]+)`)
|
||||||
|
reStreamVideoPixfmtSize := regexp.MustCompile(`, ([0-9A-Za-z]+)(\([^\)]+\))?, ([0-9]+)x([0-9]+)`)
|
||||||
|
reStreamVideoFPS := regexp.MustCompile(`, ([0-9]+(\.[0-9]+)?) fps`)
|
||||||
|
reStreamAudio := regexp.MustCompile(`, ([0-9]+) Hz, ([^,]+)`)
|
||||||
|
reStreamBitrate := regexp.MustCompile(`, ([0-9]+) kb/s`)
|
||||||
|
|
||||||
|
format := ""
|
||||||
|
address := ""
|
||||||
|
var duration float64 = 0.0
|
||||||
|
|
||||||
|
for _, line := range p.data {
|
||||||
|
if matches := reFormat.FindStringSubmatch(line.Data); matches != nil {
|
||||||
|
format = matches[2]
|
||||||
|
address = matches[4]
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches := reDuration.FindStringSubmatch(line.Data); matches != nil {
|
||||||
|
duration = 0.0
|
||||||
|
|
||||||
|
// hours
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
duration += x * 60 * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// minutes
|
||||||
|
if x, err := strconv.ParseFloat(matches[2], 64); err == nil {
|
||||||
|
duration += x * 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// seconds
|
||||||
|
if x, err := strconv.ParseFloat(matches[3], 64); err == nil {
|
||||||
|
duration += x
|
||||||
|
}
|
||||||
|
|
||||||
|
// fractions
|
||||||
|
if x, err := strconv.ParseFloat(matches[4], 64); err == nil {
|
||||||
|
duration += x / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches := reStream.FindStringSubmatch(line.Data); matches != nil {
|
||||||
|
io := probeIO{}
|
||||||
|
|
||||||
|
io.Address = address
|
||||||
|
io.Format = format
|
||||||
|
io.Duration = duration
|
||||||
|
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
io.Index = x
|
||||||
|
}
|
||||||
|
|
||||||
|
if x, err := strconv.ParseUint(matches[2], 10, 64); err == nil {
|
||||||
|
io.Stream = x
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Language = "und"
|
||||||
|
if len(matches[3]) == 3 {
|
||||||
|
io.Language = matches[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Type = strings.ToLower(matches[4])
|
||||||
|
|
||||||
|
streamDetail := matches[5]
|
||||||
|
|
||||||
|
if matches = reStreamCodec.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
io.Codec = matches[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = reStreamBitrate.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
io.Bitrate = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if io.Type == "video" {
|
||||||
|
if matches = reStreamVideoPixfmtSize.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
io.Pixfmt = matches[1]
|
||||||
|
|
||||||
|
if x, err := strconv.ParseUint(matches[3], 10, 64); err == nil {
|
||||||
|
io.Width = x
|
||||||
|
}
|
||||||
|
if x, err := strconv.ParseUint(matches[4], 10, 64); err == nil {
|
||||||
|
io.Height = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches = reStreamVideoFPS.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseFloat(matches[1], 64); err == nil {
|
||||||
|
io.FPS = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if io.Type == "audio" {
|
||||||
|
if matches = reStreamAudio.FindStringSubmatch(streamDetail); matches != nil {
|
||||||
|
if x, err := strconv.ParseUint(matches[1], 10, 64); err == nil {
|
||||||
|
io.Sampling = x
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Layout = matches[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.inputs = append(p.inputs, io)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) Log() []process.Line {
|
||||||
|
return p.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) ResetStats() {
|
||||||
|
hasJSON := false
|
||||||
|
prefix := "ffmpeg.inputs:"
|
||||||
|
|
||||||
|
for _, line := range p.data {
|
||||||
|
isFFmpegInputs := strings.HasPrefix(line.Data, prefix)
|
||||||
|
|
||||||
|
if isFFmpegInputs {
|
||||||
|
p.parseJSON(strings.TrimPrefix(line.Data, prefix))
|
||||||
|
hasJSON = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasJSON {
|
||||||
|
p.parseDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *prober) ResetLog() {
|
||||||
|
p.data = []process.Line{}
|
||||||
|
p.inputs = []probeIO{}
|
||||||
|
}
|
||||||
280
ffmpeg/probe/prober_test.go
Normal file
280
ffmpeg/probe/prober_test.go
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProber(t *testing.T) {
|
||||||
|
prober := New(Config{}).(*prober)
|
||||||
|
|
||||||
|
rawdata := `ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
|
||||||
|
built with Apple LLVM version 9.1.0 (clang-902.0.39.2)
|
||||||
|
configuration: --prefix=/usr/local/Cellar/ffmpeg/4.0.2 --enable-shared --enable-pthreads --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-gpl --enable-libmp3lame --enable-libx264 --enable-libx265 --enable-libxvid --enable-opencl --enable-videotoolbox --disable-lzma
|
||||||
|
libavutil 56. 14.100 / 56. 14.100
|
||||||
|
libavcodec 58. 18.100 / 58. 18.100
|
||||||
|
libavformat 58. 12.100 / 58. 12.100
|
||||||
|
libavdevice 58. 3.100 / 58. 3.100
|
||||||
|
libavfilter 7. 16.100 / 7. 16.100
|
||||||
|
libavresample 4. 0. 0 / 4. 0. 0
|
||||||
|
libswscale 5. 1.100 / 5. 1.100
|
||||||
|
libswresample 3. 1.100 / 3. 1.100
|
||||||
|
libpostproc 55. 1.100 / 55. 1.100
|
||||||
|
Input #0, lavfi, from 'testsrc=size=1280x720:rate=25':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: N/A
|
||||||
|
Stream #0:0: Video: rawvideo (RGB[24] / 0x18424752), rgb24, 1280x720 [SAR 1:1 DAR 16:9], 25 tbr, 25 tbn, 25 tbc
|
||||||
|
Input #1, lavfi, from 'anullsrc=r=44100:cl=stereo':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: 705 kb/s
|
||||||
|
Stream #1:0: Audio: pcm_u8, 44100 Hz, stereo, u8, 705 kb/s
|
||||||
|
Input #2, playout, from 'playout:rtmp://l5gn74l5-vpu.livespotting.com/live/0chl6hu7_360?token=m5ZuiCQYRlIon8':
|
||||||
|
Duration: N/A, start: 0.000000, bitrate: 265 kb/s
|
||||||
|
Stream #2:0: Video: h264 (Constrained Baseline), yuvj420p(pc, progressive), 640x360 [SAR 1:1 DAR 16:9], 265 kb/s, 10 fps, 10 tbr, 1000k tbn, 20 tbc
|
||||||
|
Input #3, mov,mp4,m4a,3gp,3g2,mj2, from 'movie.mp4':
|
||||||
|
Metadata:
|
||||||
|
major_brand : isom
|
||||||
|
minor_version : 512
|
||||||
|
compatible_brands: isomiso2avc1mp41
|
||||||
|
encoder : Lavf58.20.100
|
||||||
|
Duration: 00:01:02.28, start: 0.000000, bitrate: 5895 kb/s
|
||||||
|
Stream #3:0(eng): Video: h264 (Main) (avc1 / 0x31637661), yuvj420p(pc, bt709), 2560x1440 [SAR 1:1 DAR 16:9], 5894 kb/s, 23.98 fps, 25 tbr, 90k tbn, 50 tbc (default)
|
||||||
|
Stream #3:1(por): Subtitle: subrip
|
||||||
|
Stream mapping:
|
||||||
|
Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
|
||||||
|
Stream #1:0 -> #0:1 (pcm_u8 (native) -> aac (native))
|
||||||
|
Press [q] to stop, [?] for help`
|
||||||
|
|
||||||
|
data := strings.Split(rawdata, "\n")
|
||||||
|
|
||||||
|
for _, d := range data {
|
||||||
|
prober.Parse(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
prober.ResetStats()
|
||||||
|
|
||||||
|
if len(prober.inputs) != 5 {
|
||||||
|
t.Errorf("#inputs: want=5, have=%d\n", len(prober.inputs))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := prober.inputs[0]
|
||||||
|
|
||||||
|
if i.Address != "testsrc=size=1280x720:rate=25" {
|
||||||
|
t.Errorf("#input0.address: want=testsrc=size=1280x720:rate=25, have=%s\n", i.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Format != "lavfi" {
|
||||||
|
t.Errorf("#input0.format: want=lavfi, have=%s\n", i.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Index != 0 {
|
||||||
|
t.Errorf("#input0.index: want=0, have=%d\n", i.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Stream != 0 {
|
||||||
|
t.Errorf("#input0.stream: want=0, have=%d\n", i.Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Language != "und" {
|
||||||
|
t.Errorf("#input0.language: want=und, have=%s\n", i.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Type != "video" {
|
||||||
|
t.Errorf("#input0.type: want=video, have=%s\n", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Codec != "rawvideo" {
|
||||||
|
t.Errorf("#input0.codec: want=rawvideo, have=%s\n", i.Codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Bitrate != 0 {
|
||||||
|
t.Errorf("#input0.bitrate: want=0, have=%f\n", i.Bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Duration != 0 {
|
||||||
|
t.Errorf("#input0.duration: want=0, have=%f\n", i.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.FPS != 0 {
|
||||||
|
t.Errorf("#input0.fps: want=0, have=%f\n", i.FPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Pixfmt != "rgb24" {
|
||||||
|
t.Errorf("#input0.pixfmt: want=rgb24, have=%s\n", i.Pixfmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Width != 1280 {
|
||||||
|
t.Errorf("#input0.width: want=1280, have=%d\n", i.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Height != 720 {
|
||||||
|
t.Errorf("#input0.height: want=720, have=%d\n", i.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = prober.inputs[1]
|
||||||
|
|
||||||
|
if i.Address != "anullsrc=r=44100:cl=stereo" {
|
||||||
|
t.Errorf("#input1.address: want=anullsrc=r=44100:cl=stereo, have=%s\n", i.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Format != "lavfi" {
|
||||||
|
t.Errorf("#input1.format: want=lavfi, have=%s\n", i.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Index != 1 {
|
||||||
|
t.Errorf("#input1.index: want=1, have=%d\n", i.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Stream != 0 {
|
||||||
|
t.Errorf("#input1.stream: want=0, have=%d\n", i.Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Language != "und" {
|
||||||
|
t.Errorf("#input1.language: want=und, have=%s\n", i.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Type != "audio" {
|
||||||
|
t.Errorf("#input1.type: want=audio, have=%s\n", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Codec != "pcm_u8" {
|
||||||
|
t.Errorf("#input1.codec: want=pcm_u8, have=%s\n", i.Codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Bitrate != 705 {
|
||||||
|
t.Errorf("#input1.bitrate: want=705, have=%f\n", i.Bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Duration != 0 {
|
||||||
|
t.Errorf("#input1.duration: want=0, have=%f\n", i.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Sampling != 44100 {
|
||||||
|
t.Errorf("#input1.sampling: want=44100, have=%d\n", i.Sampling)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Layout != "stereo" {
|
||||||
|
t.Errorf("#input1.layout: want=stereo, have=%s\n", i.Layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = prober.inputs[2]
|
||||||
|
|
||||||
|
if i.Address != "playout:rtmp://l5gn74l5-vpu.livespotting.com/live/0chl6hu7_360?token=m5ZuiCQYRlIon8" {
|
||||||
|
t.Errorf("#input2.address: want=playout:rtmp://l5gn74l5-vpu.livespotting.com/live/0chl6hu7_360?token=m5ZuiCQYRlIon8, have=%s\n", i.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Format != "playout" {
|
||||||
|
t.Errorf("#input2.format: want=playout, have=%s\n", i.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Index != 2 {
|
||||||
|
t.Errorf("#input2.index: want=2, have=%d\n", i.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Stream != 0 {
|
||||||
|
t.Errorf("#input2.stream: want=0, have=%d\n", i.Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Language != "und" {
|
||||||
|
t.Errorf("#input2.language: want=und, have=%s\n", i.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Type != "video" {
|
||||||
|
t.Errorf("#input2.type: want=video, have=%s\n", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Codec != "h264" {
|
||||||
|
t.Errorf("#input2.codec: want=h264, have=%s\n", i.Codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Bitrate != 265 {
|
||||||
|
t.Errorf("#input2.bitrate: want=265, have=%f\n", i.Bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Duration != 0 {
|
||||||
|
t.Errorf("#input2.duration: want=0, have=%f\n", i.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.FPS != 10 {
|
||||||
|
t.Errorf("#input2.fps: want=10, have=%f\n", i.FPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Pixfmt != "yuvj420p" {
|
||||||
|
t.Errorf("#input2.pixfmt: want=yuvj420p, have=%s\n", i.Pixfmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Width != 640 {
|
||||||
|
t.Errorf("#input2.width: want=640, have=%d\n", i.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Height != 360 {
|
||||||
|
t.Errorf("#input2.height: want=360, have=%d\n", i.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = prober.inputs[3]
|
||||||
|
|
||||||
|
if i.Address != "movie.mp4" {
|
||||||
|
t.Errorf("#input3.address: want=movie.mp4, have=%s\n", i.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Format != "mov,mp4,m4a,3gp,3g2,mj2" {
|
||||||
|
t.Errorf("#input3.format: want=mov,mp4,m4a,3gp,3g2,mj2, have=%s\n", i.Format)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Index != 3 {
|
||||||
|
t.Errorf("#input3.index: want=3, have=%d\n", i.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Stream != 0 {
|
||||||
|
t.Errorf("#input3.stream: want=0, have=%d\n", i.Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Language != "eng" {
|
||||||
|
t.Errorf("#input3.language: want=eng, have=%s\n", i.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Type != "video" {
|
||||||
|
t.Errorf("#input3.type: want=video, have=%s\n", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Codec != "h264" {
|
||||||
|
t.Errorf("#input3.codec: want=h264, have=%s\n", i.Codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Bitrate != 5894 {
|
||||||
|
t.Errorf("#input3.bitrate: want=5894, have=%f\n", i.Bitrate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Duration != 62.28 {
|
||||||
|
t.Errorf("#input3.duration: want=62.82, have=%f\n", i.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.FPS != 23.98 {
|
||||||
|
t.Errorf("#input3.fps: want=23.98, have=%f\n", i.FPS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Pixfmt != "yuvj420p" {
|
||||||
|
t.Errorf("#input3.pixfmt: want=yuvj420p, have=%s\n", i.Pixfmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Width != 2560 {
|
||||||
|
t.Errorf("#input3.width: want=2560, have=%d\n", i.Width)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Height != 1440 {
|
||||||
|
t.Errorf("#input3.height: want=1440, have=%d\n", i.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = prober.inputs[4]
|
||||||
|
|
||||||
|
if i.Language != "por" {
|
||||||
|
t.Errorf("#input4.language: want=por, have=%s\n", i.Language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Type != "subtitle" {
|
||||||
|
t.Errorf("#input4.type: want=subtitle, have=%s\n", i.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Codec != "subrip" {
|
||||||
|
t.Errorf("#input4.codec: want=subtip, have=%s\n", i.Codec)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
ffmpeg/probe/types.go
Normal file
52
ffmpeg/probe/types.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package probe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/datarhei/core/restream/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
type probeIO struct {
|
||||||
|
// common
|
||||||
|
Address string `json:"url"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Index uint64 `json:"index"`
|
||||||
|
Stream uint64 `json:"stream"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Codec string `json:"codec"`
|
||||||
|
Coder string `json:"coder"`
|
||||||
|
Bitrate float64 `json:"bitrate_kbps"`
|
||||||
|
Duration float64 `json:"duration_sec"`
|
||||||
|
|
||||||
|
// video
|
||||||
|
FPS float64 `json:"fps"`
|
||||||
|
Pixfmt string `json:"pix_fmt"`
|
||||||
|
Width uint64 `json:"width"`
|
||||||
|
Height uint64 `json:"height"`
|
||||||
|
|
||||||
|
// audio
|
||||||
|
Sampling uint64 `json:"sampling_hz"`
|
||||||
|
Layout string `json:"layout"`
|
||||||
|
Channels uint64 `json:"channels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (io *probeIO) export() app.ProbeIO {
|
||||||
|
return app.ProbeIO{
|
||||||
|
Address: io.Address,
|
||||||
|
Format: io.Format,
|
||||||
|
Index: io.Index,
|
||||||
|
Stream: io.Stream,
|
||||||
|
Language: io.Language,
|
||||||
|
Type: io.Type,
|
||||||
|
Codec: io.Codec,
|
||||||
|
Coder: io.Coder,
|
||||||
|
Bitrate: io.Bitrate,
|
||||||
|
Duration: io.Duration,
|
||||||
|
FPS: io.FPS,
|
||||||
|
Pixfmt: io.Pixfmt,
|
||||||
|
Width: io.Width,
|
||||||
|
Height: io.Height,
|
||||||
|
Sampling: io.Sampling,
|
||||||
|
Layout: io.Layout,
|
||||||
|
Channels: io.Channels,
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue