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