addedd rtsp streamer inferastructure, applied fstrings, updated method names to align with standards in project

This commit is contained in:
itdominator 2022-06-13 21:51:51 -05:00
parent b30a8f4b44
commit 22c437e8ed
9 changed files with 367 additions and 73 deletions

View File

@ -29,7 +29,7 @@ class Config(object):
LOGIN_PATH = "OIDC" # Value can be OIDC or FLASK_LOGIN
OIDC_TOKEN_TYPE_HINT = 'access_token'
APP_REDIRECT_URI = "https%3A%2F%2Fwww.webfm.com%2F" # This path is submitted as the redirect URI in certain code flows
OIDC_CLIENT_SECRETS = ROOT_FILE_PTH + '/client_secrets.json'
OIDC_CLIENT_SECRETS = f'{ROOT_FILE_PTH}/client_secrets.json'
OIDC_ID_TOKEN_COOKIE_SECURE = True
OIDC_REQUIRE_VERIFIED_EMAIL = False
OIDC_USER_INFO_ENABLED = True
@ -38,14 +38,14 @@ class Config(object):
'https://www.ssoapps.com/auth/realms/apps'
]
STATIC_FPTH = ROOT_FILE_PTH + "/static"
STATIC_FPTH = f"{ROOT_FILE_PTH}/static"
REL_THUMBS_PTH = "static/imgs/thumbnails" # Used for flask thumbnail return
# We are overiding some of the the shellmen view settings with these to make it all work with flask.
# These are passed along to the shellmen view from the Routes file upon the window controller creation.
ABS_THUMBS_PTH = STATIC_FPTH + "/imgs/thumbnails" # Used for thumbnail generation
REMUX_FOLDER = STATIC_FPTH + "/remuxs" # Remuxed files folder
FFMPG_THUMBNLR = STATIC_FPTH + "/ffmpegthumbnailer" # Thumbnail generator binary
ABS_THUMBS_PTH = f"{STATIC_FPTH}/imgs/thumbnails" # Used for thumbnail generation
REMUX_FOLDER = f"{STATIC_FPTH}/remuxs" # Remuxed files folder
FFMPG_THUMBNLR = f"{STATIC_FPTH}/ffmpegthumbnailer" # Thumbnail generator binary

View File

