out source the api to separate project

This commit is contained in:
Jonathan Baecker 2020-09-10 16:29:19 +02:00
parent e2ce6afb70
commit 1d4efd2aca
87 changed files with 207 additions and 1667 deletions

104
.gitignore vendored
View File

@ -1,16 +1,90 @@
.ropeproject
**temp
*.log*
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# Mac OSX
.DS_Store
__pycache__/
venv/
*-orig.*
tests/
**/ffplayout/static/
._*
._**
*.sqlite3
**/migrations/*
!**/migrations/__init__.py
**/ffplayout/apps/*
!**/ffplayout/apps/api_player/
# Vim swap files
*.swp

View File

@ -2,6 +2,9 @@ ffplayout-gui
=====
This web GUI is for managing [ffplayout-engine](https://github.com/ffplayout/ffplayout-engine).
The frontend depend on [ffplayout-api](https://github.com/ffplayout/ffplayout-api).
For a better understanding about the functionality, take a look to the screenshots below.
You can install it on a fresh Debian/CentOS minimal like system with running `./install.sh` as root.
@ -16,25 +19,25 @@ After installations you have to setup ssl for your **https** connections.
## some impressions:
#### Login
![login](/assets/login.png)
![login](/docs/assets/login.png)
#### Landing Page
![landing-page](/assets/landing-page.png)
![landing-page](/docs/assets/landing-page.png)
#### Control Page
![control](/assets/control.png)
![control](/docs/assets/control.png)
#### Media Page
![media](/assets/media.png)
![media](/docs/assets/media.png)
#### Media Page / Upload
![media-upload](/assets/media-upload.png)
![media-upload](/docs/assets/media-upload.png)
#### Message Page
![message](/assets/message.png)
![message](/docs/assets/message.png)
#### Logging Page
![logging](/assets/logging.png)
![logging](/docs/assets/logging.png)
#### Configuration Page / GUI
![config-gui](/assets/config-gui.png)
![config-gui](/docs/assets/config-gui.png)

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 378 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -1,84 +0,0 @@
[{
"model": "api_player.guisettings",
"pk": 1,
"fields": {
"channel": "Channel 1",
"player_url": "http://localhost/live/stream.m3u8",
"playout_config": "/etc/ffplayout/ffplayout.yml",
"net_interface": "lo",
"media_disk": "/",
"extra_extensions": ".jpg,.jpeg,.png"
}
}, {
"model": "api_player.messengepresets",
"pk": 1,
"fields": {
"name": "default",
"message": "Wellcome to ffplayout!",
"x": "(w-text_w)/2",
"y": "(h-text_h)/2",
"font_size": 24,
"font_spacing": 4,
"font_color": "#ffffff",
"font_alpha": 1.0,
"show_box": true,
"box_color": "#000000",
"box_alpha": 0.8,
"border_width": 4,
"overall_alpha": "1"
}
}, {
"model": "api_player.messengepresets",
"pk": 2,
"fields": {
"name": "empty-text",
"message": null,
"x": null,
"y": null,
"font_size": 24,
"font_spacing": 4,
"font_color": "#ffffff",
"font_alpha": 1.0,
"show_box": false,
"box_color": "#000000",
"box_alpha": 0.8,
"border_width": 0,
"overall_alpha": "0"
}
}, {
"model": "api_player.messengepresets",
"pk": 3,
"fields": {
"name": "bottom-text-fade-in",
"message": "The upcoming event will be delayed by a few minutes.",
"x": "(w-text_w)/2",
"y": "(h-line_h)*0.9",
"font_size": 24,
"font_spacing": 4,
"font_color": "#ffffff",
"font_alpha": 1.0,
"show_box": true,
"box_color": "#000000",
"box_alpha": 0.8,
"border_width": 4,
"overall_alpha": "ifnot(ld(1),st(1,t));if(lt(t,ld(1)+1),0,if(lt(t,ld(1)+2),(t-(ld(1)+1))/1,if(lt(t,ld(1)+8),1,if(lt(t,ld(1)+9),(1-(t-(ld(1)+8)))/1,0))))"
}
}, {
"model": "api_player.messengepresets",
"pk": 4,
"fields": {
"name": "scrolling-text",
"message": "We have a very important announcement to make.",
"x": "ifnot(ld(1),st(1,t));if(lt(t,ld(1)+1),w+4,w-w/12*mod(t-ld(1),12*(w+tw)/w))",
"y": "(h-line_h)*0.9",
"font_size": 24,
"font_spacing": 4,
"font_color": "#ffffff",
"font_alpha": 1.0,
"show_box": true,
"box_color": "#000000",
"box_alpha": 0.8,
"border_width": 4,
"overall_alpha": "1"
}
}]

View File

@ -1,15 +0,0 @@
### Backend Apps
If you planing to extend the backend with your own apps (api endpoints),
just add your app in folder: **ffplayout/apps**.
If you planing to us a DB, put a **settings.py** file in your app. With this object:
```python
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db-name.sqlite3'),
}
}
```

View File

@ -1,14 +0,0 @@
[Unit]
Description=ffplayout api daemon
After=network.target
[Service]
WorkingDirectory=/var/www/ffplayout/ffplayout
Environment=DJANGO_SETTINGS_MODULE=ffplayout.settings.production
ExecStart=/var/www/ffplayout/venv/bin/gunicorn --workers 4 --worker-class=gevent --timeout 10800 --log-level=info --log-file=- --access-logfile=- --bind 127.0.0.1:8001 ffplayout.wsgi:application
KillMode=process
User=root
Group=root
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,40 @@
server {
listen 80;
server_name ffplayout-frontend.local;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
gzip_min_length 1000;
charset utf-8;
client_max_body_size 7000M; # should be desirable value
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location / {
if ($http_origin ~ '^https?://(localhost|ffplayout-frontend\.local)') {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
}
if ($request_method = OPTIONS ) {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
root /var/www/ffplayout-frontend/dist/;
}
access_log /var/log/nginx/ffplayout-frontend_access.log;
error_log /var/log/nginx/ffplayout-frontend_error.log warn;
}

View File

@ -1,78 +0,0 @@
server {
listen 80;
server_name ffplayout.local;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
gzip_min_length 1000;
charset utf-8;
client_max_body_size 7000M; # should be desirable value
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location / {
if ($http_origin ~ '^https?://(localhost|ffplayout\.local)') {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
}
if ($request_method = OPTIONS ) {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
root /var/www/ffplayout/ffplayout/frontend/dist/;
}
location ~ ^/(api|admin|auth|api-auth) {
if ($http_origin ~ '^https?://(localhost|ffplayout\.local)') {
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
}
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 36000s;
proxy_connect_timeout 36000s;
proxy_send_timeout 36000s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
send_timeout 36000s;
proxy_no_cache 1;
proxy_pass http://127.0.0.1:8001;
}
location /static/ {
alias /var/www/ffplayout/ffplayout/static/;
}
location /live/ {
alias /var/www/srs/live/;
}
access_log /var/log/nginx/ffplayout_access.log;
error_log /var/log/nginx/ffplayout_error.log warn;
}

View File

@ -1,76 +0,0 @@
# Manuel Installation Guide
**We are assuming that the system user `www-data` will run all processes!**
### API Setup
##### Preparation
- clone repo to `/var/www/ffplayout`
- cd in root folder from repo
- add virtual environment: `virtualenv -p python3 venv`
- run `source ./venv/bin/activate`
- install dependencies: `pip install -r requirements-base.txt`
- cd in `ffplayout`
- generate and copy secret: `python manage.py shell -c 'from django.core.management import utils; print(utils.get_random_secret_key())'`
- open **ffplayout/settings/production.py**
- past secret key in variable `SECRET_KEY`
- set `ALLOWED_HOSTS` with correct URL
- set URL in `CORS_ORIGIN_WHITELIST`
- migrate database: `python manage.py makemigrations && python manage.py migrate`
- collect static files: `python manage.py collectstatic`
- add super user to db: `python manage.py createsuperuser`
- populate some data to db: `python manage.py loaddata ../docs/db_data.json`
- run: `chown www-data. -R /var/www/ffplayout`
##### System Setup
- copy **docs/ffplayout-api.service** from root folder to **/etc/systemd/system/**
- enable service and start it: `systemctl enable ffplayout-api.service && systemctl start ffplayout-api.service`
- install **nginx**
- edit **docs/ffplayout.conf**
- set correct IP and `server_name`
- add domain `http_origin` test value
- add https redirection and SSL if is necessary
- copy **docs/ffplayout.conf** to **/etc/nginx/sites-available/**
- symlink config: `ln -s /etc/nginx/sites-available/ffplayout.conf /etc/nginx/sites-enabled/`
- restart nginx
- run `visudo` and add:
```
www-data ALL = NOPASSWD: /bin/systemctl start ffplayout-engine.service, /bin/systemctl stop ffplayout-engine.service, /bin/systemctl reload ffplayout-engine.service, /bin/systemctl restart ffplayout-engine.service, /bin/systemctl status ffplayout-engine.service, /bin/systemctl is-active ffplayout-engine.service, /bin/journalctl -n 1000 -u ffplayout-engine.service
```
### Frontend
**We need a recent version of npm**
- go to folder **/var/www/ffplayout/ffplayout/frontend**
- install dependencies: `npm install`
- create **.env** file:
```
BASE_URL='http://localhost:3000'
API_URL='/'
```
- in dev mode `API_URL` should be: `http://localhost:8000`
- for deactivating progress animation: `DEV=true`
- create symlink for the media folder
- when your media folder is a subfolder (for example `/opt/ffplayout/media`) create the same folder structure under **static**:
- `mkdir -p /var/www/ffplayout/ffplayout/frontend/static/opt/ffplayout`
- `ln -s /opt/ffplayout/media /var/www/ffplayout/ffplayout/frontend/static/opt/ffplayout/`
- build app: `npm run build`
Your frontend should be now in **/var/www/ffplayout/ffplayout/frontend/dist** folder, which we are included already in the nginx config. You can serve now the GUI under your domain URL.
### OS Specific
On debian 10 you need to install:
```
apt install -y curl
```
```
curl -sL https://deb.nodesource.com/setup_12.x | bash -
```
**For full installation (with ffmpeg/srs):**
```
apt install -y sudo net-tools git python3-dev build-essential python3-virtualenv nodejs nginx autoconf automake libtool pkg-config yasm cmake curl mercurial git wget gperf mediainfo
```

View File

@ -1,111 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
from argparse import ArgumentParser
import requests
import urllib3
from pymediainfo import MediaInfo
urllib3.disable_warnings()
# ------------------------------------------------------------------------------
# argument parsing
# ------------------------------------------------------------------------------
stdin_parser = ArgumentParser(
description='Convert text file to playlist and send it to the API')
stdin_parser.add_argument(
'-u', '--user', help='API user', required=True
)
stdin_parser.add_argument(
'-p', '--password', help='API password', required=True
)
stdin_parser.add_argument(
'-c', '--channel', help='Channel name'
)
stdin_parser.add_argument(
'--url', help='the url from the ffplayout API', required=True
)
stdin_parser.add_argument(
'-d', '--date', help='date from target playlist, in YYYY-MM-DD',
required=True
)
stdin_parser.add_argument(
'-f', '--file', help=('text file with clips, '
'paths must match the paths on ffplayout'),
required=True
)
stdin_args = stdin_parser.parse_args()
def auth():
login = {'username': stdin_args.user,
'password': stdin_args.password}
req = requests.post(
'{}/auth/token/'.format(stdin_args.url), data=login)
token = req.json()
return token['access']
def get_video_duration(clip):
"""
return video duration from container
"""
media_info = MediaInfo.parse(clip)
duration = 0
for track in media_info.tracks:
if track.track_type == 'General':
try:
duration = float(
track.to_data()["duration"]) / 1000
break
except KeyError:
pass
return duration
def gen_playlist():
json_data = {
'channel': stdin_args.channel if stdin_args.channel else 'Channel 1',
'date': stdin_args.date,
'program': []
}
with open(stdin_args.file, 'r') as content:
for line in content:
src = line.strip().strip('"').strip("'")
duration = get_video_duration(src)
json_data['program'].append({
'in': 0,
'out': duration,
'duration': duration,
'source': src
})
return json_data
if __name__ == '__main__':
playlist = gen_playlist()
req = requests.post(
'{}/api/player/playlist/'.format(stdin_args.url),
data=json.dumps({'data': playlist}),
headers={'Authorization': 'Bearer {}'.format(auth()),
'content-type': 'application/json'})
if req.status_code == 201:
print('Save remote playlist from done...')
else:
print(req.json()['detail'])

View File

@ -1,21 +0,0 @@
from apps.api_player.models import GuiSettings, MessengePresets
from django.contrib import admin
class GuiSettingsAdmin(admin.ModelAdmin):
class Meta:
model = GuiSettings
fields = '__all__'
class MessengePresetsAdmin(admin.ModelAdmin):
list_display = ('name',)
class Meta:
model = MessengePresets
fields = '__all__'
admin.site.register(GuiSettings, GuiSettingsAdmin)
admin.site.register(MessengePresets, MessengePresetsAdmin)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class ApiPlayerConfig(AppConfig):
name = 'api_player'

View File

@ -1,71 +0,0 @@
import psutil
from django.db import models
class GuiSettings(models.Model):
"""
Here we manage the settings for the web GUI:
- Player URL
- settings for the statistics
"""
addrs = psutil.net_if_addrs()
addrs = [(i, i) for i in addrs.keys()]
channel = models.CharField(max_length=255, blank=True,
default='Channel 1', null=True)
player_url = models.CharField(max_length=255, blank=True,
default=None, null=True)
playout_config = models.CharField(
max_length=255,
default='/etc/ffplayout/ffplayout.yml')
net_interface = models.CharField(
max_length=20,
choices=addrs,
blank=True, default=None, null=True,
)
media_disk = models.CharField(
max_length=255,
help_text="should be a mount point, for statistics",
blank=True, default=None, null=True)
extra_extensions = models.CharField(
max_length=255,
help_text="file extensions, that are only visible in GUI",
blank=True, null=True, default='')
class Meta:
verbose_name_plural = "guisettings"
def __str__(self):
return str(self.channel)
class MessengePresets(models.Model):
name = models.CharField(max_length=255, help_text="the preset name")
message = models.CharField(
max_length=1024, blank=True, null=True, default='')
x = models.CharField(
max_length=512, blank=True, null=True, default='')
y = models.CharField(
max_length=512, blank=True, null=True, default='')
font_size = models.IntegerField(default=24)
font_spacing = models.IntegerField(default=4)
font_color = models.CharField(max_length=12, default='#ffffff')
font_alpha = models.FloatField(default=1.0)
show_box = models.BooleanField(default=True)
box_color = models.CharField(max_length=12, default='#000000')
box_alpha = models.FloatField(default=0.8)
border_width = models.IntegerField(default=4)
overall_alpha = models.CharField(
max_length=255, blank=True, null=True, default='')
class Meta:
verbose_name_plural = "messengepresets"
def __str__(self):
return str(self.name)

View File

@ -1,54 +0,0 @@
from apps.api_player.models import GuiSettings, MessengePresets
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
new_password = serializers.CharField(write_only=True, required=False)
old_password = serializers.CharField(write_only=True, required=False)
class Meta:
model = User
fields = ['id', 'username', 'old_password',
'new_password', 'email']
def update(self, instance, validated_data):
print(validated_data)
instance.password = validated_data.get('password', instance.password)
if 'new_password' in validated_data and \
'old_password' in validated_data:
if not validated_data['new_password']:
raise serializers.ValidationError({'new_password': 'not found'})
if not validated_data['old_password']:
raise serializers.ValidationError({'old_password': 'not found'})
if not instance.check_password(validated_data['old_password']):
raise serializers.ValidationError(
{'old_password': 'wrong password'})
if validated_data['new_password'] and \
instance.check_password(validated_data['old_password']):
# instance.password = validated_data['new_password']
instance.set_password(validated_data['new_password'])
instance.save()
return instance
elif 'email' in validated_data:
instance.email = validated_data['email']
instance.save()
return instance
return instance
class GuiSettingsSerializer(serializers.ModelSerializer):
class Meta:
model = GuiSettings
fields = '__all__'
class MessengerSerializer(serializers.ModelSerializer):
class Meta:
model = MessengePresets
fields = '__all__'

View File

@ -1,10 +0,0 @@
import os
BASE_DIR = os.path.dirname(os.path.abspath(os.path.join(__file__)))
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,26 +0,0 @@
from django.urls import include, path, re_path
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'user/users', views.UserViewSet)
router.register(r'guisettings', views.GuiSettingsViewSet, 'guisettings')
router.register(r'messenger', views.MessengerViewSet, 'messenger')
app_name = 'api_player'
urlpatterns = [
path('player/', include(router.urls)),
path('player/config/', views.Config.as_view()),
path('player/log/', views.LogReader.as_view()),
path('player/media/', views.Media.as_view()),
path('player/media/op/', views.FileOperations.as_view()),
re_path(r'^player/media/upload/(?P<filename>[^/]+)$',
views.FileUpload.as_view()),
path('player/send/message/', views.MessageSender.as_view()),
path('player/playlist/', views.Playlist.as_view()),
path('player/stats/', views.Statistics.as_view()),
path('player/user/current/', views.CurrentUserView.as_view()),
path('player/system/', views.SystemCtl.as_view()),
]

View File

@ -1,332 +0,0 @@
import json
import os
import re
from datetime import datetime
from platform import uname
from subprocess import PIPE, STDOUT, run
from time import sleep
from pymediainfo import MediaInfo
import psutil
import yaml
import zmq
from apps.api_player.models import GuiSettings
from django.conf import settings
from natsort import natsorted
from rest_framework import status
from rest_framework.response import Response
def read_yaml():
setting = GuiSettings.objects.filter(id=1).values()
if setting:
config = setting[0]
if config and os.path.isfile(config['playout_config']):
with open(config['playout_config'], 'r') as config_file:
return yaml.safe_load(config_file)
def write_yaml(data):
config = GuiSettings.objects.filter(id=1).values()[0]
if os.path.isfile(config['playout_config']):
with open(config['playout_config'], 'w') as outfile:
yaml.dump(data, outfile, default_flow_style=False,
sort_keys=False, indent=4)
def read_json(date):
config = read_yaml()['playlist']['path']
y, m, d = date.split('-')
input = os.path.join(config, y, m, '{}.json'.format(date))
if os.path.isfile(input):
with open(input, 'r') as playlist:
return json.load(playlist)
def write_json(data):
config = read_yaml()['playlist']['path']
y, m, d = data['date'].split('-')
_path = os.path.join(config, y, m)
if not os.path.isdir(_path):
os.makedirs(_path, exist_ok=True)
output = os.path.join(_path, '{}.json'.format(data['date']))
if os.path.isfile(output) and data == read_json(data['date']):
return Response(
{'detail': 'Playlist from {} already exists'.format(data['date'])})
with open(output, "w") as outfile:
json.dump(data, outfile, indent=4)
return Response({'detail': 'Playlist from {} saved'.format(data['date'])})
def read_log(type, _date):
config = read_yaml()
log_path = config['logging']['log_path']
if _date == datetime.now().strftime('%Y-%m-%d'):
log_file = os.path.join(log_path, '{}.log'.format(type))
else:
log_file = os.path.join(log_path, '{}.log.{}'.format(type, _date))
if os.path.isfile(log_file):
with open(log_file, 'r') as log:
return log.read().strip()
def send_message(data):
config = read_yaml()
address, port = config['text']['bind_address'].split(':')
context = zmq.Context(1)
client = context.socket(zmq.REQ)
client.connect('tcp://{}:{}'.format(address, port))
poll = zmq.Poller()
poll.register(client, zmq.POLLIN)
request = ''
reply_msg = ''
for key, value in data.items():
request += "{}='{}':".format(key, value)
request = "{} reinit {}".format(
settings.DRAW_TEXT_NODE, request.rstrip(':'))
client.send_string(request)
socks = dict(poll.poll(settings.REQUEST_TIMEOUT))
if socks.get(client) == zmq.POLLIN:
reply = client.recv()
if reply and reply.decode() == '0 Success':
reply_msg = reply.decode()
else:
reply_msg = reply.decode()
else:
reply_msg = 'No response from server'
client.setsockopt(zmq.LINGER, 0)
client.close()
poll.unregister(client)
context.term()
return {'Success': reply_msg}
def sizeof_fmt(num, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
class PlayoutService:
def __init__(self):
self.service = ['ffplayout-engine.service']
self.cmd = ['sudo', '/bin/systemctl']
self.proc = None
def run_cmd(self):
self.proc = run(self.cmd + self.service, stdout=PIPE, stderr=STDOUT,
encoding="utf-8").stdout
def start(self):
self.cmd.append('start')
self.run_cmd()
def stop(self):
self.cmd.append('stop')
self.run_cmd()
def reload(self):
self.cmd.append('reload')
self.run_cmd()
def restart(self):
self.cmd.append('restart')
self.run_cmd()
def status(self):
self.cmd.append('is-active')
self.run_cmd()
return self.proc.replace('\n', '')
def log(self):
self.cmd = ['sudo', '/bin/journalctl', '-n', '1000', '-u']
self.run_cmd()
return self.proc
class SystemStats:
def __init__(self):
settings = GuiSettings.objects.filter(id=1).values()
self.config = settings[0] if settings else []
def all(self):
if self.config:
return {
**self.system(),
**self.cpu(), **self.ram(), **self.swap(),
**self.disk(), **self.net(), **self.net_speed()
}
def system(self):
return {
'system': uname().system,
'node': uname().node,
'machine': uname().machine
}
def cpu(self):
return {
'cpu_usage': psutil.cpu_percent(interval=1),
'cpu_load': list(psutil.getloadavg())
}
def ram(self):
mem = psutil.virtual_memory()
return {
'ram_total': [mem.total, sizeof_fmt(mem.total)],
'ram_used': [mem.used, sizeof_fmt(mem.used)],
'ram_free': [mem.free, sizeof_fmt(mem.free)],
'ram_cached': [mem.cached, sizeof_fmt(mem.cached)]
}
def swap(self):
swap = psutil.swap_memory()
return {
'swap_total': [swap.total, sizeof_fmt(swap.total)],
'swap_used': [swap.used, sizeof_fmt(swap.used)],
'swap_free': [swap.free, sizeof_fmt(swap.free)]
}
def disk(self):
if 'media_disk' in self.config and self.config['media_disk']:
root = psutil.disk_usage(self.config['media_disk'])
return {
'disk_total': [root.total, sizeof_fmt(root.total)],
'disk_used': [root.used, sizeof_fmt(root.used)],
'disk_free': [root.free, sizeof_fmt(root.free)]
}
def net(self):
net = psutil.net_io_counters()
return {
'net_send': [net.bytes_sent, sizeof_fmt(net.bytes_sent)],
'net_recv': [net.bytes_recv, sizeof_fmt(net.bytes_recv)],
'net_errin': net.errin,
'net_errout': net.errout
}
def net_speed(self):
net = psutil.net_if_stats()
if 'net_interface' not in self.config or \
not self.config['net_interface']:
return
if self.config['net_interface'] not in net:
return {
'net_speed_send': 'no network interface set!',
'net_speed_recv': 'no network interface set!'
}
net = psutil.net_io_counters(pernic=True)[self.config['net_interface']]
send_start = net.bytes_sent
recv_start = net.bytes_recv
sleep(1)
net = psutil.net_io_counters(pernic=True)[self.config['net_interface']]
send_end = net.bytes_sent
recv_end = net.bytes_recv
send_sec = send_end - send_start
recv_sec = recv_end - recv_start
return {
'net_speed_send': [send_sec, sizeof_fmt(send_sec)],
'net_speed_recv': [recv_sec, sizeof_fmt(recv_sec)]
}
def get_video_duration(clip):
"""
return video duration from container
"""
media_info = MediaInfo.parse(clip)
duration = 0
for track in media_info.tracks:
if track.track_type == 'General':
try:
duration = float(
track.to_data()["duration"]) / 1000
break
except KeyError:
pass
return duration
def get_path(input):
"""
return path and prevent breaking out of media root
"""
config = read_yaml()
media_root_list = config['storage']['path'].strip('/').split('/')
media_root_list.pop()
media_root = '/' + '/'.join(media_root_list)
if input:
input = os.path.abspath(os.path.join(media_root, input.strip('/')))
if not input.startswith(config['storage']['path']):
input = os.path.join(config['storage']['path'], input.strip('/'))
return media_root, input
def get_media_path(extensions, _dir=''):
config = read_yaml()
media_folder = config['storage']['path']
extensions = extensions.split(',')
playout_extensions = config['storage']['extensions']
gui_extensions = [x for x in extensions if x not in playout_extensions]
media_root, search_dir = get_path(_dir)
for root, dirs, files in os.walk(search_dir, topdown=True):
root = root.rstrip('/')
media_files = []
for file in files:
ext = os.path.splitext(file)[1]
if ext in playout_extensions:
duration = get_video_duration(os.path.join(root, file))
media_files.append({'file': file, 'duration': duration})
elif ext in gui_extensions:
media_files.append({'file': file, 'duration': ''})
dirs = natsorted(dirs)
if root.strip('/') != media_folder.strip('/') or not dirs:
dirs.insert(0, '..')
root = re.sub(r'^{}'.format(media_root), '', root).strip('/')
return [root, dirs, natsorted(media_files, key=lambda x: x['file'])]

View File

@ -1,286 +0,0 @@
import os
import shutil
from urllib.parse import unquote
from apps.api_player.models import GuiSettings, MessengePresets
from apps.api_player.serializers import (GuiSettingsSerializer,
MessengerSerializer, UserSerializer)
from django.contrib.auth.models import User
from django_filters import rest_framework as filters
from rest_framework import status, viewsets
from rest_framework.parsers import FileUploadParser, JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView
from .utils import (PlayoutService, SystemStats, get_media_path, read_json,
read_log, read_yaml, send_message, write_json, write_yaml)
class CurrentUserView(APIView):
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
class UserFilter(filters.FilterSet):
class Meta:
model = User
fields = ['username']
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = UserFilter
class GuiSettingsViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows media to be viewed.
"""
queryset = GuiSettings.objects.all()
serializer_class = GuiSettingsSerializer
class MessengerFilter(filters.FilterSet):
class Meta:
model = MessengePresets
fields = ['name']
class MessengerViewSet(viewsets.ModelViewSet):
queryset = MessengePresets.objects.all()
serializer_class = MessengerSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = MessengerFilter
class MessageSender(APIView):
"""
send messages with zmq to the playout engine
"""
def post(self, request, *args, **kwargs):
if 'data' in request.data:
response = send_message(request.data['data'])
return Response({"success": True, 'status': response})
return Response({"success": False})
class Config(APIView):
"""
read and write config from ffplayout engine
for reading endpoint is: http://127.0.0.1:8000/api/player/config/?config
"""
parser_classes = [JSONParser]
def get(self, request, *args, **kwargs):
if 'configPlayout' in request.GET.dict():
yaml_input = read_yaml()
if yaml_input:
return Response(yaml_input)
else:
return Response(status=204)
else:
return Response(status=404)
def post(self, request, *args, **kwargs):
if 'data' in request.data:
write_yaml(request.data['data'])
return Response(status=200)
return Response(status=404)
class SystemCtl(APIView):
"""
controlling the ffplayout-engine systemd services
"""
def post(self, request, *args, **kwargs):
if 'run' in request.data:
service = PlayoutService()
if request.data['run'] == 'start':
service.start()
return Response(status=200)
elif request.data['run'] == 'stop':
service.stop()
return Response(status=200)
elif request.data['run'] == 'reload':
service.reload()
return Response(status=200)
elif request.data['run'] == 'restart':
service.restart()
return Response(status=200)
elif request.data['run'] == 'status':
status = service.status()
return Response({"data": status})
elif request.data['run'] == 'log':
log = service.log()
return Response({"data": log})
else:
return Response(status=400)
return Response(status=404)
class LogReader(APIView):
def get(self, request, *args, **kwargs):
if 'type' in request.GET.dict() and 'date' in request.GET.dict():
type = request.GET.dict()['type']
_date = request.GET.dict()['date']
log = read_log(type, _date)
if log:
return Response({'log': log})
else:
return Response(status=204)
else:
return Response(status=404)
class Playlist(APIView):
"""
read and write config from ffplayout engine
for reading endpoint:
http://127.0.0.1:8000/api/player/playlist/?date=2020-04-12
"""
def get(self, request, *args, **kwargs):
if 'date' in request.GET.dict():
date = request.GET.dict()['date']
json_input = read_json(date)
if json_input:
return Response(json_input)
else:
return Response({
"success": False,
"error": "Playlist from {} not found!".format(date)})
else:
return Response(status=400)
def post(self, request, *args, **kwargs):
if 'data' in request.data:
return write_json(request.data['data'])
return Response({'detail': 'Unspecified save error'})
class Statistics(APIView):
"""
get system statistics: cpu, ram, etc.
for reading, endpoint is: http://127.0.0.1:8000/api/player/stats/?stats=all
"""
def get(self, request, *args, **kwargs):
stats = SystemStats()
if 'stats' in request.GET.dict() and request.GET.dict()['stats'] \
and hasattr(stats, request.GET.dict()['stats']):
return Response(
getattr(stats, request.GET.dict()['stats'])())
else:
return Response(status=404)
class Media(APIView):
"""
get folder/files tree, for building a file explorer
for reading, endpoint is: http://127.0.0.1:8000/api/player/media/?path
"""
def get(self, request, *args, **kwargs):
if 'extensions' in request.GET.dict():
extensions = request.GET.dict()['extensions']
if 'path' in request.GET.dict() and request.GET.dict()['path']:
return Response({'tree': get_media_path(
extensions, request.GET.dict()['path']
)})
elif 'path' in request.GET.dict():
return Response({'tree': get_media_path(extensions)})
else:
return Response(status=204)
else:
return Response(status=404)
class FileUpload(APIView):
parser_classes = [FileUploadParser]
def put(self, request, filename, format=None):
root = read_yaml()['storage']['path']
file_obj = request.data['file']
filename = unquote(filename)
path = unquote(request.query_params['path']).split('/')[1:]
with open(os.path.join(root, *path, filename), 'wb') as outfile:
for chunk in file_obj.chunks():
outfile.write(chunk)
return Response(status=204)
class FileOperations(APIView):
def delete(self, request, *args, **kwargs):
if 'file' in request.GET.dict() and 'path' in request.GET.dict():
root = read_yaml()['storage']['path']
_file = unquote(request.GET.dict()['file'])
folder = unquote(request.GET.dict()['path']).lstrip('/')
_path = os.path.join(*(folder.split(os.path.sep)[1:]))
fullPath = os.path.join(root, _path)
if not _file or _file == 'null':
if os.path.isdir(fullPath):
shutil.rmtree(fullPath, ignore_errors=True)
return Response(status=200)
else:
return Response(status=404)
elif os.path.isfile(os.path.join(fullPath, _file)):
os.remove(os.path.join(fullPath, _file))
return Response(status=200)
else:
return Response(status=404)
else:
return Response(status=404)
def post(self, request, *args, **kwargs):
if 'folder' in request.data and 'path' in request.data:
root = read_yaml()['storage']['path']
folder = request.data['folder']
_path = request.data['path'].split(os.path.sep)
_path = '' if len(_path) == 1 else os.path.join(*_path[1:])
fullPath = os.path.join(root, _path, folder)
try:
# TODO: check if folder exists
os.mkdir(fullPath)
return Response(status=200)
except OSError:
Response(status=500)
else:
return Response(status=404)
def patch(self, request, *args, **kwargs):
if 'path' in request.data and 'oldname' in request.data \
and 'newname' in request.data:
root = read_yaml()['storage']['path']
old_name = request.data['oldname']
new_name = request.data['newname']
_path = os.path.join(
*(request.data['path'].split(os.path.sep)[2:]))
old_file = os.path.join(root, _path, old_name)
new_file = os.path.join(root, _path, new_name)
os.rename(old_file, new_file)
return Response(status=200)
else:
return Response(status=204)

View File

@ -1,17 +0,0 @@
"""
ASGI config for ffplayout project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE', 'ffplayout.settings.development')
application = get_asgi_application()

View File

@ -1,141 +0,0 @@
"""
Django settings for ffplayout project.
Generated by 'django-admin startproject' using Django 3.0.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
from datetime import timedelta
from pydoc import locate
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(
os.path.dirname(os.path.abspath(os.path.join(__file__, '..'))))
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_filters',
'rest_framework',
'corsheaders'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'ffplayout.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'ffplayout.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
}
]
# simple JWT auth settings
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(hours=7),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
}
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# dynamic app loader
APPS_DIR = os.path.join(BASE_DIR, 'apps/')
for dir in os.listdir(APPS_DIR):
if os.path.isdir(os.path.join(APPS_DIR, dir)):
app_name = 'apps.{}'.format(dir)
if app_name not in INSTALLED_APPS:
# add app to installed apps
INSTALLED_APPS += (app_name, )
if os.path.isfile(os.path.join(APPS_DIR, dir, 'settings.py')):
db = locate('{}.settings.DATABASES'.format(app_name))
for key in db:
if key not in DATABASES:
# add app db to DATABASES
DATABASES.update({key: db[key]})
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
# ffmpeg filter node, needs to be edit only when the filter chain changes
DRAW_TEXT_NODE = 'Parsed_drawtext_2'
# zmq settings
REQUEST_TIMEOUT = 1000

View File

@ -1,26 +0,0 @@
from ffplayout.settings.common import *
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'dhgfk(gl&16krnt_7*dp(9b3w*ft%nbsg-h2)&ihbte4le#o4f'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
# REST API
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
'http://localhost:8000',
'http://ffplayout.local'
)

View File

@ -1,25 +0,0 @@
from ffplayout.settings.common import *
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '---a-very-important-secret-key:-generate-it-new---'
DEBUG = False
ALLOWED_HOSTS = ['localhost']
# REST API
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
CORS_ORIGIN_WHITELIST = (
'http://ffplayout.local',
)

View File

@ -1,44 +0,0 @@
"""ffplayout URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
import os
from django.contrib import admin
from django.conf import settings
from django.urls import include, path
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include(
'rest_framework.urls', namespace='rest_framework')),
path('auth/token/', TokenObtainPairView.as_view(),
name='token_obtain_pair'),
path('auth/token/refresh/', TokenRefreshView.as_view(),
name='token_refresh')
]
# dynamic url loader
for dir in os.listdir(settings.APPS_DIR):
if os.path.isdir(os.path.join(settings.APPS_DIR, dir)):
app_name = 'apps.{}'.format(dir)
_path = path('api/', include(
'{}.urls'.format(app_name),
namespace='{}'.format(app_name.split('.')[1])))
if _path not in urlpatterns:
urlpatterns += (_path, )

View File

@ -1,17 +0,0 @@
"""
WSGI config for ffplayout project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE', 'ffplayout.settings.development')
application = get_wsgi_application()

View File

@ -1,90 +0,0 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# Mac OSX
.DS_Store
# Vim swap files
*.swp

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'ffplayout.settings.development')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -82,11 +82,19 @@ runInstall() {
echo ""
echo "------------------------------------------------------------------------------"
echo "ffplayout gui domain name (like: example.org)"
echo "ffplayout api domain name (like: api.example.org)"
echo "------------------------------------------------------------------------------"
echo ""
read -p "domain name :$ " domain
read -p "api domain name :$ " domainApi
echo ""
echo "------------------------------------------------------------------------------"
echo "ffplayout frontend domain name (like: www.example.org)"
echo "------------------------------------------------------------------------------"
echo ""
read -p "frontend domain name :$ " domainFrontend
fi
if [[ ! -d /usr/local/srs ]]; then
@ -499,15 +507,15 @@ EOF
deactivate
fi
if [[ ! -d "/var/www/ffplayout" ]]; then
if [[ ! -d "/var/www/ffplayout-api" ]]; then
echo ""
echo "------------------------------------------------------------------------------"
echo "install ffplayout gui"
echo "install ffplayout-api"
echo "------------------------------------------------------------------------------"
cd /var/www
git clone https://github.com/ffplayout/ffplayout-gui.git ffplayout
cd ffplayout
git clone https://github.com/ffplayout/ffplayout-api.git
cd ffplayout-api
virtualenv -p python3 venv
source ./venv/bin/activate
@ -536,36 +544,60 @@ EOF
sed -i "s/User=root/User=$serviceUser/g" /etc/systemd/system/ffplayout-api.service
sed -i "s/Group=root/Group=$serviceUser/g" /etc/systemd/system/ffplayout-api.service
sed -i "s/'localhost'/'localhost', \'$domain\'/g" /var/www/ffplayout/ffplayout/ffplayout/settings/production.py
sed -i "s/ffplayout\\.local/$domain\'\n \'https\\:\/\/$domain/g" /var/www/ffplayout/ffplayout/ffplayout/settings/production.py
sed -i "s/'localhost'/'localhost', \'$domainApi\'/g" /var/www/ffplayout/ffplayout/ffplayout/settings/production.py
sed -i "s/ffplayout\\.local/$domainApi\'\n \'https\\:\/\/$domainApi/g" /var/www/ffplayout/ffplayout/ffplayout/settings/production.py
systemctl enable ffplayout-api.service
if [[ $installNginx == 'y' ]]; then
cp docs/ffplayout.conf "$nginxConfig/"
cp docs/ffplayout-api.conf "$nginxConfig/"
origin=$(echo "$domain" | sed 's/\./\\\\./g')
origin=$(echo "$domainApi" | sed 's/\./\\\\./g')
sed -i "s/ffplayout.local/$domain/g" $nginxConfig/ffplayout.conf
sed -i "s/ffplayout\\\.local/$origin/g" $nginxConfig/ffplayout.conf
sed -i "s/ffplayout-api.local/$domainApi/g" $nginxConfig/ffplayout-api.conf
sed -i "s/ffplayout-api\\\.local/$origin/g" $nginxConfig/ffplayout-api.conf
if [[ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]]; then
ln -s $nginxConfig/ffplayout.conf /etc/nginx/sites-enabled/
ln -s $nginxConfig/ffplayout-api.conf /etc/nginx/sites-enabled/
fi
fi
cd /var/www/ffplayout/ffplayout/frontend
ln -s "$mediaPath" /var/www/ffplayout/ffplayout/frontend/static/
systemctl start ffplayout-api.service
fi
if [[ ! -d "/var/www/ffplayout-frontend" ]]; then
echo ""
echo "------------------------------------------------------------------------------"
echo "install ffplayout-frontend"
echo "------------------------------------------------------------------------------"
cd /var/www
git clone https://github.com/ffplayout/ffplayout-frontend.git
cd ffplayout-frontend
ln -s "$mediaPath" /var/www/ffplayout-frontend/static/
sudo -H -u $serviceUser bash -c 'npm install'
cat <<EOF > ".env"
BASE_URL='http://$domain'
BASE_URL='http://$domainFrontend'
API_URL='/'
EOF
sudo -H -u $serviceUser bash -c 'npm run build'
systemctl start ffplayout-api.service
if [[ $installNginx == 'y' ]]; then
cp docs/ffplayout-frontend.conf "$nginxConfig/"
origin=$(echo "$domainFrontend" | sed 's/\./\\\\./g')
sed -i "s/ffplayout-frontend.local/$domainFrontend/g" $nginxConfig/ffplayout-frontend.conf
sed -i "s/ffplayout-frontend\\\.local/$origin/g" $nginxConfig/ffplayout-frontend.conf
if [[ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]]; then
ln -s $nginxConfig/ffplayout-frontend.conf /etc/nginx/sites-enabled/
fi
fi
fi
if [[ $installNginx == 'y' ]]; then
@ -615,15 +647,28 @@ runUpdate() {
echo "------------------------------------------------------------------------------"
fi
if [[ -d "/var/www/ffplayout" ]]; then
cd "/var/www/ffplayout"
if [[ -d "/var/www/ffplayout-api" ]]; then
cd "/var/www/ffplayout-api"
git pull
source ./venv/bin/activate
pip install --upgrade -r requirements-base.txt
deactivate
cd "ffplayout/frontend"
echo ""
echo "------------------------------------------------------------------------------"
echo "updating ffplayout-api done..."
echo "------------------------------------------------------------------------------"
else
echo ""
echo "------------------------------------------------------------------------------"
echo "WARNING: no ffplayout-api found..."
echo "------------------------------------------------------------------------------"
fi
if [[ -d "/var/www/ffplayout-frontend" ]]; then
cd "/var/www/ffplayout-frontend"
git pull
rm -rf node_modules
sudo -H -u $serviceUser bash -c 'npm install'
@ -631,12 +676,12 @@ runUpdate() {
echo ""
echo "------------------------------------------------------------------------------"
echo "updating ffplayout-gui done..."
echo "updating ffplayout-frontend done..."
echo "------------------------------------------------------------------------------"
else
echo ""
echo "------------------------------------------------------------------------------"
echo "WARNING: no ffplayout-gui found..."
echo "WARNING: no ffplayout-frontend found..."
echo "------------------------------------------------------------------------------"
fi

View File

@ -1,12 +0,0 @@
Django<=3.1
django-filter
django-cors-headers
djangorestframework
djangorestframework-simplejwt
gevent
gunicorn
natsort
psutil
pymediainfo
pyyaml
zmq

View File

@ -1,42 +0,0 @@
asgiref==3.2.10
astroid==2.4.2
autopep8==1.5.3
Django==3.1
django-cors-headers==3.4.0
django-filter==2.3.0
djangorestframework==3.11.1
djangorestframework-simplejwt==4.4.0
flake8==3.8.3
gevent==20.6.2
greenlet==0.4.16
gunicorn==20.0.4
isort==4.3.21
jedi==0.17.2
lazy-object-proxy==1.4.3
mccabe==0.6.1
natsort==7.0.1
parso==0.7.1
pluggy==0.13.1
psutil==5.7.2
pycodestyle==2.6.0
pydocstyle==5.0.2
pyflakes==2.2.0
PyJWT==1.7.1
pylint==2.5.3
pymediainfo==4.2.1
python-jsonrpc-server==0.3.4
python-language-server==0.34.1
pytz==2020.1
PyYAML==5.3.1
pyzmq==19.0.1
rope==0.17.0
six==1.15.0
snowballstemmer==2.0.0
sqlparse==0.3.1
toml==0.10.1
ujson==1.35
wrapt==1.12.1
yapf==0.30.0
zmq==0.0.0
zope.event==4.4
zope.interface==5.1.0

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB