done
This commit is contained in:
parent
70efdf877e
commit
bdb4f1d7bd
@ -5,11 +5,7 @@ SECRET_KEY=ANY_SECRET_KEY_HERE
|
|||||||
# Application Server
|
# Application Server
|
||||||
PORT=5000
|
PORT=5000
|
||||||
BIND=0.0.0.0
|
BIND=0.0.0.0
|
||||||
|
EXTERNAL_STORAGE_MOUNTPOINT=%application_dir%/var/run/storage
|
||||||
# HTTP External Storage Server
|
|
||||||
PORT_HTTP_EXTERNAL_STORAGE=5001
|
|
||||||
BIND_HTTP_EXTERNAL_STORAGE=0.0.0.0
|
|
||||||
CHROOT_HTTP_EXTERNAL_STORAGE=%application_dir%/var/run/storage
|
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
DEMO=false
|
DEMO=false
|
||||||
|
|||||||
@ -10,10 +10,8 @@ services:
|
|||||||
- DEBUG=false
|
- DEBUG=false
|
||||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||||
- PORT=5000
|
- PORT=5000
|
||||||
- PORT_HTTP_EXTERNAL_STORAGE=5001
|
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- ./:/app/
|
- ./:/app/
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
- 5001:5001
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ services:
|
|||||||
- DEBUG=false
|
- DEBUG=false
|
||||||
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
- SECRET_KEY=ANY_SECRET_KEY_HERE
|
||||||
- PORT=5000
|
- PORT=5000
|
||||||
- PORT_HTTP_EXTERNAL_STORAGE=5001
|
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- ./data/db:/app/data/db
|
- ./data/db:/app/data/db
|
||||||
@ -16,4 +15,3 @@ services:
|
|||||||
- ./var/run/storage:/app/var/run/storage
|
- ./var/run/storage:/app/var/run/storage
|
||||||
ports:
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
- 5001:5001
|
|
||||||
|
|||||||
@ -184,12 +184,12 @@
|
|||||||
"settings_variable_desc_auth_enabled": "Enable auth management",
|
"settings_variable_desc_auth_enabled": "Enable auth management",
|
||||||
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be %username%/%password%",
|
"settings_variable_desc_edition_auth_enabled": "Default user credentials will be %username%/%password%",
|
||||||
"settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
"settings_variable_desc_external_url": "External url (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||||
"settings_variable_desc_external_storage_url": "External url for external storage (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
|
||||||
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)",
|
"settings_variable_desc_slide_upload_limit": "Slide upload limit (in megabytes)",
|
||||||
"settings_variable_desc_dark_mode": "Dark mode",
|
"settings_variable_desc_dark_mode": "Dark mode",
|
||||||
"settings_variable_desc_intro_slide_duration": "Introduction slide duration (in seconds)",
|
"settings_variable_desc_intro_slide_duration": "Introduction slide duration (in seconds)",
|
||||||
"settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide",
|
"settings_variable_desc_default_slide_time_with_seconds": "Show the seconds on the clock in the introduction slide",
|
||||||
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
|
"settings_variable_desc_polling_interval": "Refresh interval applied for settings to the player (in seconds)",
|
||||||
|
"settings_variable_desc_player_content_cache": "Enable cache",
|
||||||
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
|
"settings_variable_desc_slide_animation_enabled": "Enable animation effect between slides",
|
||||||
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
|
"settings_variable_desc_slide_animation_entrance_effect": "Slide animation entrance effect",
|
||||||
"settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)",
|
"settings_variable_desc_slide_animation_exit_effect": "Slide animation exit effect (generally better off without it)",
|
||||||
|
|||||||
@ -185,12 +185,12 @@
|
|||||||
"settings_variable_desc_auth_enabled": "Habilitar gestión de autenticación",
|
"settings_variable_desc_auth_enabled": "Habilitar gestión de autenticación",
|
||||||
"settings_variable_desc_edition_auth_enabled": "Las credenciales predeterminadas del usuario serán %username%/%password%",
|
"settings_variable_desc_edition_auth_enabled": "Las credenciales predeterminadas del usuario serán %username%/%password%",
|
||||||
"settings_variable_desc_external_url": "URL externa (ej.: https://studio-01.company.com o http://10.10.3.100)",
|
"settings_variable_desc_external_url": "URL externa (ej.: https://studio-01.company.com o http://10.10.3.100)",
|
||||||
"settings_variable_desc_external_storage_url": "URL externa para almacenamiento externo(ej.: https://studio-01.company.com o http://10.10.3.100)",
|
|
||||||
"settings_variable_desc_slide_upload_limit": "Límite de carga de diapositivas (en megabytes)",
|
"settings_variable_desc_slide_upload_limit": "Límite de carga de diapositivas (en megabytes)",
|
||||||
"settings_variable_desc_dark_mode": "Modo oscuro",
|
"settings_variable_desc_dark_mode": "Modo oscuro",
|
||||||
"settings_variable_desc_intro_slide_duration": "Duración de la diapositiva de introducción (en segundos)",
|
"settings_variable_desc_intro_slide_duration": "Duración de la diapositiva de introducción (en segundos)",
|
||||||
"settings_variable_desc_default_slide_time_with_seconds": "Mostrar los segundos en el reloj de la diapositiva de introducción",
|
"settings_variable_desc_default_slide_time_with_seconds": "Mostrar los segundos en el reloj de la diapositiva de introducción",
|
||||||
"settings_variable_desc_polling_interval": "Intervalo de actualización aplicado para configuraciones del reproductor (en segundos)",
|
"settings_variable_desc_polling_interval": "Intervalo de actualización aplicado para configuraciones del reproductor (en segundos)",
|
||||||
|
"settings_variable_desc_player_content_cache": "Habilitar la caché",
|
||||||
"settings_variable_desc_slide_animation_enabled": "Habilitar efecto de animación entre diapositivas",
|
"settings_variable_desc_slide_animation_enabled": "Habilitar efecto de animación entre diapositivas",
|
||||||
"settings_variable_desc_slide_animation_entrance_effect": "Efecto de entrada de animación de diapositiva",
|
"settings_variable_desc_slide_animation_entrance_effect": "Efecto de entrada de animación de diapositiva",
|
||||||
"settings_variable_desc_slide_animation_exit_effect": "Efecto de salida de animación de diapositiva (generalmente mejor sin él)",
|
"settings_variable_desc_slide_animation_exit_effect": "Efecto de salida de animación de diapositiva (generalmente mejor sin él)",
|
||||||
|
|||||||
@ -186,12 +186,12 @@
|
|||||||
"settings_variable_desc_auth_enabled": "Activer la gestion de l'authentification",
|
"settings_variable_desc_auth_enabled": "Activer la gestion de l'authentification",
|
||||||
"settings_variable_desc_edition_auth_enabled": "Les identifiants de l'utilisateur par défaut seront %username%/%password%",
|
"settings_variable_desc_edition_auth_enabled": "Les identifiants de l'utilisateur par défaut seront %username%/%password%",
|
||||||
"settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
"settings_variable_desc_external_url": "URL externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
||||||
"settings_variable_desc_external_storage_url": "URL externe pour le stockage externe (i.e: https://studio-01.company.com or http://10.10.3.100)",
|
|
||||||
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)",
|
"settings_variable_desc_slide_upload_limit": "Limite d'upload du fichier d'une slide (en mégaoctets)",
|
||||||
"settings_variable_desc_dark_mode": "Mdoe sombre",
|
"settings_variable_desc_dark_mode": "Mdoe sombre",
|
||||||
"settings_variable_desc_intro_slide_duration": "Durée de la slide d'introduction (en secondes)",
|
"settings_variable_desc_intro_slide_duration": "Durée de la slide d'introduction (en secondes)",
|
||||||
"settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction",
|
"settings_variable_desc_default_slide_time_with_seconds": "Afficher les secondes de l'horloge de la slide d'introduction",
|
||||||
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
|
"settings_variable_desc_polling_interval": "Intervalle de rafraîchissement des paramètres à appliquer au lecteur (en secondes)",
|
||||||
|
"settings_variable_desc_player_content_cache": "Activer le cache",
|
||||||
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
|
"settings_variable_desc_slide_animation_enabled": "Activer les effets d'animation entre les slides",
|
||||||
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
|
"settings_variable_desc_slide_animation_entrance_effect": "Effet d'animation d'arrivée de la slide",
|
||||||
"settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)",
|
"settings_variable_desc_slide_animation_exit_effect": "Effet d'animation de sortie de la slide (généralement mieux sans)",
|
||||||
|
|||||||
@ -185,12 +185,12 @@
|
|||||||
"settings_variable_desc_auth_enabled": "Abilita la gestione autenticazione",
|
"settings_variable_desc_auth_enabled": "Abilita la gestione autenticazione",
|
||||||
"settings_variable_desc_edition_auth_enabled": "Le credenziali utente predefinite sono %username%/%password%",
|
"settings_variable_desc_edition_auth_enabled": "Le credenziali utente predefinite sono %username%/%password%",
|
||||||
"settings_variable_desc_external_url": "Url esterno (esempio: https://studio-01.company.com or http://10.10.3.100)",
|
"settings_variable_desc_external_url": "Url esterno (esempio: https://studio-01.company.com or http://10.10.3.100)",
|
||||||
"settings_variable_desc_external_storage_url": "Url esterno per l'archiviazione esterna (esempio: https://studio-01.company.com or http://10.10.3.100)",
|
|
||||||
"settings_variable_desc_slide_upload_limit": "Limite upload slide (in megabytes)",
|
"settings_variable_desc_slide_upload_limit": "Limite upload slide (in megabytes)",
|
||||||
"settings_variable_desc_dark_mode": "Modalità scura",
|
"settings_variable_desc_dark_mode": "Modalità scura",
|
||||||
"settings_variable_desc_intro_slide_duration": "Durata introduzione slide (in secondi)",
|
"settings_variable_desc_intro_slide_duration": "Durata introduzione slide (in secondi)",
|
||||||
"settings_variable_desc_default_slide_time_with_seconds": "Mostra secondi introduzione slide",
|
"settings_variable_desc_default_slide_time_with_seconds": "Mostra secondi introduzione slide",
|
||||||
"settings_variable_desc_polling_interval": "Intervallo di aggiornamento applicato per le impostazioni del monitor (in secondi)",
|
"settings_variable_desc_polling_interval": "Intervallo di aggiornamento applicato per le impostazioni del monitor (in secondi)",
|
||||||
|
"settings_variable_desc_player_content_cache": "Abilita la cache",
|
||||||
"settings_variable_desc_slide_animation_enabled": "Abilita l'effetto di animazione tra le diapositive",
|
"settings_variable_desc_slide_animation_enabled": "Abilita l'effetto di animazione tra le diapositive",
|
||||||
"settings_variable_desc_slide_animation_entrance_effect": "Effetto ingresso diapositiva",
|
"settings_variable_desc_slide_animation_entrance_effect": "Effetto ingresso diapositiva",
|
||||||
"settings_variable_desc_slide_animation_exit_effect": "Effetto di uscita della diapositiva (meglio senza)",
|
"settings_variable_desc_slide_animation_exit_effect": "Effetto di uscita della diapositiva (meglio senza)",
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import threading
|
|||||||
from src.service.ModelStore import ModelStore
|
from src.service.ModelStore import ModelStore
|
||||||
from src.service.PluginStore import PluginStore
|
from src.service.PluginStore import PluginStore
|
||||||
from src.service.TemplateRenderer import TemplateRenderer
|
from src.service.TemplateRenderer import TemplateRenderer
|
||||||
from src.service.ExternalStorageServer import ExternalStorageServer
|
|
||||||
from src.service.WebServer import WebServer
|
from src.service.WebServer import WebServer
|
||||||
from src.model.enum.HookType import HookType
|
from src.model.enum.HookType import HookType
|
||||||
|
|
||||||
@ -19,7 +18,6 @@ class Application:
|
|||||||
self._model_store = ModelStore(self, self.get_plugins)
|
self._model_store = ModelStore(self, self.get_plugins)
|
||||||
self._template_renderer = TemplateRenderer(kernel=self, model_store=self._model_store, render_hook=self.render_hook)
|
self._template_renderer = TemplateRenderer(kernel=self, model_store=self._model_store, render_hook=self.render_hook)
|
||||||
self._web_server = WebServer(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer)
|
self._web_server = WebServer(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer)
|
||||||
self._external_storage_server = ExternalStorageServer(kernel=self, model_store=self._model_store)
|
|
||||||
|
|
||||||
logging.info("[{}] Starting application v{}...".format(self.get_name(), self.get_version()))
|
logging.info("[{}] Starting application v{}...".format(self.get_name(), self.get_version()))
|
||||||
self._plugin_store = PluginStore(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
|
self._plugin_store = PluginStore(kernel=self, model_store=self._model_store, template_renderer=self._template_renderer, web_server=self._web_server)
|
||||||
@ -31,7 +29,6 @@ class Application:
|
|||||||
if variable:
|
if variable:
|
||||||
self._model_store.variable().update_by_name(variable.name, variable.as_int() + 1)
|
self._model_store.variable().update_by_name(variable.name, variable.as_int() + 1)
|
||||||
|
|
||||||
self._external_storage_server.run()
|
|
||||||
self._web_server.run()
|
self._web_server.run()
|
||||||
|
|
||||||
def signal_handler(self, signal, frame) -> None:
|
def signal_handler(self, signal, frame) -> None:
|
||||||
@ -62,7 +59,3 @@ class Application:
|
|||||||
self._model_store.lang().set_lang(lang)
|
self._model_store.lang().set_lang(lang)
|
||||||
self._model_store.variable().reload()
|
self._model_store.variable().reload()
|
||||||
self._plugin_store.reload_lang()
|
self._plugin_store.reload_lang()
|
||||||
|
|
||||||
@property
|
|
||||||
def external_storage_server(self):
|
|
||||||
return self._external_storage_server
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ from src.model.entity.Content import Content
|
|||||||
from src.model.enum.ContentType import ContentType
|
from src.model.enum.ContentType import ContentType
|
||||||
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH
|
from src.model.enum.FolderEntity import FolderEntity, FOLDER_ROOT_PATH
|
||||||
from src.interface.ObController import ObController
|
from src.interface.ObController import ObController
|
||||||
from src.service.ExternalStorageServer import ExternalStorageServer
|
|
||||||
from src.util.utils import str_to_enum, get_optional_string
|
from src.util.utils import str_to_enum, get_optional_string
|
||||||
from src.util.UtilFile import randomize_filename
|
from src.util.UtilFile import randomize_filename
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ class ContentController(ObController):
|
|||||||
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),
|
working_folder_children=self._model_store.folder().get_children(folder=working_folder, entity=FolderEntity.CONTENT, sort='created_at', ascending=False),
|
||||||
enum_content_type=ContentType,
|
enum_content_type=ContentType,
|
||||||
enum_folder_entity=FolderEntity,
|
enum_folder_entity=FolderEntity,
|
||||||
chroot_http_external_storage=self.get_external_storage_server().get_directory(),
|
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def slideshow_content_add(self):
|
def slideshow_content_add(self):
|
||||||
@ -119,7 +118,7 @@ class ContentController(ObController):
|
|||||||
working_folder_path=working_folder_path,
|
working_folder_path=working_folder_path,
|
||||||
working_folder=working_folder,
|
working_folder=working_folder,
|
||||||
enum_content_type=ContentType,
|
enum_content_type=ContentType,
|
||||||
chroot_http_external_storage=self.get_external_storage_server().get_directory(),
|
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint')
|
||||||
)
|
)
|
||||||
|
|
||||||
def slideshow_content_save(self, content_id: int = 0):
|
def slideshow_content_save(self, content_id: int = 0):
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
|
import os
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort
|
from flask import Flask, render_template, redirect, request, url_for, send_from_directory, jsonify, abort, send_file, Response
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from src.model.entity.Slide import Slide
|
from src.model.entity.Slide import Slide
|
||||||
|
from src.model.entity.Content import Content
|
||||||
from src.model.enum.ContentType import ContentType
|
from src.model.enum.ContentType import ContentType
|
||||||
from src.exceptions.NoFallbackPlaylistException import NoFallbackPlaylistException
|
from src.exceptions.NoFallbackPlaylistException import NoFallbackPlaylistException
|
||||||
from src.service.ModelStore import ModelStore
|
from src.service.ModelStore import ModelStore
|
||||||
from src.interface.ObController import ObController
|
from src.interface.ObController import ObController
|
||||||
from src.util.utils import get_safe_cron_descriptor, is_cron_in_datetime_moment, is_cron_in_week_moment, is_now_after_cron_date_time_moment, is_now_after_cron_week_moment
|
from src.util.utils import get_safe_cron_descriptor, is_cron_in_datetime_moment, is_cron_in_week_moment, is_now_after_cron_date_time_moment, is_now_after_cron_week_moment, decode_uri_component
|
||||||
from src.util.UtilNetwork import get_safe_remote_addr, get_network_interfaces
|
from src.util.UtilNetwork import get_safe_remote_addr, get_network_interfaces
|
||||||
from src.model.enum.AnimationSpeed import animation_speed_duration
|
from src.model.enum.AnimationSpeed import animation_speed_duration
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ class PlayerController(ObController):
|
|||||||
self._app.add_url_rule('/player/default', 'player_default', self.player_default, methods=['GET'])
|
self._app.add_url_rule('/player/default', 'player_default', self.player_default, methods=['GET'])
|
||||||
self._app.add_url_rule('/player/playlist', 'player_playlist', self.player_playlist, methods=['GET'])
|
self._app.add_url_rule('/player/playlist', 'player_playlist', self.player_playlist, methods=['GET'])
|
||||||
self._app.add_url_rule('/player/playlist/use/<playlist_slug_or_id>', 'player_playlist_use', self.player_playlist, methods=['GET'])
|
self._app.add_url_rule('/player/playlist/use/<playlist_slug_or_id>', 'player_playlist_use', self.player_playlist, methods=['GET'])
|
||||||
|
self._app.add_url_rule('/serve/content/<content_type>/<content_id>/<content_location>', 'serve_content_file', self.serve_content_file, methods=['GET'])
|
||||||
|
|
||||||
def player(self, playlist_slug_or_id: str = ''):
|
def player(self, playlist_slug_or_id: str = ''):
|
||||||
preview_content_id = request.args.get('preview_content_id')
|
preview_content_id = request.args.get('preview_content_id')
|
||||||
@ -136,24 +139,26 @@ class PlayerController(ObController):
|
|||||||
|
|
||||||
content = contents[int(slide['content_id'])]
|
content = contents[int(slide['content_id'])]
|
||||||
slide['name'] = content.name
|
slide['name'] = content.name
|
||||||
slide['location'] = content.location
|
|
||||||
slide['type'] = content.type.value
|
slide['type'] = content.type.value
|
||||||
|
slide['location'] = self._model_store.content().resolve_content_location(content)
|
||||||
|
|
||||||
if slide['type'] == ContentType.EXTERNAL_STORAGE.value:
|
if slide['type'] == ContentType.EXTERNAL_STORAGE.value:
|
||||||
mount_point_dir = Path(self.get_external_storage_server().get_directory(), slide['location'])
|
mount_point_dir = Path(self._model_store.config().map().get('external_storage_mountpoint'), slide['location'])
|
||||||
if mount_point_dir.is_dir():
|
if mount_point_dir.is_dir():
|
||||||
for file in mount_point_dir.iterdir():
|
for file in mount_point_dir.iterdir():
|
||||||
if file.is_file() and not file.stem.startswith('.'):
|
if file.is_file() and not file.stem.startswith('.'):
|
||||||
|
virtual_content = Content(
|
||||||
|
name=file.stem,
|
||||||
|
location=self._model_store.content().resolve_content_location(Path(content.location, file.name)),
|
||||||
|
type=ContentType.guess_content_type_file(str(file.resolve())),
|
||||||
|
)
|
||||||
slide = dict(slide)
|
slide = dict(slide)
|
||||||
slide['id'] = hashlib.md5(str(file).encode('utf-8')).hexdigest()
|
slide['id'] = hashlib.md5(str(file).encode('utf-8')).hexdigest()
|
||||||
slide['position'] = position
|
slide['position'] = position
|
||||||
slide['type'] = ContentType.guess_content_type_file(str(file.resolve())).value
|
|
||||||
slide['name'] = file.stem
|
|
||||||
slide['delegate_duration'] = 1 if slide['type'] == ContentType.VIDEO.value else 0
|
slide['delegate_duration'] = 1 if slide['type'] == ContentType.VIDEO.value else 0
|
||||||
slide['location'] = "{}/{}".format(
|
slide['name'] = virtual_content.stem
|
||||||
self._model_store.content().resolve_content_location(content),
|
slide['type'] = virtual_content.type.value
|
||||||
file.name
|
slide['location'] = self._model_store.content().resolve_content_location(virtual_content)
|
||||||
)
|
|
||||||
self._check_slide_enablement(playlist_loop, playlist_notifications, slide)
|
self._check_slide_enablement(playlist_loop, playlist_notifications, slide)
|
||||||
position = position + 1
|
position = position + 1
|
||||||
else:
|
else:
|
||||||
@ -197,3 +202,39 @@ class PlayerController(ObController):
|
|||||||
return
|
return
|
||||||
|
|
||||||
loop.append(slide)
|
loop.append(slide)
|
||||||
|
|
||||||
|
def serve_content_file(self, content_location, content_type, content_id):
|
||||||
|
content = self._model_store.content().get(content_id)
|
||||||
|
|
||||||
|
if not content:
|
||||||
|
abort(404, 'Content not found')
|
||||||
|
|
||||||
|
content_location = decode_uri_component(content_location)
|
||||||
|
|
||||||
|
content_path = str(Path(self.get_application_dir(), content_location))
|
||||||
|
|
||||||
|
if content_type == ContentType.EXTERNAL_STORAGE.value:
|
||||||
|
content_path = str(Path(self._model_store.config().map().get('external_storage_mountpoint'), content_location))
|
||||||
|
|
||||||
|
if not os.path.exists(content_path) or '..' in content_path:
|
||||||
|
abort(404, 'Content not found')
|
||||||
|
|
||||||
|
if not self._model_store.variable().get_one_by_name('player_content_cache').as_bool():
|
||||||
|
response = send_file(content_path)
|
||||||
|
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
||||||
|
response.headers['Pragma'] = 'no-cache'
|
||||||
|
response.headers['Expires'] = '0'
|
||||||
|
return response
|
||||||
|
|
||||||
|
content_path_hash = hashlib.sha256(str(content_path).encode()).hexdigest()
|
||||||
|
etag = f'"{content_path_hash}-{content_id}-{os.path.getmtime(content_path)}"'
|
||||||
|
|
||||||
|
if_none_match = request.headers.get('If-None-Match')
|
||||||
|
if if_none_match == etag:
|
||||||
|
return Response(status=304)
|
||||||
|
|
||||||
|
response = send_file(content_path)
|
||||||
|
response.headers['Cache-Control'] = 'public, max-age=3153600000' # 100 years
|
||||||
|
response.headers['ETag'] = etag
|
||||||
|
|
||||||
|
return response
|
||||||
|
|||||||
@ -40,12 +40,12 @@ class ObController(abc.ABC):
|
|||||||
def reload_lang(self, lang: str):
|
def reload_lang(self, lang: str):
|
||||||
self._kernel.reload_lang(lang)
|
self._kernel.reload_lang(lang)
|
||||||
|
|
||||||
|
def get_application_dir(self):
|
||||||
|
return self._kernel.get_application_dir()
|
||||||
|
|
||||||
def t(self, token) -> Union[Dict, str]:
|
def t(self, token) -> Union[Dict, str]:
|
||||||
return self._model_store.lang().translate(token)
|
return self._model_store.lang().translate(token)
|
||||||
|
|
||||||
def get_external_storage_server(self):
|
|
||||||
return self._kernel.external_storage_server
|
|
||||||
|
|
||||||
def render_view(self, template_file: str, **parameters: dict) -> str:
|
def render_view(self, template_file: str, **parameters: dict) -> str:
|
||||||
return self._template_renderer.render_view(template_file, self.plugin(), **parameters)
|
return self._template_renderer.render_view(template_file, self.plugin(), **parameters)
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ class ConfigManager:
|
|||||||
|
|
||||||
APPLICATION_NAME = "Obscreen"
|
APPLICATION_NAME = "Obscreen"
|
||||||
DEFAULT_PORT = 5000
|
DEFAULT_PORT = 5000
|
||||||
DEFAULT_PORT_HTTP_EXTERNAL_STORAGE = 5001
|
|
||||||
VERSION_FILE = 'version.txt'
|
VERSION_FILE = 'version.txt'
|
||||||
|
|
||||||
def __init__(self, replacers: Dict):
|
def __init__(self, replacers: Dict):
|
||||||
@ -20,9 +19,7 @@ class ConfigManager:
|
|||||||
'application_name': self.APPLICATION_NAME,
|
'application_name': self.APPLICATION_NAME,
|
||||||
'version': None,
|
'version': None,
|
||||||
'demo': False,
|
'demo': False,
|
||||||
'port_http_external_storage': self.DEFAULT_PORT_HTTP_EXTERNAL_STORAGE,
|
'external_storage_mountpoint': '%application_dir%/var/run/storage',
|
||||||
'bind_http_external_storage': '0.0.0.0',
|
|
||||||
'chroot_http_external_storage': '%application_dir%/var/run/storage',
|
|
||||||
'port': self.DEFAULT_PORT,
|
'port': self.DEFAULT_PORT,
|
||||||
'bind': '0.0.0.0',
|
'bind': '0.0.0.0',
|
||||||
'debug': False,
|
'debug': False,
|
||||||
@ -56,9 +53,7 @@ class ConfigManager:
|
|||||||
parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level')
|
parser.add_argument('--log-level', '-ll', default=self._CONFIG['log_level'], help='Log Level')
|
||||||
parser.add_argument('--log-stdout', '-ls', default=self._CONFIG['log_stdout'], action='store_true', help='Log to standard output')
|
parser.add_argument('--log-stdout', '-ls', default=self._CONFIG['log_stdout'], action='store_true', help='Log to standard output')
|
||||||
parser.add_argument('--demo', '-o', default=self._CONFIG['demo'], help='Demo mode to showcase obscreen in a sandbox')
|
parser.add_argument('--demo', '-o', default=self._CONFIG['demo'], help='Demo mode to showcase obscreen in a sandbox')
|
||||||
parser.add_argument('--port-http-external-storage', '-bx', default=self._CONFIG['port_http_external_storage'], help='Port of http server serving external storage')
|
parser.add_argument('--external-storage-mountpoint', '-e', default=self._CONFIG['external_storage_mountpoint'], help='Mountpoint directory of external storage')
|
||||||
parser.add_argument('--bind-http-external-storage', '-px', default=self._CONFIG['bind_http_external_storage'], help='Bind address of http server serving external storage')
|
|
||||||
parser.add_argument('--chroot-http-external-storage', '-cx', default=self._CONFIG['chroot_http_external_storage'], help='Chroot directory of http server serving external storage')
|
|
||||||
parser.add_argument('--version', '-v', default=None, action='store_true', help='Get version number')
|
parser.add_argument('--version', '-v', default=None, action='store_true', help='Get version number')
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
@ -74,12 +69,8 @@ class ConfigManager:
|
|||||||
self._CONFIG['debug'] = args.debug
|
self._CONFIG['debug'] = args.debug
|
||||||
if args.demo:
|
if args.demo:
|
||||||
self._CONFIG['demo'] = args.demo
|
self._CONFIG['demo'] = args.demo
|
||||||
if args.port_http_external_storage:
|
if args.external_storage_mountpoint:
|
||||||
self._CONFIG['port_http_external_storage'] = args.port_http_external_storage
|
self._CONFIG['external_storage_mountpoint'] = args.external_storage_mountpoint
|
||||||
if args.bind_http_external_storage:
|
|
||||||
self._CONFIG['bind_http_external_storage'] = args.bind_http_external_storage
|
|
||||||
if args.chroot_http_external_storage:
|
|
||||||
self._CONFIG['chroot_http_external_storage'] = args.chroot_http_external_storage
|
|
||||||
if args.log_file:
|
if args.log_file:
|
||||||
self._CONFIG['log_file'] = args.log_file
|
self._CONFIG['log_file'] = args.log_file
|
||||||
if args.secret_key:
|
if args.secret_key:
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import os
|
|||||||
|
|
||||||
from typing import Dict, Optional, List, Tuple, Union
|
from typing import Dict, Optional, List, Tuple, Union
|
||||||
from werkzeug.datastructures import FileStorage
|
from werkzeug.datastructures import FileStorage
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
from src.model.entity.Content import Content
|
from src.model.entity.Content import Content
|
||||||
from src.model.entity.Playlist import Playlist
|
from src.model.entity.Playlist import Playlist
|
||||||
@ -16,6 +17,7 @@ from src.service.ModelManager import ModelManager
|
|||||||
from src.util.UtilFile import randomize_filename
|
from src.util.UtilFile import randomize_filename
|
||||||
from src.util.UtilNetwork import get_preferred_ip_address
|
from src.util.UtilNetwork import get_preferred_ip_address
|
||||||
from src.util.UtilVideo import mp4_duration_with_ffprobe
|
from src.util.UtilVideo import mp4_duration_with_ffprobe
|
||||||
|
from src.util.utils import encode_uri_component
|
||||||
|
|
||||||
|
|
||||||
class ContentManager(ModelManager):
|
class ContentManager(ModelManager):
|
||||||
@ -233,19 +235,18 @@ class ContentManager(ModelManager):
|
|||||||
var_external_url = self._variable_manager.get_one_by_name('external_url').as_string().strip().strip('/')
|
var_external_url = self._variable_manager.get_one_by_name('external_url').as_string().strip().strip('/')
|
||||||
location = content.location
|
location = content.location
|
||||||
|
|
||||||
if content.type == ContentType.EXTERNAL_STORAGE:
|
if content.type == ContentType.YOUTUBE:
|
||||||
var_external_storage_url = self._variable_manager.get_one_by_name('external_url_storage').as_string().strip().strip('/')
|
|
||||||
port_ex_st = self._config_manager.map().get('port_http_external_storage')
|
|
||||||
location = "{}/{}".format(
|
|
||||||
var_external_storage_url if var_external_storage_url else 'http://{}:{}'.format(get_preferred_ip_address(), port_ex_st),
|
|
||||||
content.location.strip('/')
|
|
||||||
)
|
|
||||||
elif content.type == ContentType.YOUTUBE:
|
|
||||||
location = "https://www.youtube.com/watch?v={}".format(content.location)
|
location = "https://www.youtube.com/watch?v={}".format(content.location)
|
||||||
elif len(var_external_url) > 0 and content.has_file():
|
elif content.has_file() or content.type == ContentType.EXTERNAL_STORAGE:
|
||||||
location = "{}/{}".format(var_external_url, content.location)
|
location = "{}/{}".format(
|
||||||
elif content.has_file():
|
var_external_url if len(var_external_url) > 0 else "",
|
||||||
location = "/{}".format(content.location)
|
url_for(
|
||||||
|
'serve_content_file',
|
||||||
|
content_location=encode_uri_component(content.location),
|
||||||
|
content_type=content.type.value,
|
||||||
|
content_id=content.id
|
||||||
|
).strip('/')
|
||||||
|
).strip('/')
|
||||||
elif content.type == ContentType.URL:
|
elif content.type == ContentType.URL:
|
||||||
location = 'http://' + content.location if not content.location.startswith('http') else content.location
|
location = 'http://' + content.location if not content.location.startswith('http') else content.location
|
||||||
|
|
||||||
|
|||||||
@ -214,6 +214,7 @@ class DatabaseManager:
|
|||||||
"DELETE FROM settings WHERE name = 'playlist_default_time_sync'",
|
"DELETE FROM settings WHERE name = 'playlist_default_time_sync'",
|
||||||
"DELETE FROM settings WHERE name = 'slide_animation_exit_effect'",
|
"DELETE FROM settings WHERE name = 'slide_animation_exit_effect'",
|
||||||
"DELETE FROM settings WHERE name = 'playlist_enabled'",
|
"DELETE FROM settings WHERE name = 'playlist_enabled'",
|
||||||
|
"DELETE FROM settings WHERE name = 'external_url_storage'",
|
||||||
"UPDATE fleet_player_group SET slug = id WHERE slug = '' or slug is null",
|
"UPDATE fleet_player_group SET slug = id WHERE slug = '' or slug is null",
|
||||||
"UPDATE content SET uuid = id WHERE uuid = '' or uuid is null",
|
"UPDATE content SET uuid = id WHERE uuid = '' or uuid is null",
|
||||||
"UPDATE slide SET uuid = id WHERE uuid = '' or uuid is null",
|
"UPDATE slide SET uuid = id WHERE uuid = '' or uuid is null",
|
||||||
|
|||||||
@ -117,7 +117,6 @@ class VariableManager:
|
|||||||
### General
|
### General
|
||||||
{"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": self.t(ApplicationLanguage), "refresh_player": False},
|
{"name": "lang", "section": self.t(VariableSection.GENERAL), "value": "en", "type": VariableType.SELECT_SINGLE, "editable": True, "description": self.t('settings_variable_desc_lang'), "selectables": self.t(ApplicationLanguage), "refresh_player": False},
|
||||||
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": False if demo else True, "description": self.t('settings_variable_desc_external_url'), "refresh_player": False},
|
{"name": "external_url", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": False if demo else True, "description": self.t('settings_variable_desc_external_url'), "refresh_player": False},
|
||||||
{"name": "external_url_storage", "section": self.t(VariableSection.GENERAL), "value": "", "type": VariableType.STRING, "editable": False if demo else True, "description": self.t('settings_variable_desc_external_storage_url'), "refresh_player": False},
|
|
||||||
{"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32, "unit": VariableUnit.MEGABYTE, "type": VariableType.INT, "editable": False if demo else True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False},
|
{"name": "slide_upload_limit", "section": self.t(VariableSection.GENERAL), "value": 32, "unit": VariableUnit.MEGABYTE, "type": VariableType.INT, "editable": False if demo else True, "description": self.t('settings_variable_desc_slide_upload_limit'), "refresh_player": False},
|
||||||
{"name": "dark_mode", "section": self.t(VariableSection.GENERAL), "value": True, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_dark_mode'), "refresh_player": False},
|
{"name": "dark_mode", "section": self.t(VariableSection.GENERAL), "value": True, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_dark_mode'), "refresh_player": False},
|
||||||
|
|
||||||
@ -125,6 +124,7 @@ class VariableManager:
|
|||||||
{"name": "intro_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_intro_slide_duration'), "refresh_player": False},
|
{"name": "intro_slide_duration", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 3, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_intro_slide_duration'), "refresh_player": False},
|
||||||
{"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False},
|
{"name": "default_slide_time_with_seconds", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_default_slide_time_with_seconds'), "refresh_player": False},
|
||||||
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
|
{"name": "polling_interval", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": 5, "unit": VariableUnit.SECOND, "type": VariableType.INT, "editable": True, "description": self.t('settings_variable_desc_polling_interval'), "refresh_player": True},
|
||||||
|
{"name": "player_content_cache", "section": self.t(VariableSection.PLAYER_OPTIONS), "value": True, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_player_content_cache'), "refresh_player": False},
|
||||||
|
|
||||||
### Player Animation
|
### Player Animation
|
||||||
{"name": "slide_animation_enabled", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled'), "refresh_player": True},
|
{"name": "slide_animation_enabled", "section": self.t(VariableSection.PLAYER_ANIMATION), "value": False, "type": VariableType.BOOL, "editable": True, "description": self.t('settings_variable_desc_slide_animation_enabled'), "refresh_player": True},
|
||||||
|
|||||||
@ -1,103 +0,0 @@
|
|||||||
import os
|
|
||||||
import psutil
|
|
||||||
import platform
|
|
||||||
import logging
|
|
||||||
import http.server
|
|
||||||
import socketserver
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from typing import Dict, Optional, List, Tuple, Union
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from src.service.ModelStore import ModelStore
|
|
||||||
from src.model.entity.ExternalStorage import ExternalStorage
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalStorageServer:
|
|
||||||
|
|
||||||
def __init__(self, kernel, model_store: ModelStore):
|
|
||||||
self._kernel = kernel
|
|
||||||
self._model_store = model_store
|
|
||||||
|
|
||||||
def get_directory(self):
|
|
||||||
return self._model_store.config().map().get('chroot_http_external_storage').replace(
|
|
||||||
'%application_dir%', self._kernel.get_application_dir()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_port(self) -> Optional[int]:
|
|
||||||
port = self._model_store.config().map().get('port_http_external_storage')
|
|
||||||
return int(port) if port else None
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
port = self.get_port()
|
|
||||||
bind = self._model_store.config().map().get('bind_http_external_storage')
|
|
||||||
if not port:
|
|
||||||
return
|
|
||||||
|
|
||||||
thread = threading.Thread(target=self._start, args=(self.get_directory(), port, bind))
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def _start(self, directory, port, bind):
|
|
||||||
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, directory=directory, **kwargs)
|
|
||||||
|
|
||||||
Handler = CustomHTTPRequestHandler
|
|
||||||
|
|
||||||
class ReusableTCPServer(socketserver.TCPServer):
|
|
||||||
allow_reuse_address = True
|
|
||||||
|
|
||||||
with ReusableTCPServer((bind, port), Handler) as httpd:
|
|
||||||
logging.info("Serving external storage on path>{}:{}".format(directory, port))
|
|
||||||
httpd.serve_forever()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def list_usb_storage_devices() -> List[ExternalStorage]:
|
|
||||||
os_type = platform.system()
|
|
||||||
partitions = psutil.disk_partitions()
|
|
||||||
removable_devices = []
|
|
||||||
for partition in partitions:
|
|
||||||
if 'dontbrowse' in partition.opts:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if os_type == "Windows":
|
|
||||||
if 'removable' in partition.opts:
|
|
||||||
removable_devices.append(partition)
|
|
||||||
else:
|
|
||||||
if '/media' in partition.mountpoint or '/run/media' in partition.mountpoint or '/mnt' in partition.mountpoint or '/Volumes' in partition.mountpoint:
|
|
||||||
removable_devices.append(partition)
|
|
||||||
|
|
||||||
if not removable_devices:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
storages = []
|
|
||||||
|
|
||||||
for device in removable_devices:
|
|
||||||
try:
|
|
||||||
usage = psutil.disk_usage(device.mountpoint)
|
|
||||||
# total_size = usage.total / (1024 ** 3)
|
|
||||||
external_storage = ExternalStorage(
|
|
||||||
logical_name=device.device,
|
|
||||||
mount_point=device.mountpoint,
|
|
||||||
content_id=None,
|
|
||||||
total_size=usage.total,
|
|
||||||
)
|
|
||||||
storages.append(external_storage)
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Could not retrieve size for device {device.device}: {e}")
|
|
||||||
|
|
||||||
return storages
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_external_storage_devices():
|
|
||||||
return {storage.mount_point: "{} ({} - {}GB)".format(
|
|
||||||
storage.mount_point,
|
|
||||||
storage.logical_name,
|
|
||||||
storage.total_size_in_gigabytes()
|
|
||||||
) for storage in ExternalStorageServer.list_usb_storage_devices()}
|
|
||||||
54
src/util/UtilExternalStorage.py
Normal file
54
src/util/UtilExternalStorage.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
import platform
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from typing import Dict, Optional, List, Tuple, Union
|
||||||
|
|
||||||
|
from src.model.entity.ExternalStorage import ExternalStorage
|
||||||
|
|
||||||
|
|
||||||
|
def list_usb_storage_devices() -> List[ExternalStorage]:
|
||||||
|
os_type = platform.system()
|
||||||
|
partitions = psutil.disk_partitions()
|
||||||
|
removable_devices = []
|
||||||
|
for partition in partitions:
|
||||||
|
if 'dontbrowse' in partition.opts:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if os_type == "Windows":
|
||||||
|
if 'removable' in partition.opts:
|
||||||
|
removable_devices.append(partition)
|
||||||
|
else:
|
||||||
|
if '/media' in partition.mountpoint or '/run/media' in partition.mountpoint or '/mnt' in partition.mountpoint or '/Volumes' in partition.mountpoint:
|
||||||
|
removable_devices.append(partition)
|
||||||
|
|
||||||
|
if not removable_devices:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
storages = []
|
||||||
|
|
||||||
|
for device in removable_devices:
|
||||||
|
try:
|
||||||
|
usage = psutil.disk_usage(device.mountpoint)
|
||||||
|
# total_size = usage.total / (1024 ** 3)
|
||||||
|
external_storage = ExternalStorage(
|
||||||
|
logical_name=device.device,
|
||||||
|
mount_point=device.mountpoint,
|
||||||
|
content_id=None,
|
||||||
|
total_size=usage.total,
|
||||||
|
)
|
||||||
|
storages.append(external_storage)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Could not retrieve size for device {device.device}: {e}")
|
||||||
|
|
||||||
|
return storages
|
||||||
|
|
||||||
|
|
||||||
|
def get_external_storage_devices():
|
||||||
|
return {storage.mount_point: "{} ({} - {}GB)".format(
|
||||||
|
storage.mount_point,
|
||||||
|
storage.logical_name,
|
||||||
|
storage.total_size_in_gigabytes()
|
||||||
|
) for storage in ExternalStorageServer.list_usb_storage_devices()}
|
||||||
@ -6,7 +6,7 @@ import inspect
|
|||||||
import subprocess
|
import subprocess
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import platform
|
import platform
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
@ -335,3 +335,11 @@ def str_to_bool(value):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise ValueError('Boolean value expected.')
|
raise ValueError('Boolean value expected.')
|
||||||
|
|
||||||
|
|
||||||
|
def encode_uri_component(uri_component):
|
||||||
|
return urllib.parse.quote(uri_component, safe='~()*!.\'')
|
||||||
|
|
||||||
|
|
||||||
|
def decode_uri_component(encoded_component):
|
||||||
|
return urllib.parse.unquote(encoded_component)
|
||||||
|
|||||||
@ -16,4 +16,4 @@ WIDTH=$(echo $RESOLUTION | cut -d 'x' -f 1)
|
|||||||
HEIGHT=$(echo $RESOLUTION | cut -d 'x' -f 2)
|
HEIGHT=$(echo $RESOLUTION | cut -d 'x' -f 2)
|
||||||
|
|
||||||
# Start Chromium in kiosk mode
|
# Start Chromium in kiosk mode
|
||||||
chromium-browser --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --noerrdialogs --kiosk --incognito --window-position=0,0 --window-size=${WIDTH},${HEIGHT} --display=:0 http://localhost:5000
|
chromium-browser --disk-cache-size=2147483648 --disable-features=Translate --ignore-certificate-errors --disable-web-security --disable-restore-session-state --autoplay-policy=no-user-gesture-required --start-maximized --allow-running-insecure-content --remember-cert-error-decisions --noerrdialogs --kiosk --incognito --window-position=0,0 --window-size=${WIDTH},${HEIGHT} --display=:0 http://localhost:5000
|
||||||
|
|||||||
@ -351,8 +351,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadPicture = function(element, callbackReady, item) {
|
const loadPicture = function(element, callbackReady, item) {
|
||||||
const hasScheme = item.location.indexOf('://') >= 0;
|
element.innerHTML = `<img src="${item.location}" alt="" />`;
|
||||||
element.innerHTML = `<img src="${hasScheme ? item.location : ('/' + item.location)}" alt="" />`;
|
|
||||||
callbackReady(function() {});
|
callbackReady(function() {});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -378,8 +377,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const loadVideo = function(element, callbackReady, item) {
|
const loadVideo = function(element, callbackReady, item) {
|
||||||
const location = item.location.indexOf('http') === 0 ? item.location : `/${item.location}`;
|
element.innerHTML = `<video ${previewMode ? 'controls' : ''}><source src=${item.location} type="video/mp4" /></video>`;
|
||||||
element.innerHTML = `<video ${previewMode ? 'controls' : ''}><source src=${location} type="video/mp4" /></video>`;
|
|
||||||
const video = element.querySelector('video');
|
const video = element.querySelector('video');
|
||||||
callbackReady(function() {});
|
callbackReady(function() {});
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="widget vertical">
|
<div class="widget vertical">
|
||||||
{% if content.type == enum_content_type.EXTERNAL_STORAGE %}
|
{% if content.type == enum_content_type.EXTERNAL_STORAGE %}
|
||||||
<input type="text" class="disabled" disabled value="{{ chroot_http_external_storage }}/" />
|
<input type="text" class="disabled" disabled value="{{ external_storage_mountpoint }}/" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set location = content.location %}
|
{% set location = content.location %}
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="" class="object-label"></label>
|
<label for="" class="object-label"></label>
|
||||||
<div class="widget vertical">
|
<div class="widget vertical">
|
||||||
<input type="text" class="disabled" value="{{ chroot_http_external_storage }}/" />
|
<input type="text" class="disabled" value="{{ external_storage_mountpoint }}/" />
|
||||||
<input type="text" name="object" data-input-type="storage" class="content-object-input" disabled="disabled" />
|
<input type="text" name="object" data-input-type="storage" class="content-object-input" disabled="disabled" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user