@ -67,11 +67,11 @@ def listFiles(_hash = None):
msg = "Log in with an Admin privlidged user to view the requested path!"
is_locked = view.is_folder_locked(_hash)
if is_locked and not oidc.user_loggedin:
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
elif is_locked and oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
if dot_dots[0][1] != _hash and dot_dots[1][1] != _hash:
path = view.get_path_part_from_hash(_hash)
@ -80,7 +80,7 @@ def listFiles(_hash = None):
error_msg = view.get_error_message()
if error_msg != None:
view.unset_error_message()
return msgHandler.createMessageJSON("danger", error_msg)
return msgHandler.create_JSON_message("danger", error_msg)
sub_path = view.get_current_sub_path()
@ -92,7 +92,7 @@ def listFiles(_hash = None):
date = current_directory[startIndex:endIndex]
video_data = tmdb.search(title, date)
background_url = video_data[0]["backdrop_path"]
background_pth = view.get_current_directory() + "/" + "000.jpg"
background_pth = f"{view.get_current_directory()}/000.jpg"
if not os.path.isfile(background_pth):
r = requests.get(background_url, stream = True)
@ -116,20 +116,17 @@ def listFiles(_hash = None):
return files
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/get-posters', methods=['GET', 'POST'])
def getPosters():
if request.method == 'POST':
view = get_window_controller().get_window(1).get_view(0)
videos = view.get_videos()
print(videos)
# tmdb.search("Matrix")
return videos
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/file-manager-action/<_type>/<_hash>', methods=['GET', 'POST'])
def fileManagerAction(_type, _hash = None):
@ -138,7 +135,7 @@ def fileManagerAction(_type, _hash = None):
if _type == "reset-path" and _hash == "None":
view.set_to_home()
msg = "Returning to home directory..."
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
folder = view.get_current_directory()
file = view.get_path_part_from_hash(_hash)
@ -146,34 +143,39 @@ def fileManagerAction(_type, _hash = None):
logger.debug(fpath)
if _type == "files":
return send_from_directory(folder, file)
logger.debug(f"Downloading:\n\tDirectory: {folder}\n\tFile: {file}")
return send_from_directory(directory=folder, filename=file)
if _type == "remux":
# NOTE: Need to actually implimint a websocket to communicate back to client that remux has completed.
# As is, the remux thread hangs until completion and client tries waiting until server reaches connection timeout.
# I.E....this is stupid but for now works better than nothing
good_result = view.remuxVideo(_hash, fpath)
good_result = view.remux_video(_hash, fpath)
if good_result:
return '{"path":"static/remuxs/' + _hash + '.mp4"}'
else:
msg = "Remuxing: Remux failed or took too long; please, refresh the page and try again..."
return msgHandler.createMessageJSON("success", msg)
if _type == "run-locally":
msg = "Opened media..."
view.openFilelocally(fpath)
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
if _type == "remux":
stream_target = view.remux_video(_hash, fpath)
# NOTE: Positionally protecting actions further down that are privlidged
# Be aware of ordering!
msg = "Log in with an Admin privlidged user to do this action!"
if not oidc.user_loggedin:
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
if _type == "run-locally":
msg = "Opened media..."
view.open_file_locally(fpath)
return msgHandler.create_JSON_message("success", msg)
if _type == "delete":
try:
msg = f"[Success] Deleted the file/folder -->: {file} !"
@ -181,10 +183,10 @@ def fileManagerAction(_type, _hash = None):
os.unlink(fpath)
else:
shutil.rmtree(fpath)
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
except Exception as e:
msg = "[Error] Unable to delete the file/folder...."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/list-favorites', methods=['GET', 'POST'])
@ -198,7 +200,7 @@ def listFavorites():
return '{"faves_list":' + json.dumps(faves) + '}'
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/load-favorite/<_id>', methods=['GET', 'POST'])
def loadFavorite(_id):
@ -212,10 +214,10 @@ def loadFavorite(_id):
except Exception as e:
print(repr(e))
msg = "Incorrect Favorites ID..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/manage-favorites/<_action>', methods=['GET', 'POST'])
@ -235,13 +237,13 @@ def manageFavorites(_action):
msg = "Deleted from Favorites successfully..."
else:
msg = "Couldn't handle action for favorites item..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
db.session.commit()
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/api/create/<_type>', methods=['GET', 'POST'])
@ -249,42 +251,42 @@ def create_item(_type = None):
if request.method == 'POST':
msg = "Log in with an Admin privlidged user to upload files!"
if not oidc.user_loggedin:
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
TYPE = _type.strip()
FNAME = str(request.values['fname']).strip()
if not re.fullmatch(valid_fname_pat, FNAME):
msg = "A new item name can only contain alphanumeric, -, _, |, [], (), or spaces and must be minimum of 4 and max of 20 characters..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
view = get_window_controller().get_window(1).get_view(0)
folder = view.get_current_directory()
new_item = folder + '/' + FNAME
new_item = f"{folder}/{FNAME}"
try:
if TYPE == "dir":
os.mkdir(new_item)
elif TYPE == "file":
open(new_item + ".txt", 'a').close()
open(f"{new_item}.txt", 'a').close()
else:
msg = "Couldn't handle action type for api create..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
except Exception as e:
print(repr(e))
msg = "Couldn't create file/folder. An unexpected error occured..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
msg = "[Success] created the file/dir..."
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
@app.route('/upload', methods=['GET', 'POST'])
@ -292,15 +294,15 @@ def upload():
if request.method == 'POST' and len(request.files) > 0:
msg = "Log in with an Admin privlidged user to upload files!"
if not oidc.user_loggedin:
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
view = get_window_controller().get_window(1).get_view(0)
folder = view.get_current_directory()
UPLOADS_PTH = folder + '/'
UPLOADS_PTH = f'{folder}/'
files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH)
configure_uploads(app, files)
@ -310,10 +312,10 @@ def upload():
except Exception as e:
print(repr(e))
msg = "[Error] Failed to upload some or all of the file(s)..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)
msg = "[Success] Uploaded file(s)..."
return msgHandler.createMessageJSON("success", msg)
return msgHandler.create_JSON_message("success", msg)
else:
msg = "Can't manage the request type..."
return msgHandler.createMessageJSON("danger", msg)
return msgHandler.create_JSON_message("danger", msg)

