2024-04-10 20:22:59 +09:00
|
|
|
import sys
|
2025-08-21 22:43:11 +09:00
|
|
|
import os
|
|
|
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
|
|
|
QHBoxLayout, QSplitter, QLabel, QListWidget,
|
|
|
|
|
QTextEdit, QListWidgetItem)
|
2024-04-10 20:22:59 +09:00
|
|
|
from PyQt5.QtCore import Qt
|
|
|
|
|
|
2025-08-21 22:43:11 +09:00
|
|
|
from audio_visualization_widget import AudioVisualizationWidget
|
|
|
|
|
from analysis_results_manager import AnalysisResultsManager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
|
|
|
"""Main application window with modular audio analysis interface."""
|
|
|
|
|
|
2024-04-10 20:22:59 +09:00
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
2025-08-21 22:43:11 +09:00
|
|
|
self.analysis_manager = AnalysisResultsManager()
|
2024-04-10 20:22:59 +09:00
|
|
|
self.initUI()
|
2025-08-21 22:43:11 +09:00
|
|
|
self.connect_signals()
|
2024-04-10 20:15:13 +09:00
|
|
|
|
2024-04-10 20:22:59 +09:00
|
|
|
def initUI(self):
|
2025-08-21 22:43:11 +09:00
|
|
|
"""Initialize the user interface."""
|
|
|
|
|
self.setWindowTitle('Audio Mastering Analysis Toolkit')
|
|
|
|
|
self.setGeometry(100, 100, 1200, 700)
|
2024-04-10 20:22:59 +09:00
|
|
|
self.setAcceptDrops(True)
|
|
|
|
|
|
2025-08-21 22:43:11 +09:00
|
|
|
# Create central widget with splitter
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
# Main horizontal layout
|
|
|
|
|
main_layout = QHBoxLayout(central_widget)
|
|
|
|
|
splitter = QSplitter(Qt.Horizontal)
|
|
|
|
|
main_layout.addWidget(splitter)
|
|
|
|
|
|
|
|
|
|
# Left panel - File list and metadata
|
|
|
|
|
left_panel = self.create_left_panel()
|
|
|
|
|
splitter.addWidget(left_panel)
|
|
|
|
|
|
|
|
|
|
# Right panel - Visualization
|
|
|
|
|
self.visualization_widget = AudioVisualizationWidget()
|
|
|
|
|
splitter.addWidget(self.visualization_widget)
|
|
|
|
|
|
|
|
|
|
# Set splitter proportions
|
|
|
|
|
splitter.setSizes([300, 900]) # Left panel narrower than visualization
|
|
|
|
|
|
|
|
|
|
def create_left_panel(self):
|
|
|
|
|
"""Create the left panel with file list and metadata."""
|
|
|
|
|
panel = QWidget()
|
|
|
|
|
layout = QVBoxLayout(panel)
|
|
|
|
|
|
|
|
|
|
# File list
|
|
|
|
|
self.file_list_label = QLabel("Analyzed Files:")
|
|
|
|
|
layout.addWidget(self.file_list_label)
|
|
|
|
|
|
|
|
|
|
self.file_list = QListWidget()
|
|
|
|
|
self.file_list.itemClicked.connect(self.on_file_selected)
|
|
|
|
|
layout.addWidget(self.file_list)
|
|
|
|
|
|
|
|
|
|
# Metadata display
|
|
|
|
|
self.metadata_label = QLabel("File Information:")
|
|
|
|
|
layout.addWidget(self.metadata_label)
|
|
|
|
|
|
|
|
|
|
self.metadata_display = QTextEdit()
|
|
|
|
|
self.metadata_display.setReadOnly(True)
|
|
|
|
|
self.metadata_display.setMaximumHeight(150)
|
|
|
|
|
layout.addWidget(self.metadata_display)
|
|
|
|
|
|
|
|
|
|
# Instructions
|
|
|
|
|
instructions = QLabel(
|
|
|
|
|
"Drag and drop audio files (.mp3, .wav) onto this window to analyze them."
|
|
|
|
|
)
|
|
|
|
|
instructions.setWordWrap(True)
|
|
|
|
|
instructions.setStyleSheet("color: gray; font-style: italic;")
|
|
|
|
|
layout.addWidget(instructions)
|
|
|
|
|
|
|
|
|
|
return panel
|
|
|
|
|
|
|
|
|
|
def connect_signals(self):
|
|
|
|
|
"""Connect analysis manager signals to GUI updates."""
|
|
|
|
|
self.analysis_manager.analysisStarted.connect(self.on_analysis_started)
|
|
|
|
|
self.analysis_manager.analysisCompleted.connect(self.on_analysis_completed)
|
|
|
|
|
self.analysis_manager.analysisError.connect(self.on_analysis_error)
|
2024-04-10 20:15:13 +09:00
|
|
|
|
2024-04-10 20:22:59 +09:00
|
|
|
def dragEnterEvent(self, event):
|
2025-08-21 22:43:11 +09:00
|
|
|
"""Handle drag enter event for file drops."""
|
2024-04-10 20:22:59 +09:00
|
|
|
if event.mimeData().hasUrls():
|
2025-08-21 22:43:11 +09:00
|
|
|
# Check if any files have audio extensions
|
|
|
|
|
urls = event.mimeData().urls()
|
|
|
|
|
for url in urls:
|
|
|
|
|
file_path = url.toLocalFile()
|
|
|
|
|
if file_path.lower().endswith(('.mp3', '.wav', '.flac')):
|
|
|
|
|
event.accept()
|
|
|
|
|
return
|
|
|
|
|
event.ignore()
|
2024-04-10 20:22:59 +09:00
|
|
|
|
|
|
|
|
def dropEvent(self, event):
|
2025-08-21 22:43:11 +09:00
|
|
|
"""Handle file drop event."""
|
2024-04-10 20:22:59 +09:00
|
|
|
files = [u.toLocalFile() for u in event.mimeData().urls()]
|
2025-08-21 22:43:11 +09:00
|
|
|
audio_files = [f for f in files if f.lower().endswith(('.mp3', '.wav', '.flac'))]
|
|
|
|
|
|
|
|
|
|
if audio_files:
|
|
|
|
|
# Analyze the first audio file
|
|
|
|
|
# TODO: Add support for multiple file queue
|
|
|
|
|
file_path = audio_files[0]
|
|
|
|
|
self.analysis_manager.analyze_file(file_path)
|
|
|
|
|
else:
|
|
|
|
|
self.visualization_widget.set_status("No audio files detected in drop")
|
|
|
|
|
|
|
|
|
|
def on_analysis_started(self, file_path):
|
|
|
|
|
"""Called when analysis starts."""
|
|
|
|
|
filename = os.path.basename(file_path)
|
|
|
|
|
self.visualization_widget.set_status(f"Analyzing: {filename}...")
|
|
|
|
|
|
|
|
|
|
def on_analysis_completed(self, file_path, result):
|
|
|
|
|
"""Called when analysis completes successfully."""
|
|
|
|
|
filename = os.path.basename(file_path)
|
|
|
|
|
|
|
|
|
|
# Add to file list if not already there
|
|
|
|
|
existing_items = [self.file_list.item(i).text()
|
|
|
|
|
for i in range(self.file_list.count())]
|
|
|
|
|
if filename not in existing_items:
|
|
|
|
|
item = QListWidgetItem(filename)
|
|
|
|
|
item.setData(Qt.UserRole, file_path) # Store full path
|
|
|
|
|
self.file_list.addItem(item)
|
|
|
|
|
|
|
|
|
|
# Get and display the analysis figure
|
|
|
|
|
figure = self.analysis_manager.get_analysis_figure(file_path)
|
|
|
|
|
if figure:
|
|
|
|
|
self.visualization_widget.display_figure_direct(figure)
|
|
|
|
|
|
|
|
|
|
# Update metadata display
|
|
|
|
|
metadata_text = self.analysis_manager.get_metadata_text(file_path)
|
|
|
|
|
self.metadata_display.setText(metadata_text)
|
|
|
|
|
|
|
|
|
|
# Select the analyzed file in the list
|
|
|
|
|
for i in range(self.file_list.count()):
|
|
|
|
|
item = self.file_list.item(i)
|
|
|
|
|
if item.data(Qt.UserRole) == file_path:
|
|
|
|
|
self.file_list.setCurrentItem(item)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def on_analysis_error(self, file_path, error_message):
|
|
|
|
|
"""Called when analysis fails."""
|
|
|
|
|
filename = os.path.basename(file_path)
|
|
|
|
|
self.visualization_widget.set_status(f"Error analyzing {filename}: {error_message}")
|
|
|
|
|
|
|
|
|
|
def on_file_selected(self, item):
|
|
|
|
|
"""Called when a file is selected from the list."""
|
|
|
|
|
file_path = item.data(Qt.UserRole)
|
|
|
|
|
|
|
|
|
|
# Display the analysis figure
|
|
|
|
|
figure = self.analysis_manager.get_analysis_figure(file_path)
|
|
|
|
|
if figure:
|
|
|
|
|
self.visualization_widget.display_figure_direct(figure)
|
|
|
|
|
|
|
|
|
|
# Update metadata display
|
|
|
|
|
metadata_text = self.analysis_manager.get_metadata_text(file_path)
|
|
|
|
|
self.metadata_display.setText(metadata_text)
|
|
|
|
|
|
2024-04-10 20:22:59 +09:00
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
app = QApplication(sys.argv)
|
2025-08-21 22:43:11 +09:00
|
|
|
|
|
|
|
|
# Set application style
|
|
|
|
|
app.setStyle('Fusion') # Modern cross-platform style
|
|
|
|
|
|
|
|
|
|
window = MainWindow()
|
|
|
|
|
window.show()
|
|
|
|
|
|
2024-04-10 20:22:59 +09:00
|
|
|
sys.exit(app.exec_())
|