Add unified per-speaker font support and remote transcription service
Font changes: - Consolidate font settings into single Display Settings section - Support Web-Safe, Google Fonts, and Custom File uploads for both displays - Fix Google Fonts URL encoding (use + instead of %2B for spaces) - Fix per-speaker font inline style quote escaping in Node.js display - Add font debug logging to help diagnose font issues - Update web server to sync all font settings on settings change - Remove deprecated PHP server documentation files New features: - Add remote transcription service for GPU offloading - Add instance lock to prevent multiple app instances - Add version tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""PySide6 transcription display widget for showing real-time transcriptions."""
|
||||
|
||||
from PySide6.QtWidgets import QTextEdit
|
||||
from PySide6.QtGui import QFont, QTextCursor
|
||||
from PySide6.QtGui import QFont, QTextCursor, QTextCharFormat, QColor
|
||||
from PySide6.QtCore import Qt, Slot
|
||||
from datetime import datetime
|
||||
|
||||
@@ -28,6 +28,10 @@ class TranscriptionDisplay(QTextEdit):
|
||||
self.font_family = font_family
|
||||
self.font_size = font_size
|
||||
|
||||
# Track the current preview line for two-stage transcription
|
||||
self.preview_line_index = -1 # -1 means no active preview
|
||||
self.preview_block_number = -1 # Block number for the preview line
|
||||
|
||||
# Configure text widget
|
||||
self.setReadOnly(True)
|
||||
self.setFont(QFont(font_family, font_size))
|
||||
@@ -43,6 +47,36 @@ class TranscriptionDisplay(QTextEdit):
|
||||
}
|
||||
""")
|
||||
|
||||
def _format_line(self, text: str, user_name: str, timestamp: datetime, is_preview: bool = False) -> str:
|
||||
"""
|
||||
Format a transcription line.
|
||||
|
||||
Args:
|
||||
text: Transcription text
|
||||
user_name: User/speaker name
|
||||
timestamp: Timestamp of transcription
|
||||
is_preview: Whether this is a preview line
|
||||
|
||||
Returns:
|
||||
Formatted line string
|
||||
"""
|
||||
line_parts = []
|
||||
|
||||
if self.show_timestamps:
|
||||
time_str = timestamp.strftime("%H:%M:%S")
|
||||
line_parts.append(f"[{time_str}]")
|
||||
|
||||
if user_name and user_name.strip():
|
||||
line_parts.append(f"{user_name}:")
|
||||
|
||||
# Add preview indicator for visual distinction
|
||||
if is_preview:
|
||||
line_parts.append(f"[...] {text}")
|
||||
else:
|
||||
line_parts.append(text)
|
||||
|
||||
return " ".join(line_parts)
|
||||
|
||||
@Slot(str, str)
|
||||
def add_transcription(self, text: str, user_name: str = "", timestamp: datetime = None):
|
||||
"""
|
||||
@@ -56,35 +90,130 @@ class TranscriptionDisplay(QTextEdit):
|
||||
if timestamp is None:
|
||||
timestamp = datetime.now()
|
||||
|
||||
# Build the display line
|
||||
line_parts = []
|
||||
line = self._format_line(text, user_name, timestamp, is_preview=False)
|
||||
|
||||
if self.show_timestamps:
|
||||
time_str = timestamp.strftime("%H:%M:%S")
|
||||
line_parts.append(f"[{time_str}]")
|
||||
|
||||
if user_name:
|
||||
line_parts.append(f"{user_name}:")
|
||||
|
||||
line_parts.append(text)
|
||||
|
||||
line = " ".join(line_parts)
|
||||
|
||||
# Add to display
|
||||
self.append(line)
|
||||
# If there's an active preview, replace it instead of appending
|
||||
if self.preview_line_index >= 0:
|
||||
self._replace_preview_with_final(line)
|
||||
else:
|
||||
# Add to display normally
|
||||
self.append(line)
|
||||
self.line_count += 1
|
||||
|
||||
# Auto-scroll to bottom
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
# Track line count
|
||||
self.line_count += 1
|
||||
|
||||
# Remove old lines if exceeding max
|
||||
if self.line_count > self.max_lines:
|
||||
self._remove_oldest_lines(self.line_count - self.max_lines)
|
||||
|
||||
@Slot(str, str)
|
||||
def add_preview(self, text: str, user_name: str = "", timestamp: datetime = None):
|
||||
"""
|
||||
Add a preview transcription that will be replaced by the final transcription.
|
||||
|
||||
Args:
|
||||
text: Preview transcription text
|
||||
user_name: User/speaker name
|
||||
timestamp: Timestamp of transcription
|
||||
"""
|
||||
if timestamp is None:
|
||||
timestamp = datetime.now()
|
||||
|
||||
line = self._format_line(text, user_name, timestamp, is_preview=True)
|
||||
|
||||
# If there's already a preview, replace it
|
||||
if self.preview_line_index >= 0:
|
||||
self._replace_preview_line(line)
|
||||
else:
|
||||
# Add new preview line
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
|
||||
# Apply italic formatting for preview
|
||||
fmt = QTextCharFormat()
|
||||
fmt.setFontItalic(True)
|
||||
|
||||
if self.line_count > 0:
|
||||
cursor.insertText("\n")
|
||||
|
||||
cursor.insertText(line, fmt)
|
||||
|
||||
self.preview_line_index = self.line_count
|
||||
self.preview_block_number = self.document().blockCount() - 1
|
||||
self.line_count += 1
|
||||
|
||||
# Auto-scroll to bottom
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
def _replace_preview_line(self, new_text: str):
|
||||
"""Replace the current preview line with new preview text."""
|
||||
if self.preview_block_number < 0:
|
||||
return
|
||||
|
||||
doc = self.document()
|
||||
block = doc.findBlockByNumber(self.preview_block_number)
|
||||
|
||||
if block.isValid():
|
||||
cursor = QTextCursor(block)
|
||||
cursor.select(QTextCursor.BlockUnderCursor)
|
||||
|
||||
# Apply italic formatting for preview
|
||||
fmt = QTextCharFormat()
|
||||
fmt.setFontItalic(True)
|
||||
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(new_text, fmt)
|
||||
|
||||
def _replace_preview_with_final(self, final_text: str):
|
||||
"""Replace the preview line with final transcription."""
|
||||
if self.preview_block_number < 0:
|
||||
# No preview to replace, just add normally
|
||||
self.append(final_text)
|
||||
self.line_count += 1
|
||||
self.preview_line_index = -1
|
||||
self.preview_block_number = -1
|
||||
return
|
||||
|
||||
doc = self.document()
|
||||
block = doc.findBlockByNumber(self.preview_block_number)
|
||||
|
||||
if block.isValid():
|
||||
cursor = QTextCursor(block)
|
||||
cursor.select(QTextCursor.BlockUnderCursor)
|
||||
|
||||
# Apply normal formatting for final text
|
||||
fmt = QTextCharFormat()
|
||||
fmt.setFontItalic(False)
|
||||
fmt.setForeground(QColor(255, 255, 255)) # White for final
|
||||
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(final_text, fmt)
|
||||
|
||||
# Clear preview tracking
|
||||
self.preview_line_index = -1
|
||||
self.preview_block_number = -1
|
||||
|
||||
def clear_preview(self):
|
||||
"""Clear the current preview without adding a final transcription."""
|
||||
if self.preview_block_number >= 0:
|
||||
doc = self.document()
|
||||
block = doc.findBlockByNumber(self.preview_block_number)
|
||||
|
||||
if block.isValid():
|
||||
cursor = QTextCursor(block)
|
||||
cursor.select(QTextCursor.BlockUnderCursor)
|
||||
cursor.removeSelectedText()
|
||||
cursor.deleteChar() # Remove newline
|
||||
self.line_count -= 1
|
||||
|
||||
self.preview_line_index = -1
|
||||
self.preview_block_number = -1
|
||||
|
||||
def _remove_oldest_lines(self, num_lines: int):
|
||||
"""
|
||||
Remove oldest lines from the display.
|
||||
@@ -102,10 +231,20 @@ class TranscriptionDisplay(QTextEdit):
|
||||
|
||||
self.line_count -= num_lines
|
||||
|
||||
# Adjust preview tracking if lines were removed
|
||||
if self.preview_line_index >= 0:
|
||||
self.preview_line_index -= num_lines
|
||||
self.preview_block_number -= num_lines
|
||||
if self.preview_line_index < 0:
|
||||
self.preview_line_index = -1
|
||||
self.preview_block_number = -1
|
||||
|
||||
def clear_all(self):
|
||||
"""Clear all transcriptions."""
|
||||
self.clear()
|
||||
self.line_count = 0
|
||||
self.preview_line_index = -1
|
||||
self.preview_block_number = -1
|
||||
|
||||
def get_all_text(self) -> str:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user