- Improved video widget scaling to fit within its container while maintaining aspect ratio. - Added dynamic resizing of the video widget on window resize events. - Implemented a separate thread for video export to prevent UI freezing and added progress tracking. - Enhanced export process to include timeline clips as overlays on the main video. - Updated export completion handling with user feedback and error reporting. - Adjusted layout and styling for better user experience.
1432 lines
49 KiB
Python
1432 lines
49 KiB
Python
import sys
|
|
from PyQt6.QtWidgets import (
|
|
QApplication, QMainWindow, QTreeWidget, QTreeWidgetItem, QLabel,
|
|
QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QTableWidget,
|
|
QTableWidgetItem, QToolBar, QStyle, QTabWidget, QFrame, QProgressBar,
|
|
QSlider, QPushButton, QSpinBox, QComboBox, QScrollArea, QGroupBox,
|
|
QCheckBox, QLineEdit, QTextEdit, QDockWidget, QListWidget, QGridLayout,
|
|
QSpacerItem, QSizePolicy, QStatusBar, QMenuBar, QMenu, QListWidgetItem,
|
|
QStackedWidget, QFormLayout
|
|
)
|
|
from PyQt6.QtGui import (
|
|
QAction, QIcon, QPalette, QColor, QPainter, QPen, QFont, QPixmap,
|
|
QBrush, QLinearGradient, QKeySequence
|
|
)
|
|
from PyQt6.QtCore import Qt, QSize, QTimer, pyqtSignal, QRect, QPoint
|
|
|
|
|
|
class EnhancedTimelineWidget(QWidget):
|
|
"""Professional timeline widget with advanced features like Adobe Premiere."""
|
|
|
|
timecode_changed = pyqtSignal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.current_frame = 0
|
|
self.total_frames = 7200 # 5 minutes at 24fps
|
|
self.fps = 24
|
|
self.zoom_level = 50
|
|
|
|
self.setup_ui()
|
|
|
|
def setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(1)
|
|
|
|
# Timeline toolbar
|
|
toolbar_widget = self.create_timeline_toolbar()
|
|
layout.addWidget(toolbar_widget)
|
|
|
|
# Timecode display
|
|
timecode_widget = self.create_timecode_display()
|
|
layout.addWidget(timecode_widget)
|
|
|
|
# Time ruler
|
|
ruler_widget = self.create_time_ruler()
|
|
layout.addWidget(ruler_widget)
|
|
|
|
# Main timeline tracks
|
|
tracks_widget = self.create_timeline_tracks()
|
|
layout.addWidget(tracks_widget, 1) # Takes remaining space
|
|
|
|
def create_timeline_toolbar(self):
|
|
"""Create enhanced timeline toolbar with professional controls."""
|
|
toolbar = QWidget()
|
|
toolbar.setFixedHeight(35)
|
|
toolbar.setStyleSheet("""
|
|
QWidget {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
|
stop:0 #404040, stop:1 #2a2a2a);
|
|
border-bottom: 1px solid #555555;
|
|
}
|
|
""")
|
|
|
|
layout = QHBoxLayout(toolbar)
|
|
layout.setContentsMargins(8, 4, 8, 4)
|
|
layout.setSpacing(12)
|
|
|
|
# Zoom controls
|
|
zoom_group = QWidget()
|
|
zoom_layout = QHBoxLayout(zoom_group)
|
|
zoom_layout.setContentsMargins(0, 0, 0, 0)
|
|
zoom_layout.setSpacing(4)
|
|
|
|
zoom_label = QLabel("Zoom:")
|
|
zoom_label.setStyleSheet("color: #cccccc; font-size: 11px; font-weight: bold;")
|
|
|
|
self.zoom_slider = QSlider(Qt.Orientation.Horizontal)
|
|
self.zoom_slider.setRange(10, 200)
|
|
self.zoom_slider.setValue(self.zoom_level)
|
|
self.zoom_slider.setFixedWidth(120)
|
|
self.zoom_slider.setStyleSheet("""
|
|
QSlider::groove:horizontal {
|
|
border: 1px solid #555555;
|
|
height: 4px;
|
|
background: #333333;
|
|
border-radius: 2px;
|
|
}
|
|
QSlider::handle:horizontal {
|
|
background: #0078d4;
|
|
border: 1px solid #005a9e;
|
|
width: 12px;
|
|
margin: -4px 0;
|
|
border-radius: 6px;
|
|
}
|
|
""")
|
|
|
|
zoom_value = QLabel(f"{self.zoom_level}%")
|
|
zoom_value.setStyleSheet("color: #cccccc; font-size: 11px; min-width: 35px;")
|
|
zoom_value.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
zoom_layout.addWidget(zoom_label)
|
|
zoom_layout.addWidget(self.zoom_slider)
|
|
zoom_layout.addWidget(zoom_value)
|
|
|
|
# Connect zoom slider
|
|
self.zoom_slider.valueChanged.connect(lambda v: (
|
|
zoom_value.setText(f"{v}%"),
|
|
setattr(self, 'zoom_level', v),
|
|
self.update_timeline_zoom()
|
|
))
|
|
|
|
# Snap and magnetism controls
|
|
snap_group = QWidget()
|
|
snap_layout = QHBoxLayout(snap_group)
|
|
snap_layout.setContentsMargins(0, 0, 0, 0)
|
|
snap_layout.setSpacing(6)
|
|
|
|
self.snap_btn = QPushButton("🧲")
|
|
self.snap_btn.setCheckable(True)
|
|
self.snap_btn.setChecked(True)
|
|
self.snap_btn.setToolTip("Snap to clips and markers")
|
|
self.snap_btn.setFixedSize(28, 22)
|
|
|
|
self.magnet_btn = QPushButton("⚡")
|
|
self.magnet_btn.setCheckable(True)
|
|
self.magnet_btn.setToolTip("Magnetic timeline")
|
|
self.magnet_btn.setFixedSize(28, 22)
|
|
|
|
for btn in [self.snap_btn, self.magnet_btn]:
|
|
btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: #555555;
|
|
border: 1px solid #666666;
|
|
border-radius: 3px;
|
|
color: white;
|
|
font-size: 12px;
|
|
}
|
|
QPushButton:checked {
|
|
background: #0078d4;
|
|
border-color: #005a9e;
|
|
}
|
|
QPushButton:hover {
|
|
background: #666666;
|
|
}
|
|
""")
|
|
|
|
snap_layout.addWidget(self.snap_btn)
|
|
snap_layout.addWidget(self.magnet_btn)
|
|
|
|
# Track visibility and lock controls
|
|
track_controls = QWidget()
|
|
track_layout = QHBoxLayout(track_controls)
|
|
track_layout.setContentsMargins(0, 0, 0, 0)
|
|
track_layout.setSpacing(4)
|
|
|
|
track_height_label = QLabel("Height:")
|
|
track_height_label.setStyleSheet("color: #cccccc; font-size: 11px; font-weight: bold;")
|
|
|
|
self.track_height_combo = QComboBox()
|
|
self.track_height_combo.addItems(["Tiny", "Small", "Medium", "Large", "Extra Large"])
|
|
self.track_height_combo.setCurrentText("Medium")
|
|
self.track_height_combo.setFixedWidth(90)
|
|
self.track_height_combo.setStyleSheet("""
|
|
QComboBox {
|
|
background: #555555;
|
|
border: 1px solid #666666;
|
|
border-radius: 3px;
|
|
padding: 2px 8px;
|
|
color: white;
|
|
font-size: 11px;
|
|
}
|
|
QComboBox::drop-down {
|
|
border: none;
|
|
width: 20px;
|
|
}
|
|
QComboBox::down-arrow {
|
|
image: none;
|
|
border-left: 4px solid transparent;
|
|
border-right: 4px solid transparent;
|
|
border-top: 4px solid #cccccc;
|
|
}
|
|
""")
|
|
|
|
track_layout.addWidget(track_height_label)
|
|
track_layout.addWidget(self.track_height_combo)
|
|
|
|
# Playhead controls
|
|
playhead_group = QWidget()
|
|
playhead_layout = QHBoxLayout(playhead_group)
|
|
playhead_layout.setContentsMargins(0, 0, 0, 0)
|
|
playhead_layout.setSpacing(4)
|
|
|
|
frame_label = QLabel("Frame:")
|
|
frame_label.setStyleSheet("color: #cccccc; font-size: 11px; font-weight: bold;")
|
|
|
|
self.frame_spinbox = QSpinBox()
|
|
self.frame_spinbox.setRange(0, self.total_frames)
|
|
self.frame_spinbox.setValue(0)
|
|
self.frame_spinbox.setFixedWidth(80)
|
|
self.frame_spinbox.setStyleSheet("""
|
|
QSpinBox {
|
|
background: #555555;
|
|
border: 1px solid #666666;
|
|
border-radius: 3px;
|
|
padding: 2px;
|
|
color: white;
|
|
font-size: 11px;
|
|
}
|
|
""")
|
|
|
|
playhead_layout.addWidget(frame_label)
|
|
playhead_layout.addWidget(self.frame_spinbox)
|
|
|
|
# Assemble toolbar
|
|
layout.addWidget(zoom_group)
|
|
layout.addItem(QSpacerItem(10, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum))
|
|
layout.addWidget(snap_group)
|
|
layout.addItem(QSpacerItem(10, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum))
|
|
layout.addWidget(track_controls)
|
|
layout.addItem(QSpacerItem(20, 1, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
layout.addWidget(playhead_group)
|
|
|
|
return toolbar
|
|
|
|
def create_timecode_display(self):
|
|
"""Create professional timecode display."""
|
|
timecode_widget = QWidget()
|
|
timecode_widget.setFixedHeight(40)
|
|
timecode_widget.setStyleSheet("""
|
|
QWidget {
|
|
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
|
stop:0 #1a1a1a, stop:1 #0d0d0d);
|
|
border-top: 1px solid #333333;
|
|
border-bottom: 1px solid #333333;
|
|
}
|
|
""")
|
|
|
|
layout = QHBoxLayout(timecode_widget)
|
|
layout.setContentsMargins(10, 0, 10, 0)
|
|
|
|
# Current timecode
|
|
self.timecode_label = QLabel("00:00:00:00")
|
|
self.timecode_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #00ff41;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
background: #1a1a1a;
|
|
padding: 4px 8px;
|
|
border: 1px solid #333333;
|
|
border-radius: 3px;
|
|
}
|
|
""")
|
|
|
|
# Duration display
|
|
duration_label = QLabel("Duration: 00:05:00:00")
|
|
duration_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #cccccc;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
background: transparent;
|
|
}
|
|
""")
|
|
|
|
layout.addWidget(self.timecode_label)
|
|
layout.addStretch()
|
|
layout.addWidget(duration_label)
|
|
|
|
return timecode_widget
|
|
|
|
def create_time_ruler(self):
|
|
"""Create professional time ruler with frame markers."""
|
|
ruler_widget = QWidget()
|
|
ruler_widget.setFixedHeight(25)
|
|
ruler_widget.setStyleSheet("""
|
|
QWidget {
|
|
background: #2a2a2a;
|
|
border-bottom: 1px solid #555555;
|
|
}
|
|
""")
|
|
|
|
# This would normally have custom painting for time markers
|
|
# For now, just a placeholder
|
|
layout = QHBoxLayout(ruler_widget)
|
|
layout.setContentsMargins(80, 0, 0, 0) # Align with tracks
|
|
|
|
return ruler_widget
|
|
|
|
def create_timeline_tracks(self):
|
|
"""Create enhanced timeline tracks with professional styling."""
|
|
container = QWidget()
|
|
layout = QHBoxLayout(container)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
# Track headers (left side)
|
|
headers_widget = self.create_track_headers()
|
|
layout.addWidget(headers_widget)
|
|
|
|
# Timeline area (right side) with scroll
|
|
timeline_scroll = QScrollArea()
|
|
timeline_scroll.setWidgetResizable(True)
|
|
timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
timeline_scroll.setStyleSheet("""
|
|
QScrollArea {
|
|
border: none;
|
|
background: #1a1a1a;
|
|
}
|
|
QScrollBar:horizontal {
|
|
background: #333333;
|
|
height: 12px;
|
|
border-radius: 6px;
|
|
}
|
|
QScrollBar::handle:horizontal {
|
|
background: #666666;
|
|
border-radius: 6px;
|
|
min-width: 20px;
|
|
}
|
|
""")
|
|
|
|
# Create wide timeline content for horizontal scrolling
|
|
timeline_content = QWidget()
|
|
timeline_content.setFixedSize(4000, 400) # Wide for scrolling
|
|
timeline_content.setStyleSheet("background: #1a1a1a;")
|
|
timeline_scroll.setWidget(timeline_content)
|
|
|
|
layout.addWidget(timeline_scroll, 1)
|
|
|
|
return container
|
|
|
|
def create_track_headers(self):
|
|
"""Create professional track headers with controls."""
|
|
headers_widget = QWidget()
|
|
headers_widget.setFixedWidth(80)
|
|
headers_widget.setStyleSheet("""
|
|
QWidget {
|
|
background: #2d2d2d;
|
|
border-right: 1px solid #555555;
|
|
}
|
|
""")
|
|
|
|
layout = QVBoxLayout(headers_widget)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(1)
|
|
|
|
# Track configurations
|
|
tracks = [
|
|
("V4", "#4a90e2", 50),
|
|
("V3", "#5ba0f2", 50),
|
|
("V2", "#6bb0ff", 50),
|
|
("V1", "#7bc0ff", 50),
|
|
("A4", "#e24a4a", 35),
|
|
("A3", "#f25b5b", 35),
|
|
("A2", "#ff6b6b", 35),
|
|
("A1", "#ff7b7b", 35),
|
|
]
|
|
|
|
for name, color, height in tracks:
|
|
track_header = self.create_single_track_header(name, color, height)
|
|
layout.addWidget(track_header)
|
|
|
|
layout.addStretch()
|
|
return headers_widget
|
|
|
|
def create_single_track_header(self, name, color, height):
|
|
"""Create individual track header with controls."""
|
|
header = QWidget()
|
|
header.setFixedHeight(height)
|
|
header.setStyleSheet(f"""
|
|
QWidget {{
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
stop:0 {color}, stop:1 #2d2d2d);
|
|
border-bottom: 1px solid #555555;
|
|
}}
|
|
""")
|
|
|
|
layout = QHBoxLayout(header)
|
|
layout.setContentsMargins(4, 2, 4, 2)
|
|
layout.setSpacing(2)
|
|
|
|
# Track name
|
|
name_label = QLabel(name)
|
|
name_label.setStyleSheet("""
|
|
QLabel {
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 11px;
|
|
background: transparent;
|
|
}
|
|
""")
|
|
|
|
# Visibility toggle
|
|
vis_btn = QPushButton("👁")
|
|
vis_btn.setCheckable(True)
|
|
vis_btn.setChecked(True)
|
|
vis_btn.setFixedSize(16, 16)
|
|
vis_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: transparent;
|
|
border: none;
|
|
font-size: 10px;
|
|
}
|
|
QPushButton:!checked {
|
|
color: #666666;
|
|
}
|
|
""")
|
|
|
|
# Lock toggle
|
|
lock_btn = QPushButton("🔒")
|
|
lock_btn.setCheckable(True)
|
|
lock_btn.setFixedSize(16, 16)
|
|
lock_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: transparent;
|
|
border: none;
|
|
font-size: 10px;
|
|
}
|
|
QPushButton:checked {
|
|
color: #ff6b6b;
|
|
}
|
|
""")
|
|
|
|
layout.addWidget(name_label)
|
|
layout.addStretch()
|
|
layout.addWidget(vis_btn)
|
|
layout.addWidget(lock_btn)
|
|
|
|
return header
|
|
|
|
def update_timeline_zoom(self):
|
|
"""Update timeline zoom level."""
|
|
# This would update the timeline scale
|
|
pass
|
|
|
|
def update_timecode(self, frame):
|
|
"""Update timecode display."""
|
|
hours = frame // (self.fps * 3600)
|
|
minutes = (frame % (self.fps * 3600)) // (self.fps * 60)
|
|
seconds = (frame % (self.fps * 60)) // self.fps
|
|
frames = frame % self.fps
|
|
|
|
timecode = f"{hours:02d}:{minutes:02d}:{seconds:02d}:{frames:02d}"
|
|
self.timecode_label.setText(timecode)
|
|
self.timecode_changed.emit(timecode)
|
|
|
|
|
|
class ProfessionalVideoEditor(QMainWindow):
|
|
"""Enhanced video editor with professional Adobe Premiere-like interface."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("Professional Video Editor - Enhanced Prototype")
|
|
self.setMinimumSize(1400, 900)
|
|
|
|
self.setup_ui()
|
|
self.setup_dark_theme()
|
|
self.setup_menus()
|
|
self.setup_status_bar()
|
|
|
|
def setup_ui(self):
|
|
"""Setup the main user interface."""
|
|
# Create central splitter
|
|
main_splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
self.setCentralWidget(main_splitter)
|
|
|
|
# Left panel with project browser and effects
|
|
left_panel = self.create_left_panel()
|
|
main_splitter.addWidget(left_panel)
|
|
|
|
# Right panel with monitors and timeline
|
|
right_panel = self.create_right_panel()
|
|
main_splitter.addWidget(right_panel)
|
|
|
|
# Set splitter proportions
|
|
main_splitter.setStretchFactor(0, 1)
|
|
main_splitter.setStretchFactor(1, 4)
|
|
|
|
def create_left_panel(self):
|
|
"""Create enhanced left panel with dockable widgets."""
|
|
left_widget = QWidget()
|
|
left_widget.setFixedWidth(300)
|
|
|
|
layout = QVBoxLayout(left_widget)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
# Create tabbed interface
|
|
tabs = QTabWidget()
|
|
tabs.setTabPosition(QTabWidget.TabPosition.North)
|
|
|
|
# Project panel
|
|
project_panel = self.create_project_panel()
|
|
tabs.addTab(project_panel, "📁 Project")
|
|
|
|
# Effects panel
|
|
effects_panel = self.create_effects_panel()
|
|
tabs.addTab(effects_panel, "✨ Effects")
|
|
|
|
# Audio panel
|
|
audio_panel = self.create_audio_panel()
|
|
tabs.addTab(audio_panel, "🎵 Audio")
|
|
|
|
layout.addWidget(tabs)
|
|
return left_widget
|
|
|
|
def create_project_panel(self):
|
|
"""Create enhanced project panel."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
# Search bar
|
|
search_bar = QLineEdit()
|
|
search_bar.setPlaceholderText("🔍 Search media...")
|
|
search_bar.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 6px;
|
|
border: 1px solid #555555;
|
|
border-radius: 3px;
|
|
background: #333333;
|
|
color: white;
|
|
}
|
|
""")
|
|
|
|
# Project tree
|
|
project_tree = QTreeWidget()
|
|
project_tree.setHeaderLabel("Media Browser")
|
|
project_tree.setStyleSheet("""
|
|
QTreeWidget {
|
|
background: #2d2d2d;
|
|
border: 1px solid #555555;
|
|
color: white;
|
|
font-size: 12px;
|
|
}
|
|
QTreeWidget::item:selected {
|
|
background: #0078d4;
|
|
}
|
|
""")
|
|
|
|
# Add sample content
|
|
self.populate_project_tree(project_tree)
|
|
|
|
layout.addWidget(search_bar)
|
|
layout.addWidget(project_tree)
|
|
|
|
return widget
|
|
|
|
def populate_project_tree(self, tree):
|
|
"""Populate project tree with sample content."""
|
|
# Video folder
|
|
video_folder = QTreeWidgetItem(tree, ["📹 Video Files"])
|
|
video_folder.setExpanded(True)
|
|
QTreeWidgetItem(video_folder, ["🎬 interview_01.mp4"])
|
|
QTreeWidgetItem(video_folder, ["🎬 broll_nature.mov"])
|
|
QTreeWidgetItem(video_folder, ["🎬 talking_head.avi"])
|
|
QTreeWidgetItem(video_folder, ["🎬 product_demo.mkv"])
|
|
|
|
# Audio folder
|
|
audio_folder = QTreeWidgetItem(tree, ["🎵 Audio Files"])
|
|
audio_folder.setExpanded(True)
|
|
QTreeWidgetItem(audio_folder, ["🎼 background_music.mp3"])
|
|
QTreeWidgetItem(audio_folder, ["🎙️ voiceover.wav"])
|
|
QTreeWidgetItem(audio_folder, ["🔊 sound_effects.aiff"])
|
|
|
|
# Graphics folder
|
|
graphics_folder = QTreeWidgetItem(tree, ["🖼️ Graphics"])
|
|
QTreeWidgetItem(graphics_folder, ["🏷️ lower_third.png"])
|
|
QTreeWidgetItem(graphics_folder, ["📊 chart_template.psd"])
|
|
|
|
# Sequences folder
|
|
sequences_folder = QTreeWidgetItem(tree, ["📽️ Sequences"])
|
|
QTreeWidgetItem(sequences_folder, ["✂️ Main_Edit_v01"])
|
|
QTreeWidgetItem(sequences_folder, ["✂️ Rough_Cut"])
|
|
|
|
def create_effects_panel(self):
|
|
"""Create effects and transitions panel with shared Effect Controls box below."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
layout.setSpacing(8)
|
|
|
|
# Top section: Effects and Transitions tabs
|
|
effects_tabs = QTabWidget()
|
|
effects_tabs.setTabPosition(QTabWidget.TabPosition.North)
|
|
effects_tabs.setMaximumHeight(200) # Limit height to leave space for controls
|
|
|
|
# Effects tab - contains effect options/categories
|
|
video_effects = QListWidget()
|
|
effects_list = [
|
|
"🌈 Color Correction",
|
|
"🔄 Transform",
|
|
"⚡ Blur & Sharpen",
|
|
"🎨 Stylize",
|
|
"📐 Distort",
|
|
"🎭 Keying",
|
|
"⏰ Time",
|
|
"🔊 Audio Effects"
|
|
]
|
|
video_effects.addItems(effects_list)
|
|
video_effects.setStyleSheet("""
|
|
QListWidget {
|
|
background: #2d2d2d;
|
|
border: 1px solid #555555;
|
|
color: white;
|
|
font-size: 11px;
|
|
}
|
|
QListWidget::item {
|
|
padding: 8px 12px;
|
|
border-bottom: 1px solid #444444;
|
|
}
|
|
QListWidget::item:selected {
|
|
background: #0078d4;
|
|
}
|
|
QListWidget::item:hover:!selected {
|
|
background: #404040;
|
|
}
|
|
""")
|
|
|
|
# Connect effect selection to update controls below
|
|
video_effects.currentRowChanged.connect(self.on_effect_selection_changed)
|
|
|
|
# Transitions tab
|
|
transitions = QListWidget()
|
|
transition_list = [
|
|
"🔄 Cross Dissolve",
|
|
"⬛ Fade to Black",
|
|
"⬜ Fade to White",
|
|
"📱 Push",
|
|
"🌀 Spin",
|
|
"🔍 Zoom",
|
|
"📐 Wipe",
|
|
"✨ Morph"
|
|
]
|
|
transitions.addItems(transition_list)
|
|
transitions.setStyleSheet(video_effects.styleSheet())
|
|
|
|
# Connect transition selection to update controls below
|
|
transitions.currentRowChanged.connect(self.on_transition_selection_changed)
|
|
|
|
# Add tabs
|
|
effects_tabs.addTab(video_effects, "Effects")
|
|
effects_tabs.addTab(transitions, "Transitions")
|
|
|
|
# Bottom section: Effect Controls box
|
|
controls_group = QGroupBox("Effect Controls")
|
|
controls_group.setStyleSheet("""
|
|
QGroupBox {
|
|
font-weight: bold;
|
|
border: 2px solid #555555;
|
|
border-radius: 5px;
|
|
margin-top: 10px;
|
|
color: white;
|
|
background: #2d2d2d;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px 0 5px;
|
|
}
|
|
""")
|
|
|
|
# Create the effect controls content (parameters only)
|
|
effect_controls_content = self.create_effect_controls_content()
|
|
controls_layout = QVBoxLayout(controls_group)
|
|
controls_layout.addWidget(effect_controls_content)
|
|
|
|
# Store reference to tabs for selection handling
|
|
self.effects_tabs_widget = effects_tabs
|
|
|
|
# Assemble the complete panel
|
|
layout.addWidget(effects_tabs)
|
|
layout.addWidget(controls_group, 1) # Give more space to controls
|
|
|
|
return widget
|
|
|
|
def create_effect_controls_content(self):
|
|
"""Create the Effect Controls content with parameter controls only."""
|
|
container = QWidget()
|
|
layout = QVBoxLayout(container)
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
# Header showing current effect/transition
|
|
self.current_control_label = QLabel("Select an effect or transition to adjust its parameters")
|
|
self.current_control_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #cccccc;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
background: #404040;
|
|
padding: 8px;
|
|
border-radius: 4px;
|
|
border: 1px solid #555555;
|
|
}
|
|
""")
|
|
layout.addWidget(self.current_control_label)
|
|
|
|
# Parameter controls area (no selection list, just controls)
|
|
self.parameters_stack = QStackedWidget()
|
|
self.parameters_stack.setStyleSheet("""
|
|
QStackedWidget {
|
|
background: #333333;
|
|
border: 1px solid #555555;
|
|
border-radius: 4px;
|
|
}
|
|
""")
|
|
|
|
# Create parameter widgets for different types of effects
|
|
self.param_widgets = {}
|
|
self.param_widgets["Brightness & Contrast"] = self._create_brightness_contrast_widget()
|
|
self.param_widgets["Color Balance"] = self._create_color_balance_widget()
|
|
self.param_widgets["Volume"] = self._create_volume_widget()
|
|
self.param_widgets["Speed"] = self._create_speed_widget()
|
|
self.param_widgets["Transition"] = self._create_transition_widget()
|
|
|
|
# Add parameter widgets to stack
|
|
for widget in self.param_widgets.values():
|
|
self.parameters_stack.addWidget(widget)
|
|
|
|
# Default message widget
|
|
default_widget = QWidget()
|
|
default_layout = QVBoxLayout(default_widget)
|
|
default_layout.addStretch()
|
|
default_label = QLabel("👆 Select an effect or transition above\nto see its controls here")
|
|
default_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
default_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #888888;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
""")
|
|
default_layout.addWidget(default_label)
|
|
default_layout.addStretch()
|
|
self.parameters_stack.addWidget(default_widget)
|
|
|
|
# Set default to the message widget
|
|
self.parameters_stack.setCurrentWidget(default_widget)
|
|
|
|
layout.addWidget(self.parameters_stack, 1)
|
|
return container
|
|
|
|
def on_effect_selection_changed(self, index: int):
|
|
"""Handle effect selection from Effects tab."""
|
|
effects_list_widget = self.effects_tabs_widget.widget(0) # Effects tab
|
|
if isinstance(effects_list_widget, QListWidget):
|
|
item = effects_list_widget.item(index)
|
|
if item:
|
|
effect_name = item.text()
|
|
self.current_control_label.setText(f"Effect Controls: {effect_name}")
|
|
|
|
# Map effect to appropriate control widget
|
|
effect_mappings = {
|
|
"🌈 Color Correction": "Brightness & Contrast",
|
|
"🔄 Transform": "Speed",
|
|
"⚡ Blur & Sharpen": "Brightness & Contrast",
|
|
"🎨 Stylize": "Color Balance",
|
|
"📐 Distort": "Speed",
|
|
"🎭 Keying": "Color Balance",
|
|
"⏰ Time": "Speed",
|
|
"🔊 Audio Effects": "Volume"
|
|
}
|
|
|
|
control_type = effect_mappings.get(effect_name)
|
|
if control_type and control_type in self.param_widgets:
|
|
widget = self.param_widgets[control_type]
|
|
self.parameters_stack.setCurrentWidget(widget)
|
|
|
|
def on_transition_selection_changed(self, index: int):
|
|
"""Handle transition selection from Transitions tab."""
|
|
transitions_list_widget = self.effects_tabs_widget.widget(1) # Transitions tab
|
|
if isinstance(transitions_list_widget, QListWidget):
|
|
item = transitions_list_widget.item(index)
|
|
if item:
|
|
transition_name = item.text()
|
|
self.current_control_label.setText(f"Transition Controls: {transition_name}")
|
|
|
|
# All transitions use the transition control widget
|
|
widget = self.param_widgets["Transition"]
|
|
self.parameters_stack.setCurrentWidget(widget)
|
|
|
|
def _create_transition_widget(self) -> QWidget:
|
|
"""Create Transition control panel."""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(12)
|
|
|
|
# Duration slider
|
|
duration_slider = QSlider(Qt.Orientation.Horizontal)
|
|
duration_slider.setMinimum(1)
|
|
duration_slider.setMaximum(120) # 1-120 frames
|
|
duration_slider.setValue(30) # Default 30 frames
|
|
duration_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Duration (frames):", duration_slider)
|
|
|
|
# Ease In/Out controls
|
|
ease_in_slider = QSlider(Qt.Orientation.Horizontal)
|
|
ease_in_slider.setMinimum(0)
|
|
ease_in_slider.setMaximum(100)
|
|
ease_in_slider.setValue(25)
|
|
ease_in_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Ease In:", ease_in_slider)
|
|
|
|
ease_out_slider = QSlider(Qt.Orientation.Horizontal)
|
|
ease_out_slider.setMinimum(0)
|
|
ease_out_slider.setMaximum(100)
|
|
ease_out_slider.setValue(25)
|
|
ease_out_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Ease Out:", ease_out_slider)
|
|
|
|
return widget
|
|
|
|
def _create_brightness_contrast_widget(self) -> QWidget:
|
|
"""Create Brightness & Contrast control panel."""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(12)
|
|
|
|
# Brightness slider
|
|
bright_slider = QSlider(Qt.Orientation.Horizontal)
|
|
bright_slider.setMinimum(0)
|
|
bright_slider.setMaximum(100)
|
|
bright_slider.setValue(50)
|
|
bright_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Brightness:", bright_slider)
|
|
|
|
# Contrast slider
|
|
contrast_slider = QSlider(Qt.Orientation.Horizontal)
|
|
contrast_slider.setMinimum(0)
|
|
contrast_slider.setMaximum(100)
|
|
contrast_slider.setValue(50)
|
|
contrast_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Contrast:", contrast_slider)
|
|
|
|
return widget
|
|
|
|
def _create_color_balance_widget(self) -> QWidget:
|
|
"""Create Color Balance control panel."""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(12)
|
|
|
|
# Red channel
|
|
red_spin = QSpinBox()
|
|
red_spin.setRange(-100, 100)
|
|
red_spin.setValue(0)
|
|
red_spin.setStyleSheet(self._get_spinbox_style())
|
|
layout.addRow("Red:", red_spin)
|
|
|
|
# Green channel
|
|
green_spin = QSpinBox()
|
|
green_spin.setRange(-100, 100)
|
|
green_spin.setValue(0)
|
|
green_spin.setStyleSheet(self._get_spinbox_style())
|
|
layout.addRow("Green:", green_spin)
|
|
|
|
# Blue channel
|
|
blue_spin = QSpinBox()
|
|
blue_spin.setRange(-100, 100)
|
|
blue_spin.setValue(0)
|
|
blue_spin.setStyleSheet(self._get_spinbox_style())
|
|
layout.addRow("Blue:", blue_spin)
|
|
|
|
return widget
|
|
|
|
def _create_volume_widget(self) -> QWidget:
|
|
"""Create Volume control panel."""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(12)
|
|
|
|
# Volume slider
|
|
vol_slider = QSlider(Qt.Orientation.Horizontal)
|
|
vol_slider.setRange(0, 100)
|
|
vol_slider.setValue(100)
|
|
vol_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Volume:", vol_slider)
|
|
|
|
# Pan slider
|
|
pan_slider = QSlider(Qt.Orientation.Horizontal)
|
|
pan_slider.setRange(-100, 100)
|
|
pan_slider.setValue(0)
|
|
pan_slider.setStyleSheet(self._get_slider_style())
|
|
layout.addRow("Pan:", pan_slider)
|
|
|
|
return widget
|
|
|
|
def _create_speed_widget(self) -> QWidget:
|
|
"""Create Speed control panel."""
|
|
widget = QWidget()
|
|
layout = QFormLayout(widget)
|
|
layout.setContentsMargins(15, 15, 15, 15)
|
|
layout.setSpacing(12)
|
|
|
|
# Speed spinbox
|
|
speed_spin = QSpinBox()
|
|
speed_spin.setRange(10, 500)
|
|
speed_spin.setValue(100)
|
|
speed_spin.setSuffix("%")
|
|
speed_spin.setStyleSheet(self._get_spinbox_style())
|
|
layout.addRow("Speed:", speed_spin)
|
|
|
|
return widget
|
|
|
|
def _get_slider_style(self) -> str:
|
|
"""Return consistent slider styling."""
|
|
return """
|
|
QSlider::groove:horizontal {
|
|
border: 1px solid #555555;
|
|
height: 6px;
|
|
background: #404040;
|
|
border-radius: 3px;
|
|
}
|
|
QSlider::handle:horizontal {
|
|
background: #0078d4;
|
|
border: 2px solid #0078d4;
|
|
width: 16px;
|
|
border-radius: 8px;
|
|
margin: -5px 0;
|
|
}
|
|
QSlider::handle:horizontal:hover {
|
|
background: #106ebe;
|
|
border-color: #106ebe;
|
|
}
|
|
"""
|
|
|
|
def _get_spinbox_style(self) -> str:
|
|
"""Return consistent spinbox styling."""
|
|
return """
|
|
QSpinBox {
|
|
background: #404040;
|
|
border: 1px solid #555555;
|
|
border-radius: 3px;
|
|
padding: 4px;
|
|
color: white;
|
|
font-size: 11px;
|
|
min-width: 70px;
|
|
}
|
|
QSpinBox:focus {
|
|
border-color: #0078d4;
|
|
}
|
|
QSpinBox::up-button, QSpinBox::down-button {
|
|
background: #555555;
|
|
border: none;
|
|
width: 16px;
|
|
}
|
|
QSpinBox::up-button:hover, QSpinBox::down-button:hover {
|
|
background: #666666;
|
|
}
|
|
"""
|
|
|
|
def create_audio_panel(self):
|
|
"""Create audio mixing panel."""
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
# Audio mixer
|
|
mixer_group = QGroupBox("Audio Mixer")
|
|
mixer_group.setStyleSheet("""
|
|
QGroupBox {
|
|
font-weight: bold;
|
|
border: 2px solid #555555;
|
|
border-radius: 5px;
|
|
margin-top: 10px;
|
|
color: white;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px 0 5px;
|
|
}
|
|
""")
|
|
|
|
mixer_layout = QGridLayout(mixer_group)
|
|
|
|
# Create 4 audio channel strips
|
|
for i in range(4):
|
|
channel_widget = self.create_audio_channel(f"A{i+1}")
|
|
mixer_layout.addWidget(channel_widget, 0, i)
|
|
|
|
layout.addWidget(mixer_group)
|
|
layout.addStretch()
|
|
|
|
return widget
|
|
|
|
def create_audio_channel(self, name):
|
|
"""Create individual audio channel strip."""
|
|
channel = QWidget()
|
|
channel.setFixedWidth(60)
|
|
|
|
layout = QVBoxLayout(channel)
|
|
layout.setContentsMargins(2, 2, 2, 2)
|
|
layout.setSpacing(2)
|
|
|
|
# Channel label
|
|
label = QLabel(name)
|
|
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
label.setStyleSheet("color: white; font-size: 10px; font-weight: bold;")
|
|
|
|
# Volume fader
|
|
volume_slider = QSlider(Qt.Orientation.Vertical)
|
|
volume_slider.setRange(-60, 12)
|
|
volume_slider.setValue(0)
|
|
volume_slider.setFixedHeight(100)
|
|
volume_slider.setStyleSheet("""
|
|
QSlider::groove:vertical {
|
|
background: #333333;
|
|
width: 6px;
|
|
border-radius: 3px;
|
|
}
|
|
QSlider::handle:vertical {
|
|
background: #0078d4;
|
|
height: 12px;
|
|
border-radius: 6px;
|
|
margin: 0 -3px;
|
|
}
|
|
""")
|
|
|
|
# Mute button
|
|
mute_btn = QPushButton("M")
|
|
mute_btn.setCheckable(True)
|
|
mute_btn.setFixedSize(20, 20)
|
|
mute_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: #555555;
|
|
border: 1px solid #666666;
|
|
border-radius: 3px;
|
|
color: white;
|
|
font-size: 10px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:checked {
|
|
background: #ff6b6b;
|
|
}
|
|
""")
|
|
|
|
# Solo button
|
|
solo_btn = QPushButton("S")
|
|
solo_btn.setCheckable(True)
|
|
solo_btn.setFixedSize(20, 20)
|
|
solo_btn.setStyleSheet(mute_btn.styleSheet().replace("#ff6b6b", "#ffaa00"))
|
|
|
|
layout.addWidget(label)
|
|
layout.addWidget(volume_slider)
|
|
layout.addWidget(mute_btn)
|
|
layout.addWidget(solo_btn)
|
|
|
|
return channel
|
|
|
|
def create_right_panel(self):
|
|
"""Create right panel with monitors and timeline."""
|
|
right_splitter = QSplitter(Qt.Orientation.Vertical)
|
|
|
|
# Top: Monitor panels
|
|
monitors_widget = self.create_monitor_panels()
|
|
right_splitter.addWidget(monitors_widget)
|
|
|
|
# Bottom: Timeline
|
|
timeline_widget = EnhancedTimelineWidget()
|
|
right_splitter.addWidget(timeline_widget)
|
|
|
|
# Set proportions
|
|
right_splitter.setStretchFactor(0, 2)
|
|
right_splitter.setStretchFactor(1, 1)
|
|
|
|
return right_splitter
|
|
|
|
def create_monitor_panels(self):
|
|
"""Create enhanced monitor panels."""
|
|
container = QWidget()
|
|
layout = QHBoxLayout(container)
|
|
layout.setContentsMargins(5, 5, 5, 5)
|
|
layout.setSpacing(8)
|
|
|
|
# Source monitor
|
|
source_monitor = self.create_monitor("Source Monitor", "#1a1a2e")
|
|
layout.addWidget(source_monitor)
|
|
|
|
# Program monitor
|
|
program_monitor = self.create_monitor("Program Monitor", "#1a2e1a")
|
|
layout.addWidget(program_monitor)
|
|
|
|
# Audio meters
|
|
audio_meters = self.create_audio_meters()
|
|
layout.addWidget(audio_meters)
|
|
|
|
return container
|
|
|
|
def create_monitor(self, title, accent_color):
|
|
"""Create individual monitor with controls."""
|
|
monitor = QFrame()
|
|
monitor.setFrameShape(QFrame.Shape.Box)
|
|
monitor.setStyleSheet(f"""
|
|
QFrame {{
|
|
background: black;
|
|
border: 2px solid {accent_color};
|
|
border-radius: 5px;
|
|
}}
|
|
""")
|
|
|
|
layout = QVBoxLayout(monitor)
|
|
layout.setContentsMargins(2, 2, 2, 2)
|
|
layout.setSpacing(2)
|
|
|
|
# Monitor title
|
|
title_label = QLabel(title)
|
|
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
title_label.setStyleSheet(f"""
|
|
QLabel {{
|
|
color: white;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
background: {accent_color};
|
|
padding: 2px;
|
|
border-radius: 3px;
|
|
}}
|
|
""")
|
|
|
|
# Video display area
|
|
video_area = QLabel("📺 Video Preview")
|
|
video_area.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
video_area.setStyleSheet("""
|
|
QLabel {
|
|
color: #666666;
|
|
font-size: 24px;
|
|
background: #0a0a0a;
|
|
border: 1px dashed #333333;
|
|
border-radius: 3px;
|
|
}
|
|
""")
|
|
|
|
# Monitor controls
|
|
controls = QWidget()
|
|
controls_layout = QHBoxLayout(controls)
|
|
controls_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# Play/pause button
|
|
play_btn = QPushButton("▶️")
|
|
play_btn.setFixedSize(25, 20)
|
|
|
|
# Frame navigation
|
|
prev_btn = QPushButton("⏮️")
|
|
prev_btn.setFixedSize(25, 20)
|
|
next_btn = QPushButton("⏭️")
|
|
next_btn.setFixedSize(25, 20)
|
|
|
|
for btn in [play_btn, prev_btn, next_btn]:
|
|
btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: #333333;
|
|
border: 1px solid #555555;
|
|
border-radius: 3px;
|
|
color: white;
|
|
font-size: 10px;
|
|
}
|
|
QPushButton:hover {
|
|
background: #444444;
|
|
}
|
|
""")
|
|
|
|
controls_layout.addWidget(prev_btn)
|
|
controls_layout.addWidget(play_btn)
|
|
controls_layout.addWidget(next_btn)
|
|
controls_layout.addStretch()
|
|
|
|
layout.addWidget(title_label)
|
|
layout.addWidget(video_area, 1)
|
|
layout.addWidget(controls)
|
|
|
|
return monitor
|
|
|
|
def create_audio_meters(self):
|
|
"""Create professional audio level meters."""
|
|
meters_widget = QWidget()
|
|
meters_widget.setFixedWidth(60)
|
|
|
|
layout = QVBoxLayout(meters_widget)
|
|
layout.setContentsMargins(2, 2, 2, 2)
|
|
layout.setSpacing(1)
|
|
|
|
# Title
|
|
title = QLabel("Audio Levels")
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
title.setStyleSheet("""
|
|
QLabel {
|
|
color: white;
|
|
font-size: 10px;
|
|
font-weight: bold;
|
|
background: #2a2a2a;
|
|
padding: 2px;
|
|
border-radius: 3px;
|
|
}
|
|
""")
|
|
|
|
# Create stereo meters
|
|
meters_container = QWidget()
|
|
meters_layout = QHBoxLayout(meters_container)
|
|
meters_layout.setContentsMargins(0, 0, 0, 0)
|
|
meters_layout.setSpacing(2)
|
|
|
|
for channel in ["L", "R"]:
|
|
channel_widget = QWidget()
|
|
channel_layout = QVBoxLayout(channel_widget)
|
|
channel_layout.setContentsMargins(0, 0, 0, 0)
|
|
channel_layout.setSpacing(1)
|
|
|
|
# Channel label
|
|
ch_label = QLabel(channel)
|
|
ch_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
ch_label.setStyleSheet("color: white; font-size: 8px;")
|
|
|
|
# Level meter
|
|
meter = QProgressBar()
|
|
meter.setOrientation(Qt.Orientation.Vertical)
|
|
meter.setRange(-60, 0)
|
|
meter.setValue(-20)
|
|
meter.setTextVisible(False)
|
|
meter.setFixedWidth(12)
|
|
meter.setStyleSheet("""
|
|
QProgressBar {
|
|
background: #1a1a1a;
|
|
border: 1px solid #333333;
|
|
border-radius: 2px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background: qlineargradient(x1:0, y1:1, x2:0, y2:0,
|
|
stop:0 #00ff00, stop:0.7 #ffff00, stop:1 #ff0000);
|
|
border-radius: 1px;
|
|
}
|
|
""")
|
|
|
|
channel_layout.addWidget(ch_label)
|
|
channel_layout.addWidget(meter)
|
|
meters_layout.addWidget(channel_widget)
|
|
|
|
layout.addWidget(title)
|
|
layout.addWidget(meters_container, 1)
|
|
|
|
return meters_widget
|
|
|
|
def setup_menus(self):
|
|
"""Setup professional menu system."""
|
|
menubar = self.menuBar()
|
|
|
|
# File menu
|
|
file_menu = menubar.addMenu("File")
|
|
|
|
new_action = QAction("📄 New Project", self)
|
|
new_action.setShortcut(QKeySequence.StandardKey.New)
|
|
new_action.triggered.connect(self.new_project)
|
|
file_menu.addAction(new_action)
|
|
|
|
open_action = QAction("📂 Open Project", self)
|
|
open_action.setShortcut(QKeySequence.StandardKey.Open)
|
|
open_action.triggered.connect(self.open_project)
|
|
file_menu.addAction(open_action)
|
|
|
|
file_menu.addSeparator()
|
|
|
|
save_action = QAction("💾 Save Project", self)
|
|
save_action.setShortcut(QKeySequence.StandardKey.Save)
|
|
save_action.triggered.connect(self.save_project)
|
|
file_menu.addAction(save_action)
|
|
|
|
save_as_action = QAction("💾 Save As...", self)
|
|
save_as_action.setShortcut(QKeySequence.StandardKey.SaveAs)
|
|
save_as_action.triggered.connect(self.save_as_project)
|
|
file_menu.addAction(save_as_action)
|
|
|
|
file_menu.addSeparator()
|
|
|
|
import_action = QAction("📥 Import Media", self)
|
|
import_action.setShortcut(QKeySequence("Ctrl+I"))
|
|
import_action.triggered.connect(self.import_media)
|
|
file_menu.addAction(import_action)
|
|
|
|
export_action = QAction("📤 Export Media", self)
|
|
export_action.setShortcut(QKeySequence("Ctrl+M"))
|
|
export_action.triggered.connect(self.export_media)
|
|
file_menu.addAction(export_action)
|
|
|
|
# Edit menu
|
|
edit_menu = menubar.addMenu("Edit")
|
|
|
|
undo_action = QAction("↶ Undo", self)
|
|
undo_action.setShortcut(QKeySequence.StandardKey.Undo)
|
|
undo_action.triggered.connect(self.undo)
|
|
edit_menu.addAction(undo_action)
|
|
|
|
redo_action = QAction("↷ Redo", self)
|
|
redo_action.setShortcut(QKeySequence.StandardKey.Redo)
|
|
redo_action.triggered.connect(self.redo)
|
|
edit_menu.addAction(redo_action)
|
|
|
|
edit_menu.addSeparator()
|
|
|
|
cut_action = QAction("✂️ Cut", self)
|
|
cut_action.setShortcut(QKeySequence.StandardKey.Cut)
|
|
cut_action.triggered.connect(self.cut)
|
|
edit_menu.addAction(cut_action)
|
|
|
|
copy_action = QAction("📋 Copy", self)
|
|
copy_action.setShortcut(QKeySequence.StandardKey.Copy)
|
|
copy_action.triggered.connect(self.copy)
|
|
edit_menu.addAction(copy_action)
|
|
|
|
paste_action = QAction("📌 Paste", self)
|
|
paste_action.setShortcut(QKeySequence.StandardKey.Paste)
|
|
paste_action.triggered.connect(self.paste)
|
|
edit_menu.addAction(paste_action)
|
|
|
|
# Window menu
|
|
window_menu = menubar.addMenu("Window")
|
|
|
|
reset_action = QAction("🔄 Reset Layout", self)
|
|
reset_action.triggered.connect(self.reset_layout)
|
|
window_menu.addAction(reset_action)
|
|
|
|
window_menu.addSeparator()
|
|
|
|
project_action = QAction("📁 Project Panel", self)
|
|
project_action.triggered.connect(lambda: self.toggle_panel("project"))
|
|
window_menu.addAction(project_action)
|
|
|
|
effects_action = QAction("✨ Effects Panel", self)
|
|
effects_action.triggered.connect(lambda: self.toggle_panel("effects"))
|
|
window_menu.addAction(effects_action)
|
|
|
|
audio_action = QAction("🎵 Audio Mixer", self)
|
|
audio_action.triggered.connect(lambda: self.toggle_panel("audio"))
|
|
window_menu.addAction(audio_action)
|
|
|
|
def setup_status_bar(self):
|
|
"""Setup professional status bar."""
|
|
status = self.statusBar()
|
|
status.setStyleSheet("""
|
|
QStatusBar {
|
|
background: #2a2a2a;
|
|
border-top: 1px solid #555555;
|
|
color: white;
|
|
font-size: 11px;
|
|
}
|
|
""")
|
|
status.showMessage("Ready - Professional Video Editor Prototype")
|
|
|
|
def setup_dark_theme(self):
|
|
"""Apply comprehensive dark theme."""
|
|
self.setStyleSheet("""
|
|
QMainWindow {
|
|
background-color: #1e1e1e;
|
|
color: #ffffff;
|
|
}
|
|
QWidget {
|
|
background-color: #1e1e1e;
|
|
color: #ffffff;
|
|
}
|
|
QTabWidget::pane {
|
|
border: 1px solid #404040;
|
|
background-color: #2d2d2d;
|
|
border-radius: 3px;
|
|
}
|
|
QTabBar::tab {
|
|
background-color: #404040;
|
|
color: #ffffff;
|
|
padding: 8px 16px;
|
|
border: none;
|
|
margin-right: 1px;
|
|
border-top-left-radius: 3px;
|
|
border-top-right-radius: 3px;
|
|
}
|
|
QTabBar::tab:selected {
|
|
background-color: #0078d4;
|
|
}
|
|
QTabBar::tab:hover:!selected {
|
|
background-color: #555555;
|
|
}
|
|
QSplitter::handle {
|
|
background-color: #404040;
|
|
width: 3px;
|
|
height: 3px;
|
|
}
|
|
QSplitter::handle:hover {
|
|
background-color: #0078d4;
|
|
}
|
|
QMenuBar {
|
|
background-color: #2d2d2d;
|
|
color: white;
|
|
border-bottom: 1px solid #404040;
|
|
}
|
|
QMenuBar::item:selected {
|
|
background-color: #0078d4;
|
|
}
|
|
QMenu {
|
|
background-color: #2d2d2d;
|
|
color: white;
|
|
border: 1px solid #404040;
|
|
}
|
|
QMenu::item:selected {
|
|
background-color: #0078d4;
|
|
}
|
|
""")
|
|
|
|
# Menu action handlers
|
|
def new_project(self): pass
|
|
def open_project(self): pass
|
|
def save_project(self): pass
|
|
def save_as_project(self): pass
|
|
def import_media(self): pass
|
|
def export_media(self): pass
|
|
def undo(self): pass
|
|
def redo(self): pass
|
|
def cut(self): pass
|
|
def copy(self): pass
|
|
def paste(self): pass
|
|
def reset_layout(self): pass
|
|
def toggle_panel(self, panel_name): pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
window = ProfessionalVideoEditor()
|
|
window.show()
|
|
sys.exit(app.exec())
|