wip
This commit is contained in:
commit
4712047015
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -37,8 +37,6 @@ jQuery(document).ready(function ($) {
|
|||||||
'padding-top': '0px'
|
'padding-top': '0px'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function createElement() {
|
function createElement() {
|
||||||
let screen = $('#screen');
|
let screen = $('#screen');
|
||||||
let screenWidth = screen.width();
|
let screenWidth = screen.width();
|
||||||
@ -57,7 +55,7 @@ jQuery(document).ready(function ($) {
|
|||||||
y = Math.round(Math.max(0, Math.min(y, screenHeight - elementHeight)));
|
y = Math.round(Math.max(0, Math.min(y, screenHeight - elementHeight)));
|
||||||
|
|
||||||
let elementId = elementCounter++;
|
let elementId = elementCounter++;
|
||||||
let element = $('<div class="element" id="element-' + elementId + '" data-id="' + elementId + '"><button>Button</button></div>');
|
let element = $('<div class="element" id="element-' + elementId + '" data-id="' + elementId + '"><i class="fa fa-cog"></i></div>');
|
||||||
// let element = $('<div class="element" id="' + elementId + '"><button>Button</button><div class="rotate-handle"></div></div>');
|
// let element = $('<div class="element" id="' + elementId + '"><button>Button</button><div class="rotate-handle"></div></div>');
|
||||||
|
|
||||||
element.css({
|
element.css({
|
||||||
@ -106,6 +104,8 @@ jQuery(document).ready(function ($) {
|
|||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
focusElement(element);
|
focusElement(element);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).on('click', '.element-list-item', function(){
|
$(document).on('click', '.element-list-item', function(){
|
||||||
@ -113,7 +113,9 @@ jQuery(document).ready(function ($) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$(document).on('click', '.remove-element', function(){
|
$(document).on('click', '.remove-element', function(){
|
||||||
removeElementById($(this).attr('data-id'));
|
if (confirm(l.js_common_are_you_sure)) {
|
||||||
|
removeElementById($(this).attr('data-id'));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function removeElementById(elementId) {
|
function removeElementById(elementId) {
|
||||||
@ -122,8 +124,16 @@ jQuery(document).ready(function ($) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addElementToList(elementId) {
|
function addElementToList(elementId) {
|
||||||
let listItem = $('<div class="element-list-item" data-id="' + elementId + '">Element ' + elementId + ' <button type="button" class="remove-element" data-id="' + elementId + '">remove</button></div>');
|
const listItem = `<div class="element-list-item" data-id="__ID__">
|
||||||
$('#elementList').append(listItem);
|
Element __ID__
|
||||||
|
<button type="button" class="btn btn-neutral configure-element content-explr-picker" data-id="__ID__">
|
||||||
|
<i class="fa fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-naked remove-element" data-id="__ID__">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
$('#elementList').append($(listItem.replace(/__ID__/g, elementId)));
|
||||||
updateZIndexes();
|
updateZIndexes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,9 +155,11 @@ jQuery(document).ready(function ($) {
|
|||||||
function updateForm(element) {
|
function updateForm(element) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
$('form#elementForm input').val('').prop('disabled', true);
|
$('form#elementForm input').val('').prop('disabled', true);
|
||||||
|
$('.form-element-properties').addClass('hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('.form-element-properties').removeClass('hidden');
|
||||||
$('form#elementForm input').prop('disabled', false);
|
$('form#elementForm input').prop('disabled', false);
|
||||||
|
|
||||||
let offset = element.position();
|
let offset = element.position();
|
||||||
@ -158,6 +170,8 @@ jQuery(document).ready(function ($) {
|
|||||||
$('#elem-width').val(element.width());
|
$('#elem-width').val(element.width());
|
||||||
$('#elem-height').val(element.height());
|
$('#elem-height').val(element.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(element).find('i').css('font-size', Math.min(element.width(), element.height()) / 3);
|
||||||
/*
|
/*
|
||||||
let rotation = element.css('transform');
|
let rotation = element.css('transform');
|
||||||
let values = rotation.split('(')[1].split(')')[0].split(',');
|
let values = rotation.split('(')[1].split(')')[0].split(',');
|
||||||
@ -201,13 +215,19 @@ jQuery(document).ready(function ($) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '#addElement', function () {
|
// $(document).on('click', '#addElement', function () {
|
||||||
createElement();
|
// createElement();
|
||||||
});
|
// });
|
||||||
|
|
||||||
$(document).on('click', '#removeAllElements', function () {
|
$(document).on('click', '#removeAllElements', function () {
|
||||||
$('.element, .element-list-item').remove();
|
if (confirm(l.js_common_are_you_sure)) {
|
||||||
updateZIndexes();
|
$('.element, .element-list-item').remove();
|
||||||
|
updateZIndexes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('dblclick', '.element', function (e) {
|
||||||
|
$('.content-explr-picker[data-id='+$(this).attr('data-id')+']').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('mousedown', function (e) {
|
$(document).on('mousedown', function (e) {
|
||||||
@ -220,7 +240,7 @@ jQuery(document).ready(function ($) {
|
|||||||
if (!keepFocusedElement) {
|
if (!keepFocusedElement) {
|
||||||
unfocusElements();
|
unfocusElements();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
$(document).on('click', '#presetGrid2x2', function () {
|
$(document).on('click', '#presetGrid2x2', function () {
|
||||||
let screenWidth = $('#screen').width();
|
let screenWidth = $('#screen').width();
|
||||||
@ -252,6 +272,22 @@ jQuery(document).ready(function ($) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.content-explr-picker', function () {
|
||||||
|
const elementId = $(this).attr('data-id');
|
||||||
|
const isNew = !elementId;
|
||||||
|
const $element = isNew ? $(createElement()) : $('#element-'+elementId);
|
||||||
|
|
||||||
|
showPickers('modal-content-explr-picker', function (content) {
|
||||||
|
console.log(content);
|
||||||
|
$element.attr('data-content-id', content.id);
|
||||||
|
$element.attr('data-content-name', content.name);
|
||||||
|
$element.attr('data-content-type', content.type);
|
||||||
|
console.log($element)
|
||||||
|
$element.find('i').get(0).classList = ['fa', content.classIcon, content.classColor].join(' ');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function updateZIndexes() {
|
function updateZIndexes() {
|
||||||
const zindex = $('.element-list-item').length + 1;
|
const zindex = $('.element-list-item').length + 1;
|
||||||
$('.element-list-item').each(function(index) {
|
$('.element-list-item').each(function(index) {
|
||||||
@ -266,6 +302,5 @@ jQuery(document).ready(function ($) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
createElement();
|
$('#presetGrid2x2').click();
|
||||||
updateForm(null);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -155,8 +155,6 @@ form {
|
|||||||
color: $gscale5;
|
color: $gscale5;
|
||||||
background: none;
|
background: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid $gscale3;
|
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,11 +165,8 @@ form {
|
|||||||
|
|
||||||
&.disabled,
|
&.disabled,
|
||||||
&[disabled] {
|
&[disabled] {
|
||||||
border: none;
|
|
||||||
background: $gscale0;
|
background: $gscale0;
|
||||||
border-radius: $baseRadius;
|
border-radius: $baseRadius;
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,14 +45,18 @@
|
|||||||
|
|
||||||
.element {
|
.element {
|
||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
background-color: #f0f0f0;
|
background-color: $gkscaleE;
|
||||||
outline: 1px solid rgba($black, .5);
|
outline: 1px solid $gkscaleC;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&.focused {
|
&.focused {
|
||||||
border: none;
|
border: none;
|
||||||
outline: 2px solid blue;
|
outline: 2px solid $seaBlue;
|
||||||
z-index: 89 !important;
|
z-index: 89 !important;
|
||||||
|
|
||||||
.ui-resizable-handle {
|
.ui-resizable-handle {
|
||||||
@ -60,6 +64,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 20px;
|
||||||
|
color: $gkscaleC;
|
||||||
|
&.fa-cog {
|
||||||
|
text-shadow: 0 -2px $gkscaleB, 0 0px 2px $gkscaleB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rotate-handle {
|
.rotate-handle {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
@ -72,8 +84,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ui-resizable-handle {
|
.ui-resizable-handle {
|
||||||
background: black;
|
background: $gkscaleA;
|
||||||
border: 1px solid #000;
|
border: 1px solid $gkscale5;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
z-index: 90;
|
z-index: 90;
|
||||||
@ -145,25 +157,61 @@
|
|||||||
|
|
||||||
|
|
||||||
.form-element-properties {
|
.form-element-properties {
|
||||||
margin-left: 20px;
|
flex: 1;
|
||||||
padding: 10px;
|
align-self: stretch;
|
||||||
background-color: #f9f9f9;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
label,
|
|
||||||
input {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#elementList {
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 0 10px 0;
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: $gscaleD;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid $gscale2;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
padding: 8px 0 5px 8px;
|
||||||
|
border: 1px solid rgba(255,255,255,.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,7 +110,23 @@ When you run the browser yourself, don't forget to use these flags for chromium
|
|||||||
```bash
|
```bash
|
||||||
# chromium or chromium-browser or even chrome
|
# chromium or chromium-browser or even chrome
|
||||||
# replace http://localhost:5000 with your obscreen-studio instance url
|
# replace http://localhost:5000 with your obscreen-studio instance url
|
||||||
chromium --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=1920,1080 --display=:0 http://localhost:5000
|
chromium \
|
||||||
|
--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=1920,1080 \
|
||||||
|
--display=:0 \
|
||||||
|
http://localhost:5000
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -118,7 +118,23 @@ When you run the browser yourself, don't forget to use these flags for chromium
|
|||||||
```bash
|
```bash
|
||||||
# chromium or chromium-browser or even chrome
|
# chromium or chromium-browser or even chrome
|
||||||
# replace http://localhost:5000 with your obscreen-studio instance url
|
# replace http://localhost:5000 with your obscreen-studio instance url
|
||||||
chromium --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=1920,1080 --display=:0 http://localhost:5000
|
chromium \
|
||||||
|
--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=1920,1080 \
|
||||||
|
--display=:0 \
|
||||||
|
http://localhost:5000
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -110,11 +110,14 @@ class ContentController(ObController):
|
|||||||
if not content:
|
if not content:
|
||||||
return abort(404)
|
return abort(404)
|
||||||
|
|
||||||
|
vargs = {}
|
||||||
working_folder_path, working_folder = self.get_folder_context()
|
working_folder_path, working_folder = self.get_folder_context()
|
||||||
edit_view = 'slideshow/contents/edit.jinja.html'
|
edit_view = 'slideshow/contents/edit.jinja.html'
|
||||||
|
|
||||||
if content.type == ContentType.COMPOSITION:
|
if content.type == ContentType.COMPOSITION:
|
||||||
edit_view = 'slideshow/contents/edit-composition.jinja.html'
|
edit_view = 'slideshow/contents/edit-composition.jinja.html'
|
||||||
|
vargs['folders_tree'] = self._model_store.folder().get_folder_tree(FolderEntity.CONTENT)
|
||||||
|
vargs['foldered_contents'] = self._model_store.content().get_all_indexed('folder_id', multiple=True)
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
edit_view,
|
edit_view,
|
||||||
@ -122,7 +125,8 @@ 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,
|
||||||
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint')
|
external_storage_mountpoint=self._model_store.config().map().get('external_storage_mountpoint'),
|
||||||
|
**vargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def slideshow_content_save(self, content_id: int = 0):
|
def slideshow_content_save(self, content_id: int = 0):
|
||||||
|
|||||||
@ -156,7 +156,7 @@ class PlayerController(ObController):
|
|||||||
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['delegate_duration'] = 1 if slide['type'] == ContentType.VIDEO.value else 0
|
slide['delegate_duration'] = 1 if virtual_content.type == ContentType.VIDEO else 0
|
||||||
slide['name'] = file.name
|
slide['name'] = file.name
|
||||||
slide['type'] = virtual_content.type.value
|
slide['type'] = virtual_content.type.value
|
||||||
slide['location'] = self._model_store.content().resolve_content_location(virtual_content)
|
slide['location'] = self._model_store.content().resolve_content_location(virtual_content)
|
||||||
|
|||||||
@ -28,7 +28,7 @@ class ContentManager(ModelManager):
|
|||||||
"name CHAR(255)",
|
"name CHAR(255)",
|
||||||
"type CHAR(30)",
|
"type CHAR(30)",
|
||||||
"location TEXT",
|
"location TEXT",
|
||||||
"duration INTEGER",
|
"duration FLOAT",
|
||||||
"folder_id INTEGER",
|
"folder_id INTEGER",
|
||||||
"created_by CHAR(255)",
|
"created_by CHAR(255)",
|
||||||
"updated_by CHAR(255)",
|
"updated_by CHAR(255)",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import os
|
|||||||
from typing import Dict, Optional, List, Tuple, Union
|
from typing import Dict, Optional, List, Tuple, Union
|
||||||
|
|
||||||
from src.model.entity.Playlist import Playlist
|
from src.model.entity.Playlist import Playlist
|
||||||
|
from src.model.enum.ContentType import ContentType
|
||||||
from src.util.utils import get_optional_string, get_yt_video_id, slugify, slugify_next
|
from src.util.utils import get_optional_string, get_yt_video_id, slugify, slugify_next
|
||||||
from src.manager.DatabaseManager import DatabaseManager
|
from src.manager.DatabaseManager import DatabaseManager
|
||||||
from src.manager.SlideManager import SlideManager
|
from src.manager.SlideManager import SlideManager
|
||||||
@ -69,15 +70,17 @@ class PlaylistManager(ModelManager):
|
|||||||
durations = self._db.execute_read_query("""
|
durations = self._db.execute_read_query("""
|
||||||
SELECT
|
SELECT
|
||||||
playlist_id,
|
playlist_id,
|
||||||
SUM(CASE
|
ROUND(SUM(CASE
|
||||||
WHEN s.delegate_duration = 1 THEN c.duration
|
WHEN s.delegate_duration = 1 THEN c.duration
|
||||||
|
WHEN c.type = '{}' THEN s.duration
|
||||||
ELSE s.duration
|
ELSE s.duration
|
||||||
END) AS total_duration
|
END)) AS total_duration
|
||||||
FROM {} s
|
FROM {} s
|
||||||
LEFT JOIN {} c ON c.id = s.content_id
|
LEFT JOIN {} c ON c.id = s.content_id
|
||||||
WHERE cron_schedule IS NULL {}
|
WHERE cron_schedule IS NULL {} AND s.enabled is TRUE
|
||||||
GROUP BY playlist_id;
|
GROUP BY playlist_id;
|
||||||
""".format(
|
""".format(
|
||||||
|
ContentType.EXTERNAL_STORAGE.value,
|
||||||
SlideManager.TABLE_NAME,
|
SlideManager.TABLE_NAME,
|
||||||
ContentManager.TABLE_NAME,
|
ContentManager.TABLE_NAME,
|
||||||
"{}".format(
|
"{}".format(
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from src.util.utils import str_to_enum
|
|||||||
|
|
||||||
class Content:
|
class Content:
|
||||||
|
|
||||||
def __init__(self, uuid: str = '', location: str = '', type: Union[ContentType, str] = ContentType.URL, name: str = 'Untitled', id: Optional[int] = None, duration: Optional[int] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None):
|
def __init__(self, uuid: str = '', location: str = '', type: Union[ContentType, str] = ContentType.URL, name: str = 'Untitled', id: Optional[int] = None, duration: Optional[float] = None, created_by: Optional[str] = None, updated_by: Optional[str] = None, created_at: Optional[int] = None, updated_at: Optional[int] = None, folder_id: Optional[int] = None):
|
||||||
self._uuid = uuid if uuid else self.generate_and_set_uuid()
|
self._uuid = uuid if uuid else self.generate_and_set_uuid()
|
||||||
self._id = id if id else None
|
self._id = id if id else None
|
||||||
self._location = location
|
self._location = location
|
||||||
@ -88,11 +88,11 @@ class Content:
|
|||||||
self._folder_id = value
|
self._folder_id = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def duration(self) -> Optional[int]:
|
def duration(self) -> Optional[float]:
|
||||||
return self._duration
|
return self._duration
|
||||||
|
|
||||||
@duration.setter
|
@duration.setter
|
||||||
def duration(self, value: Optional[int]):
|
def duration(self, value: Optional[float]):
|
||||||
self._duration = value
|
self._duration = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -10,9 +10,9 @@ def mp4_duration_with_ffprobe(filename):
|
|||||||
fields = json.loads(result)['streams'][0]
|
fields = json.loads(result)['streams'][0]
|
||||||
|
|
||||||
if 'tags' in fields and 'DURATION' in fields['tags']:
|
if 'tags' in fields and 'DURATION' in fields['tags']:
|
||||||
return int(float(fields['tags']['DURATION']))
|
return round(float(fields['tags']['DURATION']), 2)
|
||||||
|
|
||||||
if 'duration' in fields:
|
if 'duration' in fields:
|
||||||
return int(float(fields['duration']))
|
return round(float(fields['duration']), 2)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@ -251,6 +251,7 @@ def slugify(value):
|
|||||||
|
|
||||||
|
|
||||||
def seconds_to_hhmmss(seconds):
|
def seconds_to_hhmmss(seconds):
|
||||||
|
seconds = int(seconds)
|
||||||
if not seconds:
|
if not seconds:
|
||||||
return ""
|
return ""
|
||||||
hours = seconds // 3600
|
hours = seconds // 3600
|
||||||
|
|||||||
@ -16,4 +16,20 @@ 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 --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
|
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
|
||||||
|
|||||||
@ -84,6 +84,7 @@ systemctl set-default graphical.target
|
|||||||
mkdir -p "$WORKING_DIR/obscreen/var/run"
|
mkdir -p "$WORKING_DIR/obscreen/var/run"
|
||||||
curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/autostart-browser-x11.sh | sed "s#/home/pi#$WORKING_DIR#g" | sed "s#=pi#=$OWNER#g" | sed "s#http://localhost:5000#$obscreen_studio_url#g" | tee "$WORKING_DIR/obscreen/var/run/play"
|
curl https://raw.githubusercontent.com/jr-k/obscreen/master/system/autostart-browser-x11.sh | sed "s#/home/pi#$WORKING_DIR#g" | sed "s#=pi#=$OWNER#g" | sed "s#http://localhost:5000#$obscreen_studio_url#g" | tee "$WORKING_DIR/obscreen/var/run/play"
|
||||||
chmod +x "$WORKING_DIR/obscreen/var/run/play"
|
chmod +x "$WORKING_DIR/obscreen/var/run/play"
|
||||||
|
chown -R $OWNER:$OWNER "$WORKING_DIR/obscreen"
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Start
|
# Start
|
||||||
|
|||||||
@ -25,7 +25,6 @@ apt-get install -y git python3-pip python3-venv libsqlite3-dev ntfs-3g ffmpeg
|
|||||||
cd $WORKING_DIR
|
cd $WORKING_DIR
|
||||||
git clone https://github.com/jr-k/obscreen.git
|
git clone https://github.com/jr-k/obscreen.git
|
||||||
cd obscreen
|
cd obscreen
|
||||||
chown -R $USER:$USER ./
|
|
||||||
|
|
||||||
# Install application dependencies
|
# Install application dependencies
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
@ -38,6 +37,9 @@ cp .env.dist .env
|
|||||||
# Add user to needed group
|
# Add user to needed group
|
||||||
usermod -aG plugdev $OWNER
|
usermod -aG plugdev $OWNER
|
||||||
|
|
||||||
|
# Fix permissions
|
||||||
|
chown -R $OWNER:$OWNER ./
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Automount script for external storage
|
# Automount script for external storage
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
<h2>
|
<h2>
|
||||||
{{ l.common_pick_element }}
|
{{ l.common_pick_element }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% with use_href=False %}
|
{% with use_href=False %}
|
||||||
{% include 'fleet/node-players/component/explr-sidebar.jinja.html' %}
|
{% include 'fleet/node-players/component/explr-sidebar.jinja.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="button" class="btn btn-naked picker-close">
|
<button type="button" class="btn btn-naked picker-close">
|
||||||
<i class="fa fa-close icon-left"></i>{{ l.common_close }}
|
<i class="fa fa-close icon-left"></i>{{ l.common_close }}
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
.slide, iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding-top: 0; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
|
.slide, iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding-top: 0; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
|
||||||
.slide iframe { background: white; }
|
.slide iframe { background: white; }
|
||||||
.slide img, .slide video { height: 100%; }
|
.slide img, .slide video { height: 100%; }
|
||||||
|
.slide video { width: 100%; height: 100%; }
|
||||||
</style>
|
</style>
|
||||||
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/utils.js"></script>
|
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/utils.js"></script>
|
||||||
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/lib/is-cron-now.js"></script>
|
<script type="application/javascript" src="{{ STATIC_PREFIX }}js/lib/is-cron-now.js"></script>
|
||||||
@ -82,6 +83,7 @@
|
|||||||
let curSlide = secondSlide;
|
let curSlide = secondSlide;
|
||||||
let nextSlide = firstSlide;
|
let nextSlide = firstSlide;
|
||||||
let notificationItemIndex = null;
|
let notificationItemIndex = null;
|
||||||
|
let pausableContent = null;
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
const itemsLoadedProcess = function() {
|
const itemsLoadedProcess = function() {
|
||||||
@ -126,6 +128,10 @@
|
|||||||
|
|
||||||
const resume = function() {
|
const resume = function() {
|
||||||
playState = PLAY_STATE_PLAYING;
|
playState = PLAY_STATE_PLAYING;
|
||||||
|
|
||||||
|
if (pausableContent) {
|
||||||
|
pausableContent.play();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const play = function() {
|
const play = function() {
|
||||||
@ -135,6 +141,10 @@
|
|||||||
const pause = function() {
|
const pause = function() {
|
||||||
pauseClockValue = clockValue;
|
pauseClockValue = clockValue;
|
||||||
playState = PLAY_STATE_PAUSE;
|
playState = PLAY_STATE_PAUSE;
|
||||||
|
|
||||||
|
if (pausableContent) {
|
||||||
|
pausableContent.pause();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stop = function() {
|
const stop = function() {
|
||||||
@ -203,7 +213,7 @@
|
|||||||
|
|
||||||
let duration = item.duration;
|
let duration = item.duration;
|
||||||
|
|
||||||
if (durationsOverride[item.id]) {
|
if (durationsOverride[item.id] !== undefined) {
|
||||||
duration = durationsOverride[item.id];
|
duration = durationsOverride[item.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,17 +260,15 @@
|
|||||||
|
|
||||||
if (i === curItemIndex) {
|
if (i === curItemIndex) {
|
||||||
secondsBeforeNext = accumulatedTime + safe_duration(item) - timeInCurrentLoop;
|
secondsBeforeNext = accumulatedTime + safe_duration(item) - timeInCurrentLoop;
|
||||||
//console.log("remaining:", secondsBeforeNext, "clock:",clockValue, curItemIndex);
|
//console.log("id", item.id, "secondsBeforeNext:", secondsBeforeNext, "clock:", clockValue, "clockLoopDration", timeInCurrentLoop, "<", accumulatedTime , '+', safe_duration(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeInCurrentLoop < accumulatedTime + safe_duration(item)) {
|
if (timeInCurrentLoop < accumulatedTime + safe_duration(item)) {
|
||||||
if (curItemIndex !== i) {
|
if (curItemIndex !== i) {
|
||||||
//console.log('change to ', i , item.name)
|
|
||||||
curItemIndex = i;
|
curItemIndex = i;
|
||||||
|
|
||||||
const emptySlide = getEmptySlide();
|
const emptySlide = getEmptySlide();
|
||||||
if ((emptySlide && !hasMoveOnce) || forcePreload) {
|
if ((emptySlide && !hasMoveOnce) || forcePreload) {
|
||||||
//console.log('init preload');
|
|
||||||
if (!hasMoveOnce && syncWithTime) {
|
if (!hasMoveOnce && syncWithTime) {
|
||||||
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
|
if (accumulatedTime + safe_duration(item) - timeInCurrentLoop < 1) {
|
||||||
// Prevent glitch when syncWithTime for first init
|
// Prevent glitch when syncWithTime for first init
|
||||||
@ -386,10 +394,13 @@
|
|||||||
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
|
delayNoisyContentJIT = lookupCurrentItem().id !== item.id ? delayNoisyContentJIT : 0;
|
||||||
|
|
||||||
video.addEventListener('loadedmetadata', function() {
|
video.addEventListener('loadedmetadata', function() {
|
||||||
if (item.duration !== video.duration && item.delegate_duration) {
|
if (item.duration !== video.duration && !item.delegate_duration) {
|
||||||
durationsOverride[item.id] = video.duration;
|
|
||||||
console.warn('Given duration ' + item.duration + 's is different from video file ' + Math.ceil(video.duration) + 's');
|
console.warn('Given duration ' + item.duration + 's is different from video file ' + Math.ceil(video.duration) + 's');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.delegate_duration) {
|
||||||
|
durationsOverride[item.id] = Math.ceil(video.duration);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const autoplayLoader = function() {
|
const autoplayLoader = function() {
|
||||||
@ -400,12 +411,14 @@
|
|||||||
if (element.innerHTML.match('<video>')) {
|
if (element.innerHTML.match('<video>')) {
|
||||||
if (!previewMode) {
|
if (!previewMode) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
video.play();
|
video.play();
|
||||||
|
pausableContent = video;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
|
||||||
|
setTimeout(autoplayLoader, delayNoisyContentJIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAndMoveNotifications = function() {
|
const checkAndMoveNotifications = function() {
|
||||||
|
|||||||
@ -14,10 +14,10 @@
|
|||||||
{{ render_folder(child) }}
|
{{ render_folder(child) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for content in content_children %}
|
{% for content in content_children %}
|
||||||
{% set slides = slides_with_content[content.id]|default([]) %}
|
{% set slides = slides_with_content[content.id]|default([]) if slides_with_content else [] %}
|
||||||
{% set icon = enum_content_type.get_fa_icon(content.type) %}
|
{% set icon = enum_content_type.get_fa_icon(content.type) %}
|
||||||
{% set color = enum_content_type.get_color_icon(content.type) %}
|
{% set color = enum_content_type.get_color_icon(content.type) %}
|
||||||
<li class="explr-item" data-entity-json="{{ content.to_json() }}">
|
<li class="explr-item" data-entity-json="{{ content.to_json({'classIcon': icon, 'classColor': color}) }}">
|
||||||
<i class="fa {{ icon }} {{ color }}"></i>
|
<i class="fa {{ icon }} {{ color }}"></i>
|
||||||
{% if slides|length > 0 %}
|
{% if slides|length > 0 %}
|
||||||
<sub>
|
<sub>
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block add_css %}
|
{% block add_css %}
|
||||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css"/>
|
|
||||||
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
|
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/jquery-explr-1.4.css"/>
|
||||||
|
<link rel="stylesheet" href="{{ STATIC_PREFIX }}css/lib/flatpickr.min.css"/>
|
||||||
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }}
|
{{ HOOK(H_SLIDESHOW_CONTENT_CSS) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -17,6 +17,7 @@
|
|||||||
<script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script>
|
<script src="{{ STATIC_PREFIX }}js/lib/jquery-ui.min.js"></script>
|
||||||
{# <script src="{{ STATIC_PREFIX }} js/lib/jquery-ui-rotatable.min.js"></script> #}
|
{# <script src="{{ STATIC_PREFIX }} js/lib/jquery-ui-rotatable.min.js"></script> #}
|
||||||
<script src="{{ STATIC_PREFIX }}js/slideshow/content-composition.js"></script>
|
<script src="{{ STATIC_PREFIX }}js/slideshow/content-composition.js"></script>
|
||||||
|
<script src="{{ STATIC_PREFIX }}js/explorer.js"></script>
|
||||||
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
|
{{ HOOK(H_SLIDESHOW_CONTENT_JAVASCRIPT) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -109,7 +110,7 @@
|
|||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button id="presetGrid2x2">Grid 2x2</button>
|
<button id="presetGrid2x2">Grid 2x2</button>
|
||||||
<button id="addElement">Add Element</button>
|
<button id="addElement" class="content-explr-picker">Add Element</button>
|
||||||
<button id="removeAllElements">Remove All Elements</button>
|
<button id="removeAllElements">Remove All Elements</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -123,23 +124,55 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="page-panel right-panel">
|
<div class="page-panel right-panel">
|
||||||
<div class="form-element-properties">
|
<div class="form-element-properties hidden">
|
||||||
<form id="elementForm">
|
<form id="elementForm">
|
||||||
<h3>Element Properties</h3>
|
<h3>Element Properties</h3>
|
||||||
<label for="elem-x">X:</label>
|
|
||||||
<input type="number" id="elem-x" name="elem-x"><br>
|
<div class="form-group">
|
||||||
<label for="elem-y">Y:</label>
|
<label for="elem-x">Position X</label>
|
||||||
<input type="number" id="elem-y" name="elem-y"><br>
|
<div class="widget">
|
||||||
<label for="elem-width">Width:</label>
|
<input type="number" id="elem-x" name="elem-x">
|
||||||
<input type="number" id="elem-width" name="elem-width"><br>
|
</div>
|
||||||
<label for="elem-height">Height:</label>
|
</div>
|
||||||
<input type="number" id="elem-height" name="elem-height"><br>
|
|
||||||
<!--<label for="elem-rotate">Rotate (deg):</label>-->
|
<div class="form-group">
|
||||||
<!--<input type="number" id="elem-rotate" name="elem-rotate"><br>-->
|
<label for="elem-y">Position Y</label>
|
||||||
|
<div class="widget">
|
||||||
|
<input type="number" id="elem-y" name="elem-y">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="elem-width">Width</label>
|
||||||
|
<div class="widget">
|
||||||
|
<input type="number" id="elem-width" name="elem-width">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="elem-height">Height</label>
|
||||||
|
<div class="widget">
|
||||||
|
<input type="number" id="elem-height" name="elem-height">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# <div class="form-group">#}
|
||||||
|
{# <label for="elem-rotate">Rotate (deg)</label>#}
|
||||||
|
{# <div class="widget">#}
|
||||||
|
{# <input type="number" id="elem-rotate" name="elem-rotate">#}
|
||||||
|
{# </div>#}
|
||||||
|
{# </div>#}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pickers hidden">
|
||||||
|
<div class="modals-outer">
|
||||||
|
<div class="modals-inner">
|
||||||
|
{% include 'slideshow/contents/modal/explr-picker.jinja.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user