Add user-configurable colors for transcription display
- Add color settings (user_color, text_color, background_color) to config - Add color picker buttons in Settings dialog with alpha support for backgrounds - Update local web display to use configurable colors - Send per-user colors with transcriptions to multi-user server - Update Node.js server to apply per-user colors on display page - Improve server landing page: replace tech details with display options reference - Bump version to 1.3.2 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,10 @@ class ServerSyncClient:
|
||||
font_source: str = "None",
|
||||
websafe_font: Optional[str] = None,
|
||||
google_font: Optional[str] = None,
|
||||
custom_font_file: Optional[str] = None):
|
||||
custom_font_file: Optional[str] = None,
|
||||
user_color: str = "#4CAF50",
|
||||
text_color: str = "#FFFFFF",
|
||||
background_color: str = "#000000B3"):
|
||||
"""
|
||||
Initialize server sync client.
|
||||
|
||||
@@ -33,6 +36,9 @@ class ServerSyncClient:
|
||||
websafe_font: Web-safe font name (e.g., "Arial", "Times New Roman")
|
||||
google_font: Google Font name (e.g., "Roboto", "Open Sans")
|
||||
custom_font_file: Path to a custom font file for this speaker
|
||||
user_color: User name color (hex format)
|
||||
text_color: Text color (hex format)
|
||||
background_color: Background color (hex format with optional alpha)
|
||||
"""
|
||||
self.url = url
|
||||
self.room = room
|
||||
@@ -43,6 +49,9 @@ class ServerSyncClient:
|
||||
self.websafe_font = websafe_font
|
||||
self.google_font = google_font
|
||||
self.custom_font_file = custom_font_file
|
||||
self.user_color = user_color
|
||||
self.text_color = text_color
|
||||
self.background_color = background_color
|
||||
|
||||
# Font info to send with transcriptions
|
||||
self.font_family: Optional[str] = None
|
||||
@@ -303,7 +312,11 @@ class ServerSyncClient:
|
||||
'user_name': self.user_name,
|
||||
'text': trans_data['text'],
|
||||
'timestamp': trans_data['timestamp'],
|
||||
'is_preview': trans_data.get('is_preview', False)
|
||||
'is_preview': trans_data.get('is_preview', False),
|
||||
# Always include user's color settings
|
||||
'user_color': self.user_color,
|
||||
'text_color': self.text_color,
|
||||
'background_color': self.background_color
|
||||
}
|
||||
|
||||
# Add font info if user has a custom font configured
|
||||
|
||||
@@ -59,6 +59,10 @@ display:
|
||||
font_size: 12
|
||||
theme: "dark"
|
||||
fade_after_seconds: 10 # Time before transcriptions fade out (0 = never fade)
|
||||
# Color settings (used for both local display and server sync)
|
||||
user_color: "#4CAF50" # User's name color (default green)
|
||||
text_color: "#FFFFFF" # Text/font color (default white)
|
||||
background_color: "#000000B3" # Background color with alpha (default semi-transparent black)
|
||||
|
||||
web_server:
|
||||
port: 8080
|
||||
|
||||
@@ -373,6 +373,11 @@ class MainWindow(QMainWindow):
|
||||
websafe_font = self.config.get('display.websafe_font', 'Arial')
|
||||
google_font = self.config.get('display.google_font', 'Roboto')
|
||||
|
||||
# Color settings
|
||||
user_color = self.config.get('display.user_color', '#4CAF50')
|
||||
text_color = self.config.get('display.text_color', '#FFFFFF')
|
||||
background_color = self.config.get('display.background_color', '#000000B3')
|
||||
|
||||
# Try up to 5 ports if the default is in use
|
||||
ports_to_try = [port] + [port + i for i in range(1, 5)]
|
||||
server_started = False
|
||||
@@ -390,7 +395,10 @@ class MainWindow(QMainWindow):
|
||||
fonts_dir=fonts_dir,
|
||||
font_source=font_source,
|
||||
websafe_font=websafe_font,
|
||||
google_font=google_font
|
||||
google_font=google_font,
|
||||
user_color=user_color,
|
||||
text_color=text_color,
|
||||
background_color=background_color
|
||||
)
|
||||
self.web_server_thread = WebServerThread(self.web_server)
|
||||
self.web_server_thread.start()
|
||||
@@ -643,6 +651,10 @@ class MainWindow(QMainWindow):
|
||||
self.web_server.font_source = self.config.get('display.font_source', 'System Font')
|
||||
self.web_server.websafe_font = self.config.get('display.websafe_font', 'Arial')
|
||||
self.web_server.google_font = self.config.get('display.google_font', 'Roboto')
|
||||
# Update color settings
|
||||
self.web_server.user_color = self.config.get('display.user_color', '#4CAF50')
|
||||
self.web_server.text_color = self.config.get('display.text_color', '#FFFFFF')
|
||||
self.web_server.background_color = self.config.get('display.background_color', '#000000B3')
|
||||
|
||||
# Update sync link visibility based on server sync settings
|
||||
self._update_sync_link()
|
||||
@@ -728,6 +740,11 @@ class MainWindow(QMainWindow):
|
||||
google_font = self.config.get('display.google_font', '')
|
||||
custom_font_file = self.config.get('display.custom_font_file', '')
|
||||
|
||||
# Color settings
|
||||
user_color = self.config.get('display.user_color', '#4CAF50')
|
||||
text_color = self.config.get('display.text_color', '#FFFFFF')
|
||||
background_color = self.config.get('display.background_color', '#000000B3')
|
||||
|
||||
if not url:
|
||||
print("Server sync enabled but no URL configured")
|
||||
return
|
||||
@@ -743,7 +760,10 @@ class MainWindow(QMainWindow):
|
||||
font_source=font_source,
|
||||
websafe_font=websafe_font if websafe_font else None,
|
||||
google_font=google_font if google_font else None,
|
||||
custom_font_file=custom_font_file if custom_font_file else None
|
||||
custom_font_file=custom_font_file if custom_font_file else None,
|
||||
user_color=user_color,
|
||||
text_color=text_color,
|
||||
background_color=background_color
|
||||
)
|
||||
self.server_sync_client.start()
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
|
||||
QLabel, QLineEdit, QComboBox, QCheckBox, QSlider,
|
||||
QPushButton, QMessageBox, QGroupBox, QScrollArea, QWidget,
|
||||
QFileDialog
|
||||
QFileDialog, QColorDialog
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QScreen, QFontDatabase
|
||||
from PySide6.QtGui import QScreen, QFontDatabase, QColor
|
||||
from typing import Callable, List, Tuple
|
||||
|
||||
|
||||
@@ -388,6 +388,53 @@ class SettingsDialog(QDialog):
|
||||
)
|
||||
display_layout.addRow("Fade After (seconds):", self.fade_seconds_input)
|
||||
|
||||
# Color settings
|
||||
color_label = QLabel("Color Settings")
|
||||
color_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
|
||||
display_layout.addRow("", color_label)
|
||||
|
||||
# User name color picker
|
||||
user_color_layout = QHBoxLayout()
|
||||
self.user_color_button = QPushButton()
|
||||
self.user_color_button.setFixedSize(100, 30)
|
||||
self.user_color_button.setCursor(Qt.PointingHandCursor)
|
||||
self.user_color_button.setToolTip("Click to change user name color")
|
||||
self.user_color_button.clicked.connect(self._pick_user_color)
|
||||
user_color_layout.addWidget(self.user_color_button)
|
||||
self.user_color_hex = QLabel("#4CAF50")
|
||||
self.user_color_hex.setStyleSheet("font-family: monospace;")
|
||||
user_color_layout.addWidget(self.user_color_hex)
|
||||
user_color_layout.addStretch()
|
||||
display_layout.addRow("User Name Color:", user_color_layout)
|
||||
|
||||
# Text color picker
|
||||
text_color_layout = QHBoxLayout()
|
||||
self.text_color_button = QPushButton()
|
||||
self.text_color_button.setFixedSize(100, 30)
|
||||
self.text_color_button.setCursor(Qt.PointingHandCursor)
|
||||
self.text_color_button.setToolTip("Click to change text color")
|
||||
self.text_color_button.clicked.connect(self._pick_text_color)
|
||||
text_color_layout.addWidget(self.text_color_button)
|
||||
self.text_color_hex = QLabel("#FFFFFF")
|
||||
self.text_color_hex.setStyleSheet("font-family: monospace;")
|
||||
text_color_layout.addWidget(self.text_color_hex)
|
||||
text_color_layout.addStretch()
|
||||
display_layout.addRow("Text Color:", text_color_layout)
|
||||
|
||||
# Background color picker
|
||||
bg_color_layout = QHBoxLayout()
|
||||
self.bg_color_button = QPushButton()
|
||||
self.bg_color_button.setFixedSize(100, 30)
|
||||
self.bg_color_button.setCursor(Qt.PointingHandCursor)
|
||||
self.bg_color_button.setToolTip("Click to change background color (with transparency)")
|
||||
self.bg_color_button.clicked.connect(self._pick_background_color)
|
||||
bg_color_layout.addWidget(self.bg_color_button)
|
||||
self.bg_color_hex = QLabel("#000000B3")
|
||||
self.bg_color_hex.setStyleSheet("font-family: monospace;")
|
||||
bg_color_layout.addWidget(self.bg_color_hex)
|
||||
bg_color_layout.addStretch()
|
||||
display_layout.addRow("Background Color:", bg_color_layout)
|
||||
|
||||
display_group.setLayout(display_layout)
|
||||
content_layout.addWidget(display_group)
|
||||
|
||||
@@ -577,6 +624,64 @@ class SettingsDialog(QDialog):
|
||||
if file_path:
|
||||
self.display_custom_font_input.setText(file_path)
|
||||
|
||||
def _update_color_button_style(self, button: QPushButton, color_hex: str):
|
||||
"""Update a color button's background to show the selected color."""
|
||||
# Handle colors with alpha (8-char hex)
|
||||
if len(color_hex) == 9: # #RRGGBBAA
|
||||
# For display, we just show the color (alpha is visible through the button style)
|
||||
rgb = color_hex[:7]
|
||||
alpha_hex = color_hex[7:9]
|
||||
alpha = int(alpha_hex, 16) / 255
|
||||
button.setStyleSheet(
|
||||
f"background-color: {rgb}; "
|
||||
f"border: 2px solid #888; "
|
||||
f"border-radius: 4px; "
|
||||
f"opacity: {alpha};"
|
||||
)
|
||||
else:
|
||||
button.setStyleSheet(
|
||||
f"background-color: {color_hex}; "
|
||||
f"border: 2px solid #888; "
|
||||
f"border-radius: 4px;"
|
||||
)
|
||||
|
||||
def _pick_user_color(self):
|
||||
"""Open color dialog for user name color."""
|
||||
current_color = QColor(self.user_color_hex.text())
|
||||
color = QColorDialog.getColor(current_color, self, "Select User Name Color")
|
||||
if color.isValid():
|
||||
hex_color = color.name()
|
||||
self.user_color_hex.setText(hex_color)
|
||||
self._update_color_button_style(self.user_color_button, hex_color)
|
||||
|
||||
def _pick_text_color(self):
|
||||
"""Open color dialog for text color."""
|
||||
current_color = QColor(self.text_color_hex.text())
|
||||
color = QColorDialog.getColor(current_color, self, "Select Text Color")
|
||||
if color.isValid():
|
||||
hex_color = color.name()
|
||||
self.text_color_hex.setText(hex_color)
|
||||
self._update_color_button_style(self.text_color_button, hex_color)
|
||||
|
||||
def _pick_background_color(self):
|
||||
"""Open color dialog for background color (with alpha support)."""
|
||||
current_hex = self.bg_color_hex.text()
|
||||
current_color = QColor(current_hex[:7] if len(current_hex) > 7 else current_hex)
|
||||
if len(current_hex) == 9:
|
||||
current_color.setAlpha(int(current_hex[7:9], 16))
|
||||
|
||||
color = QColorDialog.getColor(
|
||||
current_color,
|
||||
self,
|
||||
"Select Background Color",
|
||||
QColorDialog.ShowAlphaChannel
|
||||
)
|
||||
if color.isValid():
|
||||
# Include alpha in hex format: #RRGGBBAA
|
||||
hex_color = f"{color.name()}{color.alpha():02X}"
|
||||
self.bg_color_hex.setText(hex_color)
|
||||
self._update_color_button_style(self.bg_color_button, hex_color)
|
||||
|
||||
def _load_current_settings(self):
|
||||
"""Load current settings from config."""
|
||||
# User settings
|
||||
@@ -649,6 +754,19 @@ class SettingsDialog(QDialog):
|
||||
self.font_size_input.setText(str(self.config.get('display.font_size', 12)))
|
||||
self.fade_seconds_input.setText(str(self.config.get('display.fade_after_seconds', 10)))
|
||||
|
||||
# Color settings
|
||||
user_color = self.config.get('display.user_color', '#4CAF50')
|
||||
self.user_color_hex.setText(user_color)
|
||||
self._update_color_button_style(self.user_color_button, user_color)
|
||||
|
||||
text_color = self.config.get('display.text_color', '#FFFFFF')
|
||||
self.text_color_hex.setText(text_color)
|
||||
self._update_color_button_style(self.text_color_button, text_color)
|
||||
|
||||
bg_color = self.config.get('display.background_color', '#000000B3')
|
||||
self.bg_color_hex.setText(bg_color)
|
||||
self._update_color_button_style(self.bg_color_button, bg_color)
|
||||
|
||||
# Server sync settings
|
||||
self.server_enabled_check.setChecked(self.config.get('server_sync.enabled', False))
|
||||
self.server_url_input.setText(self.config.get('server_sync.url', ''))
|
||||
@@ -716,6 +834,11 @@ class SettingsDialog(QDialog):
|
||||
fade_seconds = int(self.fade_seconds_input.text())
|
||||
self.config.set('display.fade_after_seconds', fade_seconds)
|
||||
|
||||
# Color settings
|
||||
self.config.set('display.user_color', self.user_color_hex.text())
|
||||
self.config.set('display.text_color', self.text_color_hex.text())
|
||||
self.config.set('display.background_color', self.bg_color_hex.text())
|
||||
|
||||
# Server sync settings
|
||||
self.config.set('server_sync.enabled', self.server_enabled_check.isChecked())
|
||||
self.config.set('server_sync.url', self.server_url_input.text())
|
||||
|
||||
@@ -374,13 +374,29 @@ app.get('/', (req, res) => {
|
||||
overflow-x: auto;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🎤 Local Transcription</h1>
|
||||
<p>Multi-User Server (Node.js)</p>
|
||||
<p>Multi-User Transcription Server</p>
|
||||
<div class="status">🟢 Server Running</div>
|
||||
</div>
|
||||
|
||||
@@ -504,25 +520,59 @@ app.get('/', (req, res) => {
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>ℹ️ Server Information</h2>
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-value">Node.js</div>
|
||||
<div class="stat-label">Runtime</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">v1.0.0</div>
|
||||
<div class="stat-label">Version</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value"><100ms</div>
|
||||
<div class="stat-label">Latency</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-value">WebSocket</div>
|
||||
<div class="stat-label">Protocol</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>📺 Display Options Reference</h2>
|
||||
<p>When creating a display URL for OBS, you can customize it with these URL parameters:</p>
|
||||
<table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
|
||||
<tr style="background: #f5f5f5;">
|
||||
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;">Parameter</th>
|
||||
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;">Description</th>
|
||||
<th style="text-align: left; padding: 10px; border-bottom: 2px solid #ddd;">Default</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>room</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Room name (required)</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>fade</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Seconds before text fades (0 = never)</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>timestamps</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Show timestamps (true/false)</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>maxlines</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Maximum visible lines</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">50</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>fontsize</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Font size in pixels</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">16</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>fontsource</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Font source: websafe, google, or custom</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">websafe</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>websafefont</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Web-safe font name (Arial, Courier New, etc.)</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Arial</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;"><code>googlefont</code></td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Google Font name (Roboto, Open Sans, etc.)</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">Roboto</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin-top: 15px; font-size: 0.9em; color: #666;">
|
||||
<strong>Note:</strong> Per-user colors and fonts set in the desktop app will override these defaults.
|
||||
Each user can customize their name color, text color, and background color in Settings.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -602,7 +652,8 @@ app.get('/', (req, res) => {
|
||||
app.post('/api/send', async (req, res) => {
|
||||
const requestStart = Date.now();
|
||||
try {
|
||||
const { room, passphrase, user_name, text, timestamp, is_preview, font_family, font_type } = req.body;
|
||||
const { room, passphrase, user_name, text, timestamp, is_preview, font_family, font_type,
|
||||
user_color, text_color, background_color } = req.body;
|
||||
|
||||
if (!room || !passphrase || !user_name || !text) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
@@ -624,7 +675,11 @@ app.post('/api/send', async (req, res) => {
|
||||
created_at: Date.now(),
|
||||
is_preview: is_preview || false,
|
||||
font_family: font_family || null, // Per-speaker font name
|
||||
font_type: font_type || null // Font type: "websafe", "google", or "custom"
|
||||
font_type: font_type || null, // Font type: "websafe", "google", or "custom"
|
||||
// Per-user color settings
|
||||
user_color: user_color || null, // User name color (e.g., "#4CAF50")
|
||||
text_color: text_color || null, // Text color (e.g., "#FFFFFF")
|
||||
background_color: background_color || null // Background color (e.g., "#000000B3")
|
||||
};
|
||||
|
||||
const addStart = Date.now();
|
||||
@@ -931,15 +986,36 @@ app.get('/display', (req, res) => {
|
||||
return userColors.get(userName);
|
||||
}
|
||||
|
||||
// Helper to convert hex color with alpha to rgba
|
||||
function hexToRgba(hex) {
|
||||
if (!hex) return null;
|
||||
hex = hex.replace('#', '');
|
||||
if (hex.length === 8) { // RRGGBBAA
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
const a = parseInt(hex.substring(6, 8), 16) / 255;
|
||||
return \`rgba(\${r}, \${g}, \${b}, \${a.toFixed(2)})\`;
|
||||
} else if (hex.length === 6) { // RRGGBB
|
||||
return '#' + hex;
|
||||
}
|
||||
return '#' + hex;
|
||||
}
|
||||
|
||||
function addTranscription(data) {
|
||||
const isPreview = data.is_preview || false;
|
||||
const userName = data.user_name || '';
|
||||
const fontFamily = data.font_family || null; // Per-speaker font name
|
||||
const fontType = data.font_type || null; // "websafe", "google", or "custom"
|
||||
// Per-user color settings
|
||||
const userColorSetting = data.user_color || null; // User name color
|
||||
const textColorSetting = data.text_color || null; // Text color
|
||||
const bgColorSetting = data.background_color || null; // Background color
|
||||
|
||||
// Debug: Log received font info
|
||||
if (fontFamily) {
|
||||
console.log('Received transcription with font:', fontFamily, '(' + fontType + ')');
|
||||
// Debug: Log received font/color info
|
||||
if (fontFamily || userColorSetting) {
|
||||
console.log('Received transcription with font:', fontFamily, '(' + fontType + ')',
|
||||
'colors:', userColorSetting, textColorSetting, bgColorSetting);
|
||||
}
|
||||
|
||||
// Load Google Font if needed
|
||||
@@ -950,6 +1026,12 @@ app.get('/display', (req, res) => {
|
||||
// Build font style string if font is set
|
||||
// Use single quotes for font name to avoid conflict with style="" double quotes
|
||||
const fontStyle = fontFamily ? \`font-family: '\${fontFamily}', sans-serif;\` : '';
|
||||
// Build text color style
|
||||
const textStyle = textColorSetting ? \`color: \${textColorSetting};\` : '';
|
||||
// Combine font and text color for text span
|
||||
const combinedTextStyle = fontStyle + textStyle;
|
||||
// Build background style for transcription div
|
||||
const bgStyle = bgColorSetting ? \`background: \${hexToRgba(bgColorSetting)};\` : '';
|
||||
|
||||
// If this is a final transcription, remove any existing preview from this user
|
||||
if (!isPreview && userPreviews.has(userName)) {
|
||||
@@ -960,12 +1042,14 @@ app.get('/display', (req, res) => {
|
||||
userPreviews.delete(userName);
|
||||
}
|
||||
|
||||
// Determine user name color: use custom color if provided, otherwise auto-generated
|
||||
const userColor = userColorSetting || getUserColor(userName);
|
||||
|
||||
// If this is a preview, update existing preview or create new one
|
||||
if (isPreview && userPreviews.has(userName)) {
|
||||
const previewEl = userPreviews.get(userName);
|
||||
if (previewEl && previewEl.parentNode) {
|
||||
// Update existing preview
|
||||
const userColor = getUserColor(userName);
|
||||
let html = '';
|
||||
if (showTimestamps && data.timestamp) {
|
||||
html += \`<span class="timestamp">[\${data.timestamp}]</span>\`;
|
||||
@@ -974,16 +1058,23 @@ app.get('/display', (req, res) => {
|
||||
html += \`<span class="user" style="color: \${userColor}">\${userName}:</span>\`;
|
||||
}
|
||||
html += \`<span class="preview-indicator">[...]</span>\`;
|
||||
html += \`<span class="text" style="\${fontStyle}">\${data.text}</span>\`;
|
||||
html += \`<span class="text" style="\${combinedTextStyle}">\${data.text}</span>\`;
|
||||
previewEl.innerHTML = html;
|
||||
// Update background color if provided
|
||||
if (bgStyle) {
|
||||
previewEl.style.background = hexToRgba(bgColorSetting);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = isPreview ? 'transcription preview' : 'transcription';
|
||||
// Apply per-user background color if provided
|
||||
if (bgStyle) {
|
||||
div.style.background = hexToRgba(bgColorSetting);
|
||||
}
|
||||
|
||||
const userColor = getUserColor(userName);
|
||||
let html = '';
|
||||
if (showTimestamps && data.timestamp) {
|
||||
html += \`<span class="timestamp">[\${data.timestamp}]</span>\`;
|
||||
@@ -994,7 +1085,7 @@ app.get('/display', (req, res) => {
|
||||
if (isPreview) {
|
||||
html += \`<span class="preview-indicator">[...]</span>\`;
|
||||
}
|
||||
html += \`<span class="text" style="\${fontStyle}">\${data.text}</span>\`;
|
||||
html += \`<span class="text" style="\${combinedTextStyle}">\${data.text}</span>\`;
|
||||
|
||||
div.innerHTML = html;
|
||||
container.appendChild(div);
|
||||
|
||||
@@ -16,7 +16,9 @@ class TranscriptionWebServer:
|
||||
fade_after_seconds: int = 10, max_lines: int = 50, font_family: str = "Arial",
|
||||
font_size: int = 16, fonts_dir: Optional[Path] = None,
|
||||
font_source: str = "System Font", websafe_font: str = "Arial",
|
||||
google_font: str = "Roboto"):
|
||||
google_font: str = "Roboto",
|
||||
user_color: str = "#4CAF50", text_color: str = "#FFFFFF",
|
||||
background_color: str = "#000000B3"):
|
||||
"""
|
||||
Initialize web server.
|
||||
|
||||
@@ -32,6 +34,9 @@ class TranscriptionWebServer:
|
||||
font_source: Font source type ("System Font", "Web-Safe", "Google Font")
|
||||
websafe_font: Web-safe font name
|
||||
google_font: Google Font name
|
||||
user_color: User name color (hex format)
|
||||
text_color: Text color (hex format)
|
||||
background_color: Background color (hex format with optional alpha, e.g., #RRGGBBAA)
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
@@ -44,6 +49,9 @@ class TranscriptionWebServer:
|
||||
self.font_source = font_source
|
||||
self.websafe_font = websafe_font
|
||||
self.google_font = google_font
|
||||
self.user_color = user_color
|
||||
self.text_color = text_color
|
||||
self.background_color = background_color
|
||||
self.app = FastAPI()
|
||||
self.active_connections: List[WebSocket] = []
|
||||
self.transcriptions = [] # Store recent transcriptions
|
||||
@@ -138,6 +146,25 @@ class TranscriptionWebServer:
|
||||
return f'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family={font_name}&display=swap">'
|
||||
return ""
|
||||
|
||||
def _hex_to_rgba(self, hex_color: str) -> str:
|
||||
"""Convert hex color (optionally with alpha) to CSS rgba() format."""
|
||||
# Remove # if present
|
||||
hex_color = hex_color.lstrip('#')
|
||||
|
||||
if len(hex_color) == 8: # RRGGBBAA
|
||||
r = int(hex_color[0:2], 16)
|
||||
g = int(hex_color[2:4], 16)
|
||||
b = int(hex_color[4:6], 16)
|
||||
a = int(hex_color[6:8], 16) / 255
|
||||
return f"rgba({r}, {g}, {b}, {a:.2f})"
|
||||
elif len(hex_color) == 6: # RRGGBB
|
||||
r = int(hex_color[0:2], 16)
|
||||
g = int(hex_color[2:4], 16)
|
||||
b = int(hex_color[4:6], 16)
|
||||
return f"rgb({r}, {g}, {b})"
|
||||
else:
|
||||
return hex_color # Return as-is if format is unknown
|
||||
|
||||
def _get_html(self) -> str:
|
||||
"""Generate HTML for transcription display."""
|
||||
# Generate custom font CSS
|
||||
@@ -145,6 +172,9 @@ class TranscriptionWebServer:
|
||||
google_font_link = self._get_google_font_link()
|
||||
effective_font = self._get_effective_font()
|
||||
|
||||
# Convert background color to rgba for CSS
|
||||
bg_color_css = self._hex_to_rgba(self.background_color)
|
||||
|
||||
return f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -168,7 +198,7 @@ class TranscriptionWebServer:
|
||||
.transcription {{
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
background: {bg_color_css};
|
||||
border-radius: 5px;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
transition: opacity 1s ease-out;
|
||||
@@ -182,12 +212,12 @@ class TranscriptionWebServer:
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.user {{
|
||||
color: #4CAF50;
|
||||
color: {self.user_color};
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.text {{
|
||||
color: white;
|
||||
color: {self.text_color};
|
||||
}}
|
||||
.transcription.preview {{
|
||||
font-style: italic;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Version information for Local Transcription."""
|
||||
|
||||
__version__ = "1.2.4"
|
||||
__version_info__ = (1, 2, 4)
|
||||
__version__ = "1.3.1"
|
||||
__version_info__ = (1, 3, 1)
|
||||
|
||||
# Version history:
|
||||
# 1.0.0 - Initial release with:
|
||||
|
||||
Reference in New Issue
Block a user