View File

@ -10,5 +10,5 @@ class MessageHandler:
print("MessageHandler initialized...")
def createMessageJSON(self, type, text):
def create_JSON_message(self, type, text):
return '{"message": { "type": "' + type + '", "text": "' + text + '" } }'

View File

@ -11,7 +11,7 @@ class WindowController:
if window.id == win_id:
return window
raise("No Window by ID {} found!".format(win_id))
raise(f"No Window by ID {win_id} found!")
def get_windows(self):
return self.windows
@ -19,7 +19,7 @@ class WindowController:
def add_window(self):
window = Window()
window.id = len(self.windows) + 1
window.name = "window_" + str(window.id)
window.name = f"window_{window.id}"
window.create_view()
self.windows.append(window)
@ -48,10 +48,10 @@ class WindowController:
def list_windows(self):
for window in self.windows:
print("\n[ Window ]")
print("ID: " + str(window.id))
print("Name: " + window.name)
print("Nickname: " + window.nickname)
print("View Count: " + str( len(window.views) ))
print("ID: {window.id}")
print("Name: {window.name}")
print("Nickname: {window.nickname}")
print("View Count: {len(window.views)}")
def list_views_from_window(self, win_id):

View File

@ -157,7 +157,7 @@ class View(Settings, Launcher, Path):
if not os.path.exists(hashImgPth) :
fullPath = join(current_directory, video[0])
self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}")
self.generateVideoThumbnail(fullPath, hashImgPth)
self.generate_video_thumbnail(fullPath, hashImgPth)
return videos_set

View File

@ -9,7 +9,7 @@ import os, subprocess, threading
class Launcher:
def openFilelocally(self, file):
def open_file_locally(self, file):
lowerName = file.lower()
command = []
@ -38,14 +38,25 @@ class Launcher:
subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
def remuxVideo(self, hash, file):
remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4"
def create_stream(self, hash, file):
# ffmpeg -re -stream_loop -1 -i "<video file path>" -f rtsp -rtsp_transport udp rtsp://localhost:8554/live.stream
command = ["ffmpeg", "-re", "-stream_loop", "-1", "-i", file, "-f", "rtsp", "-rtsp_transport", "udp", "rtsp://www.webfm.com:8554/{hash}.stream",]
try:
proc = subprocess.Popen(command)
except Exception as e:
self.logger.debug(message)
self.logger.debug(e)
return False
def remux_video(self, hash, file):
remux_vid_pth = f"{self.REMUX_FOLDER}/{hash}.mp4"
self.logger.debug(remux_vid_pth)
if not os.path.isfile(remux_vid_pth):
self.check_remux_space()
command = ["ffmpeg", "-i", file, "-hide_banner", "-movflags", "+faststart"]
command = ["ffmpeg", "-i", file, "-hide_banner", "-movflags", "+faststart", "-sws_flags", "fast_bilinear",]
if file.endswith("mkv"):
command += ["-codec", "copy", "-strict", "-2"]
if file.endswith("avi"):
@ -67,7 +78,7 @@ class Launcher:
return True
def generateVideoThumbnail(self, fullPath, hashImgPth):
def generate_video_thumbnail(self, fullPath, hashImgPth):
try:
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", "65%", "-s", "300", "-c", "jpg", "-i", fullPath, "-o", hashImgPth])
proc.wait()
@ -83,7 +94,7 @@ class Launcher:
self.logger.debug(e)
return
usage = self.getRemuxFolderUsage(self.REMUX_FOLDER)
usage = self.get_remux_folder_usage(self.REMUX_FOLDER)
if usage > limit:
files = os.listdir(self.REMUX_FOLDER)
for file in files:
@ -91,7 +102,7 @@ class Launcher:
os.unlink(fp)
def getRemuxFolderUsage(self, start_path = "."):
def get_remux_folder_usage(self, start_path = "."):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:

View File

