diff --git a/ffplayout/frontend/.editorconfig b/.editorconfig similarity index 100% rename from ffplayout/frontend/.editorconfig rename to .editorconfig diff --git a/ffplayout/frontend/.eslintrc.js b/.eslintrc.js similarity index 100% rename from ffplayout/frontend/.eslintrc.js rename to .eslintrc.js diff --git a/.gitignore b/.gitignore index 2c69fa6a..20505dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 33c95a6b..05a32d76 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/ffplayout/frontend/assets/README.md b/assets/README.md similarity index 100% rename from ffplayout/frontend/assets/README.md rename to assets/README.md diff --git a/ffplayout/frontend/assets/css/_bootswatch.scss b/assets/css/_bootswatch.scss similarity index 100% rename from ffplayout/frontend/assets/css/_bootswatch.scss rename to assets/css/_bootswatch.scss diff --git a/ffplayout/frontend/assets/css/_variables.scss b/assets/css/_variables.scss similarity index 100% rename from ffplayout/frontend/assets/css/_variables.scss rename to assets/css/_variables.scss diff --git a/ffplayout/frontend/assets/css/bootstrap.min.css b/assets/css/bootstrap.min.css similarity index 100% rename from ffplayout/frontend/assets/css/bootstrap.min.css rename to assets/css/bootstrap.min.css diff --git a/ffplayout/frontend/assets/fonts/DigitalNumbers-Regular.woff2 b/assets/fonts/DigitalNumbers-Regular.woff2 similarity index 100% rename from ffplayout/frontend/assets/fonts/DigitalNumbers-Regular.woff2 rename to assets/fonts/DigitalNumbers-Regular.woff2 diff --git a/ffplayout/frontend/assets/fonts/font-license.txt b/assets/fonts/font-license.txt similarity index 100% rename from ffplayout/frontend/assets/fonts/font-license.txt rename to assets/fonts/font-license.txt diff --git a/ffplayout/frontend/assets/scss/globals.scss b/assets/scss/globals.scss similarity index 100% rename from ffplayout/frontend/assets/scss/globals.scss rename to assets/scss/globals.scss diff --git a/ffplayout/frontend/components/Menu.vue b/components/Menu.vue similarity index 100% rename from ffplayout/frontend/components/Menu.vue rename to components/Menu.vue diff --git a/ffplayout/frontend/components/README.md b/components/README.md similarity index 100% rename from ffplayout/frontend/components/README.md rename to components/README.md diff --git a/ffplayout/frontend/components/VideoPlayer.vue b/components/VideoPlayer.vue similarity index 100% rename from ffplayout/frontend/components/VideoPlayer.vue rename to components/VideoPlayer.vue diff --git a/ffplayout/frontend/README.md b/docs/README.md similarity index 100% rename from ffplayout/frontend/README.md rename to docs/README.md diff --git a/assets/config-gui.png b/docs/assets/config-gui.png similarity index 100% rename from assets/config-gui.png rename to docs/assets/config-gui.png diff --git a/assets/control.png b/docs/assets/control.png similarity index 100% rename from assets/control.png rename to docs/assets/control.png diff --git a/assets/landing-page.png b/docs/assets/landing-page.png similarity index 100% rename from assets/landing-page.png rename to docs/assets/landing-page.png diff --git a/assets/logging.png b/docs/assets/logging.png similarity index 100% rename from assets/logging.png rename to docs/assets/logging.png diff --git a/assets/login.png b/docs/assets/login.png similarity index 100% rename from assets/login.png rename to docs/assets/login.png diff --git a/assets/media-upload.png b/docs/assets/media-upload.png similarity index 100% rename from assets/media-upload.png rename to docs/assets/media-upload.png diff --git a/assets/media.png b/docs/assets/media.png similarity index 100% rename from assets/media.png rename to docs/assets/media.png diff --git a/assets/message.png b/docs/assets/message.png similarity index 100% rename from assets/message.png rename to docs/assets/message.png diff --git a/docs/db_data.json b/docs/db_data.json deleted file mode 100644 index 325cfcea..00000000 --- a/docs/db_data.json +++ /dev/null @@ -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" - } -}] diff --git a/docs/developer-info.md b/docs/developer-info.md deleted file mode 100644 index 6cc8338a..00000000 --- a/docs/developer-info.md +++ /dev/null @@ -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'), - } -} -``` diff --git a/docs/ffplayout-api.service b/docs/ffplayout-api.service deleted file mode 100644 index 015fca80..00000000 --- a/docs/ffplayout-api.service +++ /dev/null @@ -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 diff --git a/docs/ffplayout-frontend.conf b/docs/ffplayout-frontend.conf new file mode 100644 index 00000000..4fdb60ed --- /dev/null +++ b/docs/ffplayout-frontend.conf @@ -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; +} diff --git a/docs/ffplayout.conf b/docs/ffplayout.conf deleted file mode 100644 index c947d42b..00000000 --- a/docs/ffplayout.conf +++ /dev/null @@ -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; -} diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index 8ef5b207..00000000 --- a/docs/install.md +++ /dev/null @@ -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 -``` diff --git a/docs/send_playlist.py b/docs/send_playlist.py deleted file mode 100755 index 24960f6a..00000000 --- a/docs/send_playlist.py +++ /dev/null @@ -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']) diff --git a/ffplayout/apps/api_player/__init__.py b/ffplayout/apps/api_player/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ffplayout/apps/api_player/admin.py b/ffplayout/apps/api_player/admin.py deleted file mode 100644 index 707b4e99..00000000 --- a/ffplayout/apps/api_player/admin.py +++ /dev/null @@ -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) diff --git a/ffplayout/apps/api_player/apps.py b/ffplayout/apps/api_player/apps.py deleted file mode 100644 index 43fc82da..00000000 --- a/ffplayout/apps/api_player/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ApiPlayerConfig(AppConfig): - name = 'api_player' diff --git a/ffplayout/apps/api_player/migrations/__init__.py b/ffplayout/apps/api_player/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ffplayout/apps/api_player/models.py b/ffplayout/apps/api_player/models.py deleted file mode 100644 index 1cf6970f..00000000 --- a/ffplayout/apps/api_player/models.py +++ /dev/null @@ -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) diff --git a/ffplayout/apps/api_player/serializers.py b/ffplayout/apps/api_player/serializers.py deleted file mode 100644 index 0c21e7ae..00000000 --- a/ffplayout/apps/api_player/serializers.py +++ /dev/null @@ -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__' diff --git a/ffplayout/apps/api_player/settings.py b/ffplayout/apps/api_player/settings.py deleted file mode 100644 index 26573881..00000000 --- a/ffplayout/apps/api_player/settings.py +++ /dev/null @@ -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'), - } -} diff --git a/ffplayout/apps/api_player/tests.py b/ffplayout/apps/api_player/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/ffplayout/apps/api_player/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/ffplayout/apps/api_player/urls.py b/ffplayout/apps/api_player/urls.py deleted file mode 100644 index f7a786b5..00000000 --- a/ffplayout/apps/api_player/urls.py +++ /dev/null @@ -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[^/]+)$', - 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()), -] diff --git a/ffplayout/apps/api_player/utils.py b/ffplayout/apps/api_player/utils.py deleted file mode 100644 index 6149a0fb..00000000 --- a/ffplayout/apps/api_player/utils.py +++ /dev/null @@ -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'])] diff --git a/ffplayout/apps/api_player/views.py b/ffplayout/apps/api_player/views.py deleted file mode 100644 index 9b002068..00000000 --- a/ffplayout/apps/api_player/views.py +++ /dev/null @@ -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) diff --git a/ffplayout/ffplayout/__init__.py b/ffplayout/ffplayout/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ffplayout/ffplayout/asgi.py b/ffplayout/ffplayout/asgi.py deleted file mode 100644 index b3fde812..00000000 --- a/ffplayout/ffplayout/asgi.py +++ /dev/null @@ -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() diff --git a/ffplayout/ffplayout/settings/__init__.py b/ffplayout/ffplayout/settings/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/ffplayout/ffplayout/settings/common.py b/ffplayout/ffplayout/settings/common.py deleted file mode 100644 index c25515c5..00000000 --- a/ffplayout/ffplayout/settings/common.py +++ /dev/null @@ -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 diff --git a/ffplayout/ffplayout/settings/development.py b/ffplayout/ffplayout/settings/development.py deleted file mode 100644 index 024b454b..00000000 --- a/ffplayout/ffplayout/settings/development.py +++ /dev/null @@ -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' -) diff --git a/ffplayout/ffplayout/settings/production.py b/ffplayout/ffplayout/settings/production.py deleted file mode 100644 index 3d7d3e85..00000000 --- a/ffplayout/ffplayout/settings/production.py +++ /dev/null @@ -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', -) diff --git a/ffplayout/ffplayout/urls.py b/ffplayout/ffplayout/urls.py deleted file mode 100644 index b56c566c..00000000 --- a/ffplayout/ffplayout/urls.py +++ /dev/null @@ -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, ) diff --git a/ffplayout/ffplayout/wsgi.py b/ffplayout/ffplayout/wsgi.py deleted file mode 100644 index be3474a1..00000000 --- a/ffplayout/ffplayout/wsgi.py +++ /dev/null @@ -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() diff --git a/ffplayout/frontend/.gitignore b/ffplayout/frontend/.gitignore deleted file mode 100644 index 20505dc3..00000000 --- a/ffplayout/frontend/.gitignore +++ /dev/null @@ -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 diff --git a/ffplayout/manage.py b/ffplayout/manage.py deleted file mode 100755 index 35483ac6..00000000 --- a/ffplayout/manage.py +++ /dev/null @@ -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() diff --git a/install.sh b/install.sh index c154941f..637b68ed 100755 --- a/install.sh +++ b/install.sh @@ -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 < ".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 diff --git a/ffplayout/frontend/layouts/README.md b/layouts/README.md similarity index 100% rename from ffplayout/frontend/layouts/README.md rename to layouts/README.md diff --git a/ffplayout/frontend/layouts/default.vue b/layouts/default.vue similarity index 100% rename from ffplayout/frontend/layouts/default.vue rename to layouts/default.vue diff --git a/ffplayout/frontend/middleware/README.md b/middleware/README.md similarity index 100% rename from ffplayout/frontend/middleware/README.md rename to middleware/README.md diff --git a/ffplayout/frontend/middleware/auth.js b/middleware/auth.js similarity index 100% rename from ffplayout/frontend/middleware/auth.js rename to middleware/auth.js diff --git a/ffplayout/frontend/nuxt.config.js b/nuxt.config.js similarity index 100% rename from ffplayout/frontend/nuxt.config.js rename to nuxt.config.js diff --git a/ffplayout/frontend/package-lock.json b/package-lock.json similarity index 100% rename from ffplayout/frontend/package-lock.json rename to package-lock.json diff --git a/ffplayout/frontend/package.json b/package.json similarity index 100% rename from ffplayout/frontend/package.json rename to package.json diff --git a/ffplayout/frontend/pages/README.md b/pages/README.md similarity index 100% rename from ffplayout/frontend/pages/README.md rename to pages/README.md diff --git a/ffplayout/frontend/pages/configure.vue b/pages/configure.vue similarity index 100% rename from ffplayout/frontend/pages/configure.vue rename to pages/configure.vue diff --git a/ffplayout/frontend/pages/index.vue b/pages/index.vue similarity index 100% rename from ffplayout/frontend/pages/index.vue rename to pages/index.vue diff --git a/ffplayout/frontend/pages/logging.vue b/pages/logging.vue similarity index 100% rename from ffplayout/frontend/pages/logging.vue rename to pages/logging.vue diff --git a/ffplayout/frontend/pages/media.vue b/pages/media.vue similarity index 100% rename from ffplayout/frontend/pages/media.vue rename to pages/media.vue diff --git a/ffplayout/frontend/pages/message.vue b/pages/message.vue similarity index 100% rename from ffplayout/frontend/pages/message.vue rename to pages/message.vue diff --git a/ffplayout/frontend/pages/player.vue b/pages/player.vue similarity index 100% rename from ffplayout/frontend/pages/player.vue rename to pages/player.vue diff --git a/ffplayout/frontend/plugins/README.md b/plugins/README.md similarity index 100% rename from ffplayout/frontend/plugins/README.md rename to plugins/README.md diff --git a/ffplayout/frontend/plugins/axios.js b/plugins/axios.js similarity index 100% rename from ffplayout/frontend/plugins/axios.js rename to plugins/axios.js diff --git a/ffplayout/frontend/plugins/draggable.js b/plugins/draggable.js similarity index 100% rename from ffplayout/frontend/plugins/draggable.js rename to plugins/draggable.js diff --git a/ffplayout/frontend/plugins/filters.js b/plugins/filters.js similarity index 100% rename from ffplayout/frontend/plugins/filters.js rename to plugins/filters.js diff --git a/ffplayout/frontend/plugins/helpers.js b/plugins/helpers.js similarity index 100% rename from ffplayout/frontend/plugins/helpers.js rename to plugins/helpers.js diff --git a/ffplayout/frontend/plugins/loading.js b/plugins/loading.js similarity index 100% rename from ffplayout/frontend/plugins/loading.js rename to plugins/loading.js diff --git a/ffplayout/frontend/plugins/nuxt-client-init.js b/plugins/nuxt-client-init.js similarity index 100% rename from ffplayout/frontend/plugins/nuxt-client-init.js rename to plugins/nuxt-client-init.js diff --git a/ffplayout/frontend/plugins/scrollbar.js b/plugins/scrollbar.js similarity index 100% rename from ffplayout/frontend/plugins/scrollbar.js rename to plugins/scrollbar.js diff --git a/ffplayout/frontend/plugins/splitpanes.js b/plugins/splitpanes.js similarity index 100% rename from ffplayout/frontend/plugins/splitpanes.js rename to plugins/splitpanes.js diff --git a/ffplayout/frontend/plugins/video.js b/plugins/video.js similarity index 100% rename from ffplayout/frontend/plugins/video.js rename to plugins/video.js diff --git a/requirements-base.txt b/requirements-base.txt deleted file mode 100644 index df75282e..00000000 --- a/requirements-base.txt +++ /dev/null @@ -1,12 +0,0 @@ -Django<=3.1 -django-filter -django-cors-headers -djangorestframework -djangorestframework-simplejwt -gevent -gunicorn -natsort -psutil -pymediainfo -pyyaml -zmq diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 61228d66..00000000 --- a/requirements.txt +++ /dev/null @@ -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 diff --git a/ffplayout/frontend/static/README.md b/static/README.md similarity index 100% rename from ffplayout/frontend/static/README.md rename to static/README.md diff --git a/ffplayout/frontend/static/favicon.ico b/static/favicon.ico similarity index 100% rename from ffplayout/frontend/static/favicon.ico rename to static/favicon.ico diff --git a/ffplayout/frontend/static/robots.txt b/static/robots.txt similarity index 100% rename from ffplayout/frontend/static/robots.txt rename to static/robots.txt diff --git a/ffplayout/frontend/store/README.md b/store/README.md similarity index 100% rename from ffplayout/frontend/store/README.md rename to store/README.md diff --git a/ffplayout/frontend/store/auth.js b/store/auth.js similarity index 100% rename from ffplayout/frontend/store/auth.js rename to store/auth.js diff --git a/ffplayout/frontend/store/config.js b/store/config.js similarity index 100% rename from ffplayout/frontend/store/config.js rename to store/config.js diff --git a/ffplayout/frontend/store/index.js b/store/index.js similarity index 100% rename from ffplayout/frontend/store/index.js rename to store/index.js diff --git a/ffplayout/frontend/store/media.js b/store/media.js similarity index 100% rename from ffplayout/frontend/store/media.js rename to store/media.js diff --git a/ffplayout/frontend/store/playlist.js b/store/playlist.js similarity index 100% rename from ffplayout/frontend/store/playlist.js rename to store/playlist.js