diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46096f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +.DS_Store diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2036e24 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +jsmpeg.min.js +.git +output.css diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..564f627 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,20 @@ +module.exports = { + printWidth: 100, + useTabs: true, + tabWidth: 4, + singleQuote: true, + semi: true, + trailingComma: 'all', + arrowParens: 'always', + bracketSameLine: true, + overrides: [ + { + files: '*.html', + + options: { + useTabs: false, + tabWidth: 2, + }, + }, + ], +}; diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cabce2d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["Elem", "jsmpeg", "lowerthirds", "nofailover", "rtmp"] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..204a013 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +.PHONY: * + +pretty: + npx prettier --write . + shfmt -w . + +install: + sudo ./setup-nginx-docker.sh + +front: + npx nodemon server.js + +run: + docker compose up + +build: + docker compose build + +exec: + docker exec -it nginx_server bash + +dev: build run + +css: + npx tailwindcss -i ./html/css/input.css -o ./html/css/output.css --watch diff --git a/README.md b/README.md index fb0d565..7b14511 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,45 @@ +> This repository has been moved to a different account: +> [https://github.com/isha-live/MLS](https://github.com/isha-live/MLS) +> +--- + # NGINX Server -Default login/password for server is admin/nimda +The default login/password for the server is admin/nimda + +## Need to install on Ubuntu 16.04 -# Need to install on Ubuntu 16.04 Don't use Ubuntu minimal. Use the regular 16.04 image -#Steps to Install -1. On the terminal, type: cd ~ && sudo git clone git://github.com/regstuff/MLS.git && cd MLS && sudo ./install-nginx.sh -2. The install is automatic, except for the initial part where you need to choose your timezone for time and date configguration. (Note: Installation may take upto an hour on a single CPU instance) -3. Once installation is complete, on the terminal, type: sudo visudo -4. To the bottom of the file that opens up, add: www-data ALL=NOPASSWD: /bin/bash, /bin/ls -5. Ctrl+o to save the file, Ctrl+X to exit the notepad editor. This process is needed to give the NGINX server access to the shell scripting -6. If you want to allow uploading of lowerthirds from the settings page, on the terminal, type: sudo nano /etc/php/7.0/fpm/php.ini -7. Find the line ;file_uploads = On --> Usually around line 800. Ctrl+Shift+_ and type 800 to get there quick -8. Remove the semicolon in front of the line to uncomment it. -9. Ctrl+o to save the file, Ctrl+X to exit the notepad editor. -10. Optional additional step: By default, all server logs are cleared on instance boot. This will ensure hardisk space isn't consumed too much. If you wish, you can retain them. On the terminal, type: sudo nano /etc/init.d/nginxrestart.sh -11. In the editor, comment the line (Add # before it): sudo rm /usr/local/nginx/logs/*.log -12. Ctrl+o to save the file, Ctrl+X to exit the notepad editor. -13. Installation is complete +## Steps to Install + +1. On the terminal, type: + The installation is automatic, except for the initial part where you need to choose your timezone for time and date configuration. + Note: Installation may take up to an hour on a single CPU instance. +1. Once installation is complete, on the terminal, type: `sudo visudo` +1. To the bottom of the file that opens up, add: `www-data ALL=NOPASSWD: /bin/bash, /bin/ls` +1. `Ctrl+o` to save the file, `Ctrl+X` to exit the notepad editor. This process is needed to give the `NGINX` server access to the shell scripting. + +## Steps to Update + +There is no need to install large libraries if you want to just update the source code. In this case, you can run: + +```sh +sudo ./update-mls.sh +``` + +### Uploading Lowerthirds From The Settings + +1. If you want to allow uploading of lowerthirds from the settings page, on the terminal, `type: sudo nano /etc/php/7.0/fpm/php.ini` +1. Find the line `;file_uploads = On` –> Usually around line 800. `Ctrl+Shift+_` and type 800 to get there quickly. +1. Remove the semicolon in front of the line to uncomment it. +1. `Ctrl+o` to save the file, `Ctrl+X` to exit the notepad editor. + +### Optional additional step: +By default, all server logs are cleared on instance boot. This will ensure hardisk space isn't consumed too much. If you wish, you can retain them. +1. On the terminal, type: `sudo nano /etc/init.d/nginxrestart.sh` +1. In the editor, comment on the line (Add # before it): `sudo rm /usr/local/nginx/logs/*.log` +1. `Ctrl+o` to save the file, `Ctrl+X` to exit the notepad editor. +1. Installation is complete! diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a340f8e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3' + +services: + nginx: + image: alfg/nginx-rtmp:v1.6.0 + container_name: 'nginx-mls' + ports: + - 8080:80 + - 1935:1935 + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf.template + - .:/app + depends_on: + - php + networks: + - internal + + php: + image: php:7.0-fpm-alpine + container_name: 'php-mls' + volumes: + - .:/app + networks: + - internal + +networks: + internal: + driver: bridge diff --git a/generate-config.sh b/generate-config.sh new file mode 100755 index 0000000..6b94a56 --- /dev/null +++ b/generate-config.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +truncate -s 0 blank.txt + +STREAM_NUM=$1 +OUT_NUM=$2 + +for ((i = 1; i <= STREAM_NUM; i++)); do + for ((j = 1; j <= OUT_NUM; j++)); do + output="__stream${i}__out${j}__" + echo $output >>blank.txt + done + echo "" >>blank.txt +done diff --git a/html/1.php b/html/1.php deleted file mode 100755 index cfadca5..0000000 --- a/html/1.php +++ /dev/null @@ -1,74 +0,0 @@ - - diff --git a/html/50x.html b/html/50x.html index f60f5e7..a7cb464 100755 --- a/html/50x.html +++ b/html/50x.html @@ -1,21 +1,25 @@ - + - -Error - - - -

An error occurred.

-

Sorry, the page you are looking for is currently unavailable.
-Please try again later.

-

If you are the system administrator of this resource then you should check -the error log for details.

-

Faithfully yours, nginx.

- + } + + + +

An error occurred.

+

+ Sorry, the page you are looking for is currently unavailable.
+ Please try again later. +

+

+ If you are the system administrator of this resource then you should check the + error log for details. +

+

Faithfully yours, nginx.

+ diff --git a/html/config.php b/html/config.php index cd00d2f..a617107 100644 --- a/html/config.php +++ b/html/config.php @@ -1,198 +1,230 @@ $output"; -#$output = file_get_contents( "../scripts/streamconfig.txt" ); // get the contents, and echo it out. -#echo "
$output
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh streamlist"); + echo "
$output
"; + #$output = file_get_contents( "../scripts/streamconfig.txt" ); // get the contents, and echo it out. + #echo "
$output
"; } if (isset($_GET['streamadd'])) { -$encodeparam = $_POST['encodeparam']; -$stream_id = $_POST['stream_id']; -$stream_res = $_POST['stream_res']; -$failover_method = $_POST['failover_method']; -echo "

You entered the following information:

"; -echo "Encode Parameter: $encodeparam"; -echo "
Stream Id: $stream_id"; -echo "
Stream Reolution: $stream_res"; -echo "
Failover: $failover_method"; -echo "
"; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh streamconfig \"$stream_id\" \"$encodeparam\" \"$stream_res\" $failover_method"); -echo $output; + $encodeparam = $_POST['encodeparam']; + $stream_id = $_POST['stream_id']; + $stream_res = $_POST['stream_res']; + $failover_method = $_POST['failover_method']; + echo "

You entered the following information:

"; + echo "Encode Parameter: $encodeparam"; + echo "
Stream Id: $stream_id"; + echo "
Stream Resolution: $stream_res"; + echo "
Failover: $failover_method"; + echo "
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh streamconfig \"$stream_id\" \"$encodeparam\" \"$stream_res\" $failover_method"); + echo $output; } if (isset($_GET['audioadd'])) { -$audioparam = $_POST['audioparam']; -$stream_id = $_POST['stream_id']; -$channel1 = $_POST['channel_1']; -$channel2 = $_POST['channel_2']; -$rtmpparam = $_POST['rtmpparam']; -echo "

You entered the following information:

"; -echo "Channel Layout: $audioparam"; -echo "
Stream Id: $stream_id"; -echo "
Channel 1: $channel1"; -echo "
Channel 2: $channel2"; -echo "
Destination: $rtmpparam"; -echo "
"; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audioconfig \"$stream_id\" \"$audioparam\" \"$channel1\" \"$channel2\" $rtmpparam"); -echo $output; + $audioparam = $_POST['audioparam']; + $stream_id = $_POST['stream_id']; + $channel1 = $_POST['channel_1']; + $channel2 = $_POST['channel_2']; + $rtmpparam = $_POST['rtmpparam']; + echo "

You entered the following information:

"; + echo "Channel Layout: $audioparam"; + echo "
Stream Id: $stream_id"; + echo "
Channel 1: $channel1"; + echo "
Channel 2: $channel2"; + echo "
Destination: $rtmpparam"; + echo "
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audioconfig \"$stream_id\" \"$audioparam\" \"$channel1\" \"$channel2\" $rtmpparam"); + echo $output; } if (isset($_GET['audiopreset'])) { -$audiopreset = $_POST['audiopreset']; -echo "You loaded $audiopreset"; -echo "
"; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audiopreset $audiopreset"); -echo $output; + $audiopreset = $_POST['audiopreset']; + echo "You loaded $audiopreset"; + echo "
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audiopreset $audiopreset"); + echo $output; +} + +if (isset($_GET['upload-video'])) { + $video_id = $_POST['video_id']; + $stream_id = $_POST['stream_id']; + echo "

You entered the following information:

"; + echo "Video ID: $video_id"; + echo "
Stream ID: $stream_id"; + echo "
"; + $exec = "sudo /bin/bash /usr/local/nginx/scripts/gdrive-downloader.sh $video_id /usr/local/nginx/scripts/images/" . $stream_id . "video.mp4"; + $output = shell_exec($exec); + echo $output; } if (isset($_GET['uploadfile'])) { -$file_url = $_POST['file_url']; -$stream_no = $_POST['stream_no']; -$type_id = $_POST['type_id']; -echo "

You entered the following information:

"; -echo "File Url: $file_url"; -echo "
Stream Id: $stream_no"; -echo "
FIle Type: $type_id"; -echo "
"; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh uploadfile \"$file_url\" \"$stream_no\" $type_id"); -echo $output; + $file_url = $_POST['file_url']; + $stream_no = $_POST['stream_no']; + $type_id = $_POST['type_id']; + echo "

You entered the following information:

"; + echo "File Url: $file_url"; + echo "
Stream Id: $stream_no"; + echo "
FIle Type: $type_id"; + echo "
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh uploadfile \"$file_url\" \"$stream_no\" $type_id"); + echo $output; } if (isset($_GET['remap'])) { -$channel = $_POST['channel_no']; -$outputdest = $_POST['outputdest']; -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh remap \"$channel\" $outputdest");echo $output;} + $channel = $_POST['channel_no']; + $outputdest = $_POST['outputdest']; + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh remap \"$channel\" $outputdest"); + echo $output; +} if (isset($_GET['audiolist'])) { -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audiolist"); -echo "
$output
"; -#$output = file_get_contents( "../scripts/streamconfig.txt" ); // get the contents, and echo it out. -#echo "
$output
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh audiolist"); + echo "
$output
"; + #$output = file_get_contents( "../scripts/streamconfig.txt" ); // get the contents, and echo it out. + #echo "
$output
"; } if (isset($_GET['remapoff'])) { -$outputdest = $_POST['outputdest']; -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh remap off $outputdest");echo $output;} + $outputdest = $_POST['outputdest']; + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh remap off $outputdest"); + echo $output; +} if (isset($_GET['srtaccept'])) { -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh srtaccept on");echo $output;} + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh srtaccept on"); + echo $output; +} -if (isset($_GET['srtacceptoff'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh srtaccept off");echo $output;} +if (isset($_GET['srtacceptoff'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh srtaccept off"); + echo $output; +} if (isset($_GET['destlist'])) { -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh destlist"); -echo "
$output
"; -#$output = file_get_contents( "../scripts/1data.txt" ); // get the contents, and echo it out. -#echo "
$output
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh destlist"); + echo "
$output
"; + #$output = file_get_contents( "../scripts/1data.txt" ); // get the contents, and echo it out. + #echo "
$output
"; } if (isset($_GET['destadd'])) { -$rtmp_url = $_POST['rtmp_url']; -$stream_id = $_POST['stream_id']; -$output_id = $_POST['output_id']; -$resolution = $_POST['resolution']; -$name_id = $_POST['name_id']; - -echo "

You Entered the following information:

"; -echo "RTMP Url: $rtmp_url"; -echo "
Name: $name_id"; -echo "
Stream Id: $stream_id"; -echo "
Output Id: $output_id"; -echo "
Resolution: $resolution"; -echo "
"; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh destination \"$rtmp_url\" \"$stream_id\" \"$output_id\" \"$resolution\" $name_id"); -echo $output; + $rtmp_url = $_POST['rtmp_url']; + $stream_id = $_POST['stream_id']; + $output_id = $_POST['output_id']; + $resolution = $_POST['resolution']; + $name_id = $_POST['name_id']; + + echo "

You Entered the following information:

"; + echo "RTMP Url: $rtmp_url"; + echo "
Name: $name_id"; + echo "
Stream Id: $stream_id"; + echo "
Output Id: $output_id"; + echo "
Resolution: $resolution"; + echo "
"; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh destination \"$rtmp_url\" \"$stream_id\" \"$output_id\" \"$resolution\" $name_id"); + echo $output; +} + +if (isset($_GET['bulkset'])) { + $rawPostData = file_get_contents("php://input"); + $data = json_decode($rawPostData, true); + + $output = ""; + foreach ($data as $out) { + $name_id = $out['name_id']; + $stream_id = $out['stream_id']; + $output_id = $out['output_id']; + $resolution = $out['resolution']; + $rtmp_url = $out['rtmp_url']; + + $output .= "

You Entered the following information:

+ RTMP Url: $rtmp_url
+ Name: $name_id
+ Stream Id: $stream_id
+ Output Id: $output_id
+ Resolution: $resolution

"; + + $command = sprintf( + 'sudo /bin/bash /usr/local/nginx/scripts/config.sh destination "%s" "%s" "%s" "%s" %s', + $rtmp_url, $stream_id, $output_id, $resolution, $name_id + ); + + $output .= shell_exec($command); + } + + echo $output; } + if (isset($_GET['proclist'])) { - $output = shell_exec("sudo ls -laR /var/run/screen/S-root | grep 'main\|back\|holding\|video' | cut -d ' ' -f 8,9,10,11"); - echo "
$output
"; - } - -if (isset($_GET['instatestauth'])) { - $insta_id = $_POST['insta_id']; - $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh instatestauth $insta_id"); - echo $folder_id." :
$output
"; - } - -if (isset($_GET['instalogin'])) { -$insta_id = $_POST['insta_id']; -$insta_login = $_POST['insta_login']; -$insta_pass = $_POST['insta_pass']; - -echo "

You Entered the following information:

"; -echo "Stream id: $insta_id"; -echo "
Login: $insta_login"; -echo "
Password: $insta_pass"; -echo "
"; - - $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh instalogin \"$insta_id\" \"$insta_login\" $insta_pass"); - echo $folder_id." :
$output
"; - } + $output = shell_exec("sudo ls -laR /var/run/screen/S-root | grep 'main\|back\|holding\|video' | cut -d ' ' -f 8,9,10,11"); + echo "
$output
"; +} if (isset($_GET['videoschedule'])) { -$hour = $_POST['hour']; -$minute = $_POST['minute']; -$stream_no = $_POST['stream_no']; -$type_id = $_POST['type_id']; -$on_off = $_POST['on_off']; -$schedule_type = $_POST['schedule_type']; -$startmin = $_POST['startmin']; -$startsec = $_POST['startsec']; - -echo "

You Entered the following information:

"; -echo "Time: $hour:$minute"; -echo "
Stream id: $stream_no"; -echo "
Video: $type_id"; -echo "
Schedule: $schedule_type"; -echo "
"; - -$inputsec = 60*$startmin+$startsec; -$output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh videoschedule \"$stream_no\" \"$type_id\" \"$on_off\" \"$schedule_type\" \"$hour\" \"$minute\" $inputsec");echo $output;} + $hour = $_POST['hour']; + $minute = $_POST['minute']; + $stream_no = $_POST['stream_no']; + $type_id = $_POST['type_id']; + $on_off = $_POST['on_off']; + $schedule_type = $_POST['schedule_type']; + $startmin = $_POST['startmin']; + $startsec = $_POST['startsec']; + + echo "

You Entered the following information:

"; + echo "Time: $hour:$minute"; + echo "
Stream id: $stream_no"; + echo "
Video: $type_id"; + echo "
Schedule: $schedule_type"; + echo "
"; + + $inputsec = 60 * $startmin + $startsec; + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh videoschedule \"$stream_no\" \"$type_id\" \"$on_off\" \"$schedule_type\" \"$hour\" \"$minute\" $inputsec"); + echo $output; +} if (isset($_GET['schedulelist'])) { - $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh schedulelist"); - echo "
$output
"; - } + $output = shell_exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh schedulelist"); + echo "
$output
"; +} if (isset($_GET['uploadlower'])) { -error_reporting(E_ALL); -ini_set("display_errors", 1); -$target_dir = "/usr/local/nginx/scripts/images/lowerthird/"; -$target_file = $target_dir.basename($_FILES["fileToUpload"]["name"]); -$uploadOk = 1; -$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION)); -// Check if image file is a actual image or fake image -if(isset($_POST["submit"])) { - $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]); - if($check !== false) { -// echo "File is an image - ".$check["mime"]."."; + error_reporting(E_ALL); + ini_set("display_errors", 1); + $target_dir = "/usr/local/nginx/scripts/images/lowerthird/"; + $target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]); $uploadOk = 1; - } else { - echo "File is not an image."; - $uploadOk = 0; - } -} -if ($_FILES["fileToUpload"]["size"] > 5000000) { - echo "Sorry, your file is too large."; - $uploadOk = 0; -} - -// Allow certain file formats -if($imageFileType != "png" ) { - echo "Sorry, only PNG files are allowed."; - $uploadOk = 0; + $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); + // Check if image file is a actual image or fake image + if (isset($_POST["submit"])) { + $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]); + if ($check !== false) { + // echo "File is an image - ".$check["mime"]."."; + $uploadOk = 1; + } else { + echo "File is not an image."; + $uploadOk = 0; + } + } + if ($_FILES["fileToUpload"]["size"] > 5000000) { + echo "Sorry, your file is too large."; + $uploadOk = 0; + } + + // Allow certain file formats + if ($imageFileType != "png") { + echo "Sorry, only PNG files are allowed."; + $uploadOk = 0; + } + + // Check if $uploadOk is set to 0 by an error + if ($uploadOk == 0) { + echo "Sorry, your file was not uploaded."; + // if everything is ok, try to upload file + } else { + $temp_filename = $_FILES["fileToUpload"]["tmp_name"]; + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh uploadlower $temp_filename $target_file"); + echo $output; + } } - -// Check if $uploadOk is set to 0 by an error -if ($uploadOk == 0) { - echo "Sorry, your file was not uploaded."; -// if everything is ok, try to upload file -} else { -$temp_filename = $_FILES["fileToUpload"]["tmp_name"]; -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/config.sh uploadlower $temp_filename $target_file");echo $output; -} -} - -?> diff --git a/html/control.html b/html/control.html index f4afcbf..dc17865 100644 --- a/html/control.html +++ b/html/control.html @@ -1,591 +1,162 @@ - - -Primary Server - Location - - - -

Primary/Secondary Server - Location

-
Stats ||| Recordings ||| Settings ||| Processes
-

Test

- -

No. of Audio Channels:

-

Turn off Remapping:

- -

SRT Accept: ON ||| OFF

- -

Supers - Global Control: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Stream 1

- -
- - - -

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 2

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 3

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 4

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 5

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 6

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 7

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 8

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 9

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 10

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 11

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 12

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 13

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 14

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 15

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 16

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 17

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 18

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 19

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
- -

Stream 20

-

play_arrowM   play_arrowB   play_arrowD   stop   volume_up   volume_down

- -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| ||| Playlist ||| Turn off

-
-







- + + + + + Control + + + + + + + + + + + +
+ +
+ +
+

+ Send stream to rtmp://${address}/distribute/stream1 or stream2 or stream3 etc. +
+ Send primary stream to rtmp://${address}/main/stream1 or stream2 or stream3 etc.
+ backupaddress = Send backup stream to rtmp://${address}/backup/stream1 or stream2 or stream3 + etc. +

+
+ +
+
+ More +
+
+ +

+ No. of Audio Channels: + + + +

+ +
+

+ Turn off Remapping: + + +

+
+
+ +
+

+ SRT Accept: + ON + OFF +

+
+ +
+

+ Supers - Global Control: + Add 1 + Add 2 + Add 3 + Add 4 + Add 5 + Add 6 + Add 7 + Add 8 + Remove +