@ -14,21 +14,21 @@ class Settings:
logger = None
USER_HOME = path.expanduser('~')
CONFIG_PATH = USER_HOME + "/.config/webfm"
CONFIG_FILE = CONFIG_PATH + "/settings.json"
CONFIG_PATH = f"{USER_HOME}/.config/webfm"
CONFIG_FILE = f"{CONFIG_PATH}/settings.json"
HIDE_HIDDEN_FILES = True
GTK_ORIENTATION = 1 # HORIZONTAL (0) VERTICAL (1)
DEFAULT_ICONS = CONFIG_PATH + "/icons"
DEFAULT_ICON = DEFAULT_ICONS + "/text.png"
FFMPG_THUMBNLR = CONFIG_PATH + "/ffmpegthumbnailer" # Thumbnail generator binary
REMUX_FOLDER = USER_HOME + "/.remuxs" # Remuxed files folder
DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder
STEAM_BASE_URL = "https://steamcdn-a.akamaihd.net/steam/apps/"
ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", USER_HOME + "/.icons" ,]
BASE_THUMBS_PTH = USER_HOME + "/.thumbnails" # Used for thumbnail generation
ABS_THUMBS_PTH = BASE_THUMBS_PTH + "/normal" # Used for thumbnail generation
STEAM_ICONS_PTH = BASE_THUMBS_PTH + "/steam_icons"
ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,]
BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation
ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal" # Used for thumbnail generation
STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons"
CONTAINER_ICON_WH = [128, 128]
VIDEO_ICON_WH = [128, 64]
SYS_ICON_WH = [56, 56]
@ -74,7 +74,7 @@ class Settings:
# Filters
fvideos = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv', '.mpeg', '.mp4', '.webm')
foffice = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm', '.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga')
fimages = ('.png', '.jpg', '.jpeg', '.gif', '.ico', '.tga', '.webp')
ftext = ('.txt', '.text', '.sh', '.cfg', '.conf')
fmusic = ('.psf', '.mp3', '.ogg', '.flac', '.m4a')
fpdf = ('.pdf')

Binary file not shown.

View File

