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

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

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