diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..2e2abd1 --- /dev/null +++ b/.env.dev @@ -0,0 +1,9 @@ +IP=192.168.137.1 +PORT_1=8100 +LISTENER_PORT_1=8101 +PORT_2=8200 +LISTENER_PORT_2=8201 +PORT_3=8300 +LISTENER_PORT_3=8301 +# PORT_4=8400 +# LISTENER_PORT_4=8401 diff --git a/.gitignore b/.gitignore index bd58bb4..2fac693 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,139 @@ -creation \ No newline at end of file +creation +# mediamtx_*.yml +# ./start.bat +docker-compose.yml + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pdm +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +html/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e8fcea1 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + exclude: (observability|testing/tavern) + - id: debug-statements + exclude: tests/ + - id: destroyed-symlinks + - id: end-of-file-fixer + files: \.(py|sh|rst|yml|yaml)$ + - id: mixed-line-ending + - id: trailing-whitespace + files: \.(py|sh|rst|yml|yaml)$ + - repo: https://github.com/ambv/black + rev: 23.10.0 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + args: [--config, src/setup.cfg] + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: [ + '--profile', + 'black', + '--src-path', + 'src', + ] diff --git a/README.md b/README.md index db79d48..80fa647 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,10 @@ ## IP address configuration The `docker-compose.yaml` is hardcoded to use the IP address `192.168.137.1`. You must update this to your specific network IP from the mobile hotspot configuration. + +## Build + +``` +docker build -t se23m504/startstreaming .\src -f .\docker\Dockerfile +docker run --rm -v ${PWD}:/app/data se23m504/startstreaming +``` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 1a547f6..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: streaming - -services: - mediamtx-1: &default - container_name: mediamtx-1 - environment: - - MTX_WEBRTCADDITIONALHOSTS=192.168.137.1 - image: bluenviron/mediamtx - ports: - - 8100:8100 - - 8101:8101/udp - volumes: - - ./mediamtx/mediamtx_1.yml:/mediamtx.yml - - mediamtx-2: - <<: *default - container_name: mediamtx-2 - ports: - - 8200:8200 - - 8201:8201/udp - volumes: - - ./mediamtx/mediamtx_2.yml:/mediamtx.yml diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..8abdc94 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app/data + +COPY config.py /app/bin/ +COPY generate.py /app/bin/ + +CMD ["python", "/app/bin/generate.py"] diff --git a/mediamtx/mediamtx_1.yml b/mediamtx/mediamtx_1.yml deleted file mode 100644 index b2d0582..0000000 --- a/mediamtx/mediamtx_1.yml +++ /dev/null @@ -1,58 +0,0 @@ -webrtcAddress: :8100 -webrtcLocalUDPAddress: :8101 -webrtcLocalTCPAddress: '' - -webrtcEncryption: no -webrtcAllowOrigin: '*' -webrtcTrustedProxies: [] -webrtcIPsFromInterfaces: yes -webrtcIPsFromInterfacesList: [] -webrtcAdditionalHosts: [] -webrtcICEServers2: [] - -logLevel: debug -logDestinations: [stdout] -logFile: mediamtx.log - -readTimeout: 10s -writeTimeout: 10s -writeQueueSize: 512 -udpMaxPayloadSize: 1472 - -runOnConnectRestart: no - -api: no -metrics: no -pprof: no -playback: no -rtsp: no -rtmp: no -hls: no -srt: no -webrtc: yes - -authMethod: internal -authInternalUsers: -- user: any - pass: - ips: [] - permissions: - - action: publish - path: - - action: read - path: - - action: playback - path: - -pathDefaults: - source: publisher - sourceFingerprint: - sourceOnDemand: no - sourceOnDemandStartTimeout: 10s - sourceOnDemandCloseAfter: 10s - maxReaders: 0 - srtReadPassphrase: - fallback: - -paths: - all_others: diff --git a/mediamtx/mediamtx_2.yml b/mediamtx/mediamtx_2.yml deleted file mode 100644 index 7778f7c..0000000 --- a/mediamtx/mediamtx_2.yml +++ /dev/null @@ -1,58 +0,0 @@ -webrtcAddress: :8200 -webrtcLocalUDPAddress: :8201 -webrtcLocalTCPAddress: '' - -webrtcEncryption: no -webrtcAllowOrigin: '*' -webrtcTrustedProxies: [] -webrtcIPsFromInterfaces: yes -webrtcIPsFromInterfacesList: [] -webrtcAdditionalHosts: [] -webrtcICEServers2: [] - -logLevel: debug -logDestinations: [stdout] -logFile: mediamtx.log - -readTimeout: 10s -writeTimeout: 10s -writeQueueSize: 512 -udpMaxPayloadSize: 1472 - -runOnConnectRestart: no - -api: no -metrics: no -pprof: no -playback: no -rtsp: no -rtmp: no -hls: no -srt: no -webrtc: yes - -authMethod: internal -authInternalUsers: -- user: any - pass: - ips: [] - permissions: - - action: publish - path: - - action: read - path: - - action: playback - path: - -pathDefaults: - source: publisher - sourceFingerprint: - sourceOnDemand: no - sourceOnDemandStartTimeout: 10s - sourceOnDemandCloseAfter: 10s - maxReaders: 0 - srtReadPassphrase: - fallback: - -paths: - all_others: diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..259bb7f --- /dev/null +++ b/src/config.py @@ -0,0 +1,90 @@ +import os +import re + +def load_env(filename=".env"): + env_vars = {} + with open(filename) as f: + for line in f: + if "=" in line: + key, value = line.strip().split("=", 1) + env_vars[key] = value + return env_vars + +def count_servers(env_vars): + server_keys = [key for key in env_vars if re.match(r"PORT_\d+", key)] + return len(server_keys) + +def generate_docker_compose(env_vars, num_servers): + ip = env_vars.get("IP") + services = [] + + for i in range(1, num_servers + 1): + port = env_vars.get(f"PORT_{i}") + listener_port = env_vars.get(f"LISTENER_PORT_{i}") + service = f""" + mediamtx-{i}: + container_name: mediamtx-{i} + environment: + - MTX_WEBRTCADDITIONALHOSTS={ip} + image: bluenviron/mediamtx + ports: + - {port}:{port} + - {listener_port}:{listener_port}/udp + volumes: + - ./mediamtx/mediamtx_{i}.yml:/mediamtx.yml +""" + services.append(service) + + compose_content = f""" +name: streaming + +services: +{''.join(services)} +""" + + with open("docker-compose.yml", "w") as f: + f.write(compose_content) + +def generate_mediamtx_files(env_vars, num_servers): + # os.makedirs("mediamtx") + mediamtx_dir = "mediamtx" + if not os.path.exists(mediamtx_dir): + os.makedirs(mediamtx_dir) + + with open("templates/mediamtx.yml", "r") as template_file: + template_content = template_file.read() + + for i in range(1, num_servers + 1): + port = env_vars.get(f"PORT_{i}") + listener_port = env_vars.get(f"LISTENER_PORT_{i}") + + generated_content = template_content.replace("{{WEBRTC_PORT}}", port).replace("{{WEBRTC_LISTENER_PORT}}", listener_port) + + with open(f"mediamtx/mediamtx_{i}.yml", "w") as f: + f.write(generated_content) + + existing_files = os.listdir("mediamtx") + for file in existing_files: + if file.startswith("mediamtx_"): + file_num = int(file.split("_")[1].split(".")[0]) + if file_num > num_servers: + os.remove(f"mediamtx/{file}") + print(f"Removed extra file: {file}") + +def generate_start_bat(num_servers): + with open("templates/start.bat", "r") as template_file: + bat_template = template_file.read() + + obs_instances = "" + for i in range(1, num_servers + 1): + obs_instance = f""" +echo Launching OBS instance {i} +start /MIN "OBS{i}" /D "C:\\Program Files\\obs-studio\\bin\\64bit" "obs64.exe" --profile "Profile{i}" --collection "Profile{i}" --multi --startstreaming +timeout /t 1 >nul +""" + obs_instances += obs_instance + + bat_content = bat_template.replace("{{OBS_INSTANCES}}", obs_instances) + + with open("start.bat", "w") as f: + f.write(bat_content) diff --git a/src/generate.py b/src/generate.py new file mode 100644 index 0000000..9b520bd --- /dev/null +++ b/src/generate.py @@ -0,0 +1,13 @@ +import os +from config import * + +def main(): + env_vars = load_env(".env") + num_servers = count_servers(env_vars) + + generate_docker_compose(env_vars, num_servers) + generate_mediamtx_files(env_vars, num_servers) + generate_start_bat(num_servers) + +if __name__ == "__main__": + main() diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..3f8fa27 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1 @@ +python-dotenv==1.0.1 \ No newline at end of file diff --git a/src/setup.cfg b/src/setup.cfg new file mode 100644 index 0000000..fedd799 --- /dev/null +++ b/src/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 110 \ No newline at end of file diff --git a/mediamtx/mediamtx.template.yml b/templates/mediamtx.yml similarity index 100% rename from mediamtx/mediamtx.template.yml rename to templates/mediamtx.yml diff --git a/start.bat b/templates/start.bat similarity index 61% rename from start.bat rename to templates/start.bat index c3c57db..1991181 100644 --- a/start.bat +++ b/templates/start.bat @@ -24,9 +24,6 @@ echo Starting docker compose cd ".\mediamtx" docker compose up -d -echo Launching OBS instances -start /MIN "OBS1" /D "C:\Program Files\obs-studio\bin\64bit" "obs64.exe" --profile "Profile1" --collection "Profile1" --multi --startstreaming -timeout /t 1 >nul -start /MIN "OBS2" /D "C:\Program Files\obs-studio\bin\64bit" "obs64.exe" --profile "Profile2" --collection "Profile2" --multi --startstreaming +{{OBS_INSTANCES}} pause