@ -0,0 +1,281 @@
###############################################
# General parameters
# Sets the verbosity of the program; available values are "error", "warn", "info", "debug".
logLevel: info
# Destinations of log messages; available values are "stdout", "file" and "syslog".
logDestinations: [stdout]
# If "file" is in logDestinations, this is the file which will receive the logs.
logFile: rtsp-simple-server.log
# Timeout of read operations.
readTimeout: 10s
# Timeout of write operations.
writeTimeout: 10s
# Number of read buffers.
# A higher number allows a wider throughput, a lower number allows to save RAM.
readBufferCount: 2048
# HTTP URL to perform external authentication.
# Every time a user wants to authenticate, the server calls this URL
# with the POST method and a body containing:
# {
# "ip": "ip",
# "user": "user",
# "password": "password",
# "path": "path",
# "action": "read|publish"
# "query": "url's raw query"
# }
# If the response code is 20x, authentication is accepted, otherwise
# it is discarded.
externalAuthenticationURL:
# Enable the HTTP API.
api: no
# Address of the API listener.
apiAddress: 127.0.0.1:9997
# Enable Prometheus-compatible metrics.
metrics: no
# Address of the metrics listener.
metricsAddress: 127.0.0.1:9998
# Enable pprof-compatible endpoint to monitor performances.
pprof: no
# Address of the pprof listener.
pprofAddress: 127.0.0.1:9999
# Command to run when a client connects to the server.
# This is terminated with SIGINT when a client disconnects from the server.
# The following environment variables are available:
# * RTSP_PORT: server port
runOnConnect:
# Restart the command if it exits suddenly.
runOnConnectRestart: no
###############################################
# RTSP parameters
# Disable support for the RTSP protocol.
rtspDisable: no
# List of enabled RTSP transport protocols.
# UDP is the most performant, but doesn't work when there's a NAT/firewall between
# server and clients, and doesn't support encryption.
# UDP-multicast allows to save bandwidth when clients are all in the same LAN.
# TCP is the most versatile, and does support encryption.
# The handshake is always performed with TCP.
protocols: [udp, multicast, tcp]
# Encrypt handshake and TCP streams with TLS (RTSPS).
# Available values are "no", "strict", "optional".
encryption: "no"
# Address of the TCP/RTSP listener. This is needed only when encryption is "no" or "optional".
rtspAddress: :8554
# Address of the TCP/TLS/RTSPS listener. This is needed only when encryption is "strict" or "optional".
rtspsAddress: :8322
# Address of the UDP/RTP listener. This is needed only when "udp" is in protocols.
rtpAddress: :8000
# Address of the UDP/RTCP listener. This is needed only when "udp" is in protocols.
rtcpAddress: :8001
# IP range of all UDP-multicast listeners. This is needed only when "multicast" is in protocols.
multicastIPRange: 224.1.0.0/16
# Port of all UDP-multicast/RTP listeners. This is needed only when "multicast" is in protocols.
multicastRTPPort: 8002
# Port of all UDP-multicast/RTCP listeners. This is needed only when "multicast" is in protocols.
multicastRTCPPort: 8003
# Path to the server key. This is needed only when encryption is "strict" or "optional".
# This can be generated with:
# openssl genrsa -out server.key 2048
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
serverKey: server.key
# Path to the server certificate. This is needed only when encryption is "strict" or "optional".
serverCert: server.crt
# Authentication methods.
authMethods: [basic, digest]
###############################################
# RTMP parameters
# Disable support for the RTMP protocol.
rtmpDisable: no
# Address of the RTMP listener.
rtmpAddress: :1935
###############################################
# HLS parameters
# Disable support for the HLS protocol.
hlsDisable: no
# Address of the HLS listener.
hlsAddress: :8888
# By default, HLS is generated only when requested by a user.
# This option allows to generate it always, avoiding the delay between request and generation.
hlsAlwaysRemux: no
# Variant of the HLS protocol to use. Available options are:
# * mpegts - uses MPEG-TS segments, for maximum compatibility.
# * fmp4 - uses fragmented MP4 segments, more efficient.
# * lowLatency - uses Low-Latency HLS.
hlsVariant: mpegts
# Number of HLS segments to keep on the server.
# Segments allow to seek through the stream.
# Their number doesn't influence latency.
hlsSegmentCount: 7
# Minimum duration of each segment.
# A player usually puts 3 segments in a buffer before reproducing the stream.
# The final segment duration is also influenced by the interval between IDR frames,
# since the server changes the duration in order to include at least one IDR frame
# in each segment.
hlsSegmentDuration: 1s
# Minimum duration of each part.
# A player usually puts 3 parts in a buffer before reproducing the stream.
# Parts are used in Low-Latency HLS in place of segments.
# Part duration is influenced by the distance between video/audio samples
# and is adjusted in order to produce segments with a similar duration.
hlsPartDuration: 200ms
# Maximum size of each segment.
# This prevents RAM exhaustion.
hlsSegmentMaxSize: 50M
# Value of the Access-Control-Allow-Origin header provided in every HTTP response.
# This allows to play the HLS stream from an external website.
hlsAllowOrigin: '*'
# Enable TLS/HTTPS on the HLS server.
# This is required for Low-Latency HLS.
hlsEncryption: no
# Path to the server key. This is needed only when encryption is yes.
# This can be generated with:
# openssl genrsa -out server.key 2048
# openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650
hlsServerKey: server.key
# Path to the server certificate.
hlsServerCert: server.crt
###############################################
# Path parameters
# These settings are path-dependent, and the map key is the name of the path.
# It's possible to use regular expressions by using a tilde as prefix.
# For example, "~^(test1|test2)$" will match both "test1" and "test2".
# For example, "~^prefix" will match all paths that start with "prefix".
# The settings under the path "all" are applied to all paths that do not match
# another entry.
paths:
all:
# Source of the stream. This can be:
# * publisher -> the stream is published by a RTSP or RTMP client
# * rtsp://existing-url -> the stream is pulled from another RTSP server / camera
# * rtsps://existing-url -> the stream is pulled from another RTSP server / camera with RTSPS
# * rtmp://existing-url -> the stream is pulled from another RTMP server
# * http://existing-url/stream.m3u8 -> the stream is pulled from another HLS server
# * https://existing-url/stream.m3u8 -> the stream is pulled from another HLS server with HTTPS
# * redirect -> the stream is provided by another path or server
source: publisher
# If the source is an RTSP or RTSPS URL, this is the protocol that will be used to
# pull the stream. available values are "automatic", "udp", "multicast", "tcp".
sourceProtocol: automatic
# Tf the source is an RTSP or RTSPS URL, this allows to support sources that
# don't provide server ports or use random server ports. This is a security issue
# and must be used only when interacting with sources that require it.
sourceAnyPortEnable: no
# If the source is a RTSPS or HTTPS URL, and the source certificate is self-signed
# or invalid, you can provide the fingerprint of the certificate in order to
# validate it anyway. It can be obtained by running:
# openssl s_client -connect source_ip:source_port </dev/null 2>/dev/null | sed -n '/BEGIN/,/END/p' > server.crt
# openssl x509 -in server.crt -noout -fingerprint -sha256 | cut -d "=" -f2 | tr -d ':'
sourceFingerprint:
# If the source is an RTSP or RTMP URL, it will be pulled only when at least
# one reader is connected, saving bandwidth.
sourceOnDemand: no
# If sourceOnDemand is "yes", readers will be put on hold until the source is
# ready or until this amount of time has passed.
sourceOnDemandStartTimeout: 10s
# If sourceOnDemand is "yes", the source will be closed when there are no
# readers connected and this amount of time has passed.
sourceOnDemandCloseAfter: 10s
# If the source is "redirect", this is the RTSP URL which clients will be
# redirected to.
sourceRedirect:
# If the source is "publisher" and a client is publishing, do not allow another
# client to disconnect the former and publish in its place.
disablePublisherOverride: no
# If the source is "publisher" and no one is publishing, redirect readers to this
# path. It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL.
fallback:
# Username required to publish.
# SHA256-hashed values can be inserted with the "sha256:" prefix.
publishUser:
# Password required to publish.
# SHA256-hashed values can be inserted with the "sha256:" prefix.
publishPass:
# IPs or networks (x.x.x.x/24) allowed to publish.
publishIPs: []
# Username required to read.
# SHA256-hashed values can be inserted with the "sha256:" prefix.
readUser:
# password required to read.
# SHA256-hashed values can be inserted with the "sha256:" prefix.
readPass:
# IPs or networks (x.x.x.x/24) allowed to read.
readIPs: []
# Command to run when this path is initialized.
# This can be used to publish a stream and keep it always opened.
# This is terminated with SIGINT when the program closes.
# The following environment variables are available:
# * RTSP_PATH: path name
# * RTSP_PORT: server port
# * G1, G2, ...: regular expression groups, if path name is
# a regular expression.
runOnInit:
# Restart the command if it exits suddenly.
runOnInitRestart: no
# Command to run when this path is requested.
# This can be used to publish a stream on demand.
# This is terminated with SIGINT when the path is not requested anymore.
# The following environment variables are available:
# * RTSP_PATH: path name
# * RTSP_PORT: server port
# * G1, G2, ...: regular expression groups, if path name is
# a regular expression.
runOnDemand:
# Restart the command if it exits suddenly.
runOnDemandRestart: no
# Readers will be put on hold until the runOnDemand command starts publishing
# or until this amount of time has passed.
runOnDemandStartTimeout: 10s
# The command will be closed when there are no
# readers connected and this amount of time has passed.
runOnDemandCloseAfter: 10s
# Command to run when the stream is ready to be read, whether it is
# published by a client or pulled from a server / camera.
# This is terminated with SIGINT when the stream is not ready anymore.
# The following environment variables are available:
# * RTSP_PATH: path name
# * RTSP_PORT: server port
# * G1, G2, ...: regular expression groups, if path name is
# a regular expression.
runOnReady:
# Restart the command if it exits suddenly.
runOnReadyRestart: no
# Command to run when a clients starts reading.
# This is terminated with SIGINT when a client stops reading.
# The following environment variables are available:
# * RTSP_PATH: path name
# * RTSP_PORT: server port
# * G1, G2, ...: regular expression groups, if path name is
# a regular expression.
runOnRead:
# Restart the command if it exits suddenly.
runOnReadRestart: no