+
+ +
+ Batch Input Control:   + + + + + + + +
+
+
+
+ +
+ +
+ + + + diff --git a/html/control.php b/html/control.php index 01c8421..0c806f5 100644 --- a/html/control.php +++ b/html/control.php @@ -1,7 +1,10 @@ "; + } + echo $output; +} -if (isset($_GET['instagramon'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/insta.sh on $numberid");echo $output;} -if (isset($_GET['1dest99on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out99");echo $output;} +#### Global Controls - SUPERS ###### +for ($j = 1; $j <= $OUT_NUM; $j++) { + if (isset($_GET["allsuper{$j}on"])) { + for ($i = 1; $i <= 8; $i++) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/{$i}.sh super {$j}"); + echo $output; + } + } +} -if (isset($_GET['1dest1off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out1 off");echo $output;} -if (isset($_GET['1dest2off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out2 off");echo $output;} -if (isset($_GET['1dest3off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out3 off");echo $output;} -if (isset($_GET['1dest4off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out4 off");echo $output;} -if (isset($_GET['1dest5off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out5 off");echo $output;} -if (isset($_GET['1dest6off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out6 off");echo $output;} -if (isset($_GET['1dest7off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out7 off");echo $output;} -if (isset($_GET['1dest8off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out8 off");echo $output;} -if (isset($_GET['1dest9off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out9 off");echo $output;} -if (isset($_GET['1dest10off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out10 off");echo $output;} -if (isset($_GET['1dest98off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out98 off");echo $output;} +if (isset($_GET['allsuperoff'])) { + for ($i = 1; $i <= 8; $i++) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/{$i}.sh super off"); + echo $output; + } +} -if (isset($_GET['instagramoff'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/instaoff.sh $numberid");echo $output;} -if (isset($_GET['1dest99off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out99 off");echo $output;} +for ($i = 1; $i <= 100; $i++) { + if (isset($_GET["1dest{$i}on"])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out{$i}"); + echo $output; + } + if (isset($_GET["1dest{$i}off"])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id out{$i} off"); + echo $output; + } +} ####END OF DESTINATIONS START INPUTS##### -if (isset($_GET['1on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on");echo $output;} -if (isset($_GET['1main'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id main");echo $output;} -if (isset($_GET['1back'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id back");echo $output;} +if (isset($_GET['1on'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on"); + echo $output; +} +if (isset($_GET['1main'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id main"); + echo $output; +} +if (isset($_GET['1back'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id back"); + echo $output; +} if (isset($_GET['1holding'])) { -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id holding 100");echo $output;} -if (isset($_GET['1video'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id video $inputsec");echo $output;} -if (isset($_GET['1playlist'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id playlist");echo $output;} -if (isset($_GET['1off'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id off");echo $output;} + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id holding 100"); + echo $output; +} +if (isset($_GET['1video'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id on && sudo /bin/bash /usr/local/nginx/scripts/$id video $inputsec"); + echo $output; +} +if (isset($_GET['1playlist'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id playlist"); + echo $output; +} +if (isset($_GET['1off'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id off"); + echo $output; +} ####END OF INPUTS START MODS##### if (isset($_GET['1vol'])) { -$vol = $_POST['vol_level']; -$vol_level = 2*$vol; -$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id volume $vol_level");echo $output;} - -if (isset($_GET['1super1on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 1");echo $output;} -if (isset($_GET['1super2on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 2");echo $output;} -if (isset($_GET['1super3on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 3");echo $output;} -if (isset($_GET['1super4on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 4");echo $output;} -if (isset($_GET['1super5on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 5");echo $output;} -if (isset($_GET['1super6on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 6");echo $output;} -if (isset($_GET['1super7on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 7");echo $output;} -if (isset($_GET['1super8on'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super 8");echo $output;} -if (isset($_GET['1superoff'])) {$output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super off");echo $output;} + $vol = $_POST['vol_level']; + $vol_level = 2 * $vol; + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id volume $vol_level"); + echo $output; +} +for ($i = 1; $i <= 8; $i++) { + if (isset($_GET["1super{$i}on"])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/$id super $i"); + echo $output; + } +} +if (isset($_GET['1superoff'])) { + $output = exec("sudo /bin/bash /usr/local/nginx/scripts/${id} super off"); + echo $output; +} ?> - diff --git a/html/css/input.css b/html/css/input.css new file mode 100644 index 0000000..7edd745 --- /dev/null +++ b/html/css/input.css @@ -0,0 +1,51 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.stream-container { + @apply inline-block text-left bg-base-300 rounded-box m-2; + width: 400px; +} + +.response-box { + @apply p-2 overflow-auto fixed bottom-3 right-3 w-96 h-48 bg-base-100 opacity-90; + @apply rounded-box border border-solid border-neutral-content border-t-4 border-b-4; + z-index: 999; +} + +.stream-status { + @apply inline-block w-3 h-3 mx-1 bg-base-100; + border-radius: 50%; +} + +.stream-status.on { + @apply bg-primary; +} + +.collapse-title { + @apply !text-base; +} + +.table :where(th, td) { + @apply p-1; +} + +.input-label { + @apply form-control max-w-md inline-block mr-1; +} + +.labeled-input { + @apply input input-bordered input-xs w-full max-w-md; +} + +.labeled-select { + @apply select select-bordered select-xs; +} + +.collapsible-header { + @apply collapse collapse-arrow border border-base-300 bg-base-200; +} + +.collapse-title { + @apply text-xl font-medium; +} diff --git a/html/css/output.css b/html/css/output.css new file mode 100644 index 0000000..49cc628 --- /dev/null +++ b/html/css/output.css @@ -0,0 +1,3812 @@ +/* +! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +:root, +[data-theme] { + background-color: var(--fallback-b1,oklch(var(--b1)/1)); + color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +@supports not (color: oklch(0 0 0)) { + :root { + color-scheme: light; + --fallback-p: #491eff; + --fallback-pc: #d4dbff; + --fallback-s: #ff41c7; + --fallback-sc: #fff9fc; + --fallback-a: #00cfbd; + --fallback-ac: #00100d; + --fallback-n: #2b3440; + --fallback-nc: #d7dde4; + --fallback-b1: #ffffff; + --fallback-b2: #e5e6e6; + --fallback-b3: #e5e6e6; + --fallback-bc: #1f2937; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + + @media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --fallback-p: #7582ff; + --fallback-pc: #050617; + --fallback-s: #ff71cf; + --fallback-sc: #190211; + --fallback-a: #00c7b5; + --fallback-ac: #000e0c; + --fallback-n: #2a323c; + --fallback-nc: #a6adbb; + --fallback-b1: #1d232a; + --fallback-b2: #191e24; + --fallback-b3: #15191e; + --fallback-bc: #a6adbb; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + } +} + +html { + -webkit-tap-highlight-color: transparent; +} + +:root { + color-scheme: dark; + --pc: 0.172267 0.028331 139.549991; + --sc: 0.146752 0.033181 35.353059; + --ac: 0.148459 0.026728 311.37924; + --inc: 0.172157 0.028409 206.182959; + --suc: 0.172343 0.028437 166.534048; + --wac: 0.172327 0.028447 94.818679; + --erc: 0.164838 0.019914 33.756357; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 0.861335 0.141656 139.549991; + --s: 0.733759 0.165904 35.353059; + --a: 0.742296 0.133641 311.37924; + --n: 0.247311 0.020483 264.094728; + --nc: 0.829011 0.031335 222.959324; + --b1: 0.308577 0.023243 264.149498; + --b2: 0.280368 0.01983 264.182074; + --b3: 0.263469 0.018403 262.177739; + --bc: 0.829011 0.031335 222.959324; + --in: 0.860785 0.142046 206.182959; + --su: 0.861717 0.142187 166.534048; + --wa: 0.861634 0.142236 94.818679; + --er: 0.824189 0.09957 33.756357; +} + +[data-theme=dim] { + color-scheme: dark; + --pc: 0.172267 0.028331 139.549991; + --sc: 0.146752 0.033181 35.353059; + --ac: 0.148459 0.026728 311.37924; + --inc: 0.172157 0.028409 206.182959; + --suc: 0.172343 0.028437 166.534048; + --wac: 0.172327 0.028447 94.818679; + --erc: 0.164838 0.019914 33.756357; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 0.861335 0.141656 139.549991; + --s: 0.733759 0.165904 35.353059; + --a: 0.742296 0.133641 311.37924; + --n: 0.247311 0.020483 264.094728; + --nc: 0.829011 0.031335 222.959324; + --b1: 0.308577 0.023243 264.149498; + --b2: 0.280368 0.01983 264.182074; + --b3: 0.263469 0.018403 262.177739; + --bc: 0.829011 0.031335 222.959324; + --in: 0.860785 0.142046 206.182959; + --su: 0.861717 0.142187 166.534048; + --wa: 0.861634 0.142236 94.818679; + --er: 0.824189 0.09957 33.756357; +} + +[data-theme=night] { + color-scheme: dark; + --b2: 0.193144 0.037037 265.754874; + --b3: 0.178606 0.034249 265.754874; + --bc: 0.841536 0.007965 265.754874; + --pc: 0.150703 0.027798 232.66148; + --sc: 0.136023 0.031661 276.934902; + --ac: 0.144721 0.035244 350.048739; + --nc: 0.855899 0.00737 260.030984; + --suc: 0.156904 0.026506 181.911977; + --wac: 0.166486 0.027912 82.95003; + --erc: 0.143572 0.034051 13.11834; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 0.753513 0.138989 232.66148; + --s: 0.680113 0.158303 276.934902; + --a: 0.723603 0.176218 350.048739; + --n: 0.279495 0.036848 260.030984; + --b1: 0.207682 0.039824 265.754874; + --in: 0.684553 0.148062 237.25135; + --inc: 0 0 0; + --su: 0.78452 0.132529 181.911977; + --wa: 0.832428 0.139558 82.95003; + --er: 0.717858 0.170255 13.11834; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.prose { + color: var(--tw-prose-body); + max-width: 65ch; +} + +.prose :where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-lead); + font-size: 1.25em; + line-height: 1.6; + margin-top: 1.2em; + margin-bottom: 1.2em; +} + +.prose :where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-links); + text-decoration: underline; + font-weight: 500; +} + +.prose :where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-bold); + font-weight: 600; +} + +.prose :where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: decimal; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-left: 1.625em; +} + +.prose :where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: decimal; +} + +.prose :where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + list-style-type: disc; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-left: 1.625em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker { + font-weight: 400; + color: var(--tw-prose-counters); +} + +.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *))::marker { + color: var(--tw-prose-bullets); +} + +.prose :where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.25em; +} + +.prose :where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-color: var(--tw-prose-hr); + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} + +.prose :where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 500; + font-style: italic; + color: var(--tw-prose-quotes); + border-left-width: 0.25rem; + border-left-color: var(--tw-prose-quote-borders); + quotes: "\201C""\201D""\2018""\2019"; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1em; +} + +.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: open-quote; +} + +.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: close-quote; +} + +.prose :where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 800; + font-size: 2.25em; + margin-top: 0; + margin-bottom: 0.8888889em; + line-height: 1.1111111; +} + +.prose :where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 900; + color: inherit; +} + +.prose :where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 1.5em; + margin-top: 2em; + margin-bottom: 1em; + line-height: 1.3333333; +} + +.prose :where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 800; + color: inherit; +} + +.prose :where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; +} + +.prose :where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 700; + color: inherit; +} + +.prose :where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; +} + +.prose :where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 700; + color: inherit; +} + +.prose :where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + display: block; + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + font-weight: 500; + font-family: inherit; + color: var(--tw-prose-kbd); + box-shadow: 0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%); + font-size: 0.875em; + border-radius: 0.3125rem; + padding-top: 0.1875em; + padding-right: 0.375em; + padding-bottom: 0.1875em; + padding-left: 0.375em; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-code); + font-weight: 600; + font-size: 0.875em; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: "`"; +} + +.prose :where(code):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: "`"; +} + +.prose :where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; + font-size: 0.875em; +} + +.prose :where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; + font-size: 0.9em; +} + +.prose :where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: inherit; +} + +.prose :where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-pre-code); + background-color: var(--tw-prose-pre-bg); + overflow-x: auto; + font-weight: 400; + font-size: 0.875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: 0.375rem; + padding-top: 0.8571429em; + padding-right: 1.1428571em; + padding-bottom: 0.8571429em; + padding-left: 1.1428571em; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + background-color: transparent; + border-width: 0; + border-radius: 0; + padding: 0; + font-weight: inherit; + color: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *))::before { + content: none; +} + +.prose :where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *))::after { + content: none; +} + +.prose :where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} + +.prose :where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-th-borders); +} + +.prose :where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-td-borders); +} + +.prose :where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-bottom-width: 0; +} + +.prose :where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + vertical-align: baseline; +} + +.prose :where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + border-top-width: 1px; + border-top-color: var(--tw-prose-th-borders); +} + +.prose :where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + vertical-align: top; +} + +.prose :where(figure > *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + color: var(--tw-prose-captions); + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; +} + +.prose { + --tw-prose-body: #374151; + --tw-prose-headings: #111827; + --tw-prose-lead: #4b5563; + --tw-prose-links: #111827; + --tw-prose-bold: #111827; + --tw-prose-counters: #6b7280; + --tw-prose-bullets: #d1d5db; + --tw-prose-hr: #e5e7eb; + --tw-prose-quotes: #111827; + --tw-prose-quote-borders: #e5e7eb; + --tw-prose-captions: #6b7280; + --tw-prose-kbd: #111827; + --tw-prose-kbd-shadows: 17 24 39; + --tw-prose-code: #111827; + --tw-prose-pre-code: #e5e7eb; + --tw-prose-pre-bg: #1f2937; + --tw-prose-th-borders: #d1d5db; + --tw-prose-td-borders: #e5e7eb; + --tw-prose-invert-body: #d1d5db; + --tw-prose-invert-headings: #fff; + --tw-prose-invert-lead: #9ca3af; + --tw-prose-invert-links: #fff; + --tw-prose-invert-bold: #fff; + --tw-prose-invert-counters: #9ca3af; + --tw-prose-invert-bullets: #4b5563; + --tw-prose-invert-hr: #374151; + --tw-prose-invert-quotes: #f3f4f6; + --tw-prose-invert-quote-borders: #374151; + --tw-prose-invert-captions: #9ca3af; + --tw-prose-invert-kbd: #fff; + --tw-prose-invert-kbd-shadows: 255 255 255; + --tw-prose-invert-code: #fff; + --tw-prose-invert-pre-code: #d1d5db; + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); + --tw-prose-invert-th-borders: #4b5563; + --tw-prose-invert-td-borders: #374151; + font-size: 1rem; + line-height: 1.75; +} + +.prose :where(picture > img):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose :where(ul > li):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose :where(.prose > ul > li p):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(.prose > ul > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose :where(.prose > ul > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(.prose > ol > li > *:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose :where(.prose > ol > li > *:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0.5em; + padding-left: 1.625em; +} + +.prose :where(hr + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h2 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h3 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h4 + *):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(tbody td, tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(.prose > :first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(.prose > :last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)) { + margin-bottom: 0; +} + +.avatar.placeholder > div { + display: flex; + align-items: center; + justify-content: center; +} + +@media (hover:hover) { + .label a:hover { + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + } + + .menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + } + + .table tr.hover:hover, + .table tr.hover:nth-child(even):hover { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + } +} + +.btn { + display: inline-flex; + height: 3rem; + min-height: 3rem; + flex-shrink: 0; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + border-radius: var(--rounded-btn, 0.5rem); + border-color: transparent; + border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity)); + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-size: 0.875rem; + line-height: 1em; + gap: 0.5rem; + font-weight: 600; + text-decoration-line: none; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + border-width: var(--border-btn, 1px); + animation: button-pop var(--animation-btn, 0.25s) ease-out; + transition-property: color, background-color, border-color, opacity, box-shadow, transform; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity)); + --tw-bg-opacity: 1; + --tw-border-opacity: 1; +} + +.btn-disabled, + .btn[disabled], + .btn:disabled { + pointer-events: none; +} + +:where(.btn:is(input[type="checkbox"])), +:where(.btn:is(input[type="radio"])) { + width: auto; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.btn:is(input[type="checkbox"]):after, +.btn:is(input[type="radio"]):after { + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.collapse:not(td):not(tr):not(colgroup) { + visibility: visible; +} + +.collapse { + position: relative; + display: grid; + overflow: hidden; + grid-template-rows: auto 0fr; + transition: grid-template-rows 0.2s; + width: 100%; + border-radius: var(--rounded-box, 1rem); +} + +.collapse-title, +.collapse > input[type="checkbox"], +.collapse > input[type="radio"], +.collapse-content { + grid-column-start: 1; + grid-row-start: 1; +} + +.collapse > input[type="checkbox"], +.collapse > input[type="radio"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + opacity: 0; +} + +.collapse-content { + visibility: hidden; + grid-column-start: 1; + grid-row-start: 2; + min-height: 0px; + transition: visibility 0.2s; + transition: padding 0.2s ease-out, + background-color 0.2s ease-out; + padding-left: 1rem; + padding-right: 1rem; + cursor: unset; +} + +.collapse[open], +.collapse-open, +.collapse:focus:not(.collapse-close) { + grid-template-rows: auto 1fr; +} + +.collapse:not(.collapse-close):has(> input[type="checkbox"]:checked), +.collapse:not(.collapse-close):has(> input[type="radio"]:checked) { + grid-template-rows: auto 1fr; +} + +.collapse[open] > .collapse-content, +.collapse-open > .collapse-content, +.collapse:focus:not(.collapse-close) > .collapse-content, +.collapse:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-content, +.collapse:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content { + visibility: visible; + min-height: -moz-fit-content; + min-height: fit-content; +} + +.divider { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; + margin-top: 1rem; + margin-bottom: 1rem; + height: 1rem; + white-space: nowrap; +} + +.divider:before, + .divider:after { + height: 0.125rem; + width: 100%; + flex-grow: 1; + --tw-content: ''; + content: var(--tw-content); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); +} + +@media (hover: hover) { + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + + .btn:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn:hover { + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, + black + ); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, + black + ); + } + } + + @supports not (color: oklch(0 0 0)) { + .btn:hover { + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + } + + .btn.glass:hover { + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } + + .btn-ghost:hover { + border-color: transparent; + } + + @supports (color: oklch(0 0 0)) { + .btn-ghost:hover { + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + } + } + + .btn-outline:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity))); + } + + .btn-outline.btn-primary:hover { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-primary:hover { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + } + + .btn-outline.btn-secondary:hover { + --tw-text-opacity: 1; + color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-secondary:hover { + background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + } + } + + .btn-outline.btn-accent:hover { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-accent:hover { + background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + } + } + + .btn-outline.btn-success:hover { + --tw-text-opacity: 1; + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-success:hover { + background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + } + } + + .btn-outline.btn-info:hover { + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-info:hover { + background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + } + } + + .btn-outline.btn-warning:hover { + --tw-text-opacity: 1; + color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-warning:hover { + background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + } + } + + .btn-outline.btn-error:hover { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-error:hover { + background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + } + } + + .btn-disabled:hover, + .btn[disabled]:hover, + .btn:disabled:hover { + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + } + + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover { + cursor: pointer; + outline: 2px solid transparent; + outline-offset: 2px; + } + + @supports (color: oklch(0 0 0)) { + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(.active):hover, :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(.active):hover { + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + } + } +} + +.footer { + display: grid; + width: 100%; + grid-auto-flow: row; + place-items: start; + -moz-column-gap: 1rem; + column-gap: 1rem; + row-gap: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.footer > * { + display: grid; + place-items: start; + gap: 0.5rem; +} + +.footer-center { + place-items: center; + text-align: center; +} + +.footer-center > * { + place-items: center; +} + +@media (min-width: 48rem) { + .footer { + grid-auto-flow: column; + } + + .footer-center { + grid-auto-flow: row dense; + } +} + +.label { + display: flex; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + align-items: center; + justify-content: space-between; + padding-left: 0.25rem; + padding-right: 0.25rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.input { + flex-shrink: 1; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.join { + display: inline-flex; + align-items: stretch; + border-radius: var(--rounded-btn, 0.5rem); +} + +.join :where(.join-item) { + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item { + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item { + border-start-end-radius: 0; + border-end-end-radius: 0; +} + +.join .dropdown .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .dropdown .join-item { + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item) { + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item { + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item) { + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +@supports not selector(:has(*)) { + :where(.join *) { + border-radius: inherit; + } +} + +@supports selector(:has(*)) { + :where(.join *:has(.join-item)) { + border-radius: inherit; + } +} + +.link { + cursor: pointer; + text-decoration-line: underline; +} + +.mask { + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +.menu { + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.5rem; +} + +.menu :where(li ul) { + position: relative; + white-space: nowrap; + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), + .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: 0.5rem; + grid-auto-columns: minmax(auto, max-content) auto max-content; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.menu li.disabled { + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + color: var(--fallback-bc,oklch(var(--bc)/0.3)); +} + +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) { + display: none; +} + +:where(.menu li) { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; +} + +:where(.menu li) .badge { + justify-self: end; +} + +.navbar { + display: flex; + align-items: center; + padding: var(--navbar-padding, 0.5rem); + min-height: 4rem; + width: 100%; +} + +:where(.navbar > *) { + display: inline-flex; + align-items: center; +} + +.progress { + position: relative; + width: 100%; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + overflow: hidden; + height: 0.5rem; + border-radius: var(--rounded-box, 1rem); + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select { + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + min-height: 3rem; + padding-left: 1rem; + padding-right: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), + linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), + calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, + 4px 4px; + background-repeat: no-repeat; +} + +.select[multiple] { + height: auto; +} + +.stats { + display: inline-grid; + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +:where(.stats) { + grid-auto-flow: column; + overflow-x: auto; +} + +.stat { + display: inline-grid; + width: 100%; + grid-template-columns: repeat(1, 1fr); + -moz-column-gap: 1rem; + column-gap: 1rem; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.1; + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 1rem; + padding-bottom: 1rem; +} + +.table { + position: relative; + width: 100%; + border-radius: var(--rounded-box, 1rem); + text-align: left; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.table :where(.table-pin-rows thead tr) { + position: sticky; + top: 0px; + z-index: 1; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.table :where(.table-pin-rows tfoot tr) { + position: sticky; + bottom: 0px; + z-index: 1; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.table :where(.table-pin-cols tr th) { + position: sticky; + left: 0px; + right: 0px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.textarea { + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.btm-nav > *.disabled, + .btm-nav > *[disabled] { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.btm-nav > * .label { + font-size: 1rem; + line-height: 1.5rem; +} + +.btn:active:hover, + .btn:active:focus { + animation: button-pop 0s ease-out; + transform: scale(var(--btn-focus-scale, 0.97)); +} + +@supports not (color: oklch(0 0 0)) { + .btn { + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + + .btn-primary { + --btn-color: var(--fallback-p); + } + + .btn-accent { + --btn-color: var(--fallback-a); + } + + .btn-error { + --btn-color: var(--fallback-er); + } +} + +@supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-primary.btn-active { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + + .btn-outline.btn-secondary.btn-active { + background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + } + + .btn-outline.btn-accent.btn-active { + background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + } + + .btn-outline.btn-success.btn-active { + background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + } + + .btn-outline.btn-info.btn-active { + background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + } + + .btn-outline.btn-warning.btn-active { + background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + } + + .btn-outline.btn-error.btn-active { + background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + } +} + +.btn:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +.btn-primary { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@supports (color: oklch(0 0 0)) { + .btn-primary { + --btn-color: var(--p); + } + + .btn-accent { + --btn-color: var(--a); + } + + .btn-error { + --btn-color: var(--er); + } +} + +.btn-accent { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); + outline-color: var(--fallback-a,oklch(var(--a)/1)); +} + +.btn-error { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); + outline-color: var(--fallback-er,oklch(var(--er)/1)); +} + +.btn.glass { + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn.glass.btn-active { + --glass-opacity: 25%; + --glass-border-opacity: 15%; +} + +.btn-ghost { + border-width: 1px; + border-color: transparent; + background-color: transparent; + color: currentColor; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn-ghost.btn-active { + border-color: transparent; + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.btn-outline { + border-color: currentColor; + background-color: transparent; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.btn-outline.btn-active { + --tw-border-opacity: 1; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity))); +} + +.btn-outline.btn-primary { + --tw-text-opacity: 1; + color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); +} + +.btn-outline.btn-primary.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-secondary { + --tw-text-opacity: 1; + color: var(--fallback-s,oklch(var(--s)/var(--tw-text-opacity))); +} + +.btn-outline.btn-secondary.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-accent { + --tw-text-opacity: 1; + color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); +} + +.btn-outline.btn-accent.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); +} + +.btn-outline.btn-success { + --tw-text-opacity: 1; + color: var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity))); +} + +.btn-outline.btn-success.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-info { + --tw-text-opacity: 1; + color: var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity))); +} + +.btn-outline.btn-info.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-warning { + --tw-text-opacity: 1; + color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity))); +} + +.btn-outline.btn-warning.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity))); +} + +.btn-outline.btn-error { + --tw-text-opacity: 1; + color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); +} + +.btn-outline.btn-error.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); +} + +.btn.btn-disabled, + .btn[disabled], + .btn:disabled { + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.btn:is(input[type="checkbox"]:checked), +.btn:is(input[type="radio"]:checked) { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible { + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@keyframes button-pop { + 0% { + transform: scale(var(--btn-focus-scale, 0.98)); + } + + 40% { + transform: scale(1.02); + } + + 100% { + transform: scale(1); + } +} + +@keyframes checkmark { + 0% { + background-position-y: 5px; + } + + 50% { + background-position-y: -2px; + } + + 100% { + background-position-y: 0; + } +} + +details.collapse { + width: 100%; +} + +details.collapse summary { + position: relative; + display: block; + outline: 2px solid transparent; + outline-offset: 2px; +} + +details.collapse summary::-webkit-details-marker { + display: none; +} + +.collapse:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapse:has(.collapse-title:focus-visible), +.collapse:has(> input[type="checkbox"]:focus-visible), +.collapse:has(> input[type="radio"]:focus-visible) { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapse-arrow > .collapse-title:after { + position: absolute; + display: block; + height: 0.5rem; + width: 0.5rem; + --tw-translate-y: -100%; + --tw-rotate: 45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 150ms; + transition-duration: 0.2s; + top: 50%; + inset-inline-end: 1.4rem; + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.collapse-plus > .collapse-title:after { + position: absolute; + display: block; + height: 0.5rem; + width: 0.5rem; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 300ms; + top: 0.9rem; + inset-inline-end: 1.4rem; + content: "+"; + pointer-events: none; +} + +.collapse:not(.collapse-open):not(.collapse-close) > input[type="checkbox"], +.collapse:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked), +.collapse:not(.collapse-open):not(.collapse-close) > .collapse-title { + cursor: pointer; +} + +.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title { + cursor: unset; +} + +.collapse-title { + position: relative; +} + +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]) { + z-index: 1; +} + +.collapse-title, +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]) { + width: 100%; + padding: 1rem; + padding-inline-end: 3rem; + min-height: 3.75rem; + transition: background-color 0.2s ease-out; +} + +.collapse[open] > :where(.collapse-content), +.collapse-open > :where(.collapse-content), +.collapse:focus:not(.collapse-close) > :where(.collapse-content), +.collapse:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content), +.collapse:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content) { + padding-bottom: 1rem; + transition: padding 0.2s ease-out, + background-color 0.2s ease-out; +} + +.collapse[open].collapse-arrow > .collapse-title:after, +.collapse-open.collapse-arrow > .collapse-title:after, +.collapse-arrow:focus:not(.collapse-close) > .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-arrow:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after { + --tw-translate-y: -50%; + --tw-rotate: 225deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.collapse[open].collapse-plus > .collapse-title:after, +.collapse-open.collapse-plus > .collapse-title:after, +.collapse-plus:focus:not(.collapse-close) > .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after, +.collapse-plus:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after { + content: "−"; +} + +.divider:not(:empty) { + gap: 1rem; +} + +.label-text { + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +.input input:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.input[list]::-webkit-calendar-picker-indicator { + line-height: 1em; +} + +.input-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.input:focus, + .input:focus-within { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.input-disabled, + .input:disabled, + .input[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.input-disabled::placeholder, + .input:disabled::placeholder, + .input[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.input::-webkit-date-and-time-value { + text-align: inherit; +} + +.join > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-inline-start: -1px; +} + +@supports (color:color-mix(in oklab,black,black)) { + @media (hover:hover) { + .link-accent:hover { + color: color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 80%,black); + } + } +} + +.link-accent { + --tw-text-opacity: 1; + color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); +} + +.link:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.link:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +:where(.menu li:empty) { + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + opacity: 0.1; + margin: 0.5rem 1rem; + height: 1px; +} + +.menu :where(li ul):before { + position: absolute; + bottom: 0.75rem; + inset-inline-start: 0px; + top: 0.75rem; + width: 1px; + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + opacity: 0.1; + content: ""; +} + +.menu :where(li:not(.menu-title) > *:not(ul):not(details):not(.menu-title)), +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: start; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + text-wrap: balance; +} + +:where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > *:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active).focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):not(summary):not(.active):focus, + :where(.menu li:not(.menu-title):not(.disabled) > details > summary:not(.menu-title)):is(summary):not(.active):focus-visible { + cursor: pointer; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; +} + +.menu li > *:not(ul):not(.menu-title):not(details):active, +.menu li > *:not(ul):not(.menu-title):not(details).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.menu :where(li > details > summary)::-webkit-details-marker { + display: none; +} + +.menu :where(li > details > summary):after, +.menu :where(li > .menu-dropdown-toggle):after { + justify-self: end; + display: block; + margin-top: -0.5rem; + height: 0.5rem; + width: 0.5rem; + transform: rotate(45deg); + transition-property: transform, margin-top; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.menu :where(li > details[open] > summary):after, +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { + transform: rotate(225deg); + margin-top: 0; +} + +.mockup-phone .display { + overflow: hidden; + border-radius: 40px; + margin-top: -25px; +} + +.mockup-browser .mockup-browser-toolbar .input { + position: relative; + margin-left: auto; + margin-right: auto; + display: block; + height: 1.75rem; + width: 24rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding-left: 2rem; + direction: ltr; +} + +.mockup-browser .mockup-browser-toolbar .input:before { + content: ""; + position: absolute; + left: 0.5rem; + top: 50%; + aspect-ratio: 1 / 1; + height: 0.75rem; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 2px; + border-color: currentColor; + opacity: 0.6; +} + +.mockup-browser .mockup-browser-toolbar .input:after { + content: ""; + position: absolute; + left: 1.25rem; + top: 50%; + height: 0.5rem; + --tw-translate-y: 25%; + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 1px; + border-color: currentColor; + opacity: 0.6; +} + +@keyframes modal-pop { + 0% { + opacity: 0; + } +} + +.progress::-moz-progress-bar { + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); +} + +.progress:indeterminate { + --progress-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-image: repeating-linear-gradient( + 90deg, + var(--progress-color) -1%, + var(--progress-color) 10%, + transparent 10%, + transparent 90% + ); + background-size: 200%; + background-position-x: 15%; + animation: progress-loading 5s ease-in-out infinite; +} + +.progress::-webkit-progress-bar { + border-radius: var(--rounded-box, 1rem); + background-color: transparent; +} + +.progress::-webkit-progress-value { + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); +} + +.progress:indeterminate::-moz-progress-bar { + background-color: transparent; + background-image: repeating-linear-gradient( + 90deg, + var(--progress-color) -1%, + var(--progress-color) 10%, + transparent 10%, + transparent 90% + ); + background-size: 200%; + background-position-x: 15%; + animation: progress-loading 5s ease-in-out infinite; +} + +@keyframes progress-loading { + 50% { + background-position-x: -115%; + } +} + +@keyframes radiomark { + 0% { + box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } + + 50% { + box-shadow: 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } + + 100% { + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } +} + +@keyframes rating-pop { + 0% { + transform: translateY(-0.125em); + } + + 40% { + transform: translateY(-0.125em); + } + + 100% { + transform: translateY(0); + } +} + +.select-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select-disabled, + .select:disabled, + .select[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-disabled::placeholder, + .select:disabled::placeholder, + .select[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-multiple, + .select[multiple], + .select[size].select:not([size="1"]) { + background-image: none; + padding-right: 1rem; +} + +[dir="rtl"] .select { + background-position: calc(0% + 12px) calc(1px + 50%), + calc(0% + 16px) calc(1px + 50%); +} + +@keyframes skeleton { + from { + background-position: 150%; + } + + to { + background-position: -50%; + } +} + +:where(.stats) > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); + --tw-divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(0px * var(--tw-divide-y-reverse)); +} + +:is([dir="rtl"] .stats > :not([hidden]) ~ :not([hidden])) { + --tw-divide-x-reverse: 1; +} + +:is([dir="rtl"] .table) { + text-align: right; +} + +.table :where(th, td) { + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + vertical-align: middle; +} + +.table tr.active, + .table tr.active:nth-child(even), + .table-zebra tbody tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.table :where(thead, tbody) :where(tr:not(:last-child)), + .table :where(thead, tbody) :where(tr:first-child:last-child) { + border-bottom-width: 1px; + --tw-border-opacity: 1; + border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + +.table :where(thead, tfoot) { + white-space: nowrap; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 700; + color: var(--fallback-bc,oklch(var(--bc)/0.6)); +} + +.textarea-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +@keyframes toast-pop { + 0% { + transform: scale(0.9); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +:root .prose { + --tw-prose-body: var(--fallback-bc,oklch(var(--bc)/0.8)); + --tw-prose-headings: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-lead: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-links: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-bold: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-counters: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-bullets: var(--fallback-bc,oklch(var(--bc)/0.5)); + --tw-prose-hr: var(--fallback-bc,oklch(var(--bc)/0.2)); + --tw-prose-quotes: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-quote-borders: var(--fallback-bc,oklch(var(--bc)/0.2)); + --tw-prose-captions: var(--fallback-bc,oklch(var(--bc)/0.5)); + --tw-prose-code: var(--fallback-bc,oklch(var(--bc)/1)); + --tw-prose-pre-code: var(--fallback-nc,oklch(var(--nc)/1)); + --tw-prose-pre-bg: var(--fallback-n,oklch(var(--n)/1)); + --tw-prose-th-borders: var(--fallback-bc,oklch(var(--bc)/0.5)); + --tw-prose-td-borders: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.prose :where(code):not(:where([class~="not-prose"] *)) { + padding: 2px 8px; + border-radius: var(--rounded-badge); +} + +.prose pre code { + border-radius: 0; + padding: 0; +} + +.prose :where(tbody tr, thead):not(:where([class~="not-prose"] *)) { + border-bottom-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.btn-xs { + height: 1.5rem; + min-height: 1.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + font-size: 0.75rem; +} + +.btn-sm { + height: 2rem; + min-height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; +} + +.btn-square:where(.btn-xs) { + height: 1.5rem; + width: 1.5rem; + padding: 0px; +} + +.btn-square:where(.btn-sm) { + height: 2rem; + width: 2rem; + padding: 0px; +} + +.btn-circle:where(.btn-xs) { + height: 1.5rem; + width: 1.5rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-sm) { + height: 2rem; + width: 2rem; + border-radius: 9999px; + padding: 0px; +} + +.input-xs, input.input-xs { + height: 1.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1.625; +} + +.input-sm, input.input-sm { + height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; + line-height: 2rem; +} + +.join.join-vertical { + flex-direction: column; +} + +.join.join-vertical .join-item:first-child:not(:last-child), + .join.join-vertical *:first-child:not(:last-child) .join-item { + border-end-start-radius: 0; + border-end-end-radius: 0; + border-start-start-radius: inherit; + border-start-end-radius: inherit; +} + +.join.join-vertical .join-item:last-child:not(:first-child), + .join.join-vertical *:last-child:not(:first-child) .join-item { + border-start-start-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-end-end-radius: inherit; +} + +.join.join-horizontal { + flex-direction: row; +} + +.join.join-horizontal .join-item:first-child:not(:last-child), + .join.join-horizontal *:first-child:not(:last-child) .join-item { + border-end-end-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join.join-horizontal .join-item:last-child:not(:first-child), + .join.join-horizontal *:last-child:not(:first-child) .join-item { + border-end-start-radius: 0; + border-start-start-radius: 0; + border-end-end-radius: inherit; + border-start-end-radius: inherit; +} + +.menu-horizontal { + display: inline-flex; + flex-direction: row; +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + position: absolute; +} + +.select-xs { + height: 1.5rem; + min-height: 1.5rem; + padding-left: 0.5rem; + padding-right: 2rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1.625; +} + +[dir="rtl"] .select-xs { + padding-left: 2rem; + padding-right: 0.5rem; +} + +.tooltip { + position: relative; + display: inline-block; + --tooltip-offset: calc(100% + 1px + var(--tooltip-tail, 0px)); +} + +.tooltip:before { + position: absolute; + pointer-events: none; + z-index: 1; + content: var(--tw-content); + --tw-content: attr(data-tip); +} + +.tooltip:before, .tooltip-top:before { + transform: translateX(-50%); + top: auto; + left: 50%; + right: auto; + bottom: var(--tooltip-offset); +} + +.join.join-vertical > :where(*:not(:first-child)) { + margin-left: 0px; + margin-right: 0px; + margin-top: -1px; +} + +.join.join-horizontal > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-inline-start: -1px; +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + margin-inline-start: 0px; + margin-top: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-inline-end: 0.5rem; +} + +.menu-horizontal > li > details > ul:before { + content: none; +} + +:where(.menu-horizontal > li:not(.menu-title) > details > ul) { + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.tooltip { + position: relative; + display: inline-block; + text-align: center; + --tooltip-tail: 0.1875rem; + --tooltip-color: var(--fallback-n,oklch(var(--n)/1)); + --tooltip-text-color: var(--fallback-nc,oklch(var(--nc)/1)); + --tooltip-tail-offset: calc(100% + 0.0625rem - var(--tooltip-tail)); +} + +.tooltip:before, +.tooltip:after { + opacity: 0; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-delay: 100ms; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.tooltip:after { + position: absolute; + content: ""; + border-style: solid; + border-width: var(--tooltip-tail, 0); + width: 0; + height: 0; + display: block; +} + +.tooltip:before { + max-width: 20rem; + border-radius: 0.25rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + background-color: var(--tooltip-color); + color: var(--tooltip-text-color); + width: -moz-max-content; + width: max-content; +} + +.tooltip.tooltip-open:before { + opacity: 1; + transition-delay: 75ms; +} + +.tooltip.tooltip-open:after { + opacity: 1; + transition-delay: 75ms; +} + +.tooltip:hover:before { + opacity: 1; + transition-delay: 75ms; +} + +.tooltip:hover:after { + opacity: 1; + transition-delay: 75ms; +} + +.tooltip:has(:focus-visible):after, +.tooltip:has(:focus-visible):before { + opacity: 1; + transition-delay: 75ms; +} + +.tooltip:not([data-tip]):hover:before, +.tooltip:not([data-tip]):hover:after { + visibility: hidden; + opacity: 0; +} + +.tooltip:after, .tooltip-top:after { + transform: translateX(-50%); + border-color: var(--tooltip-color) transparent transparent transparent; + top: auto; + left: 50%; + right: auto; + bottom: var(--tooltip-tail-offset); +} + +.collapse { + visibility: collapse; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.m-auto { + margin: auto; +} + +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.mx-10 { + margin-left: 2.5rem; + margin-right: 2.5rem; +} + +.mx-6 { + margin-left: 1.5rem; + margin-right: 1.5rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +.mb-0 { + margin-bottom: 0px; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.table { + display: table; +} + +.hidden { + display: none; +} + +.h-48 { + height: 12rem; +} + +.max-h-52 { + max-height: 13rem; +} + +.w-10 { + width: 2.5rem; +} + +.w-20 { + width: 5rem; +} + +.w-32 { + width: 8rem; +} + +.w-44 { + width: 11rem; +} + +.w-9 { + width: 2.25rem; +} + +.w-96 { + width: 24rem; +} + +.w-\[40rem\] { + width: 40rem; +} + +.w-full { + width: 100%; +} + +.w-\[700px\] { + width: 700px; +} + +.w-\[900px\] { + width: 900px; +} + +.w-0 { + width: 0px; +} + +.w-\[30rem\] { + width: 30rem; +} + +.w-\[32rem\] { + width: 32rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-sm { + max-width: 24rem; +} + +.max-w-xs { + max-width: 20rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-none { + flex: none; +} + +.grow { + flex-grow: 1; +} + +.resize { + resize: both; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.border { + border-width: 1px; +} + +.border-solid { + border-style: solid; +} + +.border-gray-400 { + --tw-border-opacity: 1; + border-color: rgb(156 163 175 / var(--tw-border-opacity)); +} + +.bg-base-100 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.bg-base-200 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.bg-base-300 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; +} + +.pb-0 { + padding-bottom: 0px; +} + +.text-center { + text-align: center; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.capitalize { + text-transform: capitalize; +} + +.text-base-content { + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.stream-container { + margin: 0.5rem; + display: inline-block; + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + text-align: left; + width: 400px; +} + +.response-box { + position: fixed; + bottom: 0.75rem; + right: 0.75rem; + height: 12rem; + width: 24rem; + overflow: auto; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + padding: 0.5rem; + opacity: 0.9; + border-radius: var(--rounded-box, 1rem); + border-width: 1px; + border-top-width: 4px; + border-bottom-width: 4px; + border-style: solid; + --tw-border-opacity: 1; + border-color: var(--fallback-nc,oklch(var(--nc)/var(--tw-border-opacity))); + z-index: 999; +} + +.stream-status { + margin-left: 0.25rem; + margin-right: 0.25rem; + display: inline-block; + height: 0.75rem; + width: 0.75rem; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + border-radius: 50%; +} + +.stream-status.on { + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); +} + +.collapse-title { + font-size: 1rem !important; + line-height: 1.5rem !important; +} + +.table :where(th, td) { + padding: 0.25rem; +} + +.input-label { + display: flex; + flex-direction: column; + margin-right: 0.25rem; + display: inline-block; + max-width: 28rem; +} + +.labeled-input { + flex-shrink: 1; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.labeled-input input:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.labeled-input[list]::-webkit-calendar-picker-indicator { + line-height: 1em; +} + +.labeled-input { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.labeled-input:focus,.labeled-input:focus-within { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.labeled-input:disabled,.labeled-input[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.labeled-input:disabled::-moz-placeholder, .labeled-input[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.labeled-input:disabled::placeholder,.labeled-input[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.labeled-input::-webkit-date-and-time-value { + text-align: inherit; +} + +.mockup-browser .mockup-browser-toolbar .labeled-input { + position: relative; + margin-left: auto; + margin-right: auto; + display: block; + height: 1.75rem; + width: 24rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding-left: 2rem; + direction: ltr; +} + +.mockup-browser .mockup-browser-toolbar .labeled-input:before { + content: ""; + position: absolute; + left: 0.5rem; + top: 50%; + aspect-ratio: 1 / 1; + height: 0.75rem; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 2px; + border-color: currentColor; + opacity: 0.6; +} + +.mockup-browser .mockup-browser-toolbar .labeled-input:after { + content: ""; + position: absolute; + left: 1.25rem; + top: 50%; + height: 0.5rem; + --tw-translate-y: 25%; + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 1px; + border-color: currentColor; + opacity: 0.6; +} + +.labeled-input, input.labeled-input { + height: 1.5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1.625; +} + +.labeled-input { + width: 100%; + max-width: 28rem; +} + +.labeled-select { + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + min-height: 3rem; + padding-left: 1rem; + padding-right: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), + linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), + calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, + 4px 4px; + background-repeat: no-repeat; +} + +.labeled-select[multiple] { + height: auto; +} + +.labeled-select { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.labeled-select:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.labeled-select:disabled,.labeled-select[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + --tw-text-opacity: 0.2; +} + +.labeled-select:disabled::-moz-placeholder, .labeled-select[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.labeled-select:disabled::placeholder,.labeled-select[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.labeled-select[multiple],.labeled-select[size].select:not([size="1"]) { + background-image: none; + padding-right: 1rem; +} + +[dir="rtl"] .labeled-select { + background-position: calc(0% + 12px) calc(1px + 50%), + calc(0% + 16px) calc(1px + 50%); +} + +.labeled-select { + height: 1.5rem; + min-height: 1.5rem; + padding-left: 0.5rem; + padding-right: 2rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1.625; +} + +[dir="rtl"] .labeled-select { + padding-left: 2rem; + padding-right: 0.5rem; +} + +.collapsible-header:not(td):not(tr):not(colgroup) { + visibility: visible; +} + +.collapsible-header { + position: relative; + display: grid; + overflow: hidden; + grid-template-rows: auto 0fr; + transition: grid-template-rows 0.2s; + width: 100%; + border-radius: var(--rounded-box, 1rem); +} + +.collapsible-header > input[type="checkbox"],.collapsible-header > input[type="radio"] { + grid-column-start: 1; + grid-row-start: 1; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + opacity: 0; +} + +.collapsible-header[open],.collapsible-header:focus:not(.collapse-close) { + grid-template-rows: auto 1fr; +} + +.collapsible-header:not(.collapse-close):has(> input[type="checkbox"]:checked),.collapsible-header:not(.collapse-close):has(> input[type="radio"]:checked) { + grid-template-rows: auto 1fr; +} + +.collapsible-header[open] > .collapse-content,.collapsible-header:focus:not(.collapse-close) > .collapse-content,.collapsible-header:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-content,.collapsible-header:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-content { + visibility: visible; + min-height: -moz-fit-content; + min-height: fit-content; +} + +details.collapsible-header { + width: 100%; +} + +details.collapsible-header summary { + position: relative; + display: block; + outline: 2px solid transparent; + outline-offset: 2px; +} + +details.collapsible-header summary::-webkit-details-marker { + display: none; +} + +.collapsible-header:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapsible-header:has(.collapse-title:focus-visible),.collapsible-header:has(> input[type="checkbox"]:focus-visible),.collapsible-header:has(> input[type="radio"]:focus-visible) { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.collapsible-header > .collapse-title:after { + position: absolute; + display: block; + height: 0.5rem; + width: 0.5rem; + --tw-translate-y: -100%; + --tw-rotate: 45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 150ms; + transition-duration: 0.2s; + top: 50%; + inset-inline-end: 1.4rem; + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.collapsible-header:not(.collapse-open):not(.collapse-close) > input[type="checkbox"],.collapsible-header:not(.collapse-open):not(.collapse-close) > input[type="radio"]:not(:checked),.collapsible-header:not(.collapse-open):not(.collapse-close) > .collapse-title { + cursor: pointer; +} + +.collapsible-header:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open]) > .collapse-title { + cursor: unset; +} + +:where(.collapsible-header > input[type="checkbox"]), +:where(.collapsible-header > input[type="radio"]) { + z-index: 1; +} + + +:where(.collapsible-header > input[type="checkbox"]), +:where(.collapsible-header > input[type="radio"]) { + width: 100%; + padding: 1rem; + padding-inline-end: 3rem; + min-height: 3.75rem; + transition: background-color 0.2s ease-out; +} + +.collapsible-header[open] > :where(.collapse-content),.collapsible-header:focus:not(.collapse-close) > :where(.collapse-content),.collapsible-header:not(.collapse-close) > :where(input[type="checkbox"]:checked ~ .collapse-content),.collapsible-header:not(.collapse-close) > :where(input[type="radio"]:checked ~ .collapse-content) { + padding-bottom: 1rem; + transition: padding 0.2s ease-out, + background-color 0.2s ease-out; +} + +.collapsible-header[open].collapse-arrow > .collapse-title:after { + --tw-translate-y: -50%; + --tw-rotate: 225deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.collapse[open].collapsible-header > .collapse-title:after, +.collapse-open.collapsible-header > .collapse-title:after,.collapsible-header:focus:not(.collapse-close) > .collapse-title:after,.collapsible-header:not(.collapse-close) > input[type="checkbox"]:checked ~ .collapse-title:after,.collapsible-header:not(.collapse-close) > input[type="radio"]:checked ~ .collapse-title:after { + --tw-translate-y: -50%; + --tw-rotate: 225deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.collapsible-header[open].collapse-plus > .collapse-title:after { + content: "−"; +} + +.collapsible-header { + visibility: collapse; + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.collapse-title { + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 500; +} diff --git a/html/fetch-stream-names.php b/html/fetch-stream-names.php new file mode 100644 index 0000000..87f4e87 --- /dev/null +++ b/html/fetch-stream-names.php @@ -0,0 +1,15 @@ + $csvData)); +} +?> + diff --git a/html/img/blue-divider.svg b/html/img/blue-divider.svg new file mode 100644 index 0000000..248c1d2 --- /dev/null +++ b/html/img/blue-divider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/html/img/divider.svg b/html/img/divider.svg new file mode 100644 index 0000000..54b9fbb --- /dev/null +++ b/html/img/divider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/html/img/isha-logo.png b/html/img/isha-logo.png new file mode 100644 index 0000000..7b8d729 Binary files /dev/null and b/html/img/isha-logo.png differ diff --git a/html/img/light-divider.svg b/html/img/light-divider.svg new file mode 100644 index 0000000..71a5480 --- /dev/null +++ b/html/img/light-divider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/html/img/red-divider.svg b/html/img/red-divider.svg new file mode 100644 index 0000000..9a31c2c --- /dev/null +++ b/html/img/red-divider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/html/js/control.js b/html/js/control.js new file mode 100644 index 0000000..2bf0139 --- /dev/null +++ b/html/js/control.js @@ -0,0 +1,268 @@ +function renderStreamControls() { + const streamControls = document.getElementById('stream-controls'); + + let html = ''; + for (let i = 1; i <= STREAM_NUM; i++) { + // Create the div container + html += ` +
+
+ ${getJsmpegPlayerHtml(i)} +
+
+ More + +
`; + + html += ` +
+ + + Record +
+ +
+ Overlays: + Add 1 + Add 2 + Add 3 + Add 4 + Add 5 + Add 6 + Add 7 + Add 8 + Remove +
+ +
+

+ Volume: + + +

+
+ +
+ + Choose Input: +
+
+ + + Main Live Stream +
+
+ + + Backup Live stream +
+ +
+ + Uploaded + + m + s +
+ +
+ + Playlist +
`; + + html += ` +
+
+
`; + } + streamControls.innerHTML = html; +} + +function renderStreamHeaders() { + const streams = getActiveStreams(); + let statuses = { + distribute: Array(STREAM_NUM), + main: Array(STREAM_NUM), + backup: Array(STREAM_NUM), + }; + streams.distribute.forEach((stream) => (statuses.distribute[stream.streamId] = true)); + streams.main.forEach((stream) => (statuses.main[stream.streamId] = true)); + streams.backup.forEach((stream) => (statuses.backup[stream.streamId] = true)); + + for (let i = 1; i <= STREAM_NUM; i++) { + const headerElem = document.getElementById(`streamHeader${i}`); + const streamName = streamNames[i]; + const suffix = streamName ? ` (${streamName})` : ''; + headerElem.innerHTML = ` + + Stream ${i}${suffix}`; + + document.getElementById(`main-status${i}`).className = + `stream-status ${statuses.main[i] ? 'on' : 'off'}`; + document.getElementById(`backup-status${i}`).className = + `stream-status ${statuses.backup[i] ? 'on' : 'off'}`; + } +} + +function renderOuts() { + const actives = getActiveOuts(); + let statuses = Array(STREAM_NUM) + .fill() + .map((_) => []); + actives.forEach((out) => (statuses[out.streamId][out.outId] = true)); + + for (let i = 1; i <= STREAM_NUM; i++) { + const outsDiv = document.getElementById(`stream-outs-${i}`); + + let outsHtml = ''; + // we need to slice slice(0, STREAM_NUM) because outs 98 are used for recording. + const outSize = streamOutsConfig[i] + .slice(0, STREAM_NUM) + .findLastIndex((info) => !isOutEmpty(info)); + + for (var j = 1; j <= outSize; j++) { + let info = streamOutsConfig[i][j]; + if (isOutEmpty(info)) info = { stream: '', out: '', url: '', encoding: '', name: '' }; + const on = ``; + const off = ``; + let title = `Out ${j}`; + let destDiv = `${info.name}`; + if (info.name !== '') { + title = + (info.encoding === 'source' ? '' : `${capitalize(info.encoding)} `) + + `${title}: `; + destDiv = `
${destDiv}
`; + } + outsHtml += ` +
+ + ${on} ${off} ${title} ${destDiv} +
`; + } + if (outSize < 1) { + outsHtml += 'No configured outs...'; + } + + outsDiv.innerHTML = outsHtml; + } +} + +function parseOutputStreamName(str) { + const dashIndex = str.indexOf('-'); + return { + streamId: Number(str.substring(6, dashIndex)), + destinationName: str.substring(dashIndex + 1), + }; +} + +function getActiveOuts() { + let streams = statsJson.rtmp.server.application.find((app) => app.name['#text'] == 'output') + .live.stream; + if (streams === undefined) streams = []; // no streams + if (!Array.isArray(streams)) streams = [streams]; // only one stream + streams = streams.map((s) => s.name['#text']); + + return streams + .map((name) => parseOutputStreamName(name)) + .map((p) => ({ + streamId: p.streamId, + outId: streamOutsConfig[p.streamId].findIndex( + (info) => info?.name === p.destinationName, + ), + })) + .filter((p) => p.outId !== -1); +} + +function extractStreamIds(streamsStats) { + let streams = streamsStats; + if (streams === undefined) streams = []; // no streams + if (!Array.isArray(streams)) streams = [streams]; // only one stream + + return streams + .map((s) => s.name['#text']) + .map((name) => ({ streamId: Number(name.substring(6)) })); +} + +function getActiveStreams() { + let distribute = statsJson.rtmp.server.application.find( + (app) => app.name['#text'] == 'distribute', + ).live.stream; + let main = statsJson.rtmp.server.application.find((app) => app.name['#text'] == 'main').live + .stream; + let backup = statsJson.rtmp.server.application.find((app) => app.name['#text'] == 'backup').live + .stream; + + return { + distribute: extractStreamIds(distribute), + main: extractStreamIds(main), + backup: extractStreamIds(backup), + }; +} + +function batchInputControlClick(isOn) { + const inputType = document.getElementById('inputType').value; + const streams = document + .getElementById('batchInputStreams') + .value.split(' ') + .map((id) => id.trim()) + .filter((id) => id !== ''); + executePhpAndShowResponse( + '/control.php?batch-input-control', + { 'Content-Type': 'application/json' }, + JSON.stringify({ + inputType: inputType, + streams: streams, + state: isOn ? 'on' : 'off', + }), + ); +} + +async function rerender() { + streamNames = await fetchStreamNames(); + statsJson = await fetchStats(); + streamOutsConfig = await fetchConfigFile(); + renderStreamHeaders(); + renderOuts(); +} + +window.onload = async function () { + renderStreamControls(); + setVideoPlayers(); + rerender(); + setInterval(rerender, 5000); +}; + +(function renderServerDetails() { + const address = window.location.hostname; + const detailsElem = document.getElementById('server-details'); + detailsElem.innerHTML = detailsElem.innerHTML.replaceAll('${address}', address); +})(); diff --git a/html/js/jsmpeg.js b/html/js/jsmpeg.js new file mode 100644 index 0000000..17ee496 --- /dev/null +++ b/html/js/jsmpeg.js @@ -0,0 +1,84 @@ +function getJsmpegPlayerHtml(streamId) { + return ` +
+ ${ + streamId === 1 + ? ` + ` + : `` + } +

+ + play_arrowM + + play_arrowB + + play_arrowD + stop + volume_up + volume_down +

+
`; +} + +function genericFunction(url, cFunction, elem) { + var streamno = elem.parentNode.id; + url += streamno; + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function () { + if (this.readyState == 4 && this.status == 200) { + cFunction(this, streamno); + } + }; + xhttp.open('GET', url, true); + xhttp.send(); +} + +function jsmpegPlay(xhttp, streamno) { + var stream1 = document.getElementById(streamno); + stream1.parentElement.insertBefore(canvas1, stream1); + if (player1 == 'initial state') { + player1 = new JSMpeg.Player(url1, { + canvas: canvas1, + autoplay: false, + pauseWhenHidden: false, + videoBufferSize: 100 * 1024, + audioBufferSize: 50 * 1024, + }); + } + player1.play(); + player1.volume = 1; + return false; +} + +function jsmpegStop() { + player1.stop(); + return false; +} + +function jsmpegVolumeup() { + player1.volume = player1.volume + 0.2; + return false; +} + +function jsmpegVolumedown() { + player1.volume = player1.volume - 0.2; + if (player1.volume < 0) { + player1.volume = 0; + } + return false; +} + +function setVideoPlayers() { + for (let i = 1; i <= STREAM_NUM; i++) { + for (let j = 1; j <= OUT_NUM; j++) { + eval(`window.canvas${i} = document.getElementById('video-canvas${i}');`); + eval(`window.url${i} = 'ws://' + document.location.hostname + ':443/';`); + eval(`window.player${i} = 'initial state';`); + } + } +} diff --git a/html/js/settings.js b/html/js/settings.js new file mode 100644 index 0000000..76aaf2e --- /dev/null +++ b/html/js/settings.js @@ -0,0 +1,102 @@ +function renderStreamSelectors() { + const streamSelectors = document.getElementsByClassName('stream-selector'); + + for (let selector of streamSelectors) clearAndAddChooseOption(selector); + + for (let i = 1; i <= STREAM_NUM; i++) { + for (let selector of streamSelectors) { + let option = document.createElement('option'); + option.value = String(i); + const name = streamNames[i]; + option.text = 'Stream ' + i + (name ? ': ' + name : ''); + selector.appendChild(option); + } + } +} + +function renderOutputs() { + const outSelector = document.getElementById('out-selector'); + clearAndAddChooseOption(outSelector); + + for (let i = 1; i <= OUT_NUM; i++) { + let option = document.createElement('option'); + option.value = String(i); + option.text = 'Out ' + i; + outSelector.appendChild(option); + } +} + +function updateRtmpUrl() { + const serverUrl = document.getElementById('server-url').value; + const streamKey = document.getElementById('stream-key').value; + const rtmpUrl = document.getElementById('rtmp-url'); + rtmpUrl.value = serverUrl + streamKey; +} + +function renderStreamNameTable() { + const tableHead = document.getElementById('name-table').tHead; + let tHeadHtml = ''; + for (let i = 1; i <= STREAM_NUM; i++) { + tHeadHtml += `Stream ${i}`; + } + tHeadHtml += ''; + tableHead.innerHTML = tHeadHtml; + + const table = document.getElementById('name-table-body'); + let tr = table.insertRow(); + for (let i = 1; i <= STREAM_NUM; i++) { + const td = tr.insertCell(); + const input = document.createElement('input'); + input.className = 'input input-bordered w-20 max-w-sm input-xs'; + input.type = 'text'; + input.size = '20'; + input.value = streamNames[i]; + input.name = streamNames[i]; + td.appendChild(input); + } +} + +function extractStreamNamesFromTable() { + const table = document.getElementById('name-table'); + + const ans = ['ignored']; + const row = table.rows[1]; + for (let i = 0; i < STREAM_NUM; i++) { + const cell = row.cells[i]; + ans.push(cell.firstChild.value); + } + return ans; +} + +function saveStreamNamesTable() { + streamNames = extractStreamNamesFromTable(); + renderStreamSelectors(); + writeStreamNames(); +} + +function bulkSetOuts() { + const rawText = document.getElementById('bulk-outs').value; + const outs = rawText + .split('\n') + .map((line) => parseOutLine(line)) + .filter((out) => !isOutEmpty(out)) + .map((out) => ({ + name_id: out.name, + stream_id: out.stream, + output_id: out.out, + resolution: out.encoding, + rtmp_url: out.url, + })); + executePhpAndShowResponse( + `config.php?bulkset`, + { 'Content-Type': 'application/json' }, + JSON.stringify(outs), + ); +} + +window.onload = async function () { + streamNames = await fetchStreamNames(); + renderOutputs(); + renderStreamSelectors(); + renderStreamNameTable(); +}; diff --git a/html/js/tools.js b/html/js/tools.js new file mode 100644 index 0000000..f999e4c --- /dev/null +++ b/html/js/tools.js @@ -0,0 +1,182 @@ +// CONSTANTS +const STREAM_NUM = 25; +const OUT_NUM = 95; + +// This will be fetched from a file +let streamNames = []; +let streamOutsConfig = []; +let statsJson = []; + +function capitalize(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +// Tools +function removeAllChildNodes(parent) { + while (parent.firstChild) { + parent.removeChild(parent.firstChild); + } +} +function clearAndAddChooseOption(selector) { + removeAllChildNodes(selector); + let option = document.createElement('option'); + option.value = ''; + option.text = 'Choose'; + selector.appendChild(option); +} + +// AJAX request function +async function submitFormAndShowResponse(formId, phpUrl) { + const form = document.getElementById(formId); + if (form.checkValidity()) { + const formData = new FormData(form); + executePhpAndShowResponse(phpUrl, {}, formData); + } else { + form.reportValidity(); + } +} + +async function executePhpAndShowResponse(phpUrl, headers = {}, body = undefined) { + try { + const response = await fetch(phpUrl, { method: 'POST', headers: headers, body: body }); + if (response.ok) { + showResponse(await response.text()); + } else { + console.error('Request failed with status:', response.status); + } + } catch (error) { + console.error('Error:', error); + } +} + +function showResponse(response) { + var responseBox = document.getElementById('responseBox'); + responseBox.innerHTML = `

${response}

` + responseBox.innerHTML; +} + +async function fetchStats() { + try { + const response = await fetch('/stat-test.xml'); + const data = await response.text(); + const parser = new DOMParser(); + const xmlData = parser.parseFromString(data, 'text/xml'); + return xml2json(xmlData); + } catch (error) { + console.error('Error fetching stats data:', error); + } +} + +async function writeStreamNames() { + var xhr = new XMLHttpRequest(); + xhr.open('POST', 'save-stream-names.php', true); + xhr.setRequestHeader('Content-Type', 'application/json'); + + xhr.onreadystatechange = function () { + if (xhr.readyState === XMLHttpRequest.DONE) { + showResponse(xhr.responseText); + } + }; + + var jsonData = JSON.stringify({ csvData: [streamNames] }); + xhr.send(jsonData); +} + +async function fetchStreamNames() { + try { + const response = await fetch('fetch-stream-names.php'); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + const data = await response.json(); + const streamNames = data.csvData[0]; + return streamNames; + } catch (error) { + console.error('Error fetching stream names:', error); + } + return []; +} + +function parseOutLine(text) { + const matches = text.match(/^__stream(\d+)__out(\d+)__(.*)$/); + const split = matches ? matches[3].trim().split(' ') : []; + if (matches && split.length === 3) { + return { + stream: matches[1], + out: matches[2], + url: split[0], + encoding: split[1], + name: split[2], + }; + } else { + return {}; + } +} + +function parseOutsConfig(text) { + const lines = text.split('\n'); + + const streamOutsConfig = []; + for (let i = 1; i <= STREAM_NUM; i++) { + streamOutsConfig[i] = []; + for (let j = 1; j <= OUT_NUM; j++) { + streamOutsConfig[i][j] = {}; + } + } + + lines + .filter((line) => line !== '') + .forEach((line) => { + out = parseOutLine(line); + if (!isOutEmpty(out)) streamOutsConfig[out.stream][out.out] = out; + }); + + return streamOutsConfig; +} + +function isOutEmpty(out) { + return out?.name ? false : true; +} + +async function fetchConfigFile() { + const response = await fetch('/config.txt'); + return parseOutsConfig(await response.text()); +} + +function xml2json(xml) { + // Create the return object + var obj = {}; + + if (xml.nodeType == 1) { + // element + // do attributes + if (xml.attributes.length > 0) { + obj['@attributes'] = {}; + for (var j = 0; j < xml.attributes.length; j++) { + var attribute = xml.attributes.item(j); + obj['@attributes'][attribute.nodeName] = attribute.nodeValue; + } + } + } else if (xml.nodeType == 3) { + // text + obj = xml.nodeValue; + } + + // do children + if (xml.hasChildNodes()) { + for (var i = 0; i < xml.childNodes.length; i++) { + var item = xml.childNodes.item(i); + var nodeName = item.nodeName; + if (typeof obj[nodeName] == 'undefined') { + obj[nodeName] = xml2json(item); + } else { + if (typeof obj[nodeName].push == 'undefined') { + var old = obj[nodeName]; + obj[nodeName] = []; + obj[nodeName].push(old); + } + obj[nodeName].push(xml2json(item)); + } + } + } + return obj; +} diff --git a/html/mobile.html b/html/mobile.html deleted file mode 100644 index 1176606..0000000 --- a/html/mobile.html +++ /dev/null @@ -1,589 +0,0 @@ - -Primary Server - Location - - - -

Primary/Secondary Server - Location

-
Stats ||| Recordings ||| Settings ||| Processes ||| Mobile Version
-

Test

- -

No. of Audio Channels:

-

Turn off Remapping

- -

SRT Accept: ON ||| OFF

- -

Supers - Global Control: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Stream 1

-
black
- - - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- - -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- -

Stream 2

-
black
- - -
-

YT: On ||| Off

-

FB: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

YT_bk: On ||| Off

-

FB_bk: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

SG YT Out1: On ||| Off

-

SG FB Out2: On ||| Off

-

SG TW Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

-
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- - -
black
- - -
-

Out1: On ||| Off

-

Out2: On ||| Off

-

Out3: On ||| Off

-

Out4: On ||| Off

-

Out5: On ||| Off

-

Out6: On ||| Off

-

Out7: On ||| Off

-

Out8: On ||| Off

-

Out9: On ||| Off

-

Out10: On ||| Off

-

Record: On ||| Off

-

Instagram: On ||| Off ||| For Emergencies --> ||| Turn on out99 ||| Turn off out99

- -

Supers: Add 1 ||| Add 2 ||| Add 3 ||| Add 4 ||| Add 5 ||| Add 6 ||| Add 7 ||| Add 8 ||| Remove

- -

Volume:

- -

Choose Input: Main ||| Backup ||| Holding Screen ||| Ad Video ||| Playlist ||| Turn off

- -
- -







- diff --git a/html/player.php b/html/player.php index 16efdf9..4d2ac59 100644 --- a/html/player.php +++ b/html/player.php @@ -1,53 +1,21 @@ - diff --git a/html/save-stream-names.php b/html/save-stream-names.php new file mode 100644 index 0000000..559d004 --- /dev/null +++ b/html/save-stream-names.php @@ -0,0 +1,14 @@ + diff --git a/html/settings.html b/html/settings.html index f2d9077..04553bb 100644 --- a/html/settings.html +++ b/html/settings.html @@ -1,126 +1,493 @@ + + + + + Settings + + + + + + + + + +
- -NGINX Settings - - - -
-

Add New Destination

-
-Full RTMP Url:

-Name:
-Stream Id:
-Output Id:
-Resolution:

-
-
-

Destination List

-

Note: If a login/password has to be added, enter the details in the format rtmp://server/streamkey-+-+flashver=FMLE/3.020(compatible;20FMSc/1.0)-+-+pubUser=login-+-+pubPasswd=password

-
- -
- -
-

Add Stream Config

-
-Stream Id:
-Config:
-Resolution:
-Failover:

-
-
-

Stream Configs List

-
- -
-
-

Add Audio Config

-
-Stream Id:
-Mono/Stereo:
-Channel 1:
-Channel 2:
-Main/Distribute:
-
-
-

- -

Audio Configs List

- -
- -
-

Instagram Authentication Test

-
-Stream Id:
-
-
- -
-
-

Instagram Login/Pass

-
-Login:
-Password:
-Stream Id:
-
-
-
- - -
- -
-

Add/Remove Schedule

-

Schedule:

-

Schedule List

-
- -
-

Upload Lowerthirds

-
- Select image to upload: - - -
-
- -
- -
-

Upload Holding Screens

-
-Url:

-Stream Id:
-Video Type:
-
-
-
-
- -


+
+
+
+ Stream Names + [file] +
+
+

+ You can assign names for the streams. This name will only have visual effect, and have + no impact on the backend. +

+
+
+
+ + + +
+
+ + + +
+ +
+
+ Set Outputs + [file] +
+
+

+ The nginx server receives the feed and sends it to a multiple outputs, such as when + streaming on the YouTube or Facebook. You can use this interface to add such + destination. +
+ A few important points: +

+
    +
  • "Name" field should not contain any spaces. Please use underscores "_".
  • +
  • + If a login/password has to be added, enter the details in the format: +
    + + rtmp://server/streamkey-+-+flashver=FMLE/3.020(compatible;20FMSc/1.0)-+-+pubUser=login-+-+pubPasswd=password + +
  • +
+
+
+
+ + + + + + + + +
+ + + + + + +
+ + +
+ +
+ +
+ + + +
+ +
+
+ Add Stream Config + [file] +
+
+

+ You can apply an encoding to the stream. When encoding 1080p video into + 720p it takes about 2.5 vCores per stream. +
+ Select "None" if you don't want any encodings then it will not take much CPU resources + since it will be be only copying the data and not processing it. +
+ Select "Vertical" when streaming to Instagram. It will rotate the video by 90 degrees + clockwise and encode into + 720p resolution. +

+
+
+
+ + + + + +
+ +
+ +
+
+ Add Audio Config + [file] +
+
+

Description...

+
+
+
+ Stream Id: + +
+ Mono/Stereo: + +
+ Channel 1: + +
+ Channel 2: + +
+ Main/Distribute: + +
+ +
+
+ + +
+ +
+ +
+
+ Add/Remove Schedule + [file] +
+
+

Description...

+
+
+
+

+ Schedule: + + + + + + + + + +
+ +

+
+ +
+ +
+
+ Upload Google Drive Video + [file] +
+
+

Description...

+
+
+ +
+ + + + + +
+ +
+ +
+
Upload Lowerthirds
+
+

Description...

+
+
+
+ Select image to upload: + +
+ +
+ +
+ +
+
Upload Holding Screens
+
+

Description...

+
+
+
+ URL: + +
+ Stream Id: + +
+ Video Type: + +
+
+
+
+ + + diff --git a/html/stat-test.xml b/html/stat-test.xml new file mode 100644 index 0000000..6f2506c --- /dev/null +++ b/html/stat-test.xml @@ -0,0 +1,160 @@ + +1.13.12 +1.1.4 +gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12) +Feb 14 2021 14:34:22 +1821 +2545 +1315 +25607024 +4027782836 +18479984 +2557161163 + + +output + + +stream1-test +3882168 +7952373 +0 +0 +137496 +3744664 +2232
127.0.0.1
FMLE/3.0 (compatible; Lavf58.1202322890
+ +1 + + +
+ +stream3-LB_Wb_Tamil +1756400 +277076232 +0 +0 +107864 +1648528 +1998
127.0.0.1
FMLE/3.0 (compatible; Lavf58.12032347072
+ +1 + + +
+ +stream1-LB_Wb_Eng +3108648 +469676226 +0 +0 +111176 +2997464 +1988
127.0.0.1
FMLE/3.0 (compatible; Lavf58.12031354731
+ +1 + + +
+ +stream2-LB_Wb_Hindi +6104664 +358935901 +0 +0 +107208 +5997456 +1993
127.0.0.1
FMLE/3.0 (compatible; Lavf58.12034350634
+ +1 + + +
+4 +
+
+ +main + +0 + + + +input + +0 + + + +distribute + + +stream1 +3130264 +606316687 +9390808 +1080381775 +109656 +3020608 +2228
127.0.0.1
LNX 9,0,124,2028441228
+1985
127.0.0.1
LNX 9,0,124,2028441228
+1919
127.0.0.1
LNX 9,0,124,2028441228
+1918
127.0.0.1
FMLE/3.0 (compatible; Lavf58.12028441228
+ +4 + + +
+ +stream2 +5499688 +430162462 +5499688 +359194023 +107144 +5392544 +1990
127.0.0.1
LNX 9,0,124,200433000
+1927
127.0.0.1
FMLE/3.0 (compatible; Lavf58.1200433000
+ +2 + + +
+ +stream3 +1750832 +358274226 +1750832 +277081029 +108216 +1642608 +1995
127.0.0.1
LNX 9,0,124,203426323
+1934
127.0.0.1
FMLE/3.0 (compatible; Lavf58.1203426323
+ +2 + + +
+8 +
+
+ +backup + +0 + + + +recording + +0 + + + +live + +0 + + +
+
diff --git a/html/stat.xsl b/html/stat.xsl index c010491..314d8ee 100755 --- a/html/stat.xsl +++ b/html/stat.xsl @@ -12,45 +12,127 @@ - RTMP statistics + Stats + + + + + + + + + +
- Generated by - nginx-rtmp-module , - nginx , + Generated by + nginx-rtmp-module   , + nginx  , pid , built   + + +
- - - - - - - - - - - - +
RTMP#clientsVideoAudioIn bytesOut bytesIn bits/sOut bits/sStateTime
+ + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + +
RTMP#clientsTimeVideoAudioIn bytesOut bytesIn bits/sOut bits/sState
Accepted: codecbits/ssizefpscodecbits/sfreqchan + + + + + codecbits/ssizefpscodecbits/sfreqchan @@ -76,11 +158,7 @@ - - - - -
@@ -91,7 +169,7 @@
- + @@ -101,23 +179,23 @@ - + - live streams + Live Streams - - + + - + vod streams - + @@ -128,12 +206,12 @@ - #cccccc + #F4F6F6 #dddddd - - + +
var d=document.getElementById('-'); d.style.display=d.style.display=='none'?'':'none'; @@ -143,13 +221,22 @@ [EMPTY] - +
+ + + + + + + + + + -    - + @@ -165,7 +252,7 @@   - + @@ -203,18 +290,14 @@ - - - - - + - - +
@@ -229,6 +312,10 @@
Id State
+ + + +
@@ -287,16 +374,16 @@ - active - idle + Active + Idle - publishing - playing + Publishing + Playing @@ -305,7 +392,7 @@ - #cccccc + #F4F6F6 #eeeeee diff --git a/html/stream-names.csv b/html/stream-names.csv new file mode 100644 index 0000000..adf8a2e --- /dev/null +++ b/html/stream-names.csv @@ -0,0 +1 @@ +ignored,English,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/html/websocket-relay.js b/html/websocket-relay.js index 0b9ab57..c40404a 100644 --- a/html/websocket-relay.js +++ b/html/websocket-relay.js @@ -9,10 +9,7 @@ var fs = require('fs'), WebSocket = require('ws'); if (process.argv.length < 3) { - console.log( - 'Usage: \n' + - 'node websocket-relay.js [ ]' - ); + console.log('Usage: \n' + 'node websocket-relay.js [ ]'); process.exit(); } @@ -22,24 +19,22 @@ var STREAM_SECRET = process.argv[2], RECORD_STREAM = false; // Websocket Server -var socketServer = new WebSocket.Server({port: WEBSOCKET_PORT, perMessageDeflate: false}); +var socketServer = new WebSocket.Server({ port: WEBSOCKET_PORT, perMessageDeflate: false }); socketServer.connectionCount = 0; -socketServer.on('connection', function(socket, upgradeReq) { +socketServer.on('connection', function (socket, upgradeReq) { socketServer.connectionCount++; console.log( 'New WebSocket Connection: ', (upgradeReq || socket.upgradeReq).socket.remoteAddress, (upgradeReq || socket.upgradeReq).headers['user-agent'], - '('+socketServer.connectionCount+' total)' + '(' + socketServer.connectionCount + ' total)', ); - socket.on('close', function(code, message){ + socket.on('close', function (code, message) { socketServer.connectionCount--; - console.log( - 'Disconnected WebSocket ('+socketServer.connectionCount+' total)' - ); + console.log('Disconnected WebSocket (' + socketServer.connectionCount + ' total)'); }); }); -socketServer.broadcast = function(data) { +socketServer.broadcast = function (data) { socketServer.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(data); @@ -48,30 +43,31 @@ socketServer.broadcast = function(data) { }; // HTTP Server to accept incomming MPEG-TS Stream from ffmpeg -var streamServer = http.createServer( function(request, response) { +var streamServer = http.createServer(function (request, response) { var params = request.url.substr(1).split('/'); if (params[0] !== STREAM_SECRET) { console.log( - 'Failed Stream Connection: '+ request.socket.remoteAddress + ':' + - request.socket.remotePort + ' - wrong secret.' + 'Failed Stream Connection: ' + + request.socket.remoteAddress + + ':' + + request.socket.remotePort + + ' - wrong secret.', ); response.end(); } response.connection.setTimeout(0); console.log( - 'Stream Connected: ' + - request.socket.remoteAddress + ':' + - request.socket.remotePort + 'Stream Connected: ' + request.socket.remoteAddress + ':' + request.socket.remotePort, ); - request.on('data', function(data){ + request.on('data', function (data) { socketServer.broadcast(data); if (request.socket.recording) { request.socket.recording.write(data); } }); - request.on('end',function(){ + request.on('end', function () { console.log('close'); if (request.socket.recording) { request.socket.recording.close(); @@ -83,10 +79,12 @@ var streamServer = http.createServer( function(request, response) { var path = 'recordings/' + Date.now() + '.ts'; request.socket.recording = fs.createWriteStream(path); } -}) +}); // Keep the socket open for streaming streamServer.headersTimeout = 0; streamServer.listen(STREAM_PORT); -console.log('Listening for incomming MPEG-TS Stream on http://127.0.0.1:'+STREAM_PORT+'/'); -console.log('Awaiting WebSocket connections on ws://127.0.0.1:'+WEBSOCKET_PORT+'/'); +console.log( + 'Listening for incomming MPEG-TS Stream on http://127.0.0.1:' + STREAM_PORT + '/', +); +console.log('Awaiting WebSocket connections on ws://127.0.0.1:' + WEBSOCKET_PORT + '/'); diff --git a/insta_php.zip b/insta_php.zip deleted file mode 100644 index 8d35446..0000000 Binary files a/insta_php.zip and /dev/null differ diff --git a/install-mls.sh b/install-mls.sh new file mode 100644 index 0000000..4b8d3f8 --- /dev/null +++ b/install-mls.sh @@ -0,0 +1,179 @@ +#!/bin/bash + +source scripts/set-env.sh + +#Configure Timezone For Recording Timestamps +sudo dpkg-reconfigure tzdata + +#Install dependencies +sudo apt-get update && sudo apt-get -y install build-essential checkinstall libpcre3 libpcre3-dev libssl-dev libx264-dev libx265-dev libnuma-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libsdl2-dev libfreetype6-dev libass-dev libtool git zip unzip curl php7.0-cli php7.0-mbstring php7.0-fpm php7.0-mysql php7.0-curl php7.0-gd php7.0-bcmath autoconf automake cmake git-core pkg-config texinfo zlib1g-dev uuid-dev libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev nasm yasm htop ffmpeg youtube-dl + +#Install NGINX with RTMP module +sudo mkdir ~/build && cd ~/build +sudo git clone git://github.com/arut/nginx-rtmp-module.git +sudo wget http://nginx.org/download/nginx-1.13.12.tar.gz +sudo tar xzf nginx-1.13.12.tar.gz +cd nginx-1.13.12 +sudo ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module +echo "Hold on! NGINX is installing." +sudo make -s +sudo make install + +# Clear the default nginx.config +sudo /usr/local/nginx/sbin/nginx -s stop +sudo cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.old + +#Install PHP +cd ~ +sudo curl -sS https://getcomposer.org/installer -o composer-setup.php +sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer + +#Install Instagram-Live scripts +sudo unzip ~/MLS/insta_php.zip -d ~/ +#sudo git clone https://github.com/regstuff/InstagramLive-PHP.git +#sudo composer install -d InstagramLive-PHP/ + +# Download and move config files +cd ~/MLS/scripts/ +sudo mkdir images +cd images +sudo wget -O 1lowerthird.png https://www.dropbox.com/s/25xvndu4hzrtvom/1lowerthird.png?dl=0 +sudo wget -O 1video.mp4 https://www.dropbox.com/s/il7qa994iv9r7gu/1video.mp4?dl=0 +sudo wget -O 1holding.mp4 https://www.dropbox.com/s/vnphorklxm1xopz/1holding.mp4?dl=0 +sudo wget -O 1failover.mp4 https://www.dropbox.com/s/b595qj68l3t5g6f/1failover.mp4?dl=0 +sudo mkdir lowerthird + +#Shift files to right locations +sudo chgrp -R www-data ~/MLS +sudo chmod g+rw -R ~/MLS +sudo cp -R ~/MLS/scripts /usr/local/nginx +sudo rm -R ~/MLS/scripts/images +sudo chmod +x -R /usr/local/nginx/scripts +sudo mkdir /usr/local/nginx/scripts +sudo chmod +x -R /usr/local/nginx/scripts + +cd /usr/local/nginx/scripts/ +for ((i = 2; i <= ${STREAM_NUM}; i++)); do + sudo cp 1.sh ${i}.sh + sudo cp ./images/1lowerthird.png ./images/${i}lowerthird.png + sudo cp ./images/1video.mp4 ./images/${i}video.mp4 + sudo cp ./images/1holding.mp4 ./images/${i}holding.mp4 + sudo cp ./images/1failover.mp4 ./images/${1}failover.mp5 +done + +sudo cp /usr/local/nginx/scripts/.htpasswd /usr/local/nginx/conf/ +sudo cp /etc/php/7.0/fpm/php.ini /etc/php/7.0/fpm/php.old +sudo cp /usr/local/nginx/scripts/php.ini /etc/php/7.0/fpm/ +sudo cp /usr/local/nginx/scripts/images/*lowerthird.png /usr/local/nginx/scripts/images/lowerthird + +sudo systemctl restart php7.0-fpm + +sudo cp /usr/local/nginx/scripts/nginx.conf /usr/local/nginx/conf/ +sudo rm -R /usr/local/nginx/html +sudo cp -R ~/MLS/html /usr/local/nginx +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/2.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/3.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/4.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/5.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/6.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/7.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/8.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/9.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/10.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/11.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/12.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/13.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/14.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/15.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/16.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/17.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/18.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/19.php +#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/20.php + +#Setup HLS & Recording folders +sudo mkdir /usr/local/nginx/html/hls +sudo chmod -R 777 /usr/local/nginx/html/hls + +sudo mkdir /usr/local/nginx/html/recording +sudo chmod -R 777 /usr/local/nginx/html/recording + +cd ~ && curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - +sudo apt-get install -y nodejs && cd /usr/local/nginx/html && sudo npm init -y && sudo npm install ws && cd ~ + +#Install FFMPEG Controller +cd ~ && sudo wget https://github.com/zeromq/libzmq/releases/download/v4.2.2/zeromq-4.2.2.tar.gz && tar xvzf zeromq-4.2.2.tar.gz && cd zeromq-4.2.2 +./configure && sudo make install && sudo ldconfig + +#Install FFMPEG Components +cd ~ +git clone git://git.ffmpeg.org/rtmpdump +cd rtmpdump +make SYS=posix +sudo checkinstall --pkgname=rtmpdump --pkgversion="2:$(date +%Y%m%d%H%M)-git" --backup=no --deldoc=yes --fstrans=no --default + +sudo mkdir -p ~/ffmpeg_sources ~/bin && cd ~ && sudo wget -O ffmpeg-4.0.6.tar.bz2 https://www.dropbox.com/s/s40ux50c5d42x6s/ffmpeg-4.0.6.tar.bz2?dl=0 && tar xjvf ffmpeg-4.0.6.tar.bz2 && sudo chmod 775 -R ffmpeg-4.0.6/ && mv ffmpeg-4.0.6/ ffmpeg + +#Install SRT Components - Disabled because enable-libsrt is failing in ffmpeg +#cd ~/ffmpeg_sources +#sudo git clone --depth 1 https://github.com/Haivision/srt.git && sudo mkdir srt/build && cd srt/build +#sudo cmake -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_C_DEPS=ON -DENABLE_SHARED=OFF -DENABLE_STATIC=ON .. +#sudo make +#sudo make install + +#install Latest FFMPEG --enable-libsrt \ removed because ffmpeg build is failing with it +cd ~/ffmpeg && + PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \ + --prefix="$HOME/ffmpeg_build" \ + --pkg-config-flags="--static" \ + --extra-cflags="-I$HOME/ffmpeg_build/include" \ + --extra-ldflags="-L$HOME/ffmpeg_build/lib" \ + --extra-libs="-lpthread -lm" \ + --enable-gpl \ + --enable-openssl \ + --enable-libass \ + --enable-libfdk-aac \ + --enable-libfreetype \ + --enable-libmp3lame \ + --enable-libopus \ + --enable-libvorbis \ + --enable-libvpx \ + --enable-libx264 \ + --enable-libx265 \ + --enable-libzmq \ + --enable-network \ + --enable-nonfree && + PATH="$HOME/bin:$PATH" make + +sudo make install && hash -r + +./configure --enable-libzmq && make && make tools/zmqsend + +#Shift Latest FFMPEG & Tools to local/bin folder to avoid conflict with apt-get FFMPEG +sudo cp -R tools /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffmpeg /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffplay /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffprobe /usr/local/bin + +#Shift Instagram-Live to generic folder +sudo cp -R ~/InstagramLive-PHP /usr/local/nginx/scripts/ && sudo mv /usr/local/nginx/scripts/InstagramLive-PHP/ /usr/local/nginx/scripts/InstagramLive-PHP1/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP2/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP3/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP4/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP5/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP6/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP7/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP8/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP9/ +sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP10/ + +sudo cp -R ~/ffmpeg_sources/srt /usr/local/nginx/scripts/ +sudo cp -R ~/MLS /usr/local/nginx/scripts + +# restart nginx with new config. Set it to start on boot. +sudo /usr/local/nginx/sbin/nginx +sudo cp /usr/local/nginx/scripts/nginxrestart.sh /etc/init.d && sudo update-rc.d nginxrestart.sh defaults + +#make a little announcment with useful data for the user +WANIP=$(curl -s http://whatismyip.akamai.com/) +echo "Send source RTMP input on port 1935 to $WANIP" +echo " " +echo "Add www-data ALL=NOPASSWD: /bin/bash, /bin/ls to sudo visudo" +echo " " diff --git a/install-nginx.sh b/install-nginx.sh deleted file mode 100755 index c9ce3d9..0000000 --- a/install-nginx.sh +++ /dev/null @@ -1,264 +0,0 @@ -#!/bin/bash - -#Configure Timezone For Recording Timestamps -sudo dpkg-reconfigure tzdata - -#Install dependencies -sudo apt-get update && sudo apt-get -y install build-essential checkinstall libpcre3 libpcre3-dev libssl-dev libx264-dev libx265-dev libnuma-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libsdl2-dev libfreetype6-dev libass-dev libtool git zip unzip curl php7.0-cli php7.0-mbstring php7.0-fpm php7.0-mysql php7.0-curl php7.0-gd php7.0-bcmath autoconf automake cmake git-core pkg-config texinfo zlib1g-dev uuid-dev libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev nasm yasm htop ffmpeg youtube-dl - -#Install NGINX with RTMP module -sudo mkdir ~/build && cd ~/build -sudo git clone git://github.com/arut/nginx-rtmp-module.git -sudo wget http://nginx.org/download/nginx-1.13.12.tar.gz -sudo tar xzf nginx-1.13.12.tar.gz -cd nginx-1.13.12 -sudo ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module -echo "Hold on! NGINX is installing." -sudo make -s -sudo make install - -# Clear the default nginx.config -sudo /usr/local/nginx/sbin/nginx -s stop -sudo cp /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.old - -#Install PHP -cd ~ -sudo curl -sS https://getcomposer.org/installer -o composer-setup.php -sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer - -#Install Instagram-Live scripts -sudo unzip ~/MLS/insta_php.zip -d ~/ -#sudo git clone https://github.com/regstuff/InstagramLive-PHP.git -#sudo composer install -d InstagramLive-PHP/ - -# Download and move config files -cd ~/MLS/scripts/ -sudo mkdir images -sudo mkdir tmp -cd images -sudo wget -O 1lowerthird.png https://www.dropbox.com/s/25xvndu4hzrtvom/1lowerthird.png?dl=0 -sudo wget -O 1video.mp4 https://www.dropbox.com/s/il7qa994iv9r7gu/1video.mp4?dl=0 -sudo wget -O 1holding.mp4 https://www.dropbox.com/s/vnphorklxm1xopz/1holding.mp4?dl=0 -sudo wget -O 1failover.mp4 https://www.dropbox.com/s/b595qj68l3t5g6f/1failover.mp4?dl=0 -sudo mkdir lowerthird - -#Shift files to right locations -sudo chgrp -R www-data ~/MLS -sudo chmod g+rw -R ~/MLS -sudo cp -R ~/MLS/scripts /usr/local/nginx -sudo chmod +x -R /usr/local/nginx/scripts -sudo mkdir /usr/local/nginx/scripts -sudo chmod +x -R /usr/local/nginx/scripts -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/2.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/3.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/4.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/5.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/6.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/7.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/8.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/9.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/10.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/11.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/12.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/13.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/14.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/15.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/16.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/17.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/18.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/19.sh -sudo cp /usr/local/nginx/scripts/1.sh /usr/local/nginx/scripts/20.sh -sudo cp /usr/local/nginx/scripts/.htpasswd /usr/local/nginx/conf/ -sudo cp /etc/php/7.0/fpm/php.ini /etc/php/7.0/fpm/php.old -sudo cp /usr/local/nginx/scripts/php.ini /etc/php/7.0/fpm/ -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/2lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/3lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/4lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/5lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/6lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/7lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/8lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/9lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/10lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/11lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/12lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/13lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/14lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/15lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/16lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/17lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/18lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/19lowerthird.png -sudo cp /usr/local/nginx/scripts/images/1lowerthird.png /usr/local/nginx/scripts/images/20lowerthird.png -sudo cp /usr/local/nginx/scripts/images/*lowerthird.png /usr/local/nginx/scripts/images/lowerthird -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/2video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/3video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/4video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/5video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/6video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/7video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/8video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/9video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/10video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/11video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/12video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/13video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/14video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/15video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/16video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/17video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/18video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/19video.mp4 -sudo cp /usr/local/nginx/scripts/images/1video.mp4 /usr/local/nginx/scripts/images/20video.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/2holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/3holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/4holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/5holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/6holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/7holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/8holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/9holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/10holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/11holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/12holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/13holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/14holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/15holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/16holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/17holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/18holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/19holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/20holding.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/1failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/2failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/3failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/4failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/5failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/6failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/7failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/8failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/9failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/10failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/11failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/12failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/13failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/14failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/15failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/16failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/17failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/18failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/19failover.mp4 -sudo cp /usr/local/nginx/scripts/images/1holding.mp4 /usr/local/nginx/scripts/images/20failover.mp4 -sudo rm -R ~/MLS/scripts/images -sudo rm -R ~/MLS/scripts/tmp -sudo systemctl restart php7.0-fpm - -sudo cp /usr/local/nginx/scripts/nginx.conf /usr/local/nginx/conf/ -sudo rm -R /usr/local/nginx/html -sudo cp -R ~/MLS/html /usr/local/nginx -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/2.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/3.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/4.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/5.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/6.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/7.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/8.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/9.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/10.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/11.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/12.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/13.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/14.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/15.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/16.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/17.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/18.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/19.php -#sudo cp /usr/local/nginx/html/1.php /usr/local/nginx/html/20.php - -#Setup HLS & Recording folders -sudo mkdir /usr/local/nginx/html/hls -sudo chmod -R 777 /usr/local/nginx/html/hls - -sudo mkdir /usr/local/nginx/html/recording -sudo chmod -R 777 /usr/local/nginx/html/recording - -cd ~ && curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash - -sudo apt-get install -y nodejs && cd /usr/local/nginx/html && sudo npm init -y && sudo npm install ws && cd ~ - -#Install FFMPEG Controller -cd ~ && sudo wget https://github.com/zeromq/libzmq/releases/download/v4.2.2/zeromq-4.2.2.tar.gz && tar xvzf zeromq-4.2.2.tar.gz && cd zeromq-4.2.2 -./configure && sudo make install && sudo ldconfig - -#Install FFMPEG Components -cd ~ -git clone git://git.ffmpeg.org/rtmpdump -cd rtmpdump -make SYS=posix -sudo checkinstall --pkgname=rtmpdump --pkgversion="2:$(date +%Y%m%d%H%M)-git" --backup=no --deldoc=yes --fstrans=no --default - -sudo mkdir -p ~/ffmpeg_sources ~/bin && cd ~ && sudo wget -O ffmpeg-4.0.6.tar.bz2 https://www.dropbox.com/s/s40ux50c5d42x6s/ffmpeg-4.0.6.tar.bz2?dl=0 && tar xjvf ffmpeg-4.0.6.tar.bz2 && sudo chmod 775 -R ffmpeg-4.0.6/ && mv ffmpeg-4.0.6/ ffmpeg - -#Install SRT Components - Disabled because enable-libsrt is failing in ffmpeg -#cd ~/ffmpeg_sources -#sudo git clone --depth 1 https://github.com/Haivision/srt.git && sudo mkdir srt/build && cd srt/build -#sudo cmake -DCMAKE_INSTALL_PREFIX="$HOME/ffmpeg_build" -DENABLE_C_DEPS=ON -DENABLE_SHARED=OFF -DENABLE_STATIC=ON .. -#sudo make -#sudo make install - -#install Latest FFMPEG --enable-libsrt \ removed because ffmpeg build is failing with it -cd ~/ffmpeg && \ -PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \ - --prefix="$HOME/ffmpeg_build" \ - --pkg-config-flags="--static" \ - --extra-cflags="-I$HOME/ffmpeg_build/include" \ - --extra-ldflags="-L$HOME/ffmpeg_build/lib" \ - --extra-libs="-lpthread -lm" \ - --enable-gpl \ - --enable-openssl \ - --enable-libass \ - --enable-libfdk-aac \ - --enable-libfreetype \ - --enable-libmp3lame \ - --enable-libopus \ - --enable-libvorbis \ - --enable-libvpx \ - --enable-libx264 \ - --enable-libx265 \ - --enable-libzmq \ - --enable-network \ - --enable-nonfree && \ -PATH="$HOME/bin:$PATH" make - -sudo make install && hash -r - -./configure --enable-libzmq && make && make tools/zmqsend - -#Shift Latest FFMPEG & Tools to local/bin folder to avoid conflict with apt-get FFMPEG -sudo cp -R tools /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffmpeg /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffplay /usr/local/bin && sudo cp ~/ffmpeg_build/bin/ffprobe /usr/local/bin - -#Shift Instagram-Live to generic folder -sudo cp -R ~/InstagramLive-PHP /usr/local/nginx/scripts/ && sudo mv /usr/local/nginx/scripts/InstagramLive-PHP/ /usr/local/nginx/scripts/InstagramLive-PHP1/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP2/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP3/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP4/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP5/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP6/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP7/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP8/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP9/ -sudo cp -R /usr/local/nginx/scripts/InstagramLive-PHP1/ /usr/local/nginx/scripts/InstagramLive-PHP10/ - -sudo cp -R ~/ffmpeg_sources/srt /usr/local/nginx/scripts/ -sudo cp -R ~/MLS /usr/local/nginx/scripts - -# restart nginx with new config. Set it to start on boot. -sudo /usr/local/nginx/sbin/nginx -sudo cp /usr/local/nginx/scripts/nginxrestart.sh /etc/init.d && sudo update-rc.d nginxrestart.sh defaults - -#make a little announcment with useful data for the user -WANIP=$(curl -s http://whatismyip.akamai.com/) -echo "Send source RTMP input on port 1935 to $WANIP" -echo " " -echo "Add www-data ALL=NOPASSWD: /bin/bash, /bin/ls to sudo visudo" -echo " " diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100755 index 0000000..eb92033 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,275 @@ +daemon off; + +error_log /dev/stdout info; + +events { + worker_connections 1024; +} + +rtmp { + server { + listen 1935; + chunk_size 8192; + + application output { + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + # drop_idle_publisher 120s; + + + recorder rec { + + record off; + record_suffix .flv; + record_path /app/html/recording; + record_unique on; + # record_interval 30m; + } + } + + application main { + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /app/html/recording; + record_unique on; + record_interval 30m; + } + } + + application input { + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /app/html/recording; + record_unique on; + record_interval 30m; + } + } + + application distribute { + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 10s; + } + + application backup { + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /app/html/recording; + record_unique on; + # record_interval 30m; + } + } + + application recording { + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record all; + record_suffix -%Y%m%d-%H_%M_%S.flv; + record_path /app/html/recording; + record_unique off; + # record_interval 120m; + exec_record_done bash -c "/usr/local/nginx/scripts/config.sh convertrecording $path $dirname $basename"; + # exec_record_done ffmpeg -y -i $path -c copy $dirname/$basename.mp4; + } + } + + +# application live { +# +# live on; +# meta copy; +# hls on; +# # hls_nested on; +# hls_path /opt/data/hls; +# hls_fragment 2s; +# hls_playlist_length 10s; +# hls_sync 100ms; +# +# pull rtmp://localhost:1935/main/stream1 name=input static live=1; +# } + } +} + +############### FOR CONTROL PAGE ################## +http { + root /app/html; + sendfile off; + tcp_nopush on; + server_tokens off; + access_log /dev/stdout combined; + + keepalive_timeout 65; + default_type application/octet-stream; + + # Uncomment these lines to enable SSL. + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + # ssl_prefer_server_ciphers off; + # ssl_session_cache shared:SSL:10m; + # ssl_session_timeout 1d; + + server { + listen 80; + + # Uncomment these lines to enable SSL. + # Update the ssl paths with your own certificate and private key. + # listen ${HTTPS_PORT} ssl; + # ssl_certificate /opt/certs/example.com.crt; + # ssl_certificate_key /opt/certs/example.com.key; + + location / { + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + } + + location /css { + alias /app/html/css; + types { + text/css css; + } + } + location /img { + alias /app/html/img; + types { + image/jpeg jpg jpeg; + image/png png; + image/svg+xml svg; + image/gif gif; + } + } + + location ~ \.csv$ { + types { + text/plain csv; + } + } + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass php:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + expires -1; + } +# location ~ [^/]\.php(/|$) { +# fastcgi_split_path_info ^(.+?\.php)(/.*)$; +# if (!-f $document_root$fastcgi_script_name) { +# return 404; +# } + +# # Mitigate https://httpoxy.org/ vulnerabilities +# fastcgi_param HTTP_PROXY ""; + +# fastcgi_pass 127.0.0.1:9000; +# fastcgi_index index.php; + +# # include the fastcgi_param setting +# include fastcgi_params; + +# # SCRIPT_FILENAME parameter is used for PHP FPM determining +# # the script name. If it is not set in fastcgi_params file, +# # i.e. /etc/nginx/fastcgi_params or in the parent contexts, +# # please comment off following line: +# # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +# } +# location ~ \.php$ { +# include fastcgi_params; +# fastcgi_pass php:9000; +# fastcgi_index index.php; +# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +# } + + location /live { + alias /app/html/hls; + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + add_header Cache-Control no-cache; + add_header Access-Control-Allow-Origin *; + } + + location /stat { + rtmp_stat all; + rtmp_stat_stylesheet stat.xsl; + allow 127.0.0.1; + expires -1; + } + + location /stat.xml { + rtmp_stat all; + expires -1; + } + + location /stat-test.xml { + root /app/html; + expires -1; + } + + location /config.txt { + root /app/scripts; + expires -1; + } + + + location /stat.xsl { + root /app/html; + } + + location /crossdomain.xml { + default_type text/xml; + expires 24h; + } + + # rtmp control + location /control { + rtmp_control all; + # Enable CORS + add_header Access-Control-Allow-Origin * always; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + + root /app/html; + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..583d6aa --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "mls", + "devDependencies": { + "@tailwindcss/typography": "^0.5.10", + "daisyui": "4.4.19", + "express": "^4.19.2", + "nodemon": "^3.0.1", + "prettier": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.5.11", + "tailwindcss": "^3.4.1" + } +} diff --git a/scripts/1.sh b/scripts/1.sh index 3581e16..9ae0ef5 100755 --- a/scripts/1.sh +++ b/scripts/1.sh @@ -1,514 +1,518 @@ #!/bin/bash #Create variables/get variables from config.txt -id="$(basename "$0" .sh)"; #Which stream -streamid="stream"$id; #Same as above, just added stream to the id -encoding=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 2` #None, audio or both, defined in stream config -streamres=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 3` #Resolution of input stream defined in stream config -failmethod=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 4` #Failover method defined in stream config -dest=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 2` #Rtmp destination defined in destination config -dest=${dest//-+-+/ } #Necessary in case login and password are used -resolution=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 3` #Output resolution defined in destination config -streamname=`cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 4` #Output name defined in destination config +id="$(basename "$0" .sh)" #Which stream +streamid="stream"$id #Same as above, just added stream to the id +encoding=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 2) #None, audio or both, defined in stream config +streamres=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 3) #Resolution of input stream defined in stream config +failmethod=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__config__' | cut -d ' ' -f 4) #Failover method defined in stream config +dest=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 2) #Rtmp destination defined in destination config +dest=${dest//-+-+/ } #Necessary in case login and password are used +resolution=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 3) #Output resolution defined in destination config +streamname=$(cat /usr/local/nginx/scripts/config.txt | grep '__'$streamid'__'$1'__' | cut -d ' ' -f 4) #Output name defined in destination config #Only newffmpegparam is needed. The rest are legacy -oldffmpegparam="/usr/bin/ffmpeg -nostdin -thread_queue_size 512 -i"; -newffmpegparam="/usr/local/bin/ffmpeg -nostdin -thread_queue_size 512 -i"; -ffmpegtsparam="/usr/local/bin/ffmpeg -nostdin -thread_queue_size 512 -fflags +genpts+igndts+ignidx -avoid_negative_ts make_zero -use_wallclock_as_timestamps 1 -i"; +oldffmpegparam="/usr/bin/ffmpeg -nostdin -thread_queue_size 512 -i" +newffmpegparam="/usr/local/bin/ffmpeg -nostdin -thread_queue_size 512 -i" +ffmpegtsparam="/usr/local/bin/ffmpeg -nostdin -thread_queue_size 512 -fflags +genpts+igndts+ignidx -avoid_negative_ts make_zero -use_wallclock_as_timestamps 1 -i" #Create pipe interface between inputs and distribute -inputparam="/usr/local/nginx/scripts/tmp/input_pipe"$id; +inputparam="/usr/local/nginx/scripts/tmp/input_pipe"$id mkfifo $inputparam exec 7<>$inputparam #Create variables to use in ffmpeg command -mainparam="rtmp://127.0.0.1:1935/main/"$streamid; -backupparam="rtmp://127.0.0.1:1935/backup/"$streamid; -distributeparam="rtmp://127.0.0.1:1935/distribute/"$streamid; -outputparam="-y"; +mainparam="rtmp://127.0.0.1:1935/main/"$streamid +backupparam="rtmp://127.0.0.1:1935/backup/"$streamid +distributeparam="rtmp://127.0.0.1:1935/distribute/"$streamid +outputparam="-y" #Create variables of input names -advideo=$id"video.mp4"; -holdingvideo=$id"holding.mp4"; -failovervideo=$id"failover.mp4"; -lowerthird=$id"lowerthird.png"; +advideo=$id"video.mp4" +holdingvideo=$id"holding.mp4" +failovervideo=$id"failover.mp4" +lowerthird=$id"lowerthird.png" #Names of screens within which processes will run -screenon="[S]CREEN.* "$id"on"; -screenmain="[S]CREEN.* "$id"main"; -screenback="[S]CREEN.* "$id"back"; -screenholding="[S]CREEN.* "$id"holding"; -screenvideo="[S]CREEN.* "$id"video"; -screenplaylist="[S]CREEN.* "$id"playlist"; -screenfailover="[S]CREEN.* "$id"failover"; +screenon="[S]CREEN.* "$id"on" +screenmain="[S]CREEN.* "$id"main" +screenback="[S]CREEN.* "$id"back" +screenholding="[S]CREEN.* "$id"holding" +screenvideo="[S]CREEN.* "$id"video" +screenplaylist="[S]CREEN.* "$id"playlist" +screenfailover="[S]CREEN.* "$id"failover" #Array of all screens to delete easily later on -inputscreens=("$screenmain" "$screenback" "$screenholding" "$screenvideo" "$screenplaylist" "$screenfailover"); +inputscreens=("$screenmain" "$screenback" "$screenholding" "$screenvideo" "$screenplaylist" "$screenfailover") len=${#inputscreens[@]} #Length of above array #Audio port is used to change volume on the fly -audioport=`expr 5553 + 2 \* $id`; +audioport=$(expr 5553 + 2 \* $id) #Define bitrates for input resolutions, (input to distribute) case $streamres in 1080p) -inputres="1920x1080"; -inputbitrate="4M"; -;; + inputres="1920x1080" + inputbitrate="4M" + ;; 576p) -inputres="720x576"; -inputbitrate="1M"; -;; + inputres="720x576" + inputbitrate="1M" + ;; 480p) -inputres="854x480"; -inputbitrate="700K"; -;; + inputres="854x480" + inputbitrate="700K" + ;; *) #Defaults to 720p -inputres="1280x720"; -inputbitrate="1.5M"; + inputres="1280x720" + inputbitrate="1.5M" + ;; esac #Set up encoding profile for input encoding case $encoding in none) #Input codecs are copied to output. Minimal CPU usage -inputencodeparam="-c copy -absf aac_adtstoasc -flags +global_header -f flv -strict 2"; -#inputencodeparam="-acodec aac -af aresample=44100:async=1 -vcodec copy -f flv -strict -2"; -;; + inputencodeparam="-c copy -absf aac_adtstoasc -flags +global_header -f flv -strict 2" + #inputencodeparam="-acodec aac -af aresample=44100:async=1 -vcodec copy -f flv -strict -2"; + ;; audio) #Volume control only. By default volume is doubled -inputencodeparam="-af azmq=bind_address=tcp\\\://127.0.0.1\\\:"$audioport",volume=2,aresample=44100:async=1 -c:a aac -ar 44100 -vcodec copy -f flv -strict -2"; -;; + inputencodeparam="-af azmq=bind_address=tcp\\\://127.0.0.1\\\:"$audioport",volume=2,aresample=44100:async=1 -c:a aac -ar 44100 -vcodec copy -f flv -strict -2" + ;; *) #lowerthirds via image2. Loop to poll for changes in lowerthird. Volume control -inputencodeparam="-f image2 -loop 1 -i /usr/local/nginx/scripts/images/$lowerthird -af azmq=bind_address=tcp\\\://127.0.0.1\\\:"$audioport",volume=2,aresample=44100:async=1 -c:a aac -b:a 128k -filter_complex overlay=0:H-h -vcodec libx264 -pix_fmt yuv420p -preset veryfast -r 25 -g 50 -s $inputres -b:v $inputbitrate -maxrate $inputbitrate -minrate $inputbitrate -bufsize $inputbitrate -profile:v high -f flv -strict -2"; + inputencodeparam="-f image2 -loop 1 -i /usr/local/nginx/scripts/images/$lowerthird -af azmq=bind_address=tcp\\\://127.0.0.1\\\:"$audioport",volume=2,aresample=44100:async=1 -c:a aac -b:a 128k -filter_complex overlay=0:H-h -vcodec libx264 -pix_fmt yuv420p -preset veryfast -r 25 -g 50 -s $inputres -b:v $inputbitrate -maxrate $inputbitrate -minrate $inputbitrate -bufsize $inputbitrate -profile:v high -f flv -strict -2" ;; esac case $1 in ####### Volume Modification ######## volume) -echo 'Parsed_volume_1 volume '$2 | /usr/local/bin/tools/zmqsend -b tcp://127.0.0.1:$audioport -sleep 0.5 -;; + echo 'Parsed_volume_1 volume '$2 | /usr/local/bin/tools/zmqsend -b tcp://127.0.0.1:$audioport + sleep 0.5 + ;; ####### Lowerthird Modification ######## super) -case $2 in -off) #Turn off works by moving a blank png into lowerthird.png -sudo cp /usr/local/nginx/scripts/images/lowerthird/$lowerthird /usr/local/nginx/scripts/images/$lowerthird -echo $lowerthird " removed" -sleep 0.2 -;; -*) #Move appropriate png into lowerthird.png -lowerthirdid=$id"lowerthird"$2".png" -if test -f "/usr/local/nginx/scripts/images/lowerthird/"$lowerthirdid; then -sudo cp /usr/local/nginx/scripts/images/lowerthird/$lowerthirdid /usr/local/nginx/scripts/images/$lowerthird -echo $lowerthirdid " added" - -else #In case lowerthird does not exist -echo $lowerthirdid " does not exist. Please upload it." -fi - -sleep 0.2 -esac -;; + case $2 in + off) #Turn off works by moving a blank png into lowerthird.png + sudo cp /usr/local/nginx/scripts/images/lowerthird/$lowerthird /usr/local/nginx/scripts/images/$lowerthird + echo $lowerthird " removed" + sleep 0.2 + ;; + *) #Move appropriate png into lowerthird.png + lowerthirdid=$id"lowerthird"$2".png" + if test -f "/usr/local/nginx/scripts/images/lowerthird/"$lowerthirdid; then + sudo cp /usr/local/nginx/scripts/images/lowerthird/$lowerthirdid /usr/local/nginx/scripts/images/$lowerthird + echo $lowerthirdid " added" + + else #In case lowerthird does not exist + echo $lowerthirdid " does not exist. Please upload it." + fi + + sleep 0.2 + ;; + esac + ;; ###### INPUT CONFIGURATION ####### on) #This ffmpeg process runs between inputs pipe and distribute -screenname=$id"on"; - -#Lock file to prevent same screen being created in case user clicks twice -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; -exec 8>$LCK; - -if flock -n -x 8; then #If lock does not exist, continue with ffmpeg creation -if [ -z "$STY" ]; then #If we are in main terminal, continue -echo "Turning on $streamid" -exec screen -dm -S $screenname /bin/bash "$0" on; #Create screen and run same command inside screen -fi - -while true #This infinite loop will run inside the screen -do -$newffmpegparam $inputparam $inputencodeparam $distributeparam -echo "Restarting ffmpeg..." #When above process fails for any reason, restart -sleep .2 #Needed so CPU doesn't get stuck -done - -else #If lock exists, process is already running -echo $screenname " is already running" -fi -;; + screenname=$id"on" + + #Lock file to prevent same screen being created in case user clicks twice + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + exec 8>$LCK + + if flock -n -x 8; then #If lock does not exist, continue with ffmpeg creation + if [ -z "$STY" ]; then #If we are in main terminal, continue + echo "Turning on $streamid" + exec screen -dm -S $screenname /bin/bash "$0" on #Create screen and run same command inside screen + fi + + while true; do #This infinite loop will run inside the screen + $newffmpegparam $inputparam $inputencodeparam $distributeparam + echo "Restarting ffmpeg..." #When above process fails for any reason, restart + sleep .2 #Needed so CPU doesn't get stuck + done + + else #If lock exists, process is already running + echo $screenname " is already running" + fi + ;; ########## TURN ON ENDS. MAIN BEGINS ################ main) -screenname=$id"main"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do #Before turning on, turn of all other inputs -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenmain" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenmain" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; #Set i to len to exit loop immediately after match is found. -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid main input" -exec screen -dm -S $screenname /bin/bash "$0" main; -fi - -while true #Send main input to pipe -do -$newffmpegparam $mainparam -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam - -case $failmethod in #Failover method -mainback) #Main to backup -screenname=$id"back"; #Set screen name to back and spawn backup. Main screen will be killed by backup -screen -dm -S $screenname /bin/bash "$0" back; -;; - -mainbackfail) #Main to backup. Backup will fail to failover in its section -screenname=$id"back"; -screen -dm -S $screenname /bin/bash "$0" back; -;; - -mainfail) #Main to failover -screenname=$id"failover"; -screen -dm -S $screenname /bin/bash "$0" failover; -;; - -*) #No failover -echo "Restarting ffmpeg..." -sleep .2 -esac -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"main" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do #Before turning on, turn of all other inputs + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenmain" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenmain" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len #Set i to len to exit loop immediately after match is found. + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid main input" + exec screen -dm -S $screenname /bin/bash "$0" main + fi + + while true; do #Send main input to pipe + $newffmpegparam $mainparam -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + + case $failmethod in #Failover method + mainback) #Main to backup + screenname=$id"back" #Set screen name to back and spawn backup. Main screen will be killed by backup + screen -dm -S $screenname /bin/bash "$0" back + ;; + + mainbackfail) #Main to backup. Backup will fail to failover in its section + screenname=$id"back" + screen -dm -S $screenname /bin/bash "$0" back + ;; + + mainfail) #Main to failover + screenname=$id"failover" + screen -dm -S $screenname /bin/bash "$0" failover + ;; + + *) #No failover + echo "Restarting ffmpeg..." + sleep .2 + ;; + esac + done + + else + echo $screenname " is already running" + fi + ;; ########## MAIN ENDS. BACKUP BEGINS ################ back) -screenname=$id"back"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenback" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenback" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid backup input" -exec screen -dm -S $screenname /bin/bash "$0" back; -fi - -while true -do -$newffmpegparam $backupparam -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam - -case $failmethod in -mainback) -screenname=$id"main"; -screen -dm -S $screenname /bin/bash "$0" main; -;; - -mainbackfail) -screenname=$id"failover"; -screen -dm -S $screenname /bin/bash "$0" failover; -;; - -*) -echo "Restarting ffmpeg..." -sleep .2 -esac -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"back" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenback" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenback" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid backup input" + exec screen -dm -S $screenname /bin/bash "$0" back + fi + + while true; do + $newffmpegparam $backupparam -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + + case $failmethod in + mainback) + screenname=$id"main" + screen -dm -S $screenname /bin/bash "$0" main + ;; + + mainbackfail) + screenname=$id"failover" + screen -dm -S $screenname /bin/bash "$0" failover + ;; + + *) + echo "Restarting ffmpeg..." + sleep .2 + ;; + esac + done + + else + echo $screenname " is already running" + fi + ;; ########## BACKUP ENDS. HOLDING BEGINS ################ holding) -screenname=$id"holding"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenholding" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenholding" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid holding screen at $2 seconds" -exec screen -dm -S $screenname /bin/bash "$0" holding $2; -fi - -while true #Loop the same file using stream_loop -1. genpts needed to continue PTS for each iteration -do -/usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -ss $2 -i /usr/local/nginx/scripts/images/$holdingvideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam -echo "Restarting ffmpeg..." -sleep .2 -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"holding" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenholding" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenholding" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid holding screen at $2 seconds" + exec screen -dm -S $screenname /bin/bash "$0" holding $2 + fi + + while true; do #Loop the same file using stream_loop -1. genpts needed to continue PTS for each iteration + /usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -ss $2 -i /usr/local/nginx/scripts/images/$holdingvideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + echo "Restarting ffmpeg..." + sleep .2 + done + + else + echo $screenname " is already running" + fi + ;; ########## HOLDING ENDS. AD VIDEO BEGINS ################ video) -screenname=$id"video"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenvideo" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenvideo" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid Ad video at $2 seconds" -exec screen -dm -S $screenname /bin/bash "$0" video $2; -fi - -while true -do -/usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -ss $2 -i /usr/local/nginx/scripts/images/$advideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam -echo "Restarting ffmpeg..." -sleep .2 -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"video" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenvideo" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenvideo" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid Ad video at $2 seconds" + exec screen -dm -S $screenname /bin/bash "$0" video $2 + fi + + while true; do + /usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -ss $2 -i /usr/local/nginx/scripts/images/$advideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + echo "Restarting ffmpeg..." + sleep .2 + done + + else + echo $screenname " is already running" + fi + ;; ########## AD VIDEO ENDS. PLAYLIST BEGINS ################ playlist) -screenname=$id"playlist"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenplaylist" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenplaylist" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid playlist" -exec screen -dm -S $screenname /bin/bash "$0" playlist; -fi - -while true #Playlist works by concating files from list.txt -do -/usr/bin/ffmpeg -nostdin -thread_queue_size 512 -re -f concat -safe 0 -i /usr/local/nginx/scripts/images/set/list.txt -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam -echo "Restarting ffmpeg..." -sleep .2 -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"playlist" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenplaylist" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenplaylist" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid playlist" + exec screen -dm -S $screenname /bin/bash "$0" playlist + fi + + while true; do #Playlist works by concating files from list.txt + /usr/bin/ffmpeg -nostdin -thread_queue_size 512 -re -f concat -safe 0 -i /usr/local/nginx/scripts/images/set/list.txt -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + echo "Restarting ffmpeg..." + sleep .2 + done + + else + echo $screenname " is already running" + fi + ;; ########## PLAYLIST ENDS. FAILOVER VIDEO BEGINS ################ failover) -screenname=$id"failover"; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenfailover" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenfailover" | awk '{print $2}') -echo "Turning off ${inputscreens[i]:11} input" -i=$len; -fi -done - -if [ -z "$STY" ]; then -echo "Turning on $streamid failover" -exec screen -dm -S $screenname /bin/bash "$0" failover; -fi - -while true -do -/usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -i /usr/local/nginx/scripts/images/$failovervideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 > $inputparam -echo "Restarting ffmpeg..." -sleep .2 -done - -else -echo $screenname " is already running" -fi -;; + screenname=$id"failover" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenfailover" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | grep -v "$screenfailover" | awk '{print $2}') + echo "Turning off ${inputscreens[i]:11} input" + i=$len + fi + done + + if [ -z "$STY" ]; then + echo "Turning on $streamid failover" + exec screen -dm -S $screenname /bin/bash "$0" failover + fi + + while true; do + /usr/local/bin/ffmpeg -nostdin -re -fflags +genpts -stream_loop -1 -i /usr/local/nginx/scripts/images/$failovervideo -c copy -vbsf h264_mp4toannexb -f mpegts pipe:1 >$inputparam + echo "Restarting ffmpeg..." + sleep .2 + done + + else + echo $screenname " is already running" + fi + ;; ########## FAILOVER VIDEO ENDS. TURN OFF BEGINS ################ off) -ME=$id"off"; -echo $screenmain -LCK="/usr/local/nginx/scripts/tmp/${ME}.LCK"; + ME=$id"off" + echo $screenmain + LCK="/usr/local/nginx/scripts/tmp/${ME}.LCK" -exec 8>$LCK; + exec 8>$LCK -if flock -n -x 8; then + if flock -n -x 8; then -if [ $(ps aux | grep "$screenon" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep "$screenon" | awk '{print $2}') -echo "Turning off $streamid" -fi + if [ $(ps aux | grep "$screenon" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep "$screenon" | awk '{print $2}') + echo "Turning off $streamid" + fi -offstatus="$streamid is already off" #Initialize variable to use inside below loop -for (( i=0; i<$len; i++ )); do -if [ $(ps aux | grep -E "${inputscreens[i]}" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep -E "${inputscreens[i]}" | awk '{print $2}') -offstatus="Turning off ${inputscreens[i]:11} input" #If something exists to turn off, change variable -i=$len; -fi -done + offstatus="$streamid is already off" #Initialize variable to use inside below loop + for ((i = 0; i < $len; i++)); do + if [ $(ps aux | grep -E "${inputscreens[i]}" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep -E "${inputscreens[i]}" | awk '{print $2}') + offstatus="Turning off ${inputscreens[i]:11} input" #If something exists to turn off, change variable + i=$len + fi + done -echo $offstatus #Echoes Already off in case user clicks twice + echo $offstatus #Echoes Already off in case user clicks twice -else -echo "You're already trying to turn off $streamid. Hold on!" -fi + else + echo "You're already trying to turn off $streamid. Hold on!" + fi -sleep 0.5 -;; + sleep 0.5 + ;; ####### OUTPUT CONFIGURATION ###### out99) #For instagram -case $2 in -off) -ME="[S]CREEN.* "$id$1; #Turn off output -if [ $(ps aux | grep "$ME" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep "$ME" | awk '{print $2}') -echo "Turning off "$streamid $1 -sleep 0.5 -else -echo $streamid $1" is already off" -sleep 0.5 -fi -exit 0 -;; - -*) #Instagram encoding at 480x854 at 1mbps. Flags global headers needed because tee muxer will not automatically generate headers needed for rtmp flv format -encodeparam="-acodec aac -async 1 -ar 44100 -ac 1 -b:a 128k -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 480x854 -b:v 1000k -preset veryfast -flags +global_header" -screenname=$id$1; -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; - -#Create master-slave link to send output to nginx output app simultaneously for stats monitoring -checkout="[f=flv]$dest|[f=flv]rtmp://127.0.0.1:1935/output/$streamid-$streamname" - -exec 8>$LCK; -if flock -n -x 8; then -i="0" -echo $streamid $1 " has started at "$resolution" resolution" -if [ -z "$STY" ]; -then -exec screen -dm -S $screenname /bin/bash "$0" "$1" "$2"; -fi -while [ $i -lt 9000 ] #Turn off if output is idle for 9000*0.2=1800 seconds -do #Use transpose filter to transpose 90 degrees. -$newffmpegparam $distributeparam $encodeparam -vf "transpose=1" -f tee -map 0:v -map 0:a $checkout; -echo "Waiting for input... Feed me!!!" -sleep 0.2 -i=$[$i+1] -done - -else echo $streamid $1 " is already running" -fi -esac -;; + case $2 in + off) + ME="[S]CREEN.* "$id$1 #Turn off output + if [ $(ps aux | grep "$ME" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep "$ME" | awk '{print $2}') + echo "Turning off "$streamid $1 + sleep 0.5 + else + echo $streamid $1" is already off" + sleep 0.5 + fi + exit 0 + ;; + + *) #Instagram encoding at 480x854 at 1mbps. Flags global headers needed because tee muxer will not automatically generate headers needed for rtmp flv format + encodeparam="-acodec aac -async 1 -ar 44100 -ac 1 -b:a 128k -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 480x854 -b:v 1000k -preset veryfast -flags +global_header" + screenname=$id$1 + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + + #Create master-slave link to send output to nginx output app simultaneously for stats monitoring + checkout="[f=flv]$dest|[f=flv]rtmp://127.0.0.1:1935/output/$streamid-$streamname" + + exec 8>$LCK + if flock -n -x 8; then + i="0" + echo $streamid $1 " has started at "$resolution" resolution" + if [ -z "$STY" ]; then + exec screen -dm -S $screenname /bin/bash "$0" "$1" "$2" + fi + while [ $i -lt 9000 ]; do #Turn off if output is idle for 9000*0.2=1800 seconds + #Use transpose filter to transpose 90 degrees. + $newffmpegparam $distributeparam $encodeparam -vf "transpose=1" -f tee -map 0:v -map 0:a $checkout + echo "Waiting for input... Feed me!!!" + sleep 0.2 + i=$(($i + 1)) + done + + else + echo $streamid $1 " is already running" + fi + ;; + esac + ;; *) #Output resolution encode parameters -case $resolution in - -source) #Copy all codecs -encodeparam="-c copy -flags +global_header" -;; - -720p) #1.3mbps video, audio copy -encodeparam="-acodec copy -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 1280x720 -b:v 1300k -preset veryfast -flags +global_header" -;; - -540p) #1mbps video, audio resampled to 44.1khz 128kbps mono for Twitter -encodeparam="-acodec aac -async 1 -ar 44100 -ac 1 -b:a 128k -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 960x540 -b:v 1000k -preset veryfast -flags +global_header" -;; - -576p) #800k video, audio copy -encodeparam="-acodec copy -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 720x576 -b:v 800k -preset veryfast -flags +global_header" -esac - -case $2 in - -off) -ME="[S]CREEN.* $id$1 "; -echo $ME -if [ $(ps aux | grep "$ME" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep "$ME" | awk '{print $2}') -echo "Turning off "$streamid $1 -sleep 0.5 -else -echo $streamid $1 " is already off" -sleep 0.5 -fi -exit 0 -esac - -screenname=$id$1; -checkout="[f=flv]$dest|[f=flv]rtmp://127.0.0.1:1935/output/$streamid-$streamname" -LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK"; -exec 8>$LCK; - -if flock -n -x 8; then -i="0" -echo $streamid $1 " has started at "$resolution" resolution" -if [ -z "$STY" ]; -then -exec screen -dm -S $screenname /bin/bash "$0" "$1"; -fi - -while [ $i -lt 9000 ] -do -$newffmpegparam $distributeparam $encodeparam -strict -2 -f tee -map 0:v -map 0:a "$checkout"; -echo "Waiting for English input... Feed me!!!" -sleep 0.2 -i=$[$i+1] -done - -else echo $streamid $1 " is already running" -fi + case $resolution in + + source) #Copy all codecs + encodeparam="-c copy -flags +global_header" + ;; + + vertical) # Rotate video 90 degrees and scale to 720p + encodeparam="-vf 'transpose=1, scale=720:1280' -c:v libx264 -c:a aac -flags +global_header" + ;; + + 720p) #1.3mbps video, audio copy + encodeparam="-acodec copy -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 1280x720 -b:v 1300k -preset veryfast -flags +global_header" + ;; + + 540p) #1mbps video, audio resampled to 44.1khz 128kbps mono for Twitter + encodeparam="-acodec aac -async 1 -ar 44100 -ac 1 -b:a 128k -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 960x540 -b:v 1000k -preset veryfast -flags +global_header" + ;; + + 576p) #800k video, audio copy + encodeparam="-acodec copy -vcodec libx264 -pix_fmt yuv420p -r 25 -g 50 -s 720x576 -b:v 800k -preset veryfast -flags +global_header" ;; + esac + + case $2 in + + off) + ME="[S]CREEN.* $id$1 " + echo $ME + if [ $(ps aux | grep "$ME" | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep "$ME" | awk '{print $2}') + echo "Turning off "$streamid $1 + sleep 0.5 + else + echo $streamid $1 " is already off" + sleep 0.5 + fi + exit 0 + ;; + esac + + screenname=$id$1 + checkout="[f=flv]$dest|[f=flv]rtmp://127.0.0.1:1935/output/$streamid-$streamname" + LCK="/usr/local/nginx/scripts/tmp/${screenname}.LCK" + exec 8>$LCK + + if flock -n -x 8; then + i="0" + echo $streamid $1 " has started at "$resolution" resolution" + if [ -z "$STY" ]; then + exec screen -dm -S $screenname /bin/bash "$0" "$1" + fi + + ffmpegcommand="$newffmpegparam $distributeparam $encodeparam -strict -2 -f tee -map 0:v -map 0:a \"$checkout\"" + + while [ $i -lt 9000 ]; do + eval "$ffmpegcommand" + echo "Waiting for English input... Feed me!!!" + sleep 0.2 + i=$(($i + 1)) + done + + else + echo $streamid $1 " is already running" + fi + ;; esac ########## OUTPUTS END. ALL END ################ diff --git a/scripts/config.sh b/scripts/config.sh index 4809ad8..fad113d 100644 --- a/scripts/config.sh +++ b/scripts/config.sh @@ -3,663 +3,630 @@ ##### START ADD DESTINATION ########## case $1 in destination) -sudo sed -i "s|stream$3__out$4__.*|stream$3__out$4__ $(echo $2 | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g') $5 $6|" /usr/local/nginx/scripts/config.txt; + sudo sed -i "s|stream$3__out$4__.*|stream$3__out$4__ $(echo $2 | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g') $5 $6|" /usr/local/nginx/scripts/config.txt -;; + ;; ##### END DESTINATION - START STREAM CONFIG ########## streamconfig) -sudo sed -i "s|stream$2__config__.*|stream$2__config__ $3 $4 $5|" /usr/local/nginx/scripts/config.txt; + sudo sed -i "s|stream$2__config__.*|stream$2__config__ $3 $4 $5|" /usr/local/nginx/scripts/config.txt -;; + ;; ##### END STREAM CONFIG - START INSTA TEST AUTH ########## instatestauth) -sudo php /usr/local/nginx/scripts/InstagramLive-PHP$2/testAuth.php; + sudo php /usr/local/nginx/scripts/InstagramLive-PHP$2/testAuth.php -;; + ;; ##### END INSTA TEST AUTH - START INSTA LOGIN ########## instalogin) -sudo sed -i "s|'IG_USERNAME', '.*|'IG_USERNAME', '$3');|" /usr/local/nginx/scripts/InstagramLive-PHP$2/config.php; -sudo sed -i "s|'IG_PASS', '.*|'IG_PASS', '$4');|" /usr/local/nginx/scripts/InstagramLive-PHP$2/config.php; + sudo sed -i "s|'IG_USERNAME', '.*|'IG_USERNAME', '$3');|" /usr/local/nginx/scripts/InstagramLive-PHP$2/config.php + sudo sed -i "s|'IG_PASS', '.*|'IG_PASS', '$4');|" /usr/local/nginx/scripts/InstagramLive-PHP$2/config.php -;; + ;; ##### END INSTA LOGIN - START VIDEO SCHEDULE ########## videoschedule) -case $5 in -add) -case $4 in -on) -case $3 in -holding) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 $8") 2> /dev/null | sort -u | sudo crontab - -;; -video) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 $8") 2> /dev/null | sort -u | sudo crontab - -;; -*) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2> /dev/null | sort -u | sudo crontab - -esac -;; - -off) -case $3 in -holding) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2> /dev/null | sort -u | sudo crontab - -;; -video) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2> /dev/null | sort -u | sudo crontab - -;; -*) -(sudo crontab -l; echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 off") 2> /dev/null | sort -u | sudo crontab - -esac -esac -;; - -delete) -case $4 in -on) -case $3 in -holding) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2> /dev/null | sort -u | sudo crontab - -;; -video) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2> /dev/null | sort -u | sudo crontab - -;; -*) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2> /dev/null | sort -u | sudo crontab - -esac -;; - -off) -case $3 in -holding) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2> /dev/null | sort -u | sudo crontab - -;; -video) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2> /dev/null | sort -u | sudo crontab - -;; -*) -(sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 off") 2> /dev/null | sort -u | sudo crontab - -esac -esac - -esac -;; + case $5 in + add) + case $4 in + on) + case $3 in + holding) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 $8" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + video) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 $8" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + *) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + esac + ;; + + off) + case $3 in + holding) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh off" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + video) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh off" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + *) + ( + sudo crontab -l + echo "$7 $6 * * * sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 off" + ) 2>/dev/null | sort -u | sudo crontab - + ;; + esac + ;; + esac + ;; + + delete) + case $4 in + on) + case $3 in + holding) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2>/dev/null | sort -u | sudo crontab - + ;; + video) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh on && sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2>/dev/null | sort -u | sudo crontab - + ;; + *) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3") 2>/dev/null | sort -u | sudo crontab - + ;; + esac + ;; + + off) + case $3 in + holding) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2>/dev/null | sort -u | sudo crontab - + ;; + video) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh off") 2>/dev/null | sort -u | sudo crontab - + ;; + *) + (sudo crontab -l | grep -v "$7 $6 \* \* \* sudo /bin/bash /usr/local/nginx/scripts/$2.sh $3 off") 2>/dev/null | sort -u | sudo crontab - + ;; + esac + ;; + esac + ;; + + esac + ;; ##### END VIDEO SCHEDULE - START SCHEDULE LIST ########## schedulelist) -sudo crontab -l | grep -v "#" | sort; + sudo crontab -l | grep -v "#" | sort -;; + ;; ##### END SCHEDULE LIST- START AUDIO CONFIG ########## audioconfig) -sudo sed -i "s|stream$2__audio__.*|stream$2__audio__ $4 $5 $3 $6|" /usr/local/nginx/scripts/config.txt; + sudo sed -i "s|stream$2__audio__.*|stream$2__audio__ $4 $5 $3 $6|" /usr/local/nginx/scripts/config.txt -;; + ;; ##### END AUDIO CONFIG - START AUDIO PRESET ########## audiopreset) -case $2 in -allmono) -sudo sed -i "s|stream1__audio__.*|stream1__audio__ c0 c2 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream2__audio__.*|stream2__audio__ c1 c4 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream3__audio__.*|stream3__audio__ c2 c6 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream4__audio__.*|stream4__audio__ c3 c8 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream5__audio__.*|stream5__audio__ c4 c10 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream6__audio__.*|stream6__audio__ c5 c12 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream7__audio__.*|stream7__audio__ c6 c14 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream8__audio__.*|stream8__audio__ c7 c16 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream9__audio__.*|stream9__audio__ c8 c18 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream10__audio__.*|stream10__audio__ c9 c20 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream11__audio__.*|stream11__audio__ c10 c22 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream12__audio__.*|stream12__audio__ c11 c24 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream13__audio__.*|stream13__audio__ c12 c26 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream14__audio__.*|stream14__audio__ c13 c28 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream15__audio__.*|stream15__audio__ c14 c30 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream16__audio__.*|stream16__audio__ c15 c32 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream17__audio__.*|stream17__audio__ c16 c34 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream18__audio__.*|stream18__audio__ c17 c36 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream19__audio__.*|stream19__audio__ c18 c38 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream20__audio__.*|stream20__audio__ c19 c40 mono main|" /usr/local/nginx/scripts/config.txt; -;; - -firststereo) -sudo sed -i "s|stream1__audio__.*|stream1__audio__ c0 c1 stereo distribute|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream2__audio__.*|stream2__audio__ c2 c3 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream3__audio__.*|stream3__audio__ c3 c6 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream4__audio__.*|stream4__audio__ c4 c8 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream5__audio__.*|stream5__audio__ c5 c10 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream6__audio__.*|stream6__audio__ c6 c12 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream7__audio__.*|stream7__audio__ c7 c14 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream8__audio__.*|stream8__audio__ c8 c16 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream9__audio__.*|stream9__audio__ c9 c18 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream10__audio__.*|stream10__audio__ c10 c20 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream11__audio__.*|stream11__audio__ c11 c22 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream12__audio__.*|stream12__audio__ c12 c24 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream13__audio__.*|stream13__audio__ c13 c26 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream14__audio__.*|stream14__audio__ c14 c28 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream15__audio__.*|stream15__audio__ c15 c30 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream16__audio__.*|stream16__audio__ c16 c32 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream17__audio__.*|stream17__audio__ c17 c34 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream18__audio__.*|stream18__audio__ c18 c36 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream19__audio__.*|stream19__audio__ c19 c38 mono main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream20__audio__.*|stream20__audio__ c20 c40 mono main|" /usr/local/nginx/scripts/config.txt; -;; - -allstereo) -sudo sed -i "s|stream1__audio__.*|stream1__audio__ c0 c1 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream2__audio__.*|stream2__audio__ c2 c3 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream3__audio__.*|stream3__audio__ c4 c5 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream4__audio__.*|stream4__audio__ c6 c7 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream5__audio__.*|stream5__audio__ c8 c9 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream6__audio__.*|stream6__audio__ c10 c11 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream7__audio__.*|stream7__audio__ c12 c13 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream8__audio__.*|stream8__audio__ c14 c15 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream9__audio__.*|stream9__audio__ c16 c17 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream10__audio__.*|stream10__audio__ c18 c19 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream11__audio__.*|stream11__audio__ c20 c21 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream12__audio__.*|stream12__audio__ c22 c23 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream13__audio__.*|stream13__audio__ c24 c25 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream14__audio__.*|stream14__audio__ c26 c27 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream15__audio__.*|stream15__audio__ c28 c29 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream16__audio__.*|stream16__audio__ c30 c31 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream17__audio__.*|stream17__audio__ c32 c33 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream18__audio__.*|stream18__audio__ c34 c35 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream19__audio__.*|stream19__audio__ c36 c37 stereo main|" /usr/local/nginx/scripts/config.txt; -sudo sed -i "s|stream20__audio__.*|stream20__audio__ c38 c39 stereo main|" /usr/local/nginx/scripts/config.txt; -esac - -;; + case $2 in + allmono) + for ((i = 1; i <= STREAM_NUM; i++)); do + input_channel=$((i - 1)) + output_channel=$((i * 2 - 2)) + sudo sed -i "s|stream${i}__audio__.*|stream${i}__audio__ c${input_channel} c${output_channel} mono main|" /usr/local/nginx/scripts/config.txt + done + ;; + + firststereo) + sudo sed -i "s|stream1__audio__.*|stream1__audio__ c0 c1 stereo distribute|" /usr/local/nginx/scripts/config.txt + sudo sed -i "s|stream2__audio__.*|stream2__audio__ c2 c3 mono main|" /usr/local/nginx/scripts/config.txt + for ((i = 3; i <= STREAM_NUM; i++)); do + input_channel=$i + output_channel=$((i * 2)) + sudo sed -i "s|stream${i}__audio__.*|stream${i}__audio__ c${input_channel} c${output_channel} mono main|" /usr/local/nginx/scripts/config.txt + done + ;; + + allstereo) + sudo sed -i "s|stream1__audio__.*|stream1__audio__ c0 c1 stereo main|" /usr/local/nginx/scripts/config.txt + for ((i = 2; i <= STREAM_NUM; i++)); do + input_channel=$((i * 2 - 2)) + output_channel=$((i * 2 - 1)) + sudo sed -i "s|stream${i}__audio__.*|stream${i}__audio__ c${input_channel} c${output_channel} stereo main|" /usr/local/nginx/scripts/config.txt + done + ;; + esac + ;; ##### END AUDIO PRESET - START UPLOAD FILE ########## uploadfile) -sudo wget -O $3$4 $2 && sudo chmod +x $3$4 && sudo mv $3$4 /usr/local/nginx/scripts/images - -;; + sudo wget -O $3$4 $2 && sudo chmod +x $3$4 && sudo mv $3$4 /usr/local/nginx/scripts/images + ;; ##### END UPLOAD FILE - START UPLOAD LOWERTHIRD ########## uploadlower) -sudo mv $2 $3; -sudo chmod 755 $3; -sudo chown root:root $3; -echo $3 uploaded -;; + sudo mv $2 $3 + sudo chmod 755 $3 + sudo chown root:root $3 + echo $3 uploaded + ;; ##### END UPLOAD LOWERTHIRD - START STREAM LIST ########## streamlist) -startline=`grep -n '***STREAM CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -endline=`grep -n '***AUDIO CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -rangeoflines=$startline','$endline'p' -sed -n $rangeoflines /usr/local/nginx/scripts/config.txt + startline=$(grep -n '***STREAM CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + endline=$(grep -n '***AUDIO CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + rangeoflines=$startline','$endline'p' + sed -n $rangeoflines /usr/local/nginx/scripts/config.txt -;; + ;; ##### END STREAM LIST - START DESTINATION LIST ########## destlist) -startline=`grep -n '***DESTINATION CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -endline=`grep -n '***STREAM CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -rangeoflines=$startline','$endline'p' -sed -n $rangeoflines /usr/local/nginx/scripts/config.txt | grep -v "rtmp://unconfigured.blk source" | grep -v "instagram.*YourKey source" | grep -v "rtmp://localhost/recording/stream[0-9].* source" + startline=$(grep -n '***DESTINATION CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + endline=$(grep -n '***STREAM CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + rangeoflines=$startline','$endline'p' + sed -n $rangeoflines /usr/local/nginx/scripts/config.txt | grep -v "rtmp://unconfigured.blk source" | grep -v "instagram.*YourKey source" | grep -v "rtmp://localhost/recording/stream[0-9].* source" -;; + ;; ##### END DESTINATION LIST - START AUDIO LIST ########## audiolist) -startline=`grep -n '***AUDIO CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -endline=`grep -n '***NEXT CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1` -rangeoflines=$startline','$endline'p' -sed -n $rangeoflines /usr/local/nginx/scripts/config.txt + startline=$(grep -n '***AUDIO CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + endline=$(grep -n '***NEXT CONFIG***' /usr/local/nginx/scripts/config.txt | cut -d: -f 1) + rangeoflines=$startline','$endline'p' + sed -n $rangeoflines /usr/local/nginx/scripts/config.txt -;; + ;; ##### END AUDIO LIST - START CONVERT RECORDING ########## convertrecording) -#mv --backup=numbered $3/$4.mp4 $3/$4_`date +%Y%m%d-%H_%M_%S`.mp4; -mv --backup=numbered $3/$4.mp4 $3/$4_old.mp4; -ffmpeg -y -i $2 -c copy $3/$4.mp4; -rm -f $2; -echo "Recording Converted" + #mv --backup=numbered $3/$4.mp4 $3/$4_`date +%Y%m%d-%H_%M_%S`.mp4; + mv --backup=numbered $3/$4.mp4 $3/$4_old.mp4 + ffmpeg -y -i $2 -c copy $3/$4.mp4 + rm -f $2 + echo "Recording Converted" -;; + ;; ##### END CONVERT RECORDING - START SRT ACCEPT ########## srtaccept) -LCK="/usr/local/nginx/scripts/tmp/srtaccept.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then - - -if [ -z "$STY" ]; then -echo "Turning $2 SRT Accept" -exec screen -dm -S srtaccept /bin/bash "$0" "$1" "$2"; -fi - -case $2 in -off) -echo "SRT Accept is already off" -exit 0 -;; - -*) -while true -do -/usr/local/nginx/scripts/srt/build/srt-live-transmit "srt://:9000" "file://con" | /usr/bin/ffmpeg -re -i pipe:0 -map 0:0 -map 0:1 -vcodec libx264 -preset veryfast -profile:v high -acodec aac -f flv -strict -2 rtmp://127.0.0.1/main/stream1080 -echo "Restarting SRT Accept..." -sleep .2 -done -esac -#~/ffmpeg_sources/srt/build/srt-live-transmit "srt://:9000" "file://con" | /usr/bin/ffmpeg -re -i pipe:0 -map 0:0 -map 0:1 -vcodec libx264 -preset veryfast -level high -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream1 -map 0:0 -map 0:2 -vcodec copy -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream2 -map 0:0 -map 0:3 -vcodec copy -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream3; - -else -case $2 in -off) -kill $(ps aux | grep "[S]CREEN.* srtaccept" | awk '{print $2}') -echo "Turning off SRT Accept" -;; - -*) -echo "SRT Accept is already on" -esac -fi - -;; + LCK="/usr/local/nginx/scripts/tmp/srtaccept.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + + if [ -z "$STY" ]; then + echo "Turning $2 SRT Accept" + exec screen -dm -S srtaccept /bin/bash "$0" "$1" "$2" + fi + + case $2 in + off) + echo "SRT Accept is already off" + exit 0 + ;; + + *) + while true; do + /usr/local/nginx/scripts/srt/build/srt-live-transmit "srt://:9000" "file://con" | /usr/bin/ffmpeg -re -i pipe:0 -map 0:0 -map 0:1 -vcodec libx264 -preset veryfast -profile:v high -acodec aac -f flv -strict -2 rtmp://127.0.0.1/main/stream1080 + echo "Restarting SRT Accept..." + sleep .2 + done + ;; + esac + #~/ffmpeg_sources/srt/build/srt-live-transmit "srt://:9000" "file://con" | /usr/bin/ffmpeg -re -i pipe:0 -map 0:0 -map 0:1 -vcodec libx264 -preset veryfast -level high -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream1 -map 0:0 -map 0:2 -vcodec copy -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream2 -map 0:0 -map 0:3 -vcodec copy -acodec aac -f flv -strict -2 rtmp://127.0.0.1/input/stream3; + + else + case $2 in + off) + kill $(ps aux | grep "[S]CREEN.* srtaccept" | awk '{print $2}') + echo "Turning off SRT Accept" + ;; + + *) + echo "SRT Accept is already on" + ;; + esac + fi + + ;; ##### END SRT ACCEPT - START SRT SEND ########## srtsend) -if [ -z "$STY" ]; then -exec screen -dm -S srtsend /bin/bash "$0" "$1"; -fi -/usr/local/bin/ffmpeg -re -fflags +genpts -stream_loop -1 -i /usr/local/nginx/scripts/images/8video.mp4 -map 0:0 -map 0:1 -map 0:2 -map 0:3 -vcodec copy -acodec copy -f mpegts - | ~/ffmpeg_sources/srt/build/srt-live-transmit -v "file://con" "srt://139.59.46.142:9000" + if [ -z "$STY" ]; then + exec screen -dm -S srtsend /bin/bash "$0" "$1" + fi + /usr/local/bin/ffmpeg -re -fflags +genpts -stream_loop -1 -i /usr/local/nginx/scripts/images/8video.mp4 -map 0:0 -map 0:1 -map 0:2 -map 0:3 -vcodec copy -acodec copy -f mpegts - | ~/ffmpeg_sources/srt/build/srt-live-transmit -v "file://con" "srt://139.59.46.142:9000" -;; + ;; ##### END SRT SEND - START REMAP ########## remap) -LCK="/usr/local/nginx/scripts/tmp/remap$3.LCK"; - -exec 8>$LCK; - -if flock -n -x 8; then - -chcount=$2 -if [ -z "$STY" ]; then -if [ $chcount == "6" ]; then -#echo "6 channel remapping is not possible due to OBS issues. Set OBS audio to 7.0 and remap in NGINX with 7. Stream 1-6 will have channels 1-6. Stream 7 will have no audio." -chcount=1000 -else -echo "Remapping $2 channels of $3 audio" -exec screen -dm -S remap$3 /bin/bash "$0" "$1" "$2" "$3"; -fi -fi - -i=0 -j=1 - -for (( i=0; i<$chcount; i++ )); do -while true #Keep checking till mapping is not none -do -mapping=`cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 4` -if [ $mapping == "mono" ] || [ $mapping == "stereo" ] -then -break -fi -((j=j+1)) -done - -c0=`cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 2` -c1=`cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 3` -rtmpapp=`cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 5` -if [ $rtmpapp == "main_back" ]; then -rtmpapp=$3 -fi - -if [ $3 == "backup" ]; then #Backup goes only to backup, not distribute. Avoid conflict with main -rtmpapp=$3 -fi - -#Fix for OBS-ffmpeg remap diff -#To generate multi-channel files with ffmpeg for OBS use, upto 5.0 is safe. Beyond that there are mapping issues -#OBS-ffmpeg mapping match for 3,4,5,9,10,11,12,13,14. 6 seems to have lfe issue on channel 4 in OBS -#7,8 and 16 need to be remapped as below -#7 -- 1-2,2-3,3-1,4-6,5-7,6-4,7-5 -#8 -- 1-2,2-3,3-1,4-6,5-7,6-8,7-4,8-5 -#16 -- 1-3,2-4,3-15,4-16,5-1,6-2,7-7,8-8,9-5,10-6,11-9,12-10,13-11,14-12,15-13,16-14 -case $2 in -7) -if [ $c0 == "c0" ]; then -c0="c2" -elif [ $c0 == "c1" ]; then -c0="c0" -elif [ $c0 == "c2" ]; then -c0="c1" -elif [ $c0 == "c3" ]; then -c0="c5" -elif [ $c0 == "c4" ]; then -c0="c6" -elif [ $c0 == "c5" ]; then -c0="c3" -elif [ $c0 == "c6" ]; then -c0="c4" -fi - -if [ $c1 == "c0" ]; then -c1="c2" -elif [ $c1 == "c1" ]; then -c1="c0" -elif [ $c1 == "c2" ]; then -c1="c1" -elif [ $c1 == "c3" ]; then -c1="c5" -elif [ $c1 == "c4" ]; then -c1="c6" -elif [ $c1 == "c5" ]; then -c1="c3" -elif [ $c1 == "c6" ]; then -c1="c4" -fi -;; - -8) -if [ $c0 == "c0" ]; then -c0="c2" -elif [ $c0 == "c1" ]; then -c0="c0" -elif [ $c0 == "c2" ]; then -c0="c1" -elif [ $c0 == "c3" ]; then -c0="c6" -elif [ $c0 == "c4" ]; then -c0="c7" -elif [ $c0 == "c5" ]; then -c0="c3" -elif [ $c0 == "c6" ]; then -c0="c4" -elif [ $c0 == "c7" ]; then -c0="c5" -fi - -if [ $c1 == "c0" ]; then -c1="c2" -elif [ $c1 == "c1" ]; then -c1="c0" -elif [ $c1 == "c2" ]; then -c1="c1" -elif [ $c1 == "c3" ]; then -c1="c6" -elif [ $c1 == "c4" ]; then -c1="c7" -elif [ $c1 == "c5" ]; then -c1="c3" -elif [ $c1 == "c6" ]; then -c1="c4" -elif [ $c1 == "c7" ]; then -c1="c5" -fi -;; - -16) -if [ $c0 == "c0" ]; then -c0="c4" -elif [ $c0 == "c1" ]; then -c0="c5" -elif [ $c0 == "c2" ]; then -c0="c0" -elif [ $c0 == "c3" ]; then -c0="c1" -elif [ $c0 == "c4" ]; then -c0="c8" -elif [ $c0 == "c5" ]; then -c0="c9" -#No change for c6 and c7 -elif [ $c0 == "c8" ]; then -c0="c10" -elif [ $c0 == "c9" ]; then -c0="c11" -elif [ $c0 == "c10" ]; then -c0="c12" -elif [ $c0 == "c11" ]; then -c0="c13" -elif [ $c0 == "c12" ]; then -c0="c14" -elif [ $c0 == "c13" ]; then -c0="c15" -elif [ $c0 == "c14" ]; then -c0="c2" -elif [ $c0 == "c15" ]; then -c0="c3" -fi - -if [ $c1 == "c0" ]; then -c1="c4" -elif [ $c1 == "c1" ]; then -c1="c5" -elif [ $c1 == "c2" ]; then -c1="c0" -elif [ $c1 == "c3" ]; then -c1="c1" -elif [ $c1 == "c4" ]; then -c1="c8" -elif [ $c1 == "c5" ]; then -c1="c9" -#No change for c6 and c7 -elif [ $c1 == "c8" ]; then -c1="c10" -elif [ $c1 == "c9" ]; then -c1="c11" -elif [ $c1 == "c10" ]; then -c1="c12" -elif [ $c1 == "c11" ]; then -c1="c13" -elif [ $c1 == "c12" ]; then -c1="c14" -elif [ $c1 == "c13" ]; then -c1="c15" -elif [ $c1 == "c14" ]; then -c1="c2" -elif [ $c1 == "c15" ]; then -c1="c3" -fi -esac -#OBS-ffmpeg remap adjustment complete - -if [[ $mapping = "mono" ]] -then -stream[i+1]="-map 0:v -map [a$i] -vcodec copy -acodec aac -ab 128k -f flv -strict -2 rtmp://127.0.0.1/$rtmpapp/stream$j" -map[i]="[0:a]pan=mono|c0=$c0,aresample=async=1000[a$i]" -elif [[ $mapping = "stereo" ]] -then -stream[i+1]="-map 0:v -map [a$i] -vcodec copy -acodec aac -ac 2 -ab 256k -f flv -strict -2 rtmp://127.0.0.1/$rtmpapp/stream$j" -map[i]="[0:a]pan=stereo|c0=$c0|c1=$c1,aresample=async=1000[a$i]" -((chcount=chcount-1)) -else -: -fi -((j=j+1)) -done - -case $chcount in -0) -echo "You don't have enough channels to do that!"; exec bash -;; - -1) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]}" ${stream[1]} -echo "Restarting remapping..." -sleep .2 -done -;; - -2) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]}" ${stream[1]} ${stream[2]} -echo "Restarting remapping..." -sleep .2 -done -;; - -3) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]}" ${stream[1]} ${stream[2]} ${stream[3]} -echo "Restarting remapping..." -sleep .2 -done -;; - -4) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} -echo "Restarting remapping..." -sleep .2 -done -;; - -5) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} -echo "Restarting remapping..." -sleep .2 -done -;; - -6) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} -echo "Restarting remapping..." -sleep .2 -done -;; - -7) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} -echo "Restarting remapping..." -sleep .2 -done -;; - -8) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} -echo "Restarting remapping..." -sleep .2 -done -;; - -9) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} -echo "Restarting remapping..." -sleep .2 -done -;; - -10) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} -echo "Restarting remapping..." -sleep .2 -done -;; - -11) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} -echo "Restarting remapping..." -sleep .2 -done -;; - -12) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} -echo "Restarting remapping..." -sleep .2 -done -;; - -13) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} -echo "Restarting remapping..." -sleep .2 -done -;; - -14) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} -echo "Restarting remapping..." -sleep .2 -done -;; - -15) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]};${map[14]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} ${stream[15]} -echo "Restarting remapping..." -sleep .2 -done -;; - -16) -while true -do -/usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]};${map[14]};${map[15]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} ${stream[15]} ${stream[16]} -echo "Restarting remapping..." -sleep .2 -done -;; - -*) -echo "6 channel remapping is not possible due to OBS issues. Set OBS audio to 7.0 and remap in NGINX with 7. Stream 1-6 will have channels 1-6. Stream 7 will have no audio." -exit 0 -esac - -else -case $2 in -off) -kill $(ps aux | grep "[S]CREEN.* remap$3" | awk '{print $2}') -echo "Turning off $3 remapping" -;; - -*) -echo "$3 audio is already being remapped with $2 channels" -esac -fi + LCK="/usr/local/nginx/scripts/tmp/remap$3.LCK" + + exec 8>$LCK + + if flock -n -x 8; then + + chcount=$2 + if [ -z "$STY" ]; then + if [ $chcount == "6" ]; then + #echo "6 channel remapping is not possible due to OBS issues. Set OBS audio to 7.0 and remap in NGINX with 7. Stream 1-6 will have channels 1-6. Stream 7 will have no audio." + chcount=1000 + else + echo "Remapping $2 channels of $3 audio" + exec screen -dm -S remap$3 /bin/bash "$0" "$1" "$2" "$3" + fi + fi + + i=0 + j=1 + + for ((i = 0; i < $chcount; i++)); do + while true; do #Keep checking till mapping is not none + mapping=$(cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 4) + if [ $mapping == "mono" ] || [ $mapping == "stereo" ]; then + break + fi + ((j = j + 1)) + done + + c0=$(cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 2) + c1=$(cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 3) + rtmpapp=$(cat /usr/local/nginx/scripts/config.txt | grep '__stream'$j'__audio__' | cut -d ' ' -f 5) + if [ $rtmpapp == "main_back" ]; then + rtmpapp=$3 + fi + + if [ $3 == "backup" ]; then #Backup goes only to backup, not distribute. Avoid conflict with main + rtmpapp=$3 + fi + + #Fix for OBS-ffmpeg remap diff + #To generate multi-channel files with ffmpeg for OBS use, upto 5.0 is safe. Beyond that there are mapping issues + #OBS-ffmpeg mapping match for 3,4,5,9,10,11,12,13,14. 6 seems to have lfe issue on channel 4 in OBS + #7,8 and 16 need to be remapped as below + #7 -- 1-2,2-3,3-1,4-6,5-7,6-4,7-5 + #8 -- 1-2,2-3,3-1,4-6,5-7,6-8,7-4,8-5 + #16 -- 1-3,2-4,3-15,4-16,5-1,6-2,7-7,8-8,9-5,10-6,11-9,12-10,13-11,14-12,15-13,16-14 + case $2 in + 7) + if [ $c0 == "c0" ]; then + c0="c2" + elif [ $c0 == "c1" ]; then + c0="c0" + elif [ $c0 == "c2" ]; then + c0="c1" + elif [ $c0 == "c3" ]; then + c0="c5" + elif [ $c0 == "c4" ]; then + c0="c6" + elif [ $c0 == "c5" ]; then + c0="c3" + elif [ $c0 == "c6" ]; then + c0="c4" + fi + + if [ $c1 == "c0" ]; then + c1="c2" + elif [ $c1 == "c1" ]; then + c1="c0" + elif [ $c1 == "c2" ]; then + c1="c1" + elif [ $c1 == "c3" ]; then + c1="c5" + elif [ $c1 == "c4" ]; then + c1="c6" + elif [ $c1 == "c5" ]; then + c1="c3" + elif [ $c1 == "c6" ]; then + c1="c4" + fi + ;; + + 8) + if [ $c0 == "c0" ]; then + c0="c2" + elif [ $c0 == "c1" ]; then + c0="c0" + elif [ $c0 == "c2" ]; then + c0="c1" + elif [ $c0 == "c3" ]; then + c0="c6" + elif [ $c0 == "c4" ]; then + c0="c7" + elif [ $c0 == "c5" ]; then + c0="c3" + elif [ $c0 == "c6" ]; then + c0="c4" + elif [ $c0 == "c7" ]; then + c0="c5" + fi + + if [ $c1 == "c0" ]; then + c1="c2" + elif [ $c1 == "c1" ]; then + c1="c0" + elif [ $c1 == "c2" ]; then + c1="c1" + elif [ $c1 == "c3" ]; then + c1="c6" + elif [ $c1 == "c4" ]; then + c1="c7" + elif [ $c1 == "c5" ]; then + c1="c3" + elif [ $c1 == "c6" ]; then + c1="c4" + elif [ $c1 == "c7" ]; then + c1="c5" + fi + ;; + + 16) + if [ $c0 == "c0" ]; then + c0="c4" + elif [ $c0 == "c1" ]; then + c0="c5" + elif [ $c0 == "c2" ]; then + c0="c0" + elif [ $c0 == "c3" ]; then + c0="c1" + elif [ $c0 == "c4" ]; then + c0="c8" + elif [ $c0 == "c5" ]; then + c0="c9" + #No change for c6 and c7 + elif [ $c0 == "c8" ]; then + c0="c10" + elif [ $c0 == "c9" ]; then + c0="c11" + elif [ $c0 == "c10" ]; then + c0="c12" + elif [ $c0 == "c11" ]; then + c0="c13" + elif [ $c0 == "c12" ]; then + c0="c14" + elif [ $c0 == "c13" ]; then + c0="c15" + elif [ $c0 == "c14" ]; then + c0="c2" + elif [ $c0 == "c15" ]; then + c0="c3" + fi + + if [ $c1 == "c0" ]; then + c1="c4" + elif [ $c1 == "c1" ]; then + c1="c5" + elif [ $c1 == "c2" ]; then + c1="c0" + elif [ $c1 == "c3" ]; then + c1="c1" + elif [ $c1 == "c4" ]; then + c1="c8" + elif [ $c1 == "c5" ]; then + c1="c9" + #No change for c6 and c7 + elif [ $c1 == "c8" ]; then + c1="c10" + elif [ $c1 == "c9" ]; then + c1="c11" + elif [ $c1 == "c10" ]; then + c1="c12" + elif [ $c1 == "c11" ]; then + c1="c13" + elif [ $c1 == "c12" ]; then + c1="c14" + elif [ $c1 == "c13" ]; then + c1="c15" + elif [ $c1 == "c14" ]; then + c1="c2" + elif [ $c1 == "c15" ]; then + c1="c3" + fi + ;; + esac + #OBS-ffmpeg remap adjustment complete + + if [[ $mapping = "mono" ]]; then + stream[i + 1]="-map 0:v -map [a$i] -vcodec copy -acodec aac -ab 128k -f flv -strict -2 rtmp://127.0.0.1/$rtmpapp/stream$j" + map[i]="[0:a]pan=mono|c0=$c0,aresample=async=1000[a$i]" + elif [[ $mapping = "stereo" ]]; then + stream[i + 1]="-map 0:v -map [a$i] -vcodec copy -acodec aac -ac 2 -ab 256k -f flv -strict -2 rtmp://127.0.0.1/$rtmpapp/stream$j" + map[i]="[0:a]pan=stereo|c0=$c0|c1=$c1,aresample=async=1000[a$i]" + ((chcount = chcount - 1)) + else + : + fi + ((j = j + 1)) + done + + case $chcount in + 0) + echo "You don't have enough channels to do that!" + exec bash + ;; + + 1) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]}" ${stream[1]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 2) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]}" ${stream[1]} ${stream[2]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 3) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]}" ${stream[1]} ${stream[2]} ${stream[3]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 4) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 5) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 6) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 7) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 8) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 9) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 10) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 11) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 12) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 13) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 14) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 15) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]};${map[14]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} ${stream[15]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + 16) + while true; do + /usr/bin/ffmpeg -re -i rtmp://127.0.0.1/$3/stream1080 -filter_complex "${map[0]};${map[1]};${map[2]};${map[3]};${map[4]};${map[5]};${map[6]};${map[7]};${map[8]};${map[9]};${map[10]};${map[11]};${map[12]};${map[13]};${map[14]};${map[15]}" ${stream[1]} ${stream[2]} ${stream[3]} ${stream[4]} ${stream[5]} ${stream[6]} ${stream[7]} ${stream[8]} ${stream[9]} ${stream[10]} ${stream[11]} ${stream[12]} ${stream[13]} ${stream[14]} ${stream[15]} ${stream[16]} + echo "Restarting remapping..." + sleep .2 + done + ;; + + *) + echo "6 channel remapping is not possible due to OBS issues. Set OBS audio to 7.0 and remap in NGINX with 7. Stream 1-6 will have channels 1-6. Stream 7 will have no audio." + exit 0 + ;; + esac + + else + case $2 in + off) + kill $(ps aux | grep "[S]CREEN.* remap$3" | awk '{print $2}') + echo "Turning off $3 remapping" + ;; + + *) + echo "$3 audio is already being remapped with $2 channels" + ;; + esac + fi + ;; esac diff --git a/scripts/config.txt b/scripts/config.txt index a37291e..2bf6572 100644 --- a/scripts/config.txt +++ b/scripts/config.txt @@ -1,204 +1,2405 @@ ***DESTINATION CONFIG*** -__stream1__out1__ rtmp://unconfigured.blk source channel_name -__stream1__out2__ rtmp://unconfigured.blk source channel_name -__stream1__out3__ rtmp://unconfigured.blk source channel_name -__stream1__out4__ rtmp://unconfigured.blk source channel_name -__stream1__out5__ rtmp://unconfigured.blk source channel_name -__stream1__out6__ rtmp://unconfigured.blk source channel_name -__stream1__out7__ rtmp://unconfigured.blk source channel_name -__stream1__out8__ rtmp://unconfigured.blk source channel_name -__stream1__out9__ rtmp://unconfigured.blk source channel_name -__stream1__out10__ rtmp://unconfigured.blk source channel_name -__stream2__out1__ rtmp://unconfigured.blk source channel_name -__stream2__out2__ rtmp://unconfigured.blk source channel_name -__stream2__out3__ rtmp://unconfigured.blk source channel_name -__stream2__out4__ rtmp://unconfigured.blk source channel_name -__stream2__out5__ rtmp://unconfigured.blk source channel_name -__stream2__out6__ rtmp://unconfigured.blk source channel_name -__stream2__out7__ rtmp://unconfigured.blk source channel_name -__stream2__out8__ rtmp://unconfigured.blk source channel_name -__stream2__out9__ rtmp://unconfigured.blk source channel_name -__stream2__out10__ rtmp://unconfigured.blk source channel_name -__stream3__out1__ rtmp://unconfigured.blk source channel_name -__stream3__out2__ rtmp://unconfigured.blk source channel_name -__stream3__out3__ rtmp://unconfigured.blk source channel_name -__stream3__out4__ rtmp://unconfigured.blk source channel_name -__stream3__out5__ rtmp://unconfigured.blk source channel_name -__stream3__out6__ rtmp://unconfigured.blk source channel_name -__stream3__out7__ rtmp://unconfigured.blk source channel_name -__stream3__out8__ rtmp://unconfigured.blk source channel_name -__stream3__out9__ rtmp://unconfigured.blk source channel_name -__stream3__out10__ rtmp://unconfigured.blk source channel_name -__stream4__out1__ rtmp://unconfigured.blk source channel_name -__stream4__out2__ rtmp://unconfigured.blk source channel_name -__stream4__out3__ rtmp://unconfigured.blk source channel_name -__stream4__out4__ rtmp://unconfigured.blk source channel_name -__stream4__out5__ rtmp://unconfigured.blk source channel_name -__stream4__out6__ rtmp://unconfigured.blk source channel_name -__stream4__out7__ rtmp://unconfigured.blk source channel_name -__stream4__out8__ rtmp://unconfigured.blk source channel_name -__stream4__out9__ rtmp://unconfigured.blk source channel_name -__stream4__out10__ rtmp://unconfigured.blk source channel_name -__stream5__out1__ rtmp://unconfigured.blk source channel_name -__stream5__out2__ rtmp://unconfigured.blk source channel_name -__stream5__out3__ rtmp://unconfigured.blk source channel_name -__stream5__out4__ rtmp://unconfigured.blk source channel_name -__stream5__out5__ rtmp://unconfigured.blk source channel_name -__stream5__out6__ rtmp://unconfigured.blk source channel_name -__stream5__out7__ rtmp://unconfigured.blk source channel_name -__stream5__out8__ rtmp://unconfigured.blk source channel_name -__stream5__out9__ rtmp://unconfigured.blk source channel_name -__stream5__out10__ rtmp://unconfigured.blk source channel_name -__stream6__out1__ rtmp://unconfigured.blk source channel_name -__stream6__out2__ rtmp://unconfigured.blk source channel_name -__stream6__out3__ rtmp://unconfigured.blk source channel_name -__stream6__out4__ rtmp://unconfigured.blk source channel_name -__stream6__out5__ rtmp://unconfigured.blk source channel_name -__stream6__out6__ rtmp://unconfigured.blk source channel_name -__stream6__out7__ rtmp://unconfigured.blk source channel_name -__stream6__out8__ rtmp://unconfigured.blk source channel_name -__stream6__out9__ rtmp://unconfigured.blk source channel_name -__stream6__out10__ rtmp://unconfigured.blk source channel_name -__stream7__out1__ rtmp://unconfigured.blk source channel_name -__stream7__out2__ rtmp://unconfigured.blk source channel_name -__stream7__out3__ rtmp://unconfigured.blk source channel_name -__stream7__out4__ rtmp://unconfigured.blk source channel_name -__stream7__out5__ rtmp://unconfigured.blk source channel_name -__stream7__out6__ rtmp://unconfigured.blk source channel_name -__stream7__out7__ rtmp://unconfigured.blk source channel_name -__stream7__out8__ rtmp://unconfigured.blk source channel_name -__stream7__out9__ rtmp://unconfigured.blk source channel_name -__stream7__out10__ rtmp://unconfigured.blk source channel_name -__stream8__out1__ rtmp://unconfigured.blk source channel_name -__stream8__out2__ rtmp://unconfigured.blk source channel_name -__stream8__out3__ rtmp://unconfigured.blk source channel_name -__stream8__out4__ rtmp://unconfigured.blk source channel_name -__stream8__out5__ rtmp://unconfigured.blk source channel_name -__stream8__out6__ rtmp://unconfigured.blk source channel_name -__stream8__out7__ rtmp://unconfigured.blk source channel_name -__stream8__out8__ rtmp://unconfigured.blk source channel_name -__stream8__out9__ rtmp://unconfigured.blk source channel_name -__stream8__out10__ rtmp://unconfigured.blk source channel_name -__stream9__out1__ rtmp://unconfigured.blk source channel_name -__stream9__out2__ rtmp://unconfigured.blk source channel_name -__stream9__out3__ rtmp://unconfigured.blk source channel_name -__stream9__out4__ rtmp://unconfigured.blk source channel_name -__stream9__out5__ rtmp://unconfigured.blk source channel_name -__stream9__out6__ rtmp://unconfigured.blk source channel_name -__stream9__out7__ rtmp://unconfigured.blk source channel_name -__stream9__out8__ rtmp://unconfigured.blk source channel_name -__stream9__out9__ rtmp://unconfigured.blk source channel_name -__stream9__out10__ rtmp://unconfigured.blk source channel_name -__stream10__out1__ rtmp://unconfigured.blk source channel_name -__stream10__out2__ rtmp://unconfigured.blk source channel_name -__stream10__out3__ rtmp://unconfigured.blk source channel_name -__stream10__out4__ rtmp://unconfigured.blk source channel_name -__stream10__out5__ rtmp://unconfigured.blk source channel_name -__stream10__out6__ rtmp://unconfigured.blk source channel_name -__stream10__out7__ rtmp://unconfigured.blk source channel_name -__stream10__out8__ rtmp://unconfigured.blk source channel_name -__stream10__out9__ rtmp://unconfigured.blk source channel_name -__stream10__out10__ rtmp://unconfigured.blk source channel_name -__stream11__out1__ rtmp://unconfigured.blk source channel_name -__stream11__out2__ rtmp://unconfigured.blk source channel_name -__stream11__out3__ rtmp://unconfigured.blk source channel_name -__stream11__out4__ rtmp://unconfigured.blk source channel_name -__stream11__out5__ rtmp://unconfigured.blk source channel_name -__stream11__out6__ rtmp://unconfigured.blk source channel_name -__stream11__out7__ rtmp://unconfigured.blk source channel_name -__stream11__out8__ rtmp://unconfigured.blk source channel_name -__stream11__out9__ rtmp://unconfigured.blk source channel_name -__stream11__out10__ rtmp://unconfigured.blk source channel_name -__stream12__out1__ rtmp://unconfigured.blk source channel_name -__stream12__out2__ rtmp://unconfigured.blk source channel_name -__stream12__out3__ rtmp://unconfigured.blk source channel_name -__stream12__out4__ rtmp://unconfigured.blk source channel_name -__stream12__out5__ rtmp://unconfigured.blk source channel_name -__stream12__out6__ rtmp://unconfigured.blk source channel_name -__stream12__out7__ rtmp://unconfigured.blk source channel_name -__stream12__out8__ rtmp://unconfigured.blk source channel_name -__stream12__out9__ rtmp://unconfigured.blk source channel_name -__stream12__out10__ rtmp://unconfigured.blk source channel_name -__stream13__out1__ rtmp://unconfigured.blk source channel_name -__stream13__out2__ rtmp://unconfigured.blk source channel_name -__stream13__out3__ rtmp://unconfigured.blk source channel_name -__stream13__out4__ rtmp://unconfigured.blk source channel_name -__stream13__out5__ rtmp://unconfigured.blk source channel_name -__stream13__out6__ rtmp://unconfigured.blk source channel_name -__stream13__out7__ rtmp://unconfigured.blk source channel_name -__stream13__out8__ rtmp://unconfigured.blk source channel_name -__stream13__out9__ rtmp://unconfigured.blk source channel_name -__stream13__out10__ rtmp://unconfigured.blk source channel_name -__stream14__out1__ rtmp://unconfigured.blk source channel_name -__stream14__out2__ rtmp://unconfigured.blk source channel_name -__stream14__out3__ rtmp://unconfigured.blk source channel_name -__stream14__out4__ rtmp://unconfigured.blk source channel_name -__stream14__out5__ rtmp://unconfigured.blk source channel_name -__stream14__out6__ rtmp://unconfigured.blk source channel_name -__stream14__out7__ rtmp://unconfigured.blk source channel_name -__stream14__out8__ rtmp://unconfigured.blk source channel_name -__stream14__out9__ rtmp://unconfigured.blk source channel_name -__stream14__out10__ rtmp://unconfigured.blk source channel_name -__stream15__out1__ rtmp://unconfigured.blk source channel_name -__stream15__out2__ rtmp://unconfigured.blk source channel_name -__stream15__out3__ rtmp://unconfigured.blk source channel_name -__stream15__out4__ rtmp://unconfigured.blk source channel_name -__stream15__out5__ rtmp://unconfigured.blk source channel_name -__stream15__out6__ rtmp://unconfigured.blk source channel_name -__stream15__out7__ rtmp://unconfigured.blk source channel_name -__stream15__out8__ rtmp://unconfigured.blk source channel_name -__stream15__out9__ rtmp://unconfigured.blk source channel_name -__stream15__out10__ rtmp://unconfigured.blk source channel_name -__stream16__out1__ rtmp://unconfigured.blk source channel_name -__stream16__out2__ rtmp://unconfigured.blk source channel_name -__stream16__out3__ rtmp://unconfigured.blk source channel_name -__stream16__out4__ rtmp://unconfigured.blk source channel_name -__stream16__out5__ rtmp://unconfigured.blk source channel_name -__stream16__out6__ rtmp://unconfigured.blk source channel_name -__stream16__out7__ rtmp://unconfigured.blk source channel_name -__stream16__out8__ rtmp://unconfigured.blk source channel_name -__stream16__out9__ rtmp://unconfigured.blk source channel_name -__stream16__out10__ rtmp://unconfigured.blk source channel_name -__stream17__out1__ rtmp://unconfigured.blk source channel_name -__stream17__out2__ rtmp://unconfigured.blk source channel_name -__stream17__out3__ rtmp://unconfigured.blk source channel_name -__stream17__out4__ rtmp://unconfigured.blk source channel_name -__stream17__out5__ rtmp://unconfigured.blk source channel_name -__stream17__out6__ rtmp://unconfigured.blk source channel_name -__stream17__out7__ rtmp://unconfigured.blk source channel_name -__stream17__out8__ rtmp://unconfigured.blk source channel_name -__stream17__out9__ rtmp://unconfigured.blk source channel_name -__stream17__out10__ rtmp://unconfigured.blk source channel_name -__stream18__out1__ rtmp://unconfigured.blk source channel_name -__stream18__out2__ rtmp://unconfigured.blk source channel_name -__stream18__out3__ rtmp://unconfigured.blk source channel_name -__stream18__out4__ rtmp://unconfigured.blk source channel_name -__stream18__out5__ rtmp://unconfigured.blk source channel_name -__stream18__out6__ rtmp://unconfigured.blk source channel_name -__stream18__out7__ rtmp://unconfigured.blk source channel_name -__stream18__out8__ rtmp://unconfigured.blk source channel_name -__stream18__out9__ rtmp://unconfigured.blk source channel_name -__stream18__out10__ rtmp://unconfigured.blk source channel_name -__stream19__out1__ rtmp://unconfigured.blk source channel_name -__stream19__out2__ rtmp://unconfigured.blk source channel_name -__stream19__out3__ rtmp://unconfigured.blk source channel_name -__stream19__out4__ rtmp://unconfigured.blk source channel_name -__stream19__out5__ rtmp://unconfigured.blk source channel_name -__stream19__out6__ rtmp://unconfigured.blk source channel_name -__stream19__out7__ rtmp://unconfigured.blk source channel_name -__stream19__out8__ rtmp://unconfigured.blk source channel_name -__stream19__out9__ rtmp://unconfigured.blk source channel_name -__stream19__out10__ rtmp://unconfigured.blk source channel_name -__stream20__out1__ rtmp://unconfigured.blk source channel_name -__stream20__out2__ rtmp://unconfigured.blk source channel_name -__stream20__out3__ rtmp://unconfigured.blk source channel_name -__stream20__out4__ rtmp://unconfigured.blk source channel_name -__stream20__out5__ rtmp://unconfigured.blk source channel_name -__stream20__out6__ rtmp://unconfigured.blk source channel_name -__stream20__out7__ rtmp://unconfigured.blk source channel_name -__stream20__out8__ rtmp://unconfigured.blk source channel_name -__stream20__out9__ rtmp://unconfigured.blk source channel_name -__stream20__out10__ rtmp://unconfigured.blk source channel_name +__stream1__out1__ rtmp://a.rtmp.youtube.com/live2/1111-aaaa-1111-aaaa-1111 source LB_Wb_Eng +__stream1__out2__ rtmp://a.rtmp.youtube.com/live2/1111-aaaa-1111-aaaa-1111 vertical Isha_Foundation_YT +__stream1__out3__ +__stream1__out4__ source +__stream1__out5__ rtmp://34.93.121.211/distribute/stream2 source test +__stream1__out6__ +__stream1__out7__ +__stream1__out8__ +__stream1__out9__ +__stream1__out10__ +__stream1__out11__ +__stream1__out12__ +__stream1__out13__ +__stream1__out14__ +__stream1__out15__ +__stream1__out16__ +__stream1__out17__ +__stream1__out18__ +__stream1__out19__ +__stream1__out20__ +__stream1__out21__ +__stream1__out22__ +__stream1__out23__ +__stream1__out24__ +__stream1__out25__ +__stream1__out26__ +__stream1__out27__ +__stream1__out28__ +__stream1__out29__ +__stream1__out30__ +__stream1__out31__ +__stream1__out32__ +__stream1__out33__ +__stream1__out34__ +__stream1__out35__ +__stream1__out36__ +__stream1__out37__ +__stream1__out38__ +__stream1__out39__ +__stream1__out40__ +__stream1__out41__ +__stream1__out42__ +__stream1__out43__ +__stream1__out44__ +__stream1__out45__ +__stream1__out46__ +__stream1__out47__ +__stream1__out48__ +__stream1__out49__ +__stream1__out50__ +__stream1__out51__ +__stream1__out52__ +__stream1__out53__ +__stream1__out54__ +__stream1__out55__ +__stream1__out56__ +__stream1__out57__ +__stream1__out58__ +__stream1__out59__ +__stream1__out60__ +__stream1__out61__ +__stream1__out62__ +__stream1__out63__ +__stream1__out64__ +__stream1__out65__ +__stream1__out66__ +__stream1__out67__ +__stream1__out68__ +__stream1__out69__ +__stream1__out70__ +__stream1__out71__ +__stream1__out72__ +__stream1__out73__ +__stream1__out74__ +__stream1__out75__ +__stream1__out76__ +__stream1__out77__ +__stream1__out78__ +__stream1__out79__ +__stream1__out80__ +__stream1__out81__ +__stream1__out82__ +__stream1__out83__ +__stream1__out84__ +__stream1__out85__ +__stream1__out86__ +__stream1__out87__ +__stream1__out88__ +__stream1__out89__ +__stream1__out90__ +__stream1__out91__ +__stream1__out92__ +__stream1__out93__ +__stream1__out94__ +__stream1__out95__ + +__stream2__out1__ +__stream2__out2__ rtmp://a.rtmp.youtube.com/live2/1111-aaaa-1111-aaaa-1111 source LB_Wb_Hindi +__stream2__out3__ rtmps://live-api-s.facebook.com:443/rtmp/FB-1111-aaaa-1111-aaaa-1111 source Sadhguru_Tamil_FB_GP +__stream2__out4__ +__stream2__out5__ +__stream2__out6__ +__stream2__out7__ +__stream2__out8__ +__stream2__out9__ +__stream2__out10__ +__stream2__out11__ +__stream2__out12__ +__stream2__out13__ +__stream2__out14__ +__stream2__out15__ +__stream2__out16__ +__stream2__out17__ +__stream2__out18__ +__stream2__out19__ +__stream2__out20__ +__stream2__out21__ +__stream2__out22__ +__stream2__out23__ +__stream2__out24__ +__stream2__out25__ +__stream2__out26__ +__stream2__out27__ +__stream2__out28__ +__stream2__out29__ +__stream2__out30__ +__stream2__out31__ +__stream2__out32__ +__stream2__out33__ +__stream2__out34__ +__stream2__out35__ +__stream2__out36__ +__stream2__out37__ +__stream2__out38__ +__stream2__out39__ +__stream2__out40__ +__stream2__out41__ +__stream2__out42__ +__stream2__out43__ +__stream2__out44__ +__stream2__out45__ +__stream2__out46__ +__stream2__out47__ +__stream2__out48__ +__stream2__out49__ +__stream2__out50__ +__stream2__out51__ +__stream2__out52__ +__stream2__out53__ +__stream2__out54__ +__stream2__out55__ +__stream2__out56__ +__stream2__out57__ +__stream2__out58__ +__stream2__out59__ +__stream2__out60__ +__stream2__out61__ +__stream2__out62__ +__stream2__out63__ +__stream2__out64__ +__stream2__out65__ +__stream2__out66__ +__stream2__out67__ +__stream2__out68__ +__stream2__out69__ +__stream2__out70__ +__stream2__out71__ +__stream2__out72__ +__stream2__out73__ +__stream2__out74__ +__stream2__out75__ +__stream2__out76__ +__stream2__out77__ +__stream2__out78__ +__stream2__out79__ +__stream2__out80__ +__stream2__out81__ +__stream2__out82__ +__stream2__out83__ +__stream2__out84__ +__stream2__out85__ +__stream2__out86__ +__stream2__out87__ +__stream2__out88__ +__stream2__out89__ +__stream2__out90__ +__stream2__out91__ +__stream2__out92__ +__stream2__out93__ +__stream2__out94__ +__stream2__out95__ + +__stream3__out1__ source +__stream3__out2__ +__stream3__out3__ +__stream3__out4__ +__stream3__out5__ +__stream3__out6__ +__stream3__out7__ +__stream3__out8__ +__stream3__out9__ +__stream3__out10__ +__stream3__out11__ +__stream3__out12__ +__stream3__out13__ +__stream3__out14__ +__stream3__out15__ +__stream3__out16__ +__stream3__out17__ +__stream3__out18__ +__stream3__out19__ +__stream3__out20__ +__stream3__out21__ +__stream3__out22__ +__stream3__out23__ +__stream3__out24__ +__stream3__out25__ +__stream3__out26__ +__stream3__out27__ +__stream3__out28__ +__stream3__out29__ +__stream3__out30__ +__stream3__out31__ +__stream3__out32__ +__stream3__out33__ +__stream3__out34__ +__stream3__out35__ +__stream3__out36__ +__stream3__out37__ +__stream3__out38__ +__stream3__out39__ +__stream3__out40__ +__stream3__out41__ +__stream3__out42__ +__stream3__out43__ +__stream3__out44__ +__stream3__out45__ +__stream3__out46__ +__stream3__out47__ +__stream3__out48__ +__stream3__out49__ +__stream3__out50__ +__stream3__out51__ +__stream3__out52__ +__stream3__out53__ +__stream3__out54__ +__stream3__out55__ +__stream3__out56__ +__stream3__out57__ +__stream3__out58__ +__stream3__out59__ +__stream3__out60__ +__stream3__out61__ +__stream3__out62__ +__stream3__out63__ +__stream3__out64__ +__stream3__out65__ +__stream3__out66__ +__stream3__out67__ +__stream3__out68__ +__stream3__out69__ +__stream3__out70__ +__stream3__out71__ +__stream3__out72__ +__stream3__out73__ +__stream3__out74__ +__stream3__out75__ +__stream3__out76__ +__stream3__out77__ +__stream3__out78__ +__stream3__out79__ +__stream3__out80__ +__stream3__out81__ +__stream3__out82__ +__stream3__out83__ +__stream3__out84__ +__stream3__out85__ +__stream3__out86__ +__stream3__out87__ +__stream3__out88__ +__stream3__out89__ +__stream3__out90__ +__stream3__out91__ +__stream3__out92__ +__stream3__out93__ +__stream3__out94__ +__stream3__out95__ + +__stream4__out1__ +__stream4__out2__ +__stream4__out3__ +__stream4__out4__ +__stream4__out5__ +__stream4__out6__ +__stream4__out7__ +__stream4__out8__ +__stream4__out9__ +__stream4__out10__ +__stream4__out11__ +__stream4__out12__ +__stream4__out13__ +__stream4__out14__ +__stream4__out15__ +__stream4__out16__ +__stream4__out17__ +__stream4__out18__ +__stream4__out19__ +__stream4__out20__ +__stream4__out21__ +__stream4__out22__ +__stream4__out23__ +__stream4__out24__ +__stream4__out25__ +__stream4__out26__ +__stream4__out27__ +__stream4__out28__ +__stream4__out29__ +__stream4__out30__ +__stream4__out31__ +__stream4__out32__ +__stream4__out33__ +__stream4__out34__ +__stream4__out35__ +__stream4__out36__ +__stream4__out37__ +__stream4__out38__ +__stream4__out39__ +__stream4__out40__ +__stream4__out41__ +__stream4__out42__ +__stream4__out43__ +__stream4__out44__ +__stream4__out45__ +__stream4__out46__ +__stream4__out47__ +__stream4__out48__ +__stream4__out49__ +__stream4__out50__ +__stream4__out51__ +__stream4__out52__ +__stream4__out53__ +__stream4__out54__ +__stream4__out55__ +__stream4__out56__ +__stream4__out57__ +__stream4__out58__ +__stream4__out59__ +__stream4__out60__ +__stream4__out61__ +__stream4__out62__ +__stream4__out63__ +__stream4__out64__ +__stream4__out65__ +__stream4__out66__ +__stream4__out67__ +__stream4__out68__ +__stream4__out69__ +__stream4__out70__ +__stream4__out71__ +__stream4__out72__ +__stream4__out73__ +__stream4__out74__ +__stream4__out75__ +__stream4__out76__ +__stream4__out77__ +__stream4__out78__ +__stream4__out79__ +__stream4__out80__ +__stream4__out81__ +__stream4__out82__ +__stream4__out83__ +__stream4__out84__ +__stream4__out85__ +__stream4__out86__ +__stream4__out87__ +__stream4__out88__ +__stream4__out89__ +__stream4__out90__ +__stream4__out91__ +__stream4__out92__ +__stream4__out93__ +__stream4__out94__ +__stream4__out95__ + +__stream5__out1__ +__stream5__out2__ +__stream5__out3__ +__stream5__out4__ +__stream5__out5__ +__stream5__out6__ +__stream5__out7__ +__stream5__out8__ +__stream5__out9__ +__stream5__out10__ +__stream5__out11__ +__stream5__out12__ +__stream5__out13__ +__stream5__out14__ +__stream5__out15__ +__stream5__out16__ +__stream5__out17__ +__stream5__out18__ +__stream5__out19__ +__stream5__out20__ +__stream5__out21__ +__stream5__out22__ +__stream5__out23__ +__stream5__out24__ +__stream5__out25__ +__stream5__out26__ +__stream5__out27__ +__stream5__out28__ +__stream5__out29__ +__stream5__out30__ +__stream5__out31__ +__stream5__out32__ +__stream5__out33__ +__stream5__out34__ +__stream5__out35__ +__stream5__out36__ +__stream5__out37__ +__stream5__out38__ +__stream5__out39__ +__stream5__out40__ +__stream5__out41__ +__stream5__out42__ +__stream5__out43__ +__stream5__out44__ +__stream5__out45__ +__stream5__out46__ +__stream5__out47__ +__stream5__out48__ +__stream5__out49__ +__stream5__out50__ +__stream5__out51__ +__stream5__out52__ +__stream5__out53__ +__stream5__out54__ +__stream5__out55__ +__stream5__out56__ +__stream5__out57__ +__stream5__out58__ +__stream5__out59__ +__stream5__out60__ +__stream5__out61__ +__stream5__out62__ +__stream5__out63__ +__stream5__out64__ +__stream5__out65__ +__stream5__out66__ +__stream5__out67__ +__stream5__out68__ +__stream5__out69__ +__stream5__out70__ +__stream5__out71__ +__stream5__out72__ +__stream5__out73__ +__stream5__out74__ +__stream5__out75__ +__stream5__out76__ +__stream5__out77__ +__stream5__out78__ +__stream5__out79__ +__stream5__out80__ +__stream5__out81__ +__stream5__out82__ +__stream5__out83__ +__stream5__out84__ +__stream5__out85__ +__stream5__out86__ +__stream5__out87__ +__stream5__out88__ +__stream5__out89__ +__stream5__out90__ +__stream5__out91__ +__stream5__out92__ +__stream5__out93__ +__stream5__out94__ +__stream5__out95__ + +__stream6__out1__ +__stream6__out2__ +__stream6__out3__ +__stream6__out4__ +__stream6__out5__ +__stream6__out6__ +__stream6__out7__ +__stream6__out8__ +__stream6__out9__ +__stream6__out10__ +__stream6__out11__ +__stream6__out12__ +__stream6__out13__ +__stream6__out14__ +__stream6__out15__ +__stream6__out16__ +__stream6__out17__ +__stream6__out18__ +__stream6__out19__ +__stream6__out20__ +__stream6__out21__ +__stream6__out22__ +__stream6__out23__ +__stream6__out24__ +__stream6__out25__ +__stream6__out26__ +__stream6__out27__ +__stream6__out28__ +__stream6__out29__ +__stream6__out30__ +__stream6__out31__ +__stream6__out32__ +__stream6__out33__ +__stream6__out34__ +__stream6__out35__ +__stream6__out36__ +__stream6__out37__ +__stream6__out38__ +__stream6__out39__ +__stream6__out40__ +__stream6__out41__ +__stream6__out42__ +__stream6__out43__ +__stream6__out44__ +__stream6__out45__ +__stream6__out46__ +__stream6__out47__ +__stream6__out48__ +__stream6__out49__ +__stream6__out50__ +__stream6__out51__ +__stream6__out52__ +__stream6__out53__ +__stream6__out54__ +__stream6__out55__ +__stream6__out56__ +__stream6__out57__ +__stream6__out58__ +__stream6__out59__ +__stream6__out60__ +__stream6__out61__ +__stream6__out62__ +__stream6__out63__ +__stream6__out64__ +__stream6__out65__ +__stream6__out66__ +__stream6__out67__ +__stream6__out68__ +__stream6__out69__ +__stream6__out70__ +__stream6__out71__ +__stream6__out72__ +__stream6__out73__ +__stream6__out74__ +__stream6__out75__ +__stream6__out76__ +__stream6__out77__ +__stream6__out78__ +__stream6__out79__ +__stream6__out80__ +__stream6__out81__ +__stream6__out82__ +__stream6__out83__ +__stream6__out84__ +__stream6__out85__ +__stream6__out86__ +__stream6__out87__ +__stream6__out88__ +__stream6__out89__ +__stream6__out90__ +__stream6__out91__ +__stream6__out92__ +__stream6__out93__ +__stream6__out94__ +__stream6__out95__ + +__stream7__out1__ +__stream7__out2__ +__stream7__out3__ +__stream7__out4__ +__stream7__out5__ +__stream7__out6__ +__stream7__out7__ +__stream7__out8__ +__stream7__out9__ +__stream7__out10__ +__stream7__out11__ +__stream7__out12__ +__stream7__out13__ +__stream7__out14__ +__stream7__out15__ +__stream7__out16__ +__stream7__out17__ +__stream7__out18__ +__stream7__out19__ +__stream7__out20__ +__stream7__out21__ +__stream7__out22__ +__stream7__out23__ +__stream7__out24__ +__stream7__out25__ +__stream7__out26__ +__stream7__out27__ +__stream7__out28__ +__stream7__out29__ +__stream7__out30__ +__stream7__out31__ +__stream7__out32__ +__stream7__out33__ +__stream7__out34__ +__stream7__out35__ +__stream7__out36__ +__stream7__out37__ +__stream7__out38__ +__stream7__out39__ +__stream7__out40__ +__stream7__out41__ +__stream7__out42__ +__stream7__out43__ +__stream7__out44__ +__stream7__out45__ +__stream7__out46__ +__stream7__out47__ +__stream7__out48__ +__stream7__out49__ +__stream7__out50__ +__stream7__out51__ +__stream7__out52__ +__stream7__out53__ +__stream7__out54__ +__stream7__out55__ +__stream7__out56__ +__stream7__out57__ +__stream7__out58__ +__stream7__out59__ +__stream7__out60__ +__stream7__out61__ +__stream7__out62__ +__stream7__out63__ +__stream7__out64__ +__stream7__out65__ +__stream7__out66__ +__stream7__out67__ +__stream7__out68__ +__stream7__out69__ +__stream7__out70__ +__stream7__out71__ +__stream7__out72__ +__stream7__out73__ +__stream7__out74__ +__stream7__out75__ +__stream7__out76__ +__stream7__out77__ +__stream7__out78__ +__stream7__out79__ +__stream7__out80__ +__stream7__out81__ +__stream7__out82__ +__stream7__out83__ +__stream7__out84__ +__stream7__out85__ +__stream7__out86__ +__stream7__out87__ +__stream7__out88__ +__stream7__out89__ +__stream7__out90__ +__stream7__out91__ +__stream7__out92__ +__stream7__out93__ +__stream7__out94__ +__stream7__out95__ + +__stream8__out1__ +__stream8__out2__ +__stream8__out3__ +__stream8__out4__ +__stream8__out5__ +__stream8__out6__ +__stream8__out7__ +__stream8__out8__ +__stream8__out9__ +__stream8__out10__ +__stream8__out11__ +__stream8__out12__ +__stream8__out13__ +__stream8__out14__ +__stream8__out15__ +__stream8__out16__ +__stream8__out17__ +__stream8__out18__ +__stream8__out19__ +__stream8__out20__ +__stream8__out21__ +__stream8__out22__ +__stream8__out23__ +__stream8__out24__ +__stream8__out25__ +__stream8__out26__ +__stream8__out27__ +__stream8__out28__ +__stream8__out29__ +__stream8__out30__ +__stream8__out31__ +__stream8__out32__ +__stream8__out33__ +__stream8__out34__ +__stream8__out35__ +__stream8__out36__ +__stream8__out37__ +__stream8__out38__ +__stream8__out39__ +__stream8__out40__ +__stream8__out41__ +__stream8__out42__ +__stream8__out43__ +__stream8__out44__ +__stream8__out45__ +__stream8__out46__ +__stream8__out47__ +__stream8__out48__ +__stream8__out49__ +__stream8__out50__ +__stream8__out51__ +__stream8__out52__ +__stream8__out53__ +__stream8__out54__ +__stream8__out55__ +__stream8__out56__ +__stream8__out57__ +__stream8__out58__ +__stream8__out59__ +__stream8__out60__ +__stream8__out61__ +__stream8__out62__ +__stream8__out63__ +__stream8__out64__ +__stream8__out65__ +__stream8__out66__ +__stream8__out67__ +__stream8__out68__ +__stream8__out69__ +__stream8__out70__ +__stream8__out71__ +__stream8__out72__ +__stream8__out73__ +__stream8__out74__ +__stream8__out75__ +__stream8__out76__ +__stream8__out77__ +__stream8__out78__ +__stream8__out79__ +__stream8__out80__ +__stream8__out81__ +__stream8__out82__ +__stream8__out83__ +__stream8__out84__ +__stream8__out85__ +__stream8__out86__ +__stream8__out87__ +__stream8__out88__ +__stream8__out89__ +__stream8__out90__ +__stream8__out91__ +__stream8__out92__ +__stream8__out93__ +__stream8__out94__ +__stream8__out95__ + +__stream9__out1__ +__stream9__out2__ +__stream9__out3__ +__stream9__out4__ +__stream9__out5__ +__stream9__out6__ +__stream9__out7__ +__stream9__out8__ +__stream9__out9__ +__stream9__out10__ +__stream9__out11__ +__stream9__out12__ +__stream9__out13__ +__stream9__out14__ +__stream9__out15__ +__stream9__out16__ +__stream9__out17__ +__stream9__out18__ +__stream9__out19__ +__stream9__out20__ +__stream9__out21__ +__stream9__out22__ +__stream9__out23__ +__stream9__out24__ +__stream9__out25__ +__stream9__out26__ +__stream9__out27__ +__stream9__out28__ +__stream9__out29__ +__stream9__out30__ +__stream9__out31__ +__stream9__out32__ +__stream9__out33__ +__stream9__out34__ +__stream9__out35__ +__stream9__out36__ +__stream9__out37__ +__stream9__out38__ +__stream9__out39__ +__stream9__out40__ +__stream9__out41__ +__stream9__out42__ +__stream9__out43__ +__stream9__out44__ +__stream9__out45__ +__stream9__out46__ +__stream9__out47__ +__stream9__out48__ +__stream9__out49__ +__stream9__out50__ +__stream9__out51__ +__stream9__out52__ +__stream9__out53__ +__stream9__out54__ +__stream9__out55__ +__stream9__out56__ +__stream9__out57__ +__stream9__out58__ +__stream9__out59__ +__stream9__out60__ +__stream9__out61__ +__stream9__out62__ +__stream9__out63__ +__stream9__out64__ +__stream9__out65__ +__stream9__out66__ +__stream9__out67__ +__stream9__out68__ +__stream9__out69__ +__stream9__out70__ +__stream9__out71__ +__stream9__out72__ +__stream9__out73__ +__stream9__out74__ +__stream9__out75__ +__stream9__out76__ +__stream9__out77__ +__stream9__out78__ +__stream9__out79__ +__stream9__out80__ +__stream9__out81__ +__stream9__out82__ +__stream9__out83__ +__stream9__out84__ +__stream9__out85__ +__stream9__out86__ +__stream9__out87__ +__stream9__out88__ +__stream9__out89__ +__stream9__out90__ +__stream9__out91__ +__stream9__out92__ +__stream9__out93__ +__stream9__out94__ +__stream9__out95__ + +__stream10__out1__ +__stream10__out2__ +__stream10__out3__ +__stream10__out4__ +__stream10__out5__ +__stream10__out6__ +__stream10__out7__ +__stream10__out8__ +__stream10__out9__ +__stream10__out10__ +__stream10__out11__ +__stream10__out12__ +__stream10__out13__ +__stream10__out14__ +__stream10__out15__ +__stream10__out16__ +__stream10__out17__ +__stream10__out18__ +__stream10__out19__ +__stream10__out20__ +__stream10__out21__ +__stream10__out22__ +__stream10__out23__ +__stream10__out24__ +__stream10__out25__ +__stream10__out26__ +__stream10__out27__ +__stream10__out28__ +__stream10__out29__ +__stream10__out30__ +__stream10__out31__ +__stream10__out32__ +__stream10__out33__ +__stream10__out34__ +__stream10__out35__ +__stream10__out36__ +__stream10__out37__ +__stream10__out38__ +__stream10__out39__ +__stream10__out40__ +__stream10__out41__ +__stream10__out42__ +__stream10__out43__ +__stream10__out44__ +__stream10__out45__ +__stream10__out46__ +__stream10__out47__ +__stream10__out48__ +__stream10__out49__ +__stream10__out50__ +__stream10__out51__ +__stream10__out52__ +__stream10__out53__ +__stream10__out54__ +__stream10__out55__ +__stream10__out56__ +__stream10__out57__ +__stream10__out58__ +__stream10__out59__ +__stream10__out60__ +__stream10__out61__ +__stream10__out62__ +__stream10__out63__ +__stream10__out64__ +__stream10__out65__ +__stream10__out66__ +__stream10__out67__ +__stream10__out68__ +__stream10__out69__ +__stream10__out70__ +__stream10__out71__ +__stream10__out72__ +__stream10__out73__ +__stream10__out74__ +__stream10__out75__ +__stream10__out76__ +__stream10__out77__ +__stream10__out78__ +__stream10__out79__ +__stream10__out80__ +__stream10__out81__ +__stream10__out82__ +__stream10__out83__ +__stream10__out84__ +__stream10__out85__ +__stream10__out86__ +__stream10__out87__ +__stream10__out88__ +__stream10__out89__ +__stream10__out90__ +__stream10__out91__ +__stream10__out92__ +__stream10__out93__ +__stream10__out94__ +__stream10__out95__ + +__stream11__out1__ +__stream11__out2__ +__stream11__out3__ +__stream11__out4__ +__stream11__out5__ +__stream11__out6__ +__stream11__out7__ +__stream11__out8__ +__stream11__out9__ +__stream11__out10__ +__stream11__out11__ +__stream11__out12__ +__stream11__out13__ +__stream11__out14__ +__stream11__out15__ +__stream11__out16__ +__stream11__out17__ +__stream11__out18__ +__stream11__out19__ +__stream11__out20__ +__stream11__out21__ +__stream11__out22__ +__stream11__out23__ +__stream11__out24__ +__stream11__out25__ +__stream11__out26__ +__stream11__out27__ +__stream11__out28__ +__stream11__out29__ +__stream11__out30__ +__stream11__out31__ +__stream11__out32__ +__stream11__out33__ +__stream11__out34__ +__stream11__out35__ +__stream11__out36__ +__stream11__out37__ +__stream11__out38__ +__stream11__out39__ +__stream11__out40__ +__stream11__out41__ +__stream11__out42__ +__stream11__out43__ +__stream11__out44__ +__stream11__out45__ +__stream11__out46__ +__stream11__out47__ +__stream11__out48__ +__stream11__out49__ +__stream11__out50__ +__stream11__out51__ +__stream11__out52__ +__stream11__out53__ +__stream11__out54__ +__stream11__out55__ +__stream11__out56__ +__stream11__out57__ +__stream11__out58__ +__stream11__out59__ +__stream11__out60__ +__stream11__out61__ +__stream11__out62__ +__stream11__out63__ +__stream11__out64__ +__stream11__out65__ +__stream11__out66__ +__stream11__out67__ +__stream11__out68__ +__stream11__out69__ +__stream11__out70__ +__stream11__out71__ +__stream11__out72__ +__stream11__out73__ +__stream11__out74__ +__stream11__out75__ +__stream11__out76__ +__stream11__out77__ +__stream11__out78__ +__stream11__out79__ +__stream11__out80__ +__stream11__out81__ +__stream11__out82__ +__stream11__out83__ +__stream11__out84__ +__stream11__out85__ +__stream11__out86__ +__stream11__out87__ +__stream11__out88__ +__stream11__out89__ +__stream11__out90__ +__stream11__out91__ +__stream11__out92__ +__stream11__out93__ +__stream11__out94__ +__stream11__out95__ + +__stream12__out1__ +__stream12__out2__ +__stream12__out3__ +__stream12__out4__ +__stream12__out5__ +__stream12__out6__ +__stream12__out7__ +__stream12__out8__ +__stream12__out9__ +__stream12__out10__ +__stream12__out11__ +__stream12__out12__ +__stream12__out13__ +__stream12__out14__ +__stream12__out15__ +__stream12__out16__ +__stream12__out17__ +__stream12__out18__ +__stream12__out19__ +__stream12__out20__ +__stream12__out21__ +__stream12__out22__ +__stream12__out23__ +__stream12__out24__ +__stream12__out25__ +__stream12__out26__ +__stream12__out27__ +__stream12__out28__ +__stream12__out29__ +__stream12__out30__ +__stream12__out31__ +__stream12__out32__ +__stream12__out33__ +__stream12__out34__ +__stream12__out35__ +__stream12__out36__ +__stream12__out37__ +__stream12__out38__ +__stream12__out39__ +__stream12__out40__ +__stream12__out41__ +__stream12__out42__ +__stream12__out43__ +__stream12__out44__ +__stream12__out45__ +__stream12__out46__ +__stream12__out47__ +__stream12__out48__ +__stream12__out49__ +__stream12__out50__ +__stream12__out51__ +__stream12__out52__ +__stream12__out53__ +__stream12__out54__ +__stream12__out55__ +__stream12__out56__ +__stream12__out57__ +__stream12__out58__ +__stream12__out59__ +__stream12__out60__ +__stream12__out61__ +__stream12__out62__ +__stream12__out63__ +__stream12__out64__ +__stream12__out65__ +__stream12__out66__ +__stream12__out67__ +__stream12__out68__ +__stream12__out69__ +__stream12__out70__ +__stream12__out71__ +__stream12__out72__ +__stream12__out73__ +__stream12__out74__ +__stream12__out75__ +__stream12__out76__ +__stream12__out77__ +__stream12__out78__ +__stream12__out79__ +__stream12__out80__ +__stream12__out81__ +__stream12__out82__ +__stream12__out83__ +__stream12__out84__ +__stream12__out85__ +__stream12__out86__ +__stream12__out87__ +__stream12__out88__ +__stream12__out89__ +__stream12__out90__ +__stream12__out91__ +__stream12__out92__ +__stream12__out93__ +__stream12__out94__ +__stream12__out95__ + +__stream13__out1__ +__stream13__out2__ +__stream13__out3__ +__stream13__out4__ +__stream13__out5__ +__stream13__out6__ +__stream13__out7__ +__stream13__out8__ +__stream13__out9__ +__stream13__out10__ +__stream13__out11__ +__stream13__out12__ +__stream13__out13__ +__stream13__out14__ +__stream13__out15__ +__stream13__out16__ +__stream13__out17__ +__stream13__out18__ +__stream13__out19__ +__stream13__out20__ +__stream13__out21__ +__stream13__out22__ +__stream13__out23__ +__stream13__out24__ +__stream13__out25__ +__stream13__out26__ +__stream13__out27__ +__stream13__out28__ +__stream13__out29__ +__stream13__out30__ +__stream13__out31__ +__stream13__out32__ +__stream13__out33__ +__stream13__out34__ +__stream13__out35__ +__stream13__out36__ +__stream13__out37__ +__stream13__out38__ +__stream13__out39__ +__stream13__out40__ +__stream13__out41__ +__stream13__out42__ +__stream13__out43__ +__stream13__out44__ +__stream13__out45__ +__stream13__out46__ +__stream13__out47__ +__stream13__out48__ +__stream13__out49__ +__stream13__out50__ +__stream13__out51__ +__stream13__out52__ +__stream13__out53__ +__stream13__out54__ +__stream13__out55__ +__stream13__out56__ +__stream13__out57__ +__stream13__out58__ +__stream13__out59__ +__stream13__out60__ +__stream13__out61__ +__stream13__out62__ +__stream13__out63__ +__stream13__out64__ +__stream13__out65__ +__stream13__out66__ +__stream13__out67__ +__stream13__out68__ +__stream13__out69__ +__stream13__out70__ +__stream13__out71__ +__stream13__out72__ +__stream13__out73__ +__stream13__out74__ +__stream13__out75__ +__stream13__out76__ +__stream13__out77__ +__stream13__out78__ +__stream13__out79__ +__stream13__out80__ +__stream13__out81__ +__stream13__out82__ +__stream13__out83__ +__stream13__out84__ +__stream13__out85__ +__stream13__out86__ +__stream13__out87__ +__stream13__out88__ +__stream13__out89__ +__stream13__out90__ +__stream13__out91__ +__stream13__out92__ +__stream13__out93__ +__stream13__out94__ +__stream13__out95__ + +__stream14__out1__ +__stream14__out2__ +__stream14__out3__ +__stream14__out4__ +__stream14__out5__ +__stream14__out6__ +__stream14__out7__ +__stream14__out8__ +__stream14__out9__ +__stream14__out10__ +__stream14__out11__ +__stream14__out12__ +__stream14__out13__ +__stream14__out14__ +__stream14__out15__ +__stream14__out16__ +__stream14__out17__ +__stream14__out18__ +__stream14__out19__ +__stream14__out20__ +__stream14__out21__ +__stream14__out22__ +__stream14__out23__ +__stream14__out24__ +__stream14__out25__ +__stream14__out26__ +__stream14__out27__ +__stream14__out28__ +__stream14__out29__ +__stream14__out30__ +__stream14__out31__ +__stream14__out32__ +__stream14__out33__ +__stream14__out34__ +__stream14__out35__ +__stream14__out36__ +__stream14__out37__ +__stream14__out38__ +__stream14__out39__ +__stream14__out40__ +__stream14__out41__ +__stream14__out42__ +__stream14__out43__ +__stream14__out44__ +__stream14__out45__ +__stream14__out46__ +__stream14__out47__ +__stream14__out48__ +__stream14__out49__ +__stream14__out50__ +__stream14__out51__ +__stream14__out52__ +__stream14__out53__ +__stream14__out54__ +__stream14__out55__ +__stream14__out56__ +__stream14__out57__ +__stream14__out58__ +__stream14__out59__ +__stream14__out60__ +__stream14__out61__ +__stream14__out62__ +__stream14__out63__ +__stream14__out64__ +__stream14__out65__ +__stream14__out66__ +__stream14__out67__ +__stream14__out68__ +__stream14__out69__ +__stream14__out70__ +__stream14__out71__ +__stream14__out72__ +__stream14__out73__ +__stream14__out74__ +__stream14__out75__ +__stream14__out76__ +__stream14__out77__ +__stream14__out78__ +__stream14__out79__ +__stream14__out80__ +__stream14__out81__ +__stream14__out82__ +__stream14__out83__ +__stream14__out84__ +__stream14__out85__ +__stream14__out86__ +__stream14__out87__ +__stream14__out88__ +__stream14__out89__ +__stream14__out90__ +__stream14__out91__ +__stream14__out92__ +__stream14__out93__ +__stream14__out94__ +__stream14__out95__ + +__stream15__out1__ +__stream15__out2__ +__stream15__out3__ +__stream15__out4__ +__stream15__out5__ +__stream15__out6__ +__stream15__out7__ +__stream15__out8__ +__stream15__out9__ +__stream15__out10__ +__stream15__out11__ +__stream15__out12__ +__stream15__out13__ +__stream15__out14__ +__stream15__out15__ +__stream15__out16__ +__stream15__out17__ +__stream15__out18__ +__stream15__out19__ +__stream15__out20__ +__stream15__out21__ +__stream15__out22__ +__stream15__out23__ +__stream15__out24__ +__stream15__out25__ +__stream15__out26__ +__stream15__out27__ +__stream15__out28__ +__stream15__out29__ +__stream15__out30__ +__stream15__out31__ +__stream15__out32__ +__stream15__out33__ +__stream15__out34__ +__stream15__out35__ +__stream15__out36__ +__stream15__out37__ +__stream15__out38__ +__stream15__out39__ +__stream15__out40__ +__stream15__out41__ +__stream15__out42__ +__stream15__out43__ +__stream15__out44__ +__stream15__out45__ +__stream15__out46__ +__stream15__out47__ +__stream15__out48__ +__stream15__out49__ +__stream15__out50__ +__stream15__out51__ +__stream15__out52__ +__stream15__out53__ +__stream15__out54__ +__stream15__out55__ +__stream15__out56__ +__stream15__out57__ +__stream15__out58__ +__stream15__out59__ +__stream15__out60__ +__stream15__out61__ +__stream15__out62__ +__stream15__out63__ +__stream15__out64__ +__stream15__out65__ +__stream15__out66__ +__stream15__out67__ +__stream15__out68__ +__stream15__out69__ +__stream15__out70__ +__stream15__out71__ +__stream15__out72__ +__stream15__out73__ +__stream15__out74__ +__stream15__out75__ +__stream15__out76__ +__stream15__out77__ +__stream15__out78__ +__stream15__out79__ +__stream15__out80__ +__stream15__out81__ +__stream15__out82__ +__stream15__out83__ +__stream15__out84__ +__stream15__out85__ +__stream15__out86__ +__stream15__out87__ +__stream15__out88__ +__stream15__out89__ +__stream15__out90__ +__stream15__out91__ +__stream15__out92__ +__stream15__out93__ +__stream15__out94__ +__stream15__out95__ + +__stream16__out1__ +__stream16__out2__ +__stream16__out3__ +__stream16__out4__ +__stream16__out5__ +__stream16__out6__ +__stream16__out7__ +__stream16__out8__ +__stream16__out9__ +__stream16__out10__ +__stream16__out11__ +__stream16__out12__ +__stream16__out13__ +__stream16__out14__ +__stream16__out15__ +__stream16__out16__ +__stream16__out17__ +__stream16__out18__ +__stream16__out19__ +__stream16__out20__ +__stream16__out21__ +__stream16__out22__ +__stream16__out23__ +__stream16__out24__ +__stream16__out25__ +__stream16__out26__ +__stream16__out27__ +__stream16__out28__ +__stream16__out29__ +__stream16__out30__ +__stream16__out31__ +__stream16__out32__ +__stream16__out33__ +__stream16__out34__ +__stream16__out35__ +__stream16__out36__ +__stream16__out37__ +__stream16__out38__ +__stream16__out39__ +__stream16__out40__ +__stream16__out41__ +__stream16__out42__ +__stream16__out43__ +__stream16__out44__ +__stream16__out45__ +__stream16__out46__ +__stream16__out47__ +__stream16__out48__ +__stream16__out49__ +__stream16__out50__ +__stream16__out51__ +__stream16__out52__ +__stream16__out53__ +__stream16__out54__ +__stream16__out55__ +__stream16__out56__ +__stream16__out57__ +__stream16__out58__ +__stream16__out59__ +__stream16__out60__ +__stream16__out61__ +__stream16__out62__ +__stream16__out63__ +__stream16__out64__ +__stream16__out65__ +__stream16__out66__ +__stream16__out67__ +__stream16__out68__ +__stream16__out69__ +__stream16__out70__ +__stream16__out71__ +__stream16__out72__ +__stream16__out73__ +__stream16__out74__ +__stream16__out75__ +__stream16__out76__ +__stream16__out77__ +__stream16__out78__ +__stream16__out79__ +__stream16__out80__ +__stream16__out81__ +__stream16__out82__ +__stream16__out83__ +__stream16__out84__ +__stream16__out85__ +__stream16__out86__ +__stream16__out87__ +__stream16__out88__ +__stream16__out89__ +__stream16__out90__ +__stream16__out91__ +__stream16__out92__ +__stream16__out93__ +__stream16__out94__ +__stream16__out95__ + +__stream17__out1__ +__stream17__out2__ +__stream17__out3__ +__stream17__out4__ +__stream17__out5__ +__stream17__out6__ +__stream17__out7__ +__stream17__out8__ +__stream17__out9__ +__stream17__out10__ +__stream17__out11__ +__stream17__out12__ +__stream17__out13__ +__stream17__out14__ +__stream17__out15__ +__stream17__out16__ +__stream17__out17__ +__stream17__out18__ +__stream17__out19__ +__stream17__out20__ +__stream17__out21__ +__stream17__out22__ +__stream17__out23__ +__stream17__out24__ +__stream17__out25__ +__stream17__out26__ +__stream17__out27__ +__stream17__out28__ +__stream17__out29__ +__stream17__out30__ +__stream17__out31__ +__stream17__out32__ +__stream17__out33__ +__stream17__out34__ +__stream17__out35__ +__stream17__out36__ +__stream17__out37__ +__stream17__out38__ +__stream17__out39__ +__stream17__out40__ +__stream17__out41__ +__stream17__out42__ +__stream17__out43__ +__stream17__out44__ +__stream17__out45__ +__stream17__out46__ +__stream17__out47__ +__stream17__out48__ +__stream17__out49__ +__stream17__out50__ +__stream17__out51__ +__stream17__out52__ +__stream17__out53__ +__stream17__out54__ +__stream17__out55__ +__stream17__out56__ +__stream17__out57__ +__stream17__out58__ +__stream17__out59__ +__stream17__out60__ +__stream17__out61__ +__stream17__out62__ +__stream17__out63__ +__stream17__out64__ +__stream17__out65__ +__stream17__out66__ +__stream17__out67__ +__stream17__out68__ +__stream17__out69__ +__stream17__out70__ +__stream17__out71__ +__stream17__out72__ +__stream17__out73__ +__stream17__out74__ +__stream17__out75__ +__stream17__out76__ +__stream17__out77__ +__stream17__out78__ +__stream17__out79__ +__stream17__out80__ +__stream17__out81__ +__stream17__out82__ +__stream17__out83__ +__stream17__out84__ +__stream17__out85__ +__stream17__out86__ +__stream17__out87__ +__stream17__out88__ +__stream17__out89__ +__stream17__out90__ +__stream17__out91__ +__stream17__out92__ +__stream17__out93__ +__stream17__out94__ +__stream17__out95__ + +__stream18__out1__ +__stream18__out2__ +__stream18__out3__ +__stream18__out4__ +__stream18__out5__ +__stream18__out6__ +__stream18__out7__ +__stream18__out8__ +__stream18__out9__ +__stream18__out10__ +__stream18__out11__ +__stream18__out12__ +__stream18__out13__ +__stream18__out14__ +__stream18__out15__ +__stream18__out16__ +__stream18__out17__ +__stream18__out18__ +__stream18__out19__ +__stream18__out20__ +__stream18__out21__ +__stream18__out22__ +__stream18__out23__ +__stream18__out24__ +__stream18__out25__ +__stream18__out26__ +__stream18__out27__ +__stream18__out28__ +__stream18__out29__ +__stream18__out30__ +__stream18__out31__ +__stream18__out32__ +__stream18__out33__ +__stream18__out34__ +__stream18__out35__ +__stream18__out36__ +__stream18__out37__ +__stream18__out38__ +__stream18__out39__ +__stream18__out40__ +__stream18__out41__ +__stream18__out42__ +__stream18__out43__ +__stream18__out44__ +__stream18__out45__ +__stream18__out46__ +__stream18__out47__ +__stream18__out48__ +__stream18__out49__ +__stream18__out50__ +__stream18__out51__ +__stream18__out52__ +__stream18__out53__ +__stream18__out54__ +__stream18__out55__ +__stream18__out56__ +__stream18__out57__ +__stream18__out58__ +__stream18__out59__ +__stream18__out60__ +__stream18__out61__ +__stream18__out62__ +__stream18__out63__ +__stream18__out64__ +__stream18__out65__ +__stream18__out66__ +__stream18__out67__ +__stream18__out68__ +__stream18__out69__ +__stream18__out70__ +__stream18__out71__ +__stream18__out72__ +__stream18__out73__ +__stream18__out74__ +__stream18__out75__ +__stream18__out76__ +__stream18__out77__ +__stream18__out78__ +__stream18__out79__ +__stream18__out80__ +__stream18__out81__ +__stream18__out82__ +__stream18__out83__ +__stream18__out84__ +__stream18__out85__ +__stream18__out86__ +__stream18__out87__ +__stream18__out88__ +__stream18__out89__ +__stream18__out90__ +__stream18__out91__ +__stream18__out92__ +__stream18__out93__ +__stream18__out94__ +__stream18__out95__ + +__stream19__out1__ +__stream19__out2__ +__stream19__out3__ +__stream19__out4__ +__stream19__out5__ +__stream19__out6__ +__stream19__out7__ +__stream19__out8__ +__stream19__out9__ +__stream19__out10__ +__stream19__out11__ +__stream19__out12__ +__stream19__out13__ +__stream19__out14__ +__stream19__out15__ +__stream19__out16__ +__stream19__out17__ +__stream19__out18__ +__stream19__out19__ +__stream19__out20__ +__stream19__out21__ +__stream19__out22__ +__stream19__out23__ +__stream19__out24__ +__stream19__out25__ +__stream19__out26__ +__stream19__out27__ +__stream19__out28__ +__stream19__out29__ +__stream19__out30__ +__stream19__out31__ +__stream19__out32__ +__stream19__out33__ +__stream19__out34__ +__stream19__out35__ +__stream19__out36__ +__stream19__out37__ +__stream19__out38__ +__stream19__out39__ +__stream19__out40__ +__stream19__out41__ +__stream19__out42__ +__stream19__out43__ +__stream19__out44__ +__stream19__out45__ +__stream19__out46__ +__stream19__out47__ +__stream19__out48__ +__stream19__out49__ +__stream19__out50__ +__stream19__out51__ +__stream19__out52__ +__stream19__out53__ +__stream19__out54__ +__stream19__out55__ +__stream19__out56__ +__stream19__out57__ +__stream19__out58__ +__stream19__out59__ +__stream19__out60__ +__stream19__out61__ +__stream19__out62__ +__stream19__out63__ +__stream19__out64__ +__stream19__out65__ +__stream19__out66__ +__stream19__out67__ +__stream19__out68__ +__stream19__out69__ +__stream19__out70__ +__stream19__out71__ +__stream19__out72__ +__stream19__out73__ +__stream19__out74__ +__stream19__out75__ +__stream19__out76__ +__stream19__out77__ +__stream19__out78__ +__stream19__out79__ +__stream19__out80__ +__stream19__out81__ +__stream19__out82__ +__stream19__out83__ +__stream19__out84__ +__stream19__out85__ +__stream19__out86__ +__stream19__out87__ +__stream19__out88__ +__stream19__out89__ +__stream19__out90__ +__stream19__out91__ +__stream19__out92__ +__stream19__out93__ +__stream19__out94__ +__stream19__out95__ + +__stream20__out1__ +__stream20__out2__ +__stream20__out3__ +__stream20__out4__ +__stream20__out5__ +__stream20__out6__ +__stream20__out7__ +__stream20__out8__ +__stream20__out9__ +__stream20__out10__ +__stream20__out11__ +__stream20__out12__ +__stream20__out13__ +__stream20__out14__ +__stream20__out15__ +__stream20__out16__ +__stream20__out17__ +__stream20__out18__ +__stream20__out19__ +__stream20__out20__ +__stream20__out21__ +__stream20__out22__ +__stream20__out23__ +__stream20__out24__ +__stream20__out25__ +__stream20__out26__ +__stream20__out27__ +__stream20__out28__ +__stream20__out29__ +__stream20__out30__ +__stream20__out31__ +__stream20__out32__ +__stream20__out33__ +__stream20__out34__ +__stream20__out35__ +__stream20__out36__ +__stream20__out37__ +__stream20__out38__ +__stream20__out39__ +__stream20__out40__ +__stream20__out41__ +__stream20__out42__ +__stream20__out43__ +__stream20__out44__ +__stream20__out45__ +__stream20__out46__ +__stream20__out47__ +__stream20__out48__ +__stream20__out49__ +__stream20__out50__ +__stream20__out51__ +__stream20__out52__ +__stream20__out53__ +__stream20__out54__ +__stream20__out55__ +__stream20__out56__ +__stream20__out57__ +__stream20__out58__ +__stream20__out59__ +__stream20__out60__ +__stream20__out61__ +__stream20__out62__ +__stream20__out63__ +__stream20__out64__ +__stream20__out65__ +__stream20__out66__ +__stream20__out67__ +__stream20__out68__ +__stream20__out69__ +__stream20__out70__ +__stream20__out71__ +__stream20__out72__ +__stream20__out73__ +__stream20__out74__ +__stream20__out75__ +__stream20__out76__ +__stream20__out77__ +__stream20__out78__ +__stream20__out79__ +__stream20__out80__ +__stream20__out81__ +__stream20__out82__ +__stream20__out83__ +__stream20__out84__ +__stream20__out85__ +__stream20__out86__ +__stream20__out87__ +__stream20__out88__ +__stream20__out89__ +__stream20__out90__ +__stream20__out91__ +__stream20__out92__ +__stream20__out93__ +__stream20__out94__ +__stream20__out95__ + +__stream21__out1__ +__stream21__out2__ +__stream21__out3__ +__stream21__out4__ +__stream21__out5__ +__stream21__out6__ +__stream21__out7__ +__stream21__out8__ +__stream21__out9__ +__stream21__out10__ +__stream21__out11__ +__stream21__out12__ +__stream21__out13__ +__stream21__out14__ +__stream21__out15__ +__stream21__out16__ +__stream21__out17__ +__stream21__out18__ +__stream21__out19__ +__stream21__out20__ +__stream21__out21__ +__stream21__out22__ +__stream21__out23__ +__stream21__out24__ +__stream21__out25__ +__stream21__out26__ +__stream21__out27__ +__stream21__out28__ +__stream21__out29__ +__stream21__out30__ +__stream21__out31__ +__stream21__out32__ +__stream21__out33__ +__stream21__out34__ +__stream21__out35__ +__stream21__out36__ +__stream21__out37__ +__stream21__out38__ +__stream21__out39__ +__stream21__out40__ +__stream21__out41__ +__stream21__out42__ +__stream21__out43__ +__stream21__out44__ +__stream21__out45__ +__stream21__out46__ +__stream21__out47__ +__stream21__out48__ +__stream21__out49__ +__stream21__out50__ +__stream21__out51__ +__stream21__out52__ +__stream21__out53__ +__stream21__out54__ +__stream21__out55__ +__stream21__out56__ +__stream21__out57__ +__stream21__out58__ +__stream21__out59__ +__stream21__out60__ +__stream21__out61__ +__stream21__out62__ +__stream21__out63__ +__stream21__out64__ +__stream21__out65__ +__stream21__out66__ +__stream21__out67__ +__stream21__out68__ +__stream21__out69__ +__stream21__out70__ +__stream21__out71__ +__stream21__out72__ +__stream21__out73__ +__stream21__out74__ +__stream21__out75__ +__stream21__out76__ +__stream21__out77__ +__stream21__out78__ +__stream21__out79__ +__stream21__out80__ +__stream21__out81__ +__stream21__out82__ +__stream21__out83__ +__stream21__out84__ +__stream21__out85__ +__stream21__out86__ +__stream21__out87__ +__stream21__out88__ +__stream21__out89__ +__stream21__out90__ +__stream21__out91__ +__stream21__out92__ +__stream21__out93__ +__stream21__out94__ +__stream21__out95__ + +__stream22__out1__ +__stream22__out2__ +__stream22__out3__ +__stream22__out4__ +__stream22__out5__ +__stream22__out6__ +__stream22__out7__ +__stream22__out8__ +__stream22__out9__ +__stream22__out10__ +__stream22__out11__ +__stream22__out12__ +__stream22__out13__ +__stream22__out14__ +__stream22__out15__ +__stream22__out16__ +__stream22__out17__ +__stream22__out18__ +__stream22__out19__ +__stream22__out20__ +__stream22__out21__ +__stream22__out22__ +__stream22__out23__ +__stream22__out24__ +__stream22__out25__ +__stream22__out26__ +__stream22__out27__ +__stream22__out28__ +__stream22__out29__ +__stream22__out30__ +__stream22__out31__ +__stream22__out32__ +__stream22__out33__ +__stream22__out34__ +__stream22__out35__ +__stream22__out36__ +__stream22__out37__ +__stream22__out38__ +__stream22__out39__ +__stream22__out40__ +__stream22__out41__ +__stream22__out42__ +__stream22__out43__ +__stream22__out44__ +__stream22__out45__ +__stream22__out46__ +__stream22__out47__ +__stream22__out48__ +__stream22__out49__ +__stream22__out50__ +__stream22__out51__ +__stream22__out52__ +__stream22__out53__ +__stream22__out54__ +__stream22__out55__ +__stream22__out56__ +__stream22__out57__ +__stream22__out58__ +__stream22__out59__ +__stream22__out60__ +__stream22__out61__ +__stream22__out62__ +__stream22__out63__ +__stream22__out64__ +__stream22__out65__ +__stream22__out66__ +__stream22__out67__ +__stream22__out68__ +__stream22__out69__ +__stream22__out70__ +__stream22__out71__ +__stream22__out72__ +__stream22__out73__ +__stream22__out74__ +__stream22__out75__ +__stream22__out76__ +__stream22__out77__ +__stream22__out78__ +__stream22__out79__ +__stream22__out80__ +__stream22__out81__ +__stream22__out82__ +__stream22__out83__ +__stream22__out84__ +__stream22__out85__ +__stream22__out86__ +__stream22__out87__ +__stream22__out88__ +__stream22__out89__ +__stream22__out90__ +__stream22__out91__ +__stream22__out92__ +__stream22__out93__ +__stream22__out94__ +__stream22__out95__ + +__stream23__out1__ +__stream23__out2__ +__stream23__out3__ +__stream23__out4__ +__stream23__out5__ +__stream23__out6__ +__stream23__out7__ +__stream23__out8__ +__stream23__out9__ +__stream23__out10__ +__stream23__out11__ +__stream23__out12__ +__stream23__out13__ +__stream23__out14__ +__stream23__out15__ +__stream23__out16__ +__stream23__out17__ +__stream23__out18__ +__stream23__out19__ +__stream23__out20__ +__stream23__out21__ +__stream23__out22__ +__stream23__out23__ +__stream23__out24__ +__stream23__out25__ +__stream23__out26__ +__stream23__out27__ +__stream23__out28__ +__stream23__out29__ +__stream23__out30__ +__stream23__out31__ +__stream23__out32__ +__stream23__out33__ +__stream23__out34__ +__stream23__out35__ +__stream23__out36__ +__stream23__out37__ +__stream23__out38__ +__stream23__out39__ +__stream23__out40__ +__stream23__out41__ +__stream23__out42__ +__stream23__out43__ +__stream23__out44__ +__stream23__out45__ +__stream23__out46__ +__stream23__out47__ +__stream23__out48__ +__stream23__out49__ +__stream23__out50__ +__stream23__out51__ +__stream23__out52__ +__stream23__out53__ +__stream23__out54__ +__stream23__out55__ +__stream23__out56__ +__stream23__out57__ +__stream23__out58__ +__stream23__out59__ +__stream23__out60__ +__stream23__out61__ +__stream23__out62__ +__stream23__out63__ +__stream23__out64__ +__stream23__out65__ +__stream23__out66__ +__stream23__out67__ +__stream23__out68__ +__stream23__out69__ +__stream23__out70__ +__stream23__out71__ +__stream23__out72__ +__stream23__out73__ +__stream23__out74__ +__stream23__out75__ +__stream23__out76__ +__stream23__out77__ +__stream23__out78__ +__stream23__out79__ +__stream23__out80__ +__stream23__out81__ +__stream23__out82__ +__stream23__out83__ +__stream23__out84__ +__stream23__out85__ +__stream23__out86__ +__stream23__out87__ +__stream23__out88__ +__stream23__out89__ +__stream23__out90__ +__stream23__out91__ +__stream23__out92__ +__stream23__out93__ +__stream23__out94__ +__stream23__out95__ + +__stream24__out1__ +__stream24__out2__ +__stream24__out3__ +__stream24__out4__ +__stream24__out5__ +__stream24__out6__ +__stream24__out7__ +__stream24__out8__ +__stream24__out9__ +__stream24__out10__ +__stream24__out11__ +__stream24__out12__ +__stream24__out13__ +__stream24__out14__ +__stream24__out15__ +__stream24__out16__ +__stream24__out17__ +__stream24__out18__ +__stream24__out19__ +__stream24__out20__ +__stream24__out21__ +__stream24__out22__ +__stream24__out23__ +__stream24__out24__ +__stream24__out25__ +__stream24__out26__ +__stream24__out27__ +__stream24__out28__ +__stream24__out29__ +__stream24__out30__ +__stream24__out31__ +__stream24__out32__ +__stream24__out33__ +__stream24__out34__ +__stream24__out35__ +__stream24__out36__ +__stream24__out37__ +__stream24__out38__ +__stream24__out39__ +__stream24__out40__ +__stream24__out41__ +__stream24__out42__ +__stream24__out43__ +__stream24__out44__ +__stream24__out45__ +__stream24__out46__ +__stream24__out47__ +__stream24__out48__ +__stream24__out49__ +__stream24__out50__ +__stream24__out51__ +__stream24__out52__ +__stream24__out53__ +__stream24__out54__ +__stream24__out55__ +__stream24__out56__ +__stream24__out57__ +__stream24__out58__ +__stream24__out59__ +__stream24__out60__ +__stream24__out61__ +__stream24__out62__ +__stream24__out63__ +__stream24__out64__ +__stream24__out65__ +__stream24__out66__ +__stream24__out67__ +__stream24__out68__ +__stream24__out69__ +__stream24__out70__ +__stream24__out71__ +__stream24__out72__ +__stream24__out73__ +__stream24__out74__ +__stream24__out75__ +__stream24__out76__ +__stream24__out77__ +__stream24__out78__ +__stream24__out79__ +__stream24__out80__ +__stream24__out81__ +__stream24__out82__ +__stream24__out83__ +__stream24__out84__ +__stream24__out85__ +__stream24__out86__ +__stream24__out87__ +__stream24__out88__ +__stream24__out89__ +__stream24__out90__ +__stream24__out91__ +__stream24__out92__ +__stream24__out93__ +__stream24__out94__ +__stream24__out95__ + +__stream25__out1__ +__stream25__out2__ +__stream25__out3__ +__stream25__out4__ +__stream25__out5__ +__stream25__out6__ +__stream25__out7__ +__stream25__out8__ +__stream25__out9__ +__stream25__out10__ +__stream25__out11__ +__stream25__out12__ +__stream25__out13__ +__stream25__out14__ +__stream25__out15__ +__stream25__out16__ +__stream25__out17__ +__stream25__out18__ +__stream25__out19__ +__stream25__out20__ +__stream25__out21__ +__stream25__out22__ +__stream25__out23__ +__stream25__out24__ +__stream25__out25__ +__stream25__out26__ +__stream25__out27__ +__stream25__out28__ +__stream25__out29__ +__stream25__out30__ +__stream25__out31__ +__stream25__out32__ +__stream25__out33__ +__stream25__out34__ +__stream25__out35__ +__stream25__out36__ +__stream25__out37__ +__stream25__out38__ +__stream25__out39__ +__stream25__out40__ +__stream25__out41__ +__stream25__out42__ +__stream25__out43__ +__stream25__out44__ +__stream25__out45__ +__stream25__out46__ +__stream25__out47__ +__stream25__out48__ +__stream25__out49__ +__stream25__out50__ +__stream25__out51__ +__stream25__out52__ +__stream25__out53__ +__stream25__out54__ +__stream25__out55__ +__stream25__out56__ +__stream25__out57__ +__stream25__out58__ +__stream25__out59__ +__stream25__out60__ +__stream25__out61__ +__stream25__out62__ +__stream25__out63__ +__stream25__out64__ +__stream25__out65__ +__stream25__out66__ +__stream25__out67__ +__stream25__out68__ +__stream25__out69__ +__stream25__out70__ +__stream25__out71__ +__stream25__out72__ +__stream25__out73__ +__stream25__out74__ +__stream25__out75__ +__stream25__out76__ +__stream25__out77__ +__stream25__out78__ +__stream25__out79__ +__stream25__out80__ +__stream25__out81__ +__stream25__out82__ +__stream25__out83__ +__stream25__out84__ +__stream25__out85__ +__stream25__out86__ +__stream25__out87__ +__stream25__out88__ +__stream25__out89__ +__stream25__out90__ +__stream25__out91__ +__stream25__out92__ +__stream25__out93__ +__stream25__out94__ +__stream25__out95__ + + __stream1__out98__ rtmp://localhost/recording/stream1 source Stream1_record __stream2__out98__ rtmp://localhost/recording/stream2 source Stream2_record __stream3__out98__ rtmp://localhost/recording/stream3 source Stream3_record @@ -219,48 +2420,38 @@ __stream17__out98__ rtmp://localhost/recording/stream17 source Stream17_record __stream18__out98__ rtmp://localhost/recording/stream18 source Stream18_record __stream19__out98__ rtmp://localhost/recording/stream19 source Stream19_record __stream20__out98__ rtmp://localhost/recording/stream20 source Stream20_record -__stream1__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram1 -__stream2__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram2 -__stream3__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram3 -__stream4__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram4 -__stream5__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram5 -__stream6__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram6 -__stream7__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram7 -__stream8__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram8 -__stream9__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram9 -__stream10__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram10 -__stream11__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram11 -__stream12__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram12 -__stream13__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram13 -__stream14__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram14 -__stream15__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram15 -__stream16__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram16 -__stream17__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram17 -__stream18__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram18 -__stream19__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram19 -__stream20__out99__ rtmp://live-upload.instagram.com:80/rtmp/YourKey source Instagram20 +__stream21__out98__ rtmp://localhost/recording/stream20 source Stream21_record +__stream22__out98__ rtmp://localhost/recording/stream20 source Stream22_record +__stream23__out98__ rtmp://localhost/recording/stream20 source Stream23_record +__stream24__out98__ rtmp://localhost/recording/stream20 source Stream24_record +__stream25__out98__ rtmp://localhost/recording/stream20 source Stream25_record ***STREAM CONFIG*** -__stream1__config__ both 1080p nofailover -__stream2__config__ both 1080p nofailover -__stream3__config__ both 1080p nofailover -__stream4__config__ both 1080p nofailover -__stream5__config__ both 1080p nofailover -__stream6__config__ both 1080p nofailover -__stream7__config__ both 1080p nofailover -__stream8__config__ none 1080p nofailover -__stream9__config__ both 1080p nofailover -__stream10__config__ both 1080p nofailover -__stream11__config__ both 1080p nofailover -__stream12__config__ both 1080p nofailover -__stream13__config__ both 1080p nofailover -__stream14__config__ both 1080p nofailover -__stream15__config__ both 1080p nofailover -__stream16__config__ both 1080p nofailover -__stream17__config__ both 1080p nofailover -__stream18__config__ both 1080p nofailover -__stream19__config__ both 1080p nofailover -__stream20__config__ both 1080p nofailover +__stream1__config__ none 720p nofailover +__stream2__config__ none 720p nofailover +__stream3__config__ none 720p nofailover +__stream4__config__ none 720p nofailover +__stream5__config__ none 720p nofailover +__stream6__config__ none 720p nofailover +__stream7__config__ none 720p nofailover +__stream8__config__ none 720p nofailover +__stream9__config__ none 720p nofailover +__stream10__config__ none 720p nofailover +__stream11__config__ none 720p nofailover +__stream12__config__ none 720p nofailover +__stream13__config__ none 720p nofailover +__stream14__config__ none 720p nofailover +__stream15__config__ none 720p nofailover +__stream16__config__ none 720p nofailover +__stream17__config__ none 720p nofailover +__stream18__config__ none 720p nofailover +__stream19__config__ none 720p nofailover +__stream20__config__ none 720p nofailover +__stream21__config__ none 720p nofailover +__stream22__config__ none 720p nofailover +__stream23__config__ none 720p nofailover +__stream24__config__ none 720p nofailover +__stream25__config__ none 720p nofailover ***AUDIO CONFIG*** __stream1__audio__ c0 c1 stereo distribute @@ -283,5 +2474,10 @@ __stream17__audio__ c17 c33 mono main __stream18__audio__ c18 c35 mono main __stream19__audio__ c19 c37 mono main __stream20__audio__ c20 c39 mono main +__stream21__audio__ c21 c41 mono main +__stream22__audio__ c22 c43 mono main +__stream23__audio__ c23 c45 mono main +__stream24__audio__ c24 c47 mono main +__stream25__audio__ c25 c49 mono main ***NEXT CONFIG*** diff --git a/scripts/gdrive-downloader.sh b/scripts/gdrive-downloader.sh new file mode 100755 index 0000000..d4d82be --- /dev/null +++ b/scripts/gdrive-downloader.sh @@ -0,0 +1,8 @@ +#!/bin/bash +if [ $# != 2 ]; then + echo "Usage: googledown.sh ID save_name" + exit 0 +fi +confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id='$1 -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p') +echo $confirm +wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$confirm&id=$1" -O $2 && rm -rf /tmp/cookies.txt diff --git a/scripts/insta.sh b/scripts/insta.sh deleted file mode 100755 index a2b7080..0000000 --- a/scripts/insta.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -case $1 in -on) -screenname="$2$(basename "$0" .sh)"; -#exec screen -dm -S $screenname "php ~/InstagramLive-PHP/goLive.php"; -#if [ -z "$STY" ]; then -#exec screen -dm -S insta /bin/bash "$0"; -#exit 0; -#fi - -#screen -dmS insta; - -echo "Turning on $2insta" -screen -dmS $screenname; -sleep 1 -screen -S $screenname -X stuff "/bin/bash /usr/local/nginx/scripts/instagram.sh $2 -" -;; -#sleep 30 -#" -#sleep 10 - - - -#screen -S $screenname -X stuff "cd ~/InstagramLive-PHP -#" - - -off) -screen -S $2instagram -X stuff "stop -" -sleep 5 -if [ $(ps aux | grep " $2instagram" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep " $2instagram" | awk '{print $2}') -echo "Turning off $2instagram" -fi - -#if [ $(ps aux | grep "$screenname" | awk '{print $2}' | wc -l) -gt 0 ]; then -#kill $(ps aux | grep "$screenname" | awk '{print $2}') -#echo "Turning off instagram" -#fi -esac diff --git a/scripts/instagram.sh b/scripts/instagram.sh deleted file mode 100755 index 6762117..0000000 --- a/scripts/instagram.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -#case $1 in -#on) -screenname="$1$(basename "$0" .sh)"; -#exec screen -dm -S $screenname "php /usr/local/nginx/scripts/InstagramLive-PHP$1/goLive.php"; -#if [ -z "$STY" ]; then -#exec screen -dm -S insta /bin/bash "$0"; -#exit 0; -#fi - -#screen -dmS insta; -echo "Turning on $1instagram" -screen -dmS $screenname; -sleep 1 - -i="0" -#while [ $i -lt 10 ] -while true -do -screen -S $screenname -X stuff "cd /usr/local/nginx/scripts/InstagramLive-PHP$1 && php goLive.php -a --dcomments -" -sleep 3480 -i=$[$i+1] -echo "$i hours up" -screen -S $screenname -X stuff "stop -" -sleep 10 -done -#;; - -#off) - - -#if [ $(ps aux | grep "$screenname" | awk '{print $2}' | wc -l) -gt 0 ]; then -#kill $(ps aux | grep "$screenname" | awk '{print $2}') -#echo "Turning off instagram" -#fi - -#screen -S $screenname -X stuff "cd ~/InstagramLive-PHP -#" diff --git a/scripts/instaoff.sh b/scripts/instaoff.sh deleted file mode 100755 index 2f17eb8..0000000 --- a/scripts/instaoff.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -screen -S $1instagram -X stuff "stop -" - -sleep 5 -if [ $(ps aux | grep "[S]CREEN.* $1instagram" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep "[S]CREEN.* $1instagram" | awk '{print $2}') -echo "Turning off $1instagram" -fi - -if [ $(ps aux | grep "[S]CREEN.* $1insta" | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep "[S]CREEN.* $1insta" | awk '{print $2}') -echo "Turning off $1insta" -fi - - diff --git a/scripts/nginx.conf b/scripts/nginx.conf index 51ab13c..88e001e 100755 --- a/scripts/nginx.conf +++ b/scripts/nginx.conf @@ -1,172 +1,208 @@ user root; -worker_processes 1; +worker_processes 1; -error_log logs/rtmp_error.log emerg; -pid logs/nginx.pid; +error_log logs/rtmp_error.log emerg; +pid logs/nginx.pid; events { - worker_connections 1024; + + worker_connections 1024; } rtmp { - server { - listen 1935; - chunk_size 8192; - -# allow publish 192.168.0.0/24; -# deny publish all; - - application output { - live on; - meta copy; - push_reconnect 1s; -# idle_streams off; -# drop_idle_publisher 120s; - - - recorder rec { record off; - record_suffix .flv; - record_path /usr/local/nginx/html/recording/; - record_unique on; -# record_interval 30m; - } - } - application main { - live on; - meta copy; - push_reconnect 1s; - idle_streams off; - drop_idle_publisher 5s; - - recorder rec { record off; - record_suffix .flv; - record_path /usr/local/nginx/html/recording/; - record_unique on; - record_interval 30m; - } - } - - application input { - live on; - meta copy; - push_reconnect 1s; -# idle_streams off; - drop_idle_publisher 5s; - - recorder rec { record off; - record_suffix .flv; - record_path /usr/local/nginx/html/recording/; - record_unique on; - record_interval 30m; - } - } - - application distribute { - live on; - meta copy; - push_reconnect 1s; - idle_streams off; - drop_idle_publisher 10s; - } - - application backup { - live on; - meta copy; - push_reconnect 1s; - idle_streams off; - drop_idle_publisher 5s; - - recorder rec { record off; - record_suffix .flv; - record_path /usr/local/nginx/html/recording/; - record_unique on; -# record_interval 30m; - } - } + server { + + listen 1935; + chunk_size 8192; + + # allow publish 192.168.0.0/24; + # deny publish all; + + application output { + + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + # drop_idle_publisher 120s; + + + recorder rec { + + record off; + record_suffix .flv; + record_path /usr/local/nginx/html/recording/; + record_unique on; + # record_interval 30m; + } + } + + application main { + + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /usr/local/nginx/html/recording/; + record_unique on; + record_interval 30m; + } + } + + application input { + + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /usr/local/nginx/html/recording/; + record_unique on; + record_interval 30m; + } + } + + application distribute { + + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 10s; + } + + application backup { + + live on; + meta copy; + push_reconnect 1s; + idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record off; + record_suffix .flv; + record_path /usr/local/nginx/html/recording/; + record_unique on; + # record_interval 30m; + } + } + + application recording { + + live on; + meta copy; + push_reconnect 1s; + # idle_streams off; + drop_idle_publisher 5s; + + recorder rec { + + record all; + record_suffix -%Y%m%d-%H_%M_%S.flv; + record_path /usr/local/nginx/html/recording/; + record_unique off; + # record_interval 120m; + exec_record_done bash -c "/usr/local/nginx/scripts/config.sh convertrecording $path $dirname $basename"; + # exec_record_done ffmpeg -y -i $path -c copy $dirname/$basename.mp4; + } + } - application recording { - live on; - meta copy; - push_reconnect 1s; -# idle_streams off; - drop_idle_publisher 5s; - - recorder rec { record all; - record_suffix -%Y%m%d-%H_%M_%S.flv; - record_path /usr/local/nginx/html/recording/; - record_unique off; -# record_interval 120m; - exec_record_done bash -c "/usr/local/nginx/scripts/config.sh convertrecording $path $dirname $basename"; -# exec_record_done ffmpeg -y -i $path -c copy $dirname/$basename.mp4; - } - } - - application live { - live on; - meta copy; - hls on; -# hls_nested on; - hls_path /usr/local/nginx/html/hls; - hls_fragment 2s; - hls_playlist_length 10s; - hls_sync 100ms; - - pull rtmp://localhost:1935/main/stream1 name=input static live=1; - } - - } + application live { + + live on; + meta copy; + hls on; + # hls_nested on; + hls_path /usr/local/nginx/html/hls; + hls_fragment 2s; + hls_playlist_length 10s; + hls_sync 100ms; + + pull rtmp://localhost:1935/main/stream1 name=input static live=1; + } + + } } ############### FOR CONTROL PAGE ################## http { - #Firewall settings, only allow Local IP's -# allow all -# allow 192.168.0.0/16; -# allow 127.0.0.1; -# deny all; - - access_log logs/http_access.log; - include mime.types; - default_type application/octet-stream; - sendfile on; - keepalive_timeout 65; - - server { - #HTTP Server port and name - listen 80; - server_name localhost; + + #Firewall settings, only allow Local IP's + # allow all + # allow 192.168.0.0/16; + # allow 127.0.0.1; + # deny all; + + access_log logs/http_access.log; + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + + server { + + #HTTP Server port and name + listen 80; + server_name localhost; auth_basic "Restricted Content"; - auth_basic_user_file /usr/local/nginx/conf/.htpasswd; - - # rtmp statistics - location /stat { - rtmp_stat all; - rtmp_stat_stylesheet stat.xsl; - allow 127.0.0.1; - } - - location /stat.xsl { - # you can move stat.xsl to a different location - root html; - } - - location /live { - types { - application/vnd.apple.mpegurl m3u8; - video/mp2t ts; - } - alias /usr/local/nginx/html/hls; - add_header Cache-Control no-cache; - } - + auth_basic_user_file /usr/local/nginx/conf/.htpasswd; + + # rtmp statistics + location /stat { + rtmp_stat all; + rtmp_stat_stylesheet stat.xsl; + allow 127.0.0.1; + expires -1; + } + + location /stat.xml { + rtmp_stat all; + expires -1; + } + + location /stat.xsl { + # you can move stat.xsl to a different location + root html; + expires -1; + } + + location /config.txt { + root /usr/local/nginx/scripts; + expires -1; + } + + + + location /live { + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + alias /usr/local/nginx/html/hls; + add_header Cache-Control no-cache; + } + location / { -# types { -# application/xslt+xml xsl; -# } + #types { + # application/xslt+xml xsl; + #} root html; index index.html index.htm index.php index.cgi; autoindex on; @@ -175,27 +211,38 @@ http { autoindex_localtime on; } - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; - fastcgi_index index.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - include fastcgi_params; + location ~ \.csv$ { + types { + text/plain csv; + } + } + + location ~ \.php$ { + + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + expires -1; } - # rtmp control - location /control { - rtmp_control all; - # Enable CORS - add_header Access-Control-Allow-Origin * always; + # rtmp control + location /control { - } + rtmp_control all; + # Enable CORS + add_header Access-Control-Allow-Origin * always; - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + + root html; + } + } } + diff --git a/scripts/nginxrestart.sh b/scripts/nginxrestart.sh index 8ecdad2..2737fe3 100755 --- a/scripts/nginxrestart.sh +++ b/scripts/nginxrestart.sh @@ -6,10 +6,11 @@ sudo rm /usr/local/nginx/logs/*.log sudo cp /usr/local/nginx/scripts/images/lowerthird/*lowerthird.png /usr/local/nginx/scripts/images # Replaces page titles in webpages to name of instance -sudo sed -i "s|.*|$(hostname)|" /usr/local/nginx/html/control.html -sudo sed -i "s|

.*

|

$(hostname)

|" /usr/local/nginx/html/control.html -sudo sed -i "s|.*|$(hostname) Settings|" /usr/local/nginx/html/settings.html +sudo sed -i "s|.*|$(hostname) Control|" /usr/local/nginx/html/control.html +sudo sed -i "s|id="\""server-name"\"">.*MLS: $(hostname).*MLS: $(hostname).*|$(hostname) Stats|" /usr/local/nginx/html/stat.xsl # Restart NGINX -sudo /usr/local/nginx/sbin/nginx -s stop; sudo /usr/local/nginx/sbin/nginx \ No newline at end of file +sudo /usr/local/nginx/sbin/nginx -s stop +sudo /usr/local/nginx/sbin/nginx diff --git a/scripts/player.sh b/scripts/player.sh index 854ca4d..7fd1269 100644 --- a/scripts/player.sh +++ b/scripts/player.sh @@ -1,46 +1,46 @@ #!/bin/bash -streamid=$1; -streamapp=$2; -ffscreen="fftots"$1_$2; +streamid=$1 +streamapp=$2 +ffscreen="fftots"$1_$2 case $3 in on) -if [ $(ps aux | grep "wstoff" | awk '{print $2}' | wc -l) -eq 1 ]; then -screen -dm -S wstoff -screen -S wstoff -p 0 -X stuff 'node /usr/local/nginx/html/websocket-relay.js supersecret 8031 443\n'; -fi - -LCK="/usr/local/nginx/scripts/tmp/${ffscreen}.LCK"; -exec 8>$LCK; - -if flock -n -x 8; then -if [ -z "$STY" ]; then -kill $(ps aux | grep "fftots" | awk '{print $2}') -echo "Turning on $streamid player" -#sleep .2 -exec screen -dm -S $ffscreen /bin/bash "$0" "$1" "$2" "$3" -fi - -while true -do -ffmpeg -i rtmp://127.0.0.1/$streamapp/$streamid -f mpegts -acodec mp2 -ar 44100 -ac 1 -b:a 96k -vcodec mpeg1video -r 25 -bf 0 -s 320x180 -b:v 200k -muxdelay 0.001 http://localhost:8031/supersecret -#echo "Restarting ffmpeg..." -sleep .2 -done - -else -echo $streamid " player is already on" -fi -;; + if [ $(ps aux | grep "wstoff" | awk '{print $2}' | wc -l) -eq 1 ]; then + screen -dm -S wstoff + screen -S wstoff -p 0 -X stuff 'node /usr/local/nginx/html/websocket-relay.js supersecret 8031 443\n' + fi + + LCK="/usr/local/nginx/scripts/tmp/${ffscreen}.LCK" + exec 8>$LCK + + if flock -n -x 8; then + if [ -z "$STY" ]; then + kill $(ps aux | grep "fftots" | awk '{print $2}') + echo "Turning on $streamid player" + #sleep .2 + exec screen -dm -S $ffscreen /bin/bash "$0" "$1" "$2" "$3" + fi + + while true; do + ffmpeg -i rtmp://127.0.0.1/$streamapp/$streamid -f mpegts -acodec mp2 -ar 44100 -ac 1 -b:a 96k -vcodec mpeg1video -r 25 -bf 0 -s 320x180 -b:v 200k -muxdelay 0.001 http://localhost:8031/supersecret + #echo "Restarting ffmpeg..." + sleep .2 + done + + else + echo $streamid " player is already on" + fi + ;; off) -if [ $(ps aux | grep $ffscreen | awk '{print $2}' | wc -l) -gt 0 ]; then -kill $(ps aux | grep $ffscreen | awk '{print $2}') -fi - -#if [ $(ps aux | grep "wstoff" | awk '{print $2}' | wc -l) -gt 0 ]; then -#kill $(ps aux | grep "wstoff" | awk '{print $2}') -#fi -echo "Turning off $streamid player" + if [ $(ps aux | grep $ffscreen | awk '{print $2}' | wc -l) -gt 0 ]; then + kill $(ps aux | grep $ffscreen | awk '{print $2}') + fi + + #if [ $(ps aux | grep "wstoff" | awk '{print $2}' | wc -l) -gt 0 ]; then + #kill $(ps aux | grep "wstoff" | awk '{print $2}') + #fi + echo "Turning off $streamid player" + ;; esac diff --git a/scripts/set-env.sh b/scripts/set-env.sh new file mode 100755 index 0000000..7bac390 --- /dev/null +++ b/scripts/set-env.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Function to update or add a global variable to ~/.bashrc +update_env_variable() { + local variable_name="$1" + local new_value="$2" + local grep_result + + # Check if the variable is already set in ~/.bashrc + grep_result=$(grep -E "^export $variable_name=" ~/.bashrc) + + if [ -n "$grep_result" ]; then + # Variable exists, replace its value + sed -i "s/^export $variable_name=.*/export $variable_name=\"$new_value\"/" ~/.bashrc + else + # Variable does not exist, add it to ~/.bashrc + echo -e "\nexport $variable_name=\"$new_value\"\n" >>~/.bashrc + fi +} + +# Set global variables +update_env_variable "STREAM_NUM" "25" +update_env_variable "OUT_NUM" "95" diff --git a/server.js b/server.js new file mode 100644 index 0000000..c408287 --- /dev/null +++ b/server.js @@ -0,0 +1,18 @@ +const express = require('express'); +const path = require('path'); + +const PORT = 3000; +const app = express(); + +// Serve static files from the specified folder +app.use(express.static(path.join(__dirname, './html'))); + +// Handle 404 errors +app.use((req, res) => { + res.status(404).send('404: Not Found'); +}); + +// Start the server +app.listen(PORT, () => { + console.log(`Server is running at http://localhost:${PORT}`); +}); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..0296f42 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./html/**/*.{html,js}'], + theme: { + extend: {}, + }, + plugins: [require('@tailwindcss/typography'), require('daisyui')], + daisyui: { + themes: ['dim', 'night'], + }, +}; diff --git a/update-mls.sh b/update-mls.sh new file mode 100755 index 0000000..1cbd1b8 --- /dev/null +++ b/update-mls.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +source scripts/set-env.sh + +# Production files folder +cd /usr/local/nginx/ + +# Keep configs +cp ./html/stream-names.csv ~/MLS/html/ +cp ./scripts/config.txt ~/MLS/scripts/ + +# Shift files to right locations +sudo chgrp -R www-data ~/MLS +sudo chmod g+rw -R ~/MLS +sudo cp -R ~/MLS/html . +sudo cp scripts/nginx.conf ./conf/ +sudo cp -R ~/MLS/scripts . +sudo chmod +x -R ./scripts + +for ((i = 2; i <= STREAM_NUM; i++)); do + sudo cp ./scripts/1.sh ./scripts/${i}.sh +done + +sudo ./scripts/nginxrestart.sh