Fbrowser/Fbrowser.py
Stan44 a69391b1d8 toggle logging added (--debug-on) to enable logging
minor bug fixes.
known stutter bug in database/meta systems
appears to be the delay in connecting to the database.

future work:
1. intergrate firefly.dll as server module.
2. ensure full database funcctionality.
3. add toggle to use database or not. (by default we check for firefly if we don't find we default to python systems. if found we automatically use firefly.(so maybe on toggles))
4. investigate and fix the stutter bug.
2025-04-11 23:59:50 -05:00

908 lines
32 KiB
Python

"""File browser implementation with tools and audio playback.
this includes a midi player"""
# flake8: noqa: E501
# This is the main file this file is the one to run.
import sys
import os
import warnings
import argparse
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QPushButton,
QTreeView,
QLabel,
QHBoxLayout,
QLineEdit,
QProgressBar,
QSplitter,
QMessageBox,
QSlider,
QMenu,
QFileDialog,
QDialog,
QToolTip,
QFrame,
)
from PyQt6.QtCore import Qt, QDir, QUrl, QTimer, QEvent, QPoint
from PyQt6.QtGui import QFileSystemModel, QCursor
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
from MidPlay import MidPlay
from timer_m import Timer_Ui
from ScanOrg101 import (
Organizer,
FileFilterProxyModel,
DirectoryFilterProxyModel,
ArchiveExtractor,
)
import mutagen
from logging_setup import setup_logging
warnings.filterwarnings("ignore", category=DeprecationWarning)
# Parse command line arguments
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(description="File Browser Application")
parser.add_argument(
"--debug-on", action="store_true", help="Enable debug logging mode"
)
return parser.parse_args()
class MetadataTooltip(QFrame):
"""Custom styled tooltip for displaying audio metadata"""
def __init__(self, parent=None):
super().__init__(
parent,
Qt.WindowType.ToolTip | Qt.WindowType.FramelessWindowHint,
)
self.setStyleSheet(
"""
MetadataTooltip {
background-color: #2a2a2a;
border: 1px solid #555555;
border-radius: 5px;
color: white;
padding: 5px;
}
QLabel {
color: white;
}
QLabel#title {
font-weight: bold;
font-size: 14px;
color: #e0e0e0;
}
"""
)
self.organizer = Organizer(
use_db=True,
db_host="localhost", # Make sure this matches your FireflyDB server
db_port=6379,
db_password="", # Add password if needed
)
layout = QVBoxLayout(self)
self.title_label = QLabel("")
self.title_label.setObjectName("title")
self.artist_label = QLabel("")
self.album_label = QLabel("")
self.genre_label = QLabel("")
self.year_label = QLabel("")
self.size_label = QLabel("")
layout.addWidget(self.title_label)
layout.addWidget(self.artist_label)
layout.addWidget(self.album_label)
layout.addWidget(self.genre_label)
layout.addWidget(self.year_label)
layout.addWidget(self.size_label)
self.setLayout(layout)
self.hide()
def set_metadata(self, metadata):
"""Update the tooltip with metadata"""
self.title_label.setText(metadata["title"])
self.artist_label.setText(f"Artist: {metadata['artist']}")
self.album_label.setText(f"Album: {metadata['album']}")
self.genre_label.setText(f"Genre: {metadata['genre']}")
self.year_label.setText(f"Year: {metadata['year']}")
self.size_label.setText(f"Size: {metadata['size']}")
# Adjust size to fit content
self.adjustSize()
class Fbrowser(QMainWindow):
"""Fbrowser main class"""
def __init__(self, logger):
super().__init__()
self.logger = logger
self.logger.info("Initializing Fbrowser application")
self.midplay = None
self.current_path = QDir.homePath()
self.organizer = Organizer()
self.player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.player.setAudioOutput(self.audio_output)
self.audio_output.setVolume(0.5) # 50% volume
self.init_ui()
self.timer = Timer_Ui()
self.history = []
self.history_position = -1
self.hover_timer = QTimer()
self.hover_timer.setSingleShot(True)
self.hover_timer.timeout.connect(self.on_hover_timeout)
self.hover_position = None
self.hover_path = None
self.metadata_cache = {}
self.logger.debug("Fbrowser initialization complete")
def init_ui(self):
"""initilize UI"""
self.logger.debug("Initializing UI components")
self.setWindowTitle("File Browser")
central_widget = QWidget()
layout = QVBoxLayout(central_widget)
# Address bar
address_layout = QHBoxLayout()
self.address_bar = QLineEdit(self.current_path)
self.address_bar.returnPressed.connect(self.navigate_to_address)
address_layout.addWidget(self.address_bar)
# Navigation buttons
back_button = QPushButton("Back")
back_button.clicked.connect(self.go_back)
address_layout.addWidget(back_button)
forward_button = QPushButton("Forward")
forward_button.clicked.connect(self.go_forward_directory)
address_layout.addWidget(forward_button)
up_button = QPushButton("Up")
up_button.clicked.connect(self.go_up_directory)
address_layout.addWidget(up_button)
layout.addLayout(address_layout)
# Add a progress bar
self.progress_bar = QProgressBar()
layout.addWidget(self.progress_bar)
# File view with splitter
splitter = QSplitter()
# Directory tree
self.tree_model = QFileSystemModel()
self.tree_model.setRootPath(self.current_path)
self.directory_model = DirectoryFilterProxyModel()
self.directory_model.setSourceModel(self.tree_model)
self.file_tree = QTreeView()
self.file_tree.setModel(self.directory_model)
self.file_tree.setRootIndex(
self.directory_model.mapFromSource(
self.tree_model.index(self.current_path)
)
)
self.file_tree.setHeaderHidden(True)
self.file_tree.clicked.connect(self.change_directory)
splitter.addWidget(self.file_tree)
# File list
self.list_model = QFileSystemModel()
self.list_model.setRootPath(self.current_path)
self.file_filter_model = FileFilterProxyModel()
self.file_filter_model.setSourceModel(self.list_model)
self.folder_contents_view = QTreeView()
self.folder_contents_view.setModel(self.file_filter_model)
self.folder_contents_view.setRootIndex(
self.file_filter_model.mapFromSource(
self.list_model.index(self.current_path)
)
)
self.folder_contents_view.setEditTriggers(
QTreeView.EditTrigger.NoEditTriggers
)
self.folder_contents_view.doubleClicked.connect(
self.on_item_double_clicked
)
splitter.addWidget(self.folder_contents_view)
# Set up context menu for both views
self.folder_contents_view.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu
)
self.folder_contents_view.customContextMenuRequested.connect(
self.on_folder_contents_context_menu
)
self.file_tree.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu
)
self.file_tree.customContextMenuRequested.connect(
self.on_file_tree_context_menu
)
layout.addWidget(splitter)
# Current directory label
self.current_dir_label = QLabel(self.current_path)
layout.addWidget(self.current_dir_label)
# Media controls
media_controls = QHBoxLayout()
play_button = QPushButton("Play")
play_button.clicked.connect(self.player.play)
media_controls.addWidget(play_button)
pause_button = QPushButton("Pause")
pause_button.clicked.connect(self.player.pause)
media_controls.addWidget(pause_button)
stop_button = QPushButton("Stop")
stop_button.clicked.connect(self.player.stop)
media_controls.addWidget(stop_button)
volume_slider = QSlider(Qt.Orientation.Horizontal)
volume_slider.setRange(0, 100)
volume_slider.setValue(50)
volume_slider.valueChanged.connect(self.set_volume)
media_controls.addWidget(volume_slider)
layout.addLayout(media_controls)
# MidPlay button
open_midplay_button = QPushButton("Open MidPlay")
open_midplay_button.clicked.connect(self.open_midplay)
layout.addWidget(open_midplay_button)
# Timer Button
self.timer_button = QPushButton("Start Timer")
self.timer_button.clicked.connect(self.open_timer)
layout.addWidget(self.timer_button)
# Exit button
exit_button = QPushButton("Exit")
exit_button.clicked.connect(self.show_exit_popup)
layout.addWidget(exit_button)
self.setCentralWidget(central_widget)
self.resize(800, 600)
self.folder_contents_view.setMouseTracking(True)
self.folder_contents_view.viewport().setMouseTracking(True) # type: ignore
self.folder_contents_view.viewport().installEventFilter(self) # type: ignore
self.logger.debug("UI initialization complete")
def set_volume(self, volume):
"""Set Volume"""
self.audio_output.setVolume(volume / 100.0)
self.logger.debug(f"Volume set to {volume}%")
def show_exit_popup(self):
"""Exit Prompt"""
self.logger.debug("Exit prompt displayed")
reply = QMessageBox.question(
self,
"Exit",
"Are you sure you want to exit?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
self.logger.info("User confirmed exit, shutting down application")
sys.exit()
def navigate_to_address(self):
"""Navigation Via Address Bar"""
new_path = self.address_bar.text()
self.logger.debug(f"Navigating to address: {new_path}")
if os.path.isdir(new_path):
self.add_to_history(self.current_path)
self.current_path = new_path
self.update_file_views(new_path)
else:
self.logger.warning(f"Invalid directory path: {new_path}")
self.address_bar.setText(self.current_path)
def add_to_history(self, path):
"""History"""
self.logger.debug(f"Adding path to history: {path}")
# If we're not at the end of the history, truncate it
if self.history_position < len(self.history) - 1:
self.history = self.history[: self.history_position + 1]
self.history.append(path)
self.history_position = len(self.history) - 1
def change_directory(self, index):
"""Change dir to user dir"""
index = self.directory_model.mapToSource(index)
try:
file_path = self.tree_model.filePath(index)
self.logger.debug(f"Changing directory to: {file_path}")
if os.path.isdir(file_path):
self.add_to_history(self.current_path)
self.current_path = file_path
self.update_file_views(file_path)
except (OSError, IOError) as e:
self.logger.error(f"Error changing directory: {e}")
logger.debug(f"Error Changing Dirs.: {e}")
def update_file_views(self, path):
"""Updates the File trees"""
self.address_bar.setText(path)
self.current_dir_label.setText(path)
# Update tree view
self.file_tree.setRootIndex(
self.directory_model.mapFromSource(self.tree_model.index(path))
)
# Update file list view
self.list_model.setRootPath(path)
self.folder_contents_view.setRootIndex(
self.file_filter_model.mapFromSource(self.list_model.index(path))
)
# Automatically scan the directory and extract metadata
self.auto_scan_directory(path)
def on_item_double_clicked(self, index):
"""on double click do stuff"""
source_index = self.file_filter_model.mapToSource(index)
path = self.list_model.filePath(source_index)
if os.path.isdir(path):
self.add_to_history(self.current_path)
self.current_path = path
self.update_file_views(path)
else:
self.play_file(path)
def play_file(self, file_path):
"""Play a Audio file if midi load midi player"""
if file_path.lower().endswith((".mid", ".midi")):
if not self.midplay:
self.midplay = MidPlay()
self.midplay.add_to_playlist(file_path)
self.midplay.load_midi(file_path)
self.midplay.play_midi(file_path)
else:
# For audio files
self.player.setSource(QUrl.fromLocalFile(file_path))
self.player.play()
def go_back(self):
"""Go back a dir"""
if self.history_position > 0:
self.history_position -= 1
path = self.history[self.history_position]
self.current_path = path
self.update_file_views(path)
def go_forward_directory(self):
"""Go forward a dir"""
if self.history_position < len(self.history) - 1:
self.history_position += 1
path = self.history[self.history_position]
self.current_path = path
self.update_file_views(path)
def go_up_directory(self):
"""Similar to back"""
parent_dir = os.path.dirname(self.current_path)
if parent_dir != self.current_path:
self.add_to_history(self.current_path)
self.current_path = parent_dir
self.update_file_views(parent_dir)
def open_midplay(self):
"""Opens the midi player"""
if not self.midplay:
self.midplay = MidPlay()
self.midplay.showUI()
def open_timer(self):
"""Opens the Timer"""
self.timer.showUI()
def create_context_menu(self, position, view):
"""Create and display a context menu for files and folders"""
# Get the index at position
if view == self.folder_contents_view:
index = self.folder_contents_view.indexAt(position)
if not index.isValid():
return
# Map to source model to get the actual file path
source_index = self.file_filter_model.mapToSource(index)
path = self.list_model.filePath(source_index)
else: # Directory tree
index = self.file_tree.indexAt(position)
if not index.isValid():
return
source_index = self.directory_model.mapToSource(index)
path = self.tree_model.filePath(source_index)
# Create menu
menu = QMenu()
if os.path.isdir(path):
# Directory options
scan_action = menu.addAction("Scan Directory")
scan_action.triggered.connect(lambda: self.scan_directory(path)) # type: ignore
extract_metadata_action = menu.addAction("Extract Audio Metadata")
extract_metadata_action.triggered.connect( # type: ignore
lambda: self.extract_audio_metadata(path)
)
menu.addSeparator()
elif os.path.isfile(path):
# File options
if path.lower().endswith((".zip", ".rar", ".7z")):
# Archive options
extract_here_action = menu.addAction("Extract Here")
extract_here_action.triggered.connect( # type: ignore
lambda: self.extract_archive(path, self.current_path)
)
extract_to_action = menu.addAction("Extract To...")
extract_to_action.triggered.connect( # type: ignore
lambda: self.extract_archive_to(path)
)
if path.lower().endswith(
(".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi")
):
# Audio file options
play_action = menu.addAction("Play")
play_action.triggered.connect(lambda: self.play_file(path)) # type: ignore
extract_metadata_action = menu.addAction("Extract Metadata")
extract_metadata_action.triggered.connect( # type: ignore
lambda: self.extract_file_metadata(path)
)
# Add general options
menu.addSeparator()
refresh_action = menu.addAction("Refresh")
refresh_action.triggered.connect( # type: ignore
lambda: self.update_file_views(self.current_path)
)
# Show menu
menu.exec(view.viewport().mapToGlobal(position))
def on_folder_contents_context_menu(self, position):
"""Handle context menu in folder contents view"""
self.create_context_menu(position, self.folder_contents_view)
def on_file_tree_context_menu(self, position):
"""Handle context menu in file tree view"""
self.create_context_menu(position, self.file_tree)
def scan_directory(self, path):
"""Scan a directory using ScanOrg101"""
self.progress_bar.setValue(0)
# Connect organizer signals
self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_scan_complete = lambda: self.show_scan_results(path) # type: ignore
# Start scan
self.organizer.start_scan(path)
def extract_archive(self, archive_path, extract_dir):
"""Extract an archive to specified directory"""
self.progress_bar.setValue(0)
# Create an extractor instance
self.extract_dir = extract_dir
self.archive_extractor = self.organizer.archive_extractor = (
ArchiveExtractor(archive_path, extract_dir)
)
# Connect signals
self.archive_extractor.extraction_progress.connect(
self.update_progress
)
self.archive_extractor.extraction_complete.connect(
self.extraction_complete
)
self.archive_extractor.extraction_error.connect(
self.show_error_message
)
# Start extraction
self.archive_extractor.start()
def extract_archive_to(self, archive_path):
"""Extract an archive to a user-selected directory"""
extract_dir = QFileDialog.getExistingDirectory(
self, "Select Extraction Directory", self.current_path
)
if extract_dir:
self.extract_archive(archive_path, extract_dir)
def extraction_complete(self, extracted_files):
"""Handle archive extraction completion"""
self.progress_bar.setValue(100)
self.update_file_views(self.extract_dir)
self.show_message(
f"Extraction complete: {len(extracted_files)} files extracted"
)
def extract_audio_metadata(self, directory_path):
"""Extract metadata from audio files in a directory"""
self.progress_bar.setValue(0)
# Scan directory first to get file list
self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_scan_complete = ( # type: ignore
lambda: self.start_metadata_extraction(directory_path)
)
self.organizer.start_scan(directory_path)
def start_metadata_extraction(self, directory_path):
"""Start metadata extraction after scanning is complete"""
self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_metadata_complete = ( # type: ignore
lambda: self.show_metadata_results()
)
self.organizer.extract_metadata()
def extract_file_metadata(self, file_path):
"""Extract metadata from a single audio file"""
self.progress_bar.setValue(0)
# Create a temporary file list with just this file
self.organizer.file_list = [file_path]
# Set up extraction
self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_metadata_complete = ( # type: ignore
lambda: self.show_metadata_results(single_file=True)
)
# Start extraction
self.organizer.extract_metadata()
def update_progress(self, value):
"""Update the progress bar"""
self.progress_bar.setValue(value)
def show_scan_results(self, path):
"""Display scan results"""
self.progress_bar.setValue(100)
file_count = len(self.organizer.file_list)
dir_count = len(self.organizer.dir_list)
self.show_message(
f"Scan complete: {file_count} files, {dir_count} directories found"
)
def show_metadata_results(self, single_file=False):
"""Display metadata extraction results"""
self.progress_bar.setValue(100)
if single_file:
self.show_message("Metadata extraction complete")
# dialog to show metadata in a context menu
self.show_metadata_dialog()
else:
artists = len(self.organizer.artists)
albums = len(self.organizer.albums)
genres = len(self.organizer.genres)
self.show_message(
f"Metadata extraction complete: {artists} artists, {albums} albums, {genres} genres found"
)
def show_metadata_dialog(self):
"""Show a dialog with metadata information"""
# Create a dialog with metadata information
dialog = QDialog(self)
dialog.setWindowTitle("Metadata Information")
# layout = QVBoxLayout()
def show_message(self, message):
"""Show a message in a message box"""
QMessageBox.information(self, "Information", message)
def show_error_message(self, error):
"""Show an error message"""
QMessageBox.critical(self, "Error", error)
def eventFilter(self, obj, event):
"""Handle hover events for metadata tooltips"""
if obj == self.folder_contents_view.viewport():
if event.type() == QEvent.Type.HoverMove:
pos = event.position().toPoint()
# Get the item under the cursor
index = self.folder_contents_view.indexAt(pos)
if index.isValid():
source_index = self.file_filter_model.mapToSource(index)
path = self.list_model.filePath(source_index)
# Only proceed for audio files
if path.lower().endswith(
(
".mp3",
".wav",
".flac",
".m4a",
".wma",
".mid",
".midi",
)
):
# If we've moved to a new position or new file, restart the timer
if self.hover_path != path or (
self.hover_position
and (
abs(self.hover_position.x() - pos.x()) > 5
or abs(self.hover_position.y() - pos.y()) > 5
)
):
self.hover_timer.stop()
self.hover_position = pos
self.hover_path = path
self.hover_timer.start(1200)
return True
else:
# Not an audio file, stop any running timer
self.hover_timer.stop()
self.hover_path = None
else:
# No valid item under cursor
self.hover_timer.stop()
self.hover_path = None
elif event.type() == QEvent.Type.Leave:
# Mouse left the widget, stop the timer
self.hover_timer.stop()
self.hover_path = None
QToolTip.hideText()
return super().eventFilter(obj, event)
def on_hover_timeout(self):
"""Called when the hover timer expires, extract and show metadata"""
if not self.hover_path:
return
# Check if we already have metadata for this file in cache
if self.hover_path in self.metadata_cache:
self.show_metadata_tooltip(self.metadata_cache[self.hover_path])
else:
# Extract metadata in background
self.extract_hover_metadata(self.hover_path)
def extract_hover_metadata(self, file_path):
"""Extract metadata for the hovered file"""
# First check if we have this metadata in the database
if hasattr(self.organizer, "db") and self.organizer.db:
db_metadata = self.organizer.get_metadata_from_db(file_path)
if db_metadata:
# Add the size field which might not be in the database
try:
db_metadata["size"] = (
f"{os.path.getsize(file_path) / (1024*1024):.2f} MB"
)
except:
db_metadata["size"] = "Unknown"
# Cache the metadata
self.metadata_cache[file_path] = db_metadata
# Show the tooltip if we're still hovering over the same file
if self.hover_path == file_path:
self.show_metadata_tooltip(db_metadata)
return
# If not in database, extract it directly
try:
# Use mutagen directly for speed
audio = mutagen.File(file_path)
if not audio:
return
# Extract basic metadata
metadata = {
"file_path": file_path,
"filename": os.path.basename(file_path),
"size": f"{os.path.getsize(file_path) / (1024*1024):.2f} MB",
"artist": self._get_tag(audio, "artist", "Unknown Artist"),
"album": self._get_tag(audio, "album", "Unknown Album"),
"title": self._get_tag(
audio, "title", os.path.basename(file_path)
),
"genre": self._get_tag(audio, "genre", "Unknown Genre"),
"year": self._get_tag(audio, "date", "Unknown Year"),
}
# Cache the metadata
self.metadata_cache[file_path] = metadata
# Store in database for future use
if hasattr(self.organizer, "db") and self.organizer.db:
self.organizer.store_metadata(metadata)
# Show the tooltip if we're still hovering over the same file
if self.hover_path == file_path:
self.show_metadata_tooltip(metadata)
except Exception as e:
logger.debug(f"Error extracting metadata for hover: {e}")
def _get_tag(self, audio, tag_name, default_value):
"""Helper method to safely extract tags from audio files"""
try:
if tag_name in audio:
value = audio[tag_name]
if isinstance(value, list) and len(value) > 0:
return str(value[0])
return str(value)
except Exception:
pass
return default_value
def show_metadata_tooltip(self, metadata):
"""Display metadata in a stylish custom tooltip"""
# Create or get the tooltip
if not hasattr(self, "metadata_tooltip"):
self.metadata_tooltip = MetadataTooltip()
# Update tooltip content
self.metadata_tooltip.set_metadata(metadata)
# Position near but not under cursor
cursor_pos = QCursor.pos()
tooltip_pos = QPoint(cursor_pos.x() + 15, cursor_pos.y() + 15)
# Show the tooltip
self.metadata_tooltip.move(tooltip_pos)
self.metadata_tooltip.show()
# Auto-hide after 8 seconds
QTimer.singleShot(8000, self.metadata_tooltip.hide)
def closeEvent(self, event):
"""Handle application close event"""
self.logger.info("Application closing")
# Close database connection
if hasattr(self, "organizer") and self.organizer:
self.organizer.close()
event.accept()
def auto_scan_directory(self, path):
"""Automatically scan a directory and extract metadata when opened"""
# Only scan directories that are likely to contain audio files
# to avoid unnecessary scanning of system folders
if not self.should_auto_scan(path):
return
self.logger.info(f"Auto-scanning directory: {path}")
# Initialize organizer with database connection if not already done
if not hasattr(self.organizer, "db") or self.organizer.db is None:
self.organizer = Organizer(
use_db=True, db_host="localhost", db_port=6379, db_password=""
)
# Connect organizer signals
self.organizer.on_progress_update = self.update_progress
# Custom scan handler that checks for existing metadata
def scan_and_extract():
# Filter files that don't have metadata in DB
files_to_process = []
for file_path in self.organizer.file_list:
if file_path.lower().endswith(
(".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi")
):
if not self.organizer.has_metadata_in_db(file_path):
files_to_process.append(file_path)
if files_to_process:
self.logger.debug(
f"Found {len(files_to_process)} files without metadata, extracting..."
)
self.organizer.file_list = files_to_process
self.organizer.extract_metadata()
else:
self.logger.debug(
"All files already have metadata in database, skipping extraction"
)
self.progress_bar.setValue(100)
# When scan completes, check and extract metadata as needed
self.organizer.on_scan_complete = scan_and_extract
# Start scan
self.organizer.start_scan(path)
def auto_extract_metadata(self):
"""Automatically extract metadata after scanning"""
if not self.organizer.file_list:
logger.debug("No audio files found to extract metadata from")
return
self.logger.debug(
f"Auto-extracting metadata for {len(self.organizer.file_list)} files"
)
# Connect signals
self.organizer.on_progress_update = self.update_progress # type: ignore
self.organizer.on_metadata_complete = lambda: logger.debug("Metadata extraction complete") # type: ignore
# Start extraction
self.organizer.extract_metadata()
def should_auto_scan(self, path):
"""Determine if a directory should be automatically scanned"""
# Skip system directories and very large directories
if any(
system_dir in path
for system_dir in [
"/Windows",
"C:\\Windows",
"/System",
"C:\\Program Files",
"C:\\Program Files (x86)",
"/usr/bin",
"/bin",
]
):
return False
# Check if the directory has a reasonable number of files
try:
file_count = len(os.listdir(path))
if file_count > 1000: # Skip very large directories
return False
except (PermissionError, OSError):
return False
return True
def main():
# Parse command line arguments
args = parse_arguments()
# Setup logging
logger = setup_logging(args)
logger.info("Starting Fbrowser application")
app = QApplication(sys.argv)
ex = Fbrowser(logger)
ex.show()
logger.info("Application UI displayed")
sys.exit(app.exec())
if __name__ == "__main__":
main()