feat: Complete template management system with live switching and persistent storage

- Template Management System: Complete HTML overlay template upload, delete, and real-time editing
- Persistent Template Storage: Cross-platform user template storage (AppData/Library/config)
- Template Priority System: Uploaded templates override built-in templates with same filename
- Custom URL Scheme Handler: overlay:// protocol enables uploaded templates to access overlay.js
- Live Template Switching: Dashboard forms instantly switch to selected template with overlay updates
- Template Code Viewer: View and copy template source code with syntax highlighting
- File Watcher Integration: Automatic template reloading when files change on disk
- Enhanced Template UI: Popup previews, source viewer, dynamic template selection

Fixes:
- API endpoint mismatch: Fixed /api/overlay/update vs /api/overlay URL inconsistency
- Template selection: Fixed video test page template selection with auto .html extension
- JavaScript accessibility: Custom overlay:// scheme allows uploaded templates to access overlay.js
- MessageBuilder: Added missing overlay_update() method
- Template reload logic: Load specific template from form instead of reloading current

Technical details:
- OverlayUrlSchemeHandler for JavaScript file serving via overlay:// protocol
- Qt Resource Collections (QRC) integration for embedded JavaScript files
- PyInstaller compatibility with persistent user data outside executable bundle
- WebChannel communication enhanced between Qt application and JavaScript overlays
- Cross-platform persistent storage paths with proper fallback mechanisms
parent 9ea25a11
......@@ -2,6 +2,45 @@
All notable changes to this project will be documented in this file.
## [1.2.2] - 2025-08-21
### Added
- **Template Management System**: Complete HTML overlay template management with upload, delete, and real-time editing capabilities
- **Persistent Template Storage**: Cross-platform user template storage (Windows: %APPDATA%, macOS: ~/Library/Application Support, Linux: ~/.config)
- **Template Priority System**: Uploaded templates automatically override built-in templates with same filename
- **Custom URL Scheme Handler**: overlay:// protocol enables uploaded templates to access JavaScript overlay.js functionality
- **Template Code Viewer**: View and copy template source code directly from web interface with syntax highlighting
- **Live Template Switching**: Dashboard and video control forms now instantly switch to selected template with overlay updates
- **Template File Watcher**: Automatic template reloading when template files change on disk (using watchdog library)
- **Enhanced Template UI**:
- Popup preview windows for better template testing
- Template source code modal with copy-to-clipboard functionality
- Dynamic template dropdown population from API
- Comprehensive template management interface
### Fixed
- **API Endpoint Mismatch**: Fixed frontend calling wrong overlay update URL (/api/overlay/update vs /api/overlay)
- **Template Selection Issues**: Fixed template selection not working from video test page by adding automatic .html extension handling
- **JavaScript Accessibility**: Solved uploaded templates unable to access overlay.js by implementing custom overlay:// URL scheme
- **MessageBuilder Methods**: Added missing overlay_update() method to MessageBuilder class
- **Template Reload Logic**: Enhanced overlay update functionality to load specific template from form selection instead of just reloading current template
### Enhanced
- **Template Loading**: Prioritizes uploaded templates over built-in ones with same filename
- **File System Integration**: Qt Resource Collections (QRC) integration for JavaScript file embedding
- **Cross-Platform Paths**: Proper persistent storage paths for all operating systems
- **PyInstaller Compatibility**: Template system works correctly in compiled executable with persistent user data outside bundle
- **WebChannel Communication**: Enhanced Qt WebChannel integration between application and JavaScript overlay templates
- **Template Name Processing**: Automatic .html extension handling and proper template name normalization
### Technical Details
- Implemented OverlayUrlSchemeHandler for serving JavaScript files via custom overlay:// protocol
- Enhanced template loading with priority system (uploaded > built-in > default fallback)
- Added comprehensive file watcher monitoring both built-in and uploaded template directories
- Integrated Qt Resource system for embedding JavaScript files accessible via qrc:// URLs
- Enhanced template selection with automatic extension handling and proper fallback mechanisms
- Updated PyInstaller configuration to include templates directory and watchdog dependency
## [1.2.1] - 2025-08-20
### Fixed
......
......@@ -5,6 +5,7 @@ A cross-platform multimedia client application with video playback, web dashboar
## Features
- **PyQt Video Player**: Fullscreen video playback with dual overlay system (WebEngine and native Qt widgets)
- **Template Management System**: Upload, manage, and live-reload HTML overlay templates with persistent storage
- **Web Dashboard**: Authentication, user management, configuration interface, and admin system controls
- **REST API Client**: Configurable external API integration with automatic retry
- **Multi-threaded Architecture**: Four threads with Queue-based message passing and proper daemon thread management
......@@ -19,6 +20,17 @@ A cross-platform multimedia client application with video playback, web dashboar
## Recent Improvements
### Version 1.2.2 (August 2025)
-**Template Management System**: Complete HTML overlay template management with upload, delete, and real-time editing capabilities
-**Persistent Template Storage**: Cross-platform user template storage that survives PyInstaller executable updates
-**Template Priority System**: Uploaded templates automatically override built-in templates with same filename
-**JavaScript Resource Access**: Custom URL scheme handler enables uploaded templates to access overlay.js functionality
-**Live Template Switching**: Dashboard forms now switch to selected template instantly with overlay updates
-**Template Code Viewer**: View and copy template source code directly from web interface
-**File Watcher Integration**: Automatic template reloading when files change on disk
-**Enhanced Template UI**: Popup preview windows and comprehensive template management interface
### Version 1.2.1 (August 2025)
-**CRITICAL FIX: Video Display Resolved**: Completely fixed Qt video player black screen issue - video frames now render properly on all platforms
......@@ -144,9 +156,14 @@ mbetterc/
│ ├── core/ # Main loop and message handling
│ └── utils/ # Utility functions
├── assets/ # Static assets (images, templates)
├── templates/ # Video overlay templates
├── templates/ # Video overlay templates (built-in)
├── tests/ # Unit tests
└── docs/ # Documentation
# User Data Directories (Created automatically)
# Windows: %APPDATA%\MbetterClient\templates\
# macOS: ~/Library/Application Support/MbetterClient/templates/
# Linux: ~/.config/MbetterClient/templates/
```
### Message System
......@@ -189,10 +206,18 @@ Threads communicate via Python Queues with structured messages:
- `PUT /api/config/{section}` - Update configuration section
- `GET /api/config/{section}` - Get specific configuration section
#### Template Management
- `GET /api/templates` - List available overlay templates
- `POST /api/templates/upload` - Upload new template file
- `DELETE /api/templates/{name}` - Delete uploaded template
- `GET /api/templates/{name}/preview` - Preview template in browser
- `GET /api/templates/{name}/source` - Get template source code
#### Video Control
- `POST /api/video/control` - Control video playback (play, pause, stop, etc.)
- `GET /api/video/status` - Get current video player status
- `POST /api/video/upload` - Upload video file for playback
- `POST /api/overlay` - Update overlay content and switch templates
### Message Types
......@@ -210,6 +235,7 @@ Threads communicate via Python Queues with structured messages:
#### Configuration
- `CONFIG_UPDATE` - Configuration changed
- `TEMPLATE_CHANGE` - Video template changed
- `OVERLAY_UPDATE` - Overlay content updated
#### System Messages
- `SYSTEM_SHUTDOWN` - Application shutdown request
......
......@@ -124,6 +124,33 @@ def collect_data_files() -> List[tuple]:
relative_path = file_path.relative_to(project_root)
data_files.append((str(file_path), str(relative_path.parent)))
# Include Qt player templates directory and all assets
templates_dir = project_root / 'mbetterclient' / 'qt_player' / 'templates'
if templates_dir.exists():
for file_path in templates_dir.rglob('*'):
if file_path.is_file():
relative_path = file_path.relative_to(project_root)
data_files.append((str(file_path), str(relative_path.parent)))
print(f" 📁 Including templates directory: {templates_dir}")
# Specifically ensure JavaScript files in templates are included
js_files = list(templates_dir.rglob('*.js'))
css_files = list(templates_dir.rglob('*.css'))
print(f" 📄 Found {len(js_files)} JavaScript files in templates")
print(f" 🎨 Found {len(css_files)} CSS files in templates")
# Include any external JavaScript/CSS assets that templates might reference
# Look for common asset directories that templates might use
asset_patterns = ['assets', 'js', 'css', 'fonts', 'images']
for pattern in asset_patterns:
asset_dir = project_root / 'mbetterclient' / 'qt_player' / pattern
if asset_dir.exists():
for file_path in asset_dir.rglob('*'):
if file_path.is_file():
relative_path = file_path.relative_to(project_root)
data_files.append((str(file_path), str(relative_path.parent)))
print(f" 📁 Including asset directory: {asset_dir}")
return data_files
......@@ -157,6 +184,11 @@ def collect_hidden_imports() -> List[str]:
# Logging
'loguru',
# File watching for template system
'watchdog',
'watchdog.observers',
'watchdog.events',
# Other dependencies
'packaging',
'pkg_resources',
......
......@@ -37,6 +37,7 @@ class MbetterClientApplication:
self.qt_player = None
self.web_dashboard = None
self.api_client = None
self.template_watcher = None
# Main loop thread
self._main_loop_thread: Optional[threading.Thread] = None
......@@ -171,6 +172,13 @@ class MbetterClientApplication:
try:
components_initialized = 0
# Initialize template watcher
if self._initialize_template_watcher():
components_initialized += 1
else:
logger.error("Template watcher initialization failed")
return False
# Initialize PyQt video player
if self.settings.enable_qt:
if self._initialize_qt_player():
......@@ -206,6 +214,63 @@ class MbetterClientApplication:
logger.error(f"Component initialization failed: {e}")
return False
def _initialize_template_watcher(self) -> bool:
"""Initialize template file watcher"""
try:
from ..qt_player.template_watcher import TemplateWatcher
# Built-in templates directory is in qt_player folder
builtin_templates_dir = Path(__file__).parent.parent / "qt_player" / "templates"
# Get persistent uploaded templates directory
uploaded_templates_dir = self._get_persistent_templates_dir()
uploaded_templates_dir.mkdir(parents=True, exist_ok=True)
self.template_watcher = TemplateWatcher(
message_bus=self.message_bus,
templates_dir=str(builtin_templates_dir),
uploaded_templates_dir=str(uploaded_templates_dir)
)
# Register with thread manager
self.thread_manager.register_component("template_watcher", self.template_watcher)
logger.info(f"Template watcher initialized - builtin: {builtin_templates_dir}, uploaded: {uploaded_templates_dir}")
return True
except Exception as e:
logger.error(f"Template watcher initialization failed: {e}")
return False
def _get_persistent_templates_dir(self) -> Path:
"""Get persistent templates directory for user uploads"""
try:
import platform
import os
from pathlib import Path
system = platform.system()
if system == "Windows":
# Use AppData/Roaming on Windows
app_data = os.getenv('APPDATA', os.path.expanduser('~'))
templates_dir = Path(app_data) / "MbetterClient" / "templates"
elif system == "Darwin": # macOS
# Use ~/Library/Application Support on macOS
templates_dir = Path.home() / "Library" / "Application Support" / "MbetterClient" / "templates"
else: # Linux and other Unix-like systems
# Use ~/.config on Linux
config_home = os.getenv('XDG_CONFIG_HOME', str(Path.home() / ".config"))
templates_dir = Path(config_home) / "MbetterClient" / "templates"
logger.debug(f"Persistent templates directory: {templates_dir}")
return templates_dir
except Exception as e:
logger.error(f"Failed to determine persistent templates directory: {e}")
# Fallback to local directory
return Path.cwd() / "user_templates"
def _initialize_qt_player(self) -> bool:
"""Initialize PyQt video player"""
try:
......
......@@ -233,8 +233,33 @@ class MessageBus:
def _deliver_to_queue(self, queue: Queue, message: Message) -> bool:
"""Deliver message to a specific queue"""
try:
# Special handling for high-frequency messages like VIDEO_PROGRESS
if message.type == MessageType.VIDEO_PROGRESS:
# Try to remove any existing VIDEO_PROGRESS message from the queue
try:
temp_queue = Queue()
removed = False
# Drain the queue, keeping all messages except VIDEO_PROGRESS
while True:
item = queue.get_nowait()
if item.type == MessageType.VIDEO_PROGRESS:
removed = True
else:
temp_queue.put(item)
except Empty:
pass
# Put back all non-VIDEO_PROGRESS messages
while not temp_queue.empty():
queue.put(temp_queue.get())
# Now add the new VIDEO_PROGRESS message
queue.put(message, block=False)
return True
# Priority handling - critical messages skip queue size limits
if message.priority >= 2:
elif message.priority >= 2:
queue.put(message, block=False)
else:
# Check queue size for normal messages
......@@ -420,6 +445,17 @@ class MessageBuilder:
}
)
@staticmethod
def overlay_update(sender: str, overlay_data: Dict[str, Any]) -> Message:
"""Create OVERLAY_UPDATE message"""
return Message(
type=MessageType.OVERLAY_UPDATE,
sender=sender,
data={
"overlay_data": overlay_data
}
)
@staticmethod
def api_request(sender: str, url: str, method: str = "GET",
headers: Optional[Dict[str, str]] = None,
......
"""
Custom URL scheme handler for serving overlay JavaScript files
"""
import logging
from pathlib import Path
from PyQt6.QtCore import QBuffer, QIODevice, QByteArray
from PyQt6.QtWebEngineCore import QWebEngineUrlRequestJob, QWebEngineUrlSchemeHandler
logger = logging.getLogger(__name__)
class OverlayUrlSchemeHandler(QWebEngineUrlSchemeHandler):
"""Custom URL scheme handler for overlay:// URLs"""
def __init__(self, parent=None):
super().__init__(parent)
logger.info("OverlayUrlSchemeHandler initialized")
def requestStarted(self, job: QWebEngineUrlRequestJob):
"""Handle URL requests for overlay:// scheme"""
try:
url = job.requestUrl()
path = url.path()
logger.debug(f"Overlay URL requested: {url.toString()}")
if path == "/overlay.js":
# Serve the overlay.js file
self._serve_overlay_js(job)
else:
# Unknown resource
logger.warning(f"Unknown overlay resource requested: {path}")
job.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
except Exception as e:
logger.error(f"Error handling overlay URL request: {e}")
job.fail(QWebEngineUrlRequestJob.Error.RequestFailed)
def _serve_overlay_js(self, job: QWebEngineUrlRequestJob):
"""Serve the overlay.js file content"""
try:
# Get overlay.js file from web dashboard static directory
overlay_js_path = Path(__file__).parent.parent / "web_dashboard" / "static" / "overlay.js"
if overlay_js_path.exists():
# Read the file content
with open(overlay_js_path, 'r', encoding='utf-8') as f:
content = f.read()
# Create QByteArray with content
data = QByteArray(content.encode('utf-8'))
# Create QBuffer for the data
buffer = QBuffer()
buffer.setData(data)
buffer.open(QIODevice.OpenModeFlag.ReadOnly)
# Reply with JavaScript content type
job.reply(b"application/javascript", buffer)
logger.debug("Served overlay.js successfully")
else:
logger.error(f"overlay.js file not found: {overlay_js_path}")
job.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
except Exception as e:
logger.error(f"Failed to serve overlay.js: {e}")
job.fail(QWebEngineUrlRequestJob.Error.RequestFailed)
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
<RCC>
<qresource prefix="/js">
<file alias="overlay.js">../web_dashboard/static/overlay.js</file>
</qresource>
</RCC>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -7,6 +7,7 @@ from datetime import datetime
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, session
from flask_login import login_required, current_user, login_user, logout_user
from werkzeug.security import check_password_hash
from werkzeug.utils import secure_filename
from .auth import AuthenticatedUser
from ..core.message_bus import Message, MessageType
......@@ -665,4 +666,77 @@ def shutdown_application():
except Exception as e:
logger.error(f"API shutdown error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/templates/upload', methods=['POST'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
@api_bp.auth_manager.require_admin if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def upload_template():
"""Upload template file"""
try:
if 'template' not in request.files:
return jsonify({"error": "No template file provided"}), 400
file = request.files['template']
if file.filename == '':
return jsonify({"error": "No file selected"}), 400
# Get template name from form or use filename
template_name = request.form.get('template_name', '')
result = api_bp.api.upload_template(file, template_name)
return jsonify(result)
except Exception as e:
logger.error(f"Template upload error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/templates/<template_name>', methods=['GET'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def get_template_content(template_name):
"""Get template content for preview"""
try:
from pathlib import Path
# Add .html extension if not present
if not template_name.endswith('.html'):
template_name += '.html'
# Get persistent uploaded templates directory
uploaded_templates_dir = api_bp.api._get_persistent_templates_dir()
# Get built-in templates directory
builtin_templates_dir = Path(__file__).parent.parent / "qt_player" / "templates"
# First try uploaded templates (user uploads take priority)
template_path = uploaded_templates_dir / template_name
# If not found in uploaded, try built-in templates
if not template_path.exists():
template_path = builtin_templates_dir / template_name
if template_path.exists():
with open(template_path, 'r', encoding='utf-8') as f:
content = f.read()
return content, 200, {'Content-Type': 'text/html'}
else:
return jsonify({"error": "Template not found"}), 404
except Exception as e:
logger.error(f"Template content fetch error: {e}")
return jsonify({"error": str(e)}), 500
@api_bp.route('/templates/<template_name>', methods=['DELETE'])
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
@api_bp.auth_manager.require_admin if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def delete_template(template_name):
"""Delete uploaded template"""
try:
result = api_bp.api.delete_template(template_name)
return jsonify(result)
except Exception as e:
logger.error(f"Template deletion error: {e}")
return jsonify({"error": str(e)}), 500
\ No newline at end of file
This diff is collapsed.
......@@ -245,9 +245,7 @@
<div class="mb-3">
<label class="form-label">Template</label>
<select class="form-select" id="video-template">
<option value="news_template">News Template</option>
<option value="sports_template">Sports Template</option>
<option value="simple_template">Simple Template</option>
<option value="">Loading templates...</option>
</select>
</div>
</form>
......@@ -274,9 +272,7 @@
<div class="mb-3">
<label class="form-label">Template</label>
<select class="form-select" id="overlay-template">
<option value="news_template">News Template</option>
<option value="sports_template">Sports Template</option>
<option value="simple_template">Simple Template</option>
<option value="">Loading templates...</option>
</select>
</div>
<div class="mb-3">
......@@ -305,6 +301,9 @@
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load available templates on page load
loadAvailableTemplates();
// Quick action buttons
document.getElementById('btn-play-video').addEventListener('click', function() {
new bootstrap.Modal(document.getElementById('playVideoModal')).show();
......@@ -465,5 +464,65 @@ document.addEventListener('DOMContentLoaded', function() {
setInterval(updateSystemStatus, 30000); // 30 seconds
setInterval(updateVideoStatus, 10000); // 10 seconds
});
function loadAvailableTemplates() {
fetch('/api/templates')
.then(response => response.json())
.then(data => {
const videoTemplateSelect = document.getElementById('video-template');
const overlayTemplateSelect = document.getElementById('overlay-template');
// Clear loading options
videoTemplateSelect.innerHTML = '';
overlayTemplateSelect.innerHTML = '';
if (data.templates && Array.isArray(data.templates)) {
data.templates.forEach(template => {
// Add to video template select
const videoOption = document.createElement('option');
videoOption.value = template.name;
videoOption.textContent = template.display_name || template.name;
videoTemplateSelect.appendChild(videoOption);
// Add to overlay template select
const overlayOption = document.createElement('option');
overlayOption.value = template.name;
overlayOption.textContent = template.display_name || template.name;
overlayTemplateSelect.appendChild(overlayOption);
});
// Select default template if available
const defaultVideoOption = videoTemplateSelect.querySelector('option[value="default"]');
if (defaultVideoOption) {
defaultVideoOption.selected = true;
}
const defaultOverlayOption = overlayTemplateSelect.querySelector('option[value="default"]');
if (defaultOverlayOption) {
defaultOverlayOption.selected = true;
}
} else {
// Fallback if no templates found
const videoOption = document.createElement('option');
videoOption.value = 'default';
videoOption.textContent = 'Default';
videoTemplateSelect.appendChild(videoOption);
const overlayOption = document.createElement('option');
overlayOption.value = 'default';
overlayOption.textContent = 'Default';
overlayTemplateSelect.appendChild(overlayOption);
}
})
.catch(error => {
console.error('Error loading templates:', error);
// Fallback template options
const videoTemplateSelect = document.getElementById('video-template');
const overlayTemplateSelect = document.getElementById('overlay-template');
videoTemplateSelect.innerHTML = '<option value="default">Default</option>';
overlayTemplateSelect.innerHTML = '<option value="default">Default</option>';
});
}
</script>
{% endblock %}
\ No newline at end of file
......@@ -38,9 +38,7 @@
<div class="mb-3">
<label for="template-select" class="form-label">Template</label>
<select class="form-select" id="template-select">
<option value="news_template">News Template</option>
<option value="sports_template">Sports Template</option>
<option value="simple_template">Simple Template</option>
<option value="">Loading templates...</option>
</select>
</div>
<div class="mb-3">
......@@ -56,6 +54,47 @@
</div>
<script>
// Load available templates on page load
document.addEventListener('DOMContentLoaded', function() {
loadAvailableTemplates();
});
function loadAvailableTemplates() {
fetch('/api/templates')
.then(response => response.json())
.then(data => {
const templateSelect = document.getElementById('template-select');
templateSelect.innerHTML = ''; // Clear loading option
if (data.templates && Array.isArray(data.templates)) {
data.templates.forEach(template => {
const option = document.createElement('option');
option.value = template.name;
option.textContent = template.display_name || template.name;
templateSelect.appendChild(option);
});
// Select default template if available
const defaultOption = templateSelect.querySelector('option[value="default"]');
if (defaultOption) {
defaultOption.selected = true;
}
} else {
// Fallback if no templates found
const option = document.createElement('option');
option.value = 'default';
option.textContent = 'Default';
templateSelect.appendChild(option);
}
})
.catch(error => {
console.error('Error loading templates:', error);
// Fallback template option
const templateSelect = document.getElementById('template-select');
templateSelect.innerHTML = '<option value="default">Default</option>';
});
}
// Video control functions
document.getElementById('play-btn').addEventListener('click', function() {
fetch('/api/video/control', {
......@@ -114,7 +153,7 @@
const template = document.getElementById('template-select').value;
const text = document.getElementById('overlay-text').value;
fetch('/api/overlay/update', {
fetch('/api/overlay', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
......
......@@ -27,9 +27,7 @@
<div class="mb-3">
<label class="form-label">Template</label>
<select class="form-select" id="video-template">
<option value="news_template">News Template</option>
<option value="sports_template">Sports Template</option>
<option value="simple_template">Simple Template</option>
<option value="">Loading templates...</option>
</select>
</div>
<button type="submit" class="btn btn-primary" id="upload-btn">
......@@ -114,6 +112,9 @@
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load available templates on page load
loadAvailableTemplates();
// Elements
const uploadForm = document.getElementById('upload-form');
const videoFileInput = document.getElementById('video-file');
......@@ -130,6 +131,43 @@ document.addEventListener('DOMContentLoaded', function() {
// Store uploaded videos
let uploadedVideos = [];
// Load available templates function
function loadAvailableTemplates() {
fetch('/api/templates')
.then(response => response.json())
.then(data => {
const templateSelect = document.getElementById('video-template');
templateSelect.innerHTML = ''; // Clear loading option
if (data.templates && Array.isArray(data.templates)) {
data.templates.forEach(template => {
const option = document.createElement('option');
option.value = template.name;
option.textContent = template.display_name || template.name;
templateSelect.appendChild(option);
});
// Select default template if available
const defaultOption = templateSelect.querySelector('option[value="default"]');
if (defaultOption) {
defaultOption.selected = true;
}
} else {
// Fallback if no templates found
const option = document.createElement('option');
option.value = 'default';
option.textContent = 'Default';
templateSelect.appendChild(option);
}
})
.catch(error => {
console.error('Error loading templates:', error);
// Fallback template option
const templateSelect = document.getElementById('video-template');
templateSelect.innerHTML = '<option value="default">Default</option>';
});
}
// Handle form submission
uploadForm.addEventListener('submit', function(e) {
e.preventDefault();
......
......@@ -27,6 +27,7 @@ python-dotenv>=0.19.0
# Utilities and system
psutil>=5.8.0
click>=8.0.0
watchdog>=3.0.0
# Video and image processing
opencv-python>=4.5.0
......
......@@ -70,8 +70,11 @@ def test_video_playback_native():
'subtitle': 'Video should be VISIBLE underneath this overlay',
'ticker': 'If you can see moving colors/patterns, video is working! Native overlay should not block video.'
}
overlay_view = window.video_widget.get_overlay_view()
overlay_view.update_overlay_data(overlay_data)
# Use the new separate window overlay
if hasattr(window, 'window_overlay') and window.window_overlay:
window.window_overlay.update_overlay_data(overlay_data)
else:
print("Warning: No window overlay available")
# Play video after 2 seconds
QTimer.singleShot(2000, play_test_video)
......@@ -117,16 +120,20 @@ def test_video_playback_webengine():
# Wait for WebEngine to be ready before updating
def update_overlay_when_ready():
overlay_view = window.video_widget.get_overlay_view()
if hasattr(overlay_view, 'overlay_channel') and overlay_view.overlay_channel:
if window._is_webengine_ready(overlay_view):
overlay_view.update_overlay_data(overlay_data)
print("WebEngine overlay updated")
# Use the new separate window overlay
if hasattr(window, 'window_overlay') and window.window_overlay:
overlay_view = window.window_overlay
if hasattr(overlay_view, 'overlay_channel') and overlay_view.overlay_channel:
if window._is_webengine_ready(overlay_view):
overlay_view.update_overlay_data(overlay_data)
print("WebEngine overlay updated")
else:
print("WebEngine not ready, retrying...")
QTimer.singleShot(1000, update_overlay_when_ready)
else:
print("WebEngine not ready, retrying...")
QTimer.singleShot(1000, update_overlay_when_ready)
overlay_view.update_overlay_data(overlay_data)
else:
overlay_view.update_overlay_data(overlay_data)
print("Warning: No window overlay available")
QTimer.singleShot(3000, update_overlay_when_ready)
......@@ -173,8 +180,11 @@ def test_uploaded_video():
'subtitle': 'Testing uploaded video with native overlay',
'ticker': 'This is a real uploaded video file. Video should be visible with native overlay.'
}
overlay_view = window.video_widget.get_overlay_view()
overlay_view.update_overlay_data(overlay_data)
# Use the new separate window overlay
if hasattr(window, 'window_overlay') and window.window_overlay:
window.window_overlay.update_overlay_data(overlay_data)
else:
print("Warning: No window overlay available")
QTimer.singleShot(2000, play_uploaded_